Commit d84890f9 authored by Lukas Burgey's avatar Lukas Burgey

Implement simultaneous VO and Service Deployments

Makes Deployment and DeploymentState many to many.
parent 5197f6c6
# Generated by Django 2.1.3 on 2018-11-28 13:00
from django.conf import settings
from django.db import migrations, models
import django.db.models.deletion
class Migration(migrations.Migration):
dependencies = [
('backend', '0027_auto_20181126_1314'),
]
operations = [
migrations.RemoveField(
model_name='deploymentstate',
name='parent',
),
migrations.AddField(
model_name='deploymentstate',
name='deployments',
field=models.ManyToManyField(related_name='states', to='backend.Deployment'),
),
migrations.AlterField(
model_name='deploymentstate',
name='service',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='states', to='backend.Service'),
),
migrations.AlterField(
model_name='deploymentstate',
name='site',
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='states', to='backend.Site'),
),
migrations.AlterField(
model_name='deploymentstate',
name='user',
field=models.ForeignKey(blank=True, null=True, on_delete=django.db.models.deletion.SET_NULL, related_name='states', to=settings.AUTH_USER_MODEL),
),
]
...@@ -43,15 +43,19 @@ class Site(models.Model): ...@@ -43,15 +43,19 @@ class Site(models.Model):
# ignores orphaned deployment states # ignores orphaned deployment states
dep_states = [ dep_states = [
state state
for state in self.state_items.all() for state in self.states.all()
if ( if (
state.is_pending or state.is_credential_pending state.is_pending or state.is_credential_pending
) and state.parent is not None ) and state.deployments.exists()
] ]
# make the deployments unique here # make the deployments unique here
for item in dep_states: for state in dep_states:
deployments[item.parent.id] = item.parent # we filter for deployments of the same state_target here
# if we have multiple deployments per deployment state this causes only the
# deployments with the same state_target to be pending
for deployment in state.deployments.filter(state_target=state.state_target):
deployments[deployment.id] = deployment
return deployments.values() return deployments.values()
......
This diff is collapsed.
...@@ -72,28 +72,29 @@ class DeploymentStateSerializer(serializers.ModelSerializer): ...@@ -72,28 +72,29 @@ class DeploymentStateSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = DeploymentState model = DeploymentState
fields = [ fields = [
'state',
'id',
'site',
'questionnaire',
'credentials',
'service',
'message',
'credential_states', 'credential_states',
'credentials',
'id',
'is_credential_pending', 'is_credential_pending',
'is_pending', 'is_pending',
'message',
'questionnaire',
'service',
'site',
'state',
'state_target',
] ]
DEPLOYMENT_FIELDS = ( DEPLOYMENT_FIELDS = (
'state_target', 'state_target',
'id', 'id',
'state_items', 'states',
) )
class AbstractDeploymentSerializer(serializers.ModelSerializer): class AbstractDeploymentSerializer(serializers.ModelSerializer):
state_items = DeploymentStateSerializer(many=True) states = DeploymentStateSerializer(many=True)
class Meta: class Meta:
model = Deployment model = Deployment
...@@ -101,7 +102,7 @@ class AbstractDeploymentSerializer(serializers.ModelSerializer): ...@@ -101,7 +102,7 @@ class AbstractDeploymentSerializer(serializers.ModelSerializer):
class VODeploymentSerializer(serializers.ModelSerializer): class VODeploymentSerializer(serializers.ModelSerializer):
state_items = DeploymentStateSerializer(many=True) states = DeploymentStateSerializer(many=True)
vo = VOSerializer() vo = VOSerializer()
services = ServiceSerializer(many=True) services = ServiceSerializer(many=True)
...@@ -114,7 +115,7 @@ class VODeploymentSerializer(serializers.ModelSerializer): ...@@ -114,7 +115,7 @@ class VODeploymentSerializer(serializers.ModelSerializer):
class ServiceDeploymentSerializer(serializers.ModelSerializer): class ServiceDeploymentSerializer(serializers.ModelSerializer):
state_items = DeploymentStateSerializer(many=True) states = DeploymentStateSerializer(many=True)
service = ServiceSerializer() service = ServiceSerializer()
class Meta: class Meta:
model = ServiceDeployment model = ServiceDeployment
......
...@@ -17,7 +17,7 @@ class DeploymentTest(TestCase): ...@@ -17,7 +17,7 @@ class DeploymentTest(TestCase):
def deployment_run(self, deployment, service_count): def deployment_run(self, deployment, service_count):
self.assertIsNotNone(deployment) self.assertIsNotNone(deployment)
self.assertEqual(len(deployment.services), service_count) self.assertEqual(len(deployment.services), service_count)
self.assertEqual(deployment.state_items.count(), service_count) self.assertEqual(deployment.states.count(), service_count)
self.assertEqual(deployment.state_target, models.NOT_DEPLOYED) self.assertEqual(deployment.state_target, models.NOT_DEPLOYED)
if service_count > 0: if service_count > 0:
...@@ -28,15 +28,15 @@ class DeploymentTest(TestCase): ...@@ -28,15 +28,15 @@ class DeploymentTest(TestCase):
if service_count > 0: if service_count > 0:
self.assertEqual(deployment.state, models.DEPLOYMENT_PENDING) self.assertEqual(deployment.state, models.DEPLOYMENT_PENDING)
self.assertFalse(deployment.target_reached) self.assertFalse(deployment.target_reached)
for item in deployment.state_items.all(): for item in deployment.states.all():
self.assertEqual(item.state, models.DEPLOYMENT_PENDING) self.assertEqual(item.state, models.DEPLOYMENT_PENDING)
else: else:
self.assertEqual(deployment.state, models.DEPLOYED) self.assertEqual(deployment.state, models.DEPLOYED)
self.assertTrue(deployment.target_reached) self.assertTrue(deployment.target_reached)
# execute deployment # execute deployment
LOGGER.debug('deployment_run: %s state items', deployment.state_items.count()) LOGGER.debug('deployment_run: %s state items', deployment.states.count())
for item in deployment.state_items.all(): for item in deployment.states.all():
item.client_response({'state': models.DEPLOYED}) item.client_response({'state': models.DEPLOYED})
self.assertEqual(item.state, models.DEPLOYED) self.assertEqual(item.state, models.DEPLOYED)
...@@ -49,14 +49,14 @@ class DeploymentTest(TestCase): ...@@ -49,14 +49,14 @@ class DeploymentTest(TestCase):
if service_count > 0: if service_count > 0:
self.assertEqual(deployment.state, models.REMOVAL_PENDING) self.assertEqual(deployment.state, models.REMOVAL_PENDING)
self.assertFalse(deployment.target_reached) self.assertFalse(deployment.target_reached)
for item in deployment.state_items.all(): for item in deployment.states.all():
self.assertEqual(item.state, models.REMOVAL_PENDING) self.assertEqual(item.state, models.REMOVAL_PENDING)
else: else:
self.assertEqual(deployment.state, models.NOT_DEPLOYED) self.assertEqual(deployment.state, models.NOT_DEPLOYED)
self.assertTrue(deployment.target_reached) self.assertTrue(deployment.target_reached)
# execute removals # execute removals
for item in deployment.state_items.all(): for item in deployment.states.all():
item.client_response({'state': models.NOT_DEPLOYED}) item.client_response({'state': models.NOT_DEPLOYED})
self.assertEqual(item.state, models.NOT_DEPLOYED) self.assertEqual(item.state, models.NOT_DEPLOYED)
...@@ -69,13 +69,13 @@ class DeploymentTest(TestCase): ...@@ -69,13 +69,13 @@ class DeploymentTest(TestCase):
self.assertEqual(deployment.state, models.NOT_DEPLOYED) self.assertEqual(deployment.state, models.NOT_DEPLOYED)
self.assertEqual(deployment.state_target, models.NOT_DEPLOYED) self.assertEqual(deployment.state_target, models.NOT_DEPLOYED)
self.assertEqual(len(deployment.services), service_count) self.assertEqual(len(deployment.services), service_count)
self.assertEqual(deployment.state_items.count(), service_count) self.assertEqual(deployment.states.count(), service_count)
# start deployment # start deployment
deployment.user_deploy() deployment.user_deploy()
if service_count == 0: if service_count == 0:
self.assertFalse(deployment.state_items.exists()) self.assertFalse(deployment.states.exists())
self.assertTrue(deployment.target_reached) self.assertTrue(deployment.target_reached)
# add service # add service
...@@ -86,14 +86,14 @@ class DeploymentTest(TestCase): ...@@ -86,14 +86,14 @@ class DeploymentTest(TestCase):
) )
deployment.service_added(delayed_service) deployment.service_added(delayed_service)
for item in deployment.state_items.all(): for item in deployment.states.all():
self.assertEqual(item.state, models.DEPLOYMENT_PENDING) self.assertEqual(item.state, models.DEPLOYMENT_PENDING)
self.assertEqual(deployment.state_items.count(), service_count + 1) self.assertEqual(deployment.states.count(), service_count + 1)
self.assertEqual(deployment.state, models.DEPLOYMENT_PENDING) self.assertEqual(deployment.state, models.DEPLOYMENT_PENDING)
# execute deployment # execute deployment
for item in deployment.state_items.all(): for item in deployment.states.all():
item.client_response({'state': models.DEPLOYED}) item.client_response({'state': models.DEPLOYED})
# the deployment was not done for new_service # the deployment was not done for new_service
...@@ -105,7 +105,7 @@ class DeploymentTest(TestCase): ...@@ -105,7 +105,7 @@ class DeploymentTest(TestCase):
self.assertEqual(deployment.state, models.REMOVAL_PENDING) self.assertEqual(deployment.state, models.REMOVAL_PENDING)
# execute removals # execute removals
for item in deployment.state_items.all(): for item in deployment.states.all():
self.assertEqual(item.state, models.REMOVAL_PENDING) self.assertEqual(item.state, models.REMOVAL_PENDING)
item.client_response({'state': models.NOT_DEPLOYED}) item.client_response({'state': models.NOT_DEPLOYED})
......
...@@ -238,7 +238,7 @@ class User(AbstractUser): ...@@ -238,7 +238,7 @@ class User(AbstractUser):
# find which sites need to be notified # find which sites need to be notified
for dep in self.deployments.all(): for dep in self.deployments.all():
for state_item in dep.state_items.all(): for state_item in dep.states.all():
if state_item.state != 'not_deployed': if state_item.state != 'not_deployed':
sites.append(state_item.site) sites.append(state_item.site)
...@@ -265,8 +265,6 @@ class User(AbstractUser): ...@@ -265,8 +265,6 @@ class User(AbstractUser):
# check if user needs to be in this entitlement # check if user needs to be in this entitlement
if not self.vos.filter(name=rem_ent_name, idp=self.idp).exists(): if not self.vos.filter(name=rem_ent_name, idp=self.idp).exists():
LOGGER.info(self.msg('New: %s'), ent)
self.vos.add(ent) self.vos.add(ent)
self.user_changed_vo_added(ent) self.user_changed_vo_added(ent)
...@@ -290,8 +288,6 @@ class User(AbstractUser): ...@@ -290,8 +288,6 @@ class User(AbstractUser):
# check if user needs to be in this group # check if user needs to be in this group
if not self.vos.filter(name=group_name, idp=self.idp).exists(): if not self.vos.filter(name=group_name, idp=self.idp).exists():
LOGGER.info(self.msg('New: %s'), group)
self.user_changed_vo_added(group) self.user_changed_vo_added(group)
self.vos.add(group) self.vos.add(group)
...@@ -370,22 +366,22 @@ class User(AbstractUser): ...@@ -370,22 +366,22 @@ class User(AbstractUser):
LOGGER.info('user_changed') LOGGER.info('user_changed')
def user_changed_key_added(self, key): def user_changed_key_added(self, key):
LOGGER.debug('user_changed_key_added: %s %s', self, key) LOGGER.debug(self.msg('Added: {}'.format(key)))
for dep in self.deployments.all(): for dep in self.deployments.all():
dep.user_credential_added(key) dep.user_credential_added(key)
def user_remove_key(self, key): def user_remove_key(self, key):
LOGGER.debug(self.msg('Remove: {}'.format(key)))
if key.delete_key(): if key.delete_key():
return return
LOGGER.debug('user_changed_key_removed: %s %s', self, key)
for dep in self.deployments.all(): for dep in self.deployments.all():
dep.user_credential_removed(key) dep.user_credential_removed(key)
def user_changed_vo_added(self, vo): def user_changed_vo_added(self, vo):
LOGGER.debug('user_changed_vo_added: %s %s', self, vo) LOGGER.debug(self.msg('Added: {}'.format(vo)))
# check if the user has deactivated deployments for this exact vo # check if the user has deactivated deployments for this exact vo
# if yes: reactivate the deployments # if yes: reactivate the deployments
...@@ -395,7 +391,7 @@ class User(AbstractUser): ...@@ -395,7 +391,7 @@ class User(AbstractUser):
LOGGER.debug('user_changed_vo_added: need to activate deployment %s', dep) LOGGER.debug('user_changed_vo_added: need to activate deployment %s', dep)
def user_changed_vo_removed(self, vo): def user_changed_vo_removed(self, vo):
LOGGER.debug('user_changed_vo_removed: %s %s', self, vo) LOGGER.debug(self.msg('Removed: {}'.format(vo)))
# TODO this does nothing for ServiceDeployments # TODO this does nothing for ServiceDeployments
......
...@@ -35,10 +35,10 @@ class ResponseView(views.APIView): ...@@ -35,10 +35,10 @@ class ResponseView(views.APIView):
def post(self, request): def post(self, request):
client_site = request.user.site client_site = request.user.site
output = request.data.get('output', {}) output = request.data.get('output', {})
state_id = request.data.get('id', None) deployment_id = request.data.get('id', None)
_service = request.data.get('service', {}) _service = request.data.get('service', {})
if state_id is None: if deployment_id is None:
err = 'no state id' err = 'no state id'
LOGGER.error('Error parsing response from %s: %s', request.user, err) LOGGER.error('Error parsing response from %s: %s', request.user, err)
return response_view_error(err) return response_view_error(err)
...@@ -60,12 +60,13 @@ class ResponseView(views.APIView): ...@@ -60,12 +60,13 @@ class ResponseView(views.APIView):
# find the corresponding DeploymentState for this response # find the corresponding DeploymentState for this response
try: try:
state_item = client_site.state_items.get( deployment_state = client_site.states.get(
parent__id=int(state_id), # TODO this query might not work
deployments__id=int(deployment_id),
site=client_site, site=client_site,
service=service, service=service,
) )
err = state_item.client_response(output) err = deployment_state.client_response(output)
if err is not None: if err is not None:
LOGGER.error('[ResponseView] Error parsing response from %s: %s', request.user, err) LOGGER.error('[ResponseView] Error parsing response from %s: %s', request.user, err)
return response_view_error(err) return response_view_error(err)
......
...@@ -47,6 +47,7 @@ class ProvisioningView(views.APIView): ...@@ -47,6 +47,7 @@ class ProvisioningView(views.APIView):
issuer_uri = request.data.get('iss', '') issuer_uri = request.data.get('iss', '')
key_name = request.data.get('key_name', '') key_name = request.data.get('key_name', '')
service_name = request.data.get('s', '') service_name = request.data.get('s', '')
state_target = request.data.get('state_target', deployments.DEPLOYED)
user = authenticate( user = authenticate(
request, request,
...@@ -91,7 +92,12 @@ class ProvisioningView(views.APIView): ...@@ -91,7 +92,12 @@ class ProvisioningView(views.APIView):
deployment = deployments.get_deployment(user, service=service) deployment = deployments.get_deployment(user, service=service)
if deployment is not None: if deployment is not None:
deployment.user_deploy() if state_target == deployments.DEPLOYED:
deployment.user_deploy()
elif state_target == deployments.NOT_DEPLOYED:
deployment.user_remove()
else:
return _error_response(request, 'Invalid state_target "{}"'.format(state_target))
return Response( return Response(
serializers.DeploymentSerializer(deployment).data, serializers.DeploymentSerializer(deployment).data,
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment