Commit 762f7dd8 authored by Lukas Burgey's avatar Lukas Burgey

Rename ..Task to ..State

parent ca3d7553
......@@ -40,7 +40,7 @@ class DeploymentsSerializer(serializers.Serializer):
)
class DeploymentTaskSerializer(serializers.Serializer):
class DeploymentStateSerializer(serializers.Serializer):
id = serializers.IntegerField()
action = serializers.CharField()
user = UserSerializer()
......@@ -49,7 +49,7 @@ class DeploymentTaskSerializer(serializers.Serializer):
class SiteSerializer(serializers.Serializer):
tasks = DeploymentTaskSerializer(many=True)
states = DeploymentStateSerializer(many=True)
class RabbitMQInstanceSerializer(serializers.ModelSerializer):
......
......@@ -4,6 +4,6 @@ from . import views
URLPATTERNS = [
url(r'^deployments', views.DeploymentsView.as_view()),
url(r'^config', views.ConfigurationView.as_view()),
url(r'^ack/(?P<task_id>\d+)', views.AckView.as_view()),
url(r'^ack/(?P<state_id>\d+)', views.AckView.as_view()),
url(r'^response', views.ResponseView.as_view()),
]
......@@ -3,7 +3,7 @@ import logging
from rest_framework import generics, views
from rest_framework.authentication import BasicAuthentication
from rest_framework.response import Response
from .serializers import DeploymentTaskSerializer, ServiceSerializer, RabbitMQInstanceSerializer
from .serializers import DeploymentStateSerializer, ServiceSerializer, RabbitMQInstanceSerializer
from ..models import RabbitMQInstance
LOGGER = logging.getLogger(__name__)
......@@ -15,10 +15,10 @@ AUTHENTICATION_CLASSES = (BasicAuthentication, )
class DeploymentsView(generics.ListAPIView):
authentication_classes = AUTHENTICATION_CLASSES
serializer_class = DeploymentTaskSerializer
serializer_class = DeploymentStateSerializer
def get_queryset(self):
return self.request.user.site.tasks
return self.request.user.site.states
# the client has to fetch the configuration (like services etc.) here
......@@ -42,15 +42,15 @@ class ConfigurationView(views.APIView):
class AckView(views.APIView):
authentication_classes = AUTHENTICATION_CLASSES
def delete(self, request, task_id=None):
# find the corresponding task for this item
for item in request.user.site.task_items.all():
if item.task.id == int(task_id):
def delete(self, request, state_id=None):
# find the corresponding state for this item
for item in request.user.site.state_items.all():
if item.state.id == int(state_id):
item.success()
return Response({'ok': True})
# this is no critical
LOGGER.info('%s executed the obsolete task#%s', request.user, task_id)
LOGGER.info('%s executed the obsolete state#%s', request.user, state_id)
return Response({'ok': True})
......@@ -60,33 +60,33 @@ class ResponseView(views.APIView):
def post(self, request):
output = request.data['output']
status = output['status']
task_id = request.data['id']
state_id = request.data['id']
LOGGER.debug('%s responded to task %s:\n%s', request.user, task_id, request.data)
LOGGER.debug('%s responded to state %s:\n%s', request.user, state_id, request.data)
# find the corresponding task for this item
task_item = None
for item in request.user.site.task_items.all():
if item.task.id == int(task_id):
task_item = item
# find the corresponding state for this item
state_item = None
for item in request.user.site.state_items.all():
if item.state.id == int(state_id):
state_item = item
if task_item is not None:
if state_item is not None:
if status == 'success':
task_item.success(
state_item.success(
credentials=request.data.output.get('credentials', None),
)
return Response({})
elif status == 'fail':
task_item.failed()
state_item.failed()
return Response({})
elif status == 'reject':
task_item.rejected(output['questionnaire'])
state_item.rejected(output['questionnaire'])
return Response({})
LOGGER.info('%s executed the obsolete task#%s', request.user, task_id)
LOGGER.info('%s executed the obsolete state#%s', request.user, state_id)
return Response(
data={'error': 'obsolete_task'},
data={'error': 'obsolete_state'},
status=500,
)
......@@ -20,14 +20,14 @@ class ServiceSerializer(serializers.ModelSerializer):
exclude = []
class DeploymentTaskItemSerializer(serializers.ModelSerializer):
class DeploymentStateItemSerializer(serializers.ModelSerializer):
service = ServiceSerializer()
key = backend_serializers.SSHPublicKeySerializerB()
site = SiteSerializer()
questionnaire = serializers.JSONField()
class Meta:
model = models.DeploymentTaskItem
model = models.DeploymentStateItem
fields = [
'action',
'key',
......@@ -39,11 +39,11 @@ class DeploymentTaskItemSerializer(serializers.ModelSerializer):
]
class DeploymentTaskSerializer(serializers.ModelSerializer):
class DeploymentStateSerializer(serializers.ModelSerializer):
key = backend_serializers.SSHPublicKeySerializerB()
service = ServiceSerializer()
class Meta:
model = models.DeploymentTask
model = models.DeploymentState
fields = [
'action',
'key',
......@@ -56,8 +56,8 @@ class DeploymentSerializer(serializers.Serializer):
service = ServiceSerializer()
ssh_keys = backend_serializers.SSHPublicKeySerializer(many=True)
ssh_keys_to_withdraw = backend_serializers.SSHPublicKeySerializer(many=True)
deploys = DeploymentTaskSerializer(many=True)
withdrawals = DeploymentTaskSerializer(many=True)
deploys = DeploymentStateSerializer(many=True)
withdrawals = DeploymentStateSerializer(many=True)
class Meta:
model = models.Deployment
......@@ -76,8 +76,8 @@ class UserSerializer(serializers.ModelSerializer):
ssh_keys = backend_serializers.SSHPublicKeySerializer(many=True)
deployments = DeploymentSerializer(many=True)
auth_groups = backend_serializers.AuthGroupSerializer(many=True)
deployment_tasks = DeploymentTaskSerializer(many=True)
deployment_task_items = DeploymentTaskItemSerializer(many=True)
deployment_states = DeploymentStateSerializer(many=True)
deployment_state_items = DeploymentStateItemSerializer(many=True)
class Meta:
model = models.User
......@@ -89,8 +89,8 @@ class UserSerializer(serializers.ModelSerializer):
'groups',
'deployments',
'auth_groups',
'deployment_tasks',
'deployment_task_items',
'deployment_states',
'deployment_state_items',
]
......
......@@ -131,9 +131,9 @@ class DeploymentView(views.APIView):
class QuestionnaireView(views.APIView):
def post(self, request):
task_item_id = request.query_params.get('id', '')
if task_item_id != '':
item = models.DeploymentTaskItem.objects.filter(id=int(task_item_id))
state_item_id = request.query_params.get('id', '')
if state_item_id != '':
item = models.DeploymentStateItem.objects.filter(id=int(state_item_id))
if item.exists():
item.first().questionnaire_answered(
answers=request.data,
......
......@@ -330,7 +330,7 @@ class User(AbstractUser):
self.deactivate()
# FIXME: deleting the user brings problems:
# the deletion cascades down to DeploymentTask and DeploymentTaskItem
# the deletion cascades down to DeploymentState and DeploymentStateItem
# but these need to be conserved so all clients withdrawals can be tracked
LOGGER.info(self.msg('Deleting'))
self.delete()
......@@ -399,15 +399,15 @@ class Site(models.Model):
def __str__(self):
return self.name
# tasks which are still to be executed on this site
# states which are still to be executed on this site
@property
def tasks(self):
task_items = self.task_items.filter(state='pending')\
| self.task_items.filter(state='failed')\
| self.task_items.filter(state='answered')
return [item.task
def states(self):
state_items = self.state_items.filter(state='pending')\
| self.state_items.filter(state='failed')\
| self.state_items.filter(state='answered')
return [item.state
for item
in task_items]
in state_items]
class Service(models.Model):
......@@ -458,7 +458,7 @@ class SSHPublicKey(models.Model):
# somewhere
# the receiver 'delete_withdrawn_ssh_key' does the actual deletion
def delete_key(self):
if (not self.tasks.exists() and not self.deployments.exists()):
if (not self.states.exists() and not self.deployments.exists()):
LOGGER.info(self.msg('Direct deletion of key'))
self.delete()
return
......@@ -473,7 +473,7 @@ class SSHPublicKey(models.Model):
# when a key is withdrawn by a client we try to finally delete it
def try_final_deletion(self):
if (self.deleted and not self.tasks.exists()):
if (self.deleted and not self.states.exists()):
LOGGER.info(self.msg(
'All clients have withdrawn this key. Final deletion'))
self.delete()
......@@ -490,8 +490,8 @@ class SSHPublicKey(models.Model):
# (exception: if is_active=False the ssh_keys contain the keys to be deployed
# if the deployment is reactivated)
#
# DeploymentTask is what is sent to the clients via rabbitmq
# The DeploymentTaskItem track the acknowledgements from the clients
# DeploymentState is what is sent to the clients via rabbitmq
# The DeploymentStateItem track the acknowledgements from the clients
class Deployment(models.Model):
user = models.ForeignKey(
User,
......@@ -539,11 +539,11 @@ class Deployment(models.Model):
@property
def withdrawals(self):
return self.tasks.filter(action='withdraw')
return self.states.filter(action='withdraw')
@property
def deploys(self):
return self.tasks.filter(action='deploy')
return self.states.filter(action='deploy')
def __str__(self):
return '{}:{}'.format(self.service, self.user)
......@@ -581,21 +581,21 @@ class Deployment(models.Model):
# only deploy the key
def _deploy_key(self, key):
task = DeploymentTask.construct_deployment_task(
state = DeploymentState.construct_deployment_state(
deployment=self,
key=key,
)
# publish the task
task.publish()
# publish the state
state.publish()
def _withdraw_key(self, key):
task = DeploymentTask.construct_withdrawal_task(
state = DeploymentState.construct_withdrawal_state(
deployment=self,
key=key,
)
# publish the task
task.publish()
# publish the state
state.publish()
# deploy key and track changes in the key lists
def deploy_key(self, key):
......@@ -633,9 +633,9 @@ def invert_action(action):
return 'deploy'
# DeploymentTask: knows:
# DeploymentState: knows:
# user, service, key, action
class DeploymentTask(models.Model):
class DeploymentState(models.Model):
ACTION_CHOICES = (
('deploy', 'deploy'),
('withdraw', 'withdraw'),
......@@ -646,38 +646,38 @@ class DeploymentTask(models.Model):
)
key = models.ForeignKey(
SSHPublicKey,
related_name='tasks',
related_name='states',
on_delete=models.CASCADE,
)
deployment = models.ForeignKey(
Deployment,
related_name='tasks',
related_name='states',
on_delete=models.CASCADE,
)
user = models.ForeignKey(
User,
related_name='deployment_tasks',
related_name='deployment_states',
on_delete=models.CASCADE,
)
# the inverse action of this task is requirred
# so we invert the task and manage its task_items accordingly
def invert_task(self):
# the inverse action of this state is requirred
# so we invert the state and manage its state_items accordingly
def invert_state(self):
LOGGER.debug(self.msg('inverting'))
previous_action = self.action
self.action = invert_action(previous_action)
self.save()
pending_sites = [task.site for task in self.task_items.all()]
pending_sites = [state.site for state in self.state_items.all()]
self.chancel_items()
# sites which already executed the task
# sites which already executed the state
# we have to send them an order to rollback the changes
for site in self.deployment.service.site.all():
if site not in pending_sites:
deploy = DeploymentTaskItem(
task=self,
deploy = DeploymentStateItem(
state=self,
site=site,
user=self.deployment.user
)
......@@ -685,83 +685,83 @@ class DeploymentTask(models.Model):
LOGGER.debug(deploy.msg('pending'))
@classmethod
def construct_deployment_task(cls, deployment, key):
# does a task exist for this key?
query = deployment.tasks.filter(key=key)
def construct_deployment_state(cls, deployment, key):
# does a state exist for this key?
query = deployment.states.filter(key=key)
if query.exists():
if len(query) > 1:
raise Exception('Unexpected query result')
task = query.first()
if task.action == 'deploy':
raise Exception('Constructing deployment task when one already exists')
state = query.first()
if state.action == 'deploy':
raise Exception('Constructing deployment state when one already exists')
task.invert_task()
return task
state.invert_state()
return state
else:
#create new task
task = cls(
#create new state
state = cls(
action='deploy',
deployment=deployment,
key=key,
user=deployment.user,
)
task.save()
LOGGER.debug(task.msg('pending'))
state.save()
LOGGER.debug(state.msg('pending'))
# generate task items
# generate state items
for site in deployment.service.site.all():
deploy = DeploymentTaskItem(
task=task,
deploy = DeploymentStateItem(
state=state,
site=site,
user=deployment.user
)
deploy.save()
LOGGER.debug(deploy.msg('pending'))
return task
return state
@classmethod
def construct_withdrawal_task(cls, deployment, key):
# does a task exist for this key?
query = deployment.tasks.filter(key=key)
def construct_withdrawal_state(cls, deployment, key):
# does a state exist for this key?
query = deployment.states.filter(key=key)
if query.exists():
if len(query) > 1:
raise Exception('Unexpected query result')
task = query.first()
if task.action == 'withdraw':
raise Exception('Constructing deployment task when one already exists')
state = query.first()
if state.action == 'withdraw':
raise Exception('Constructing deployment state when one already exists')
task.invert_task()
return task
state.invert_state()
return state
else:
# create a new task
task = cls(
# create a new state
state = cls(
action='withdraw',
deployment=deployment,
key=key,
user=deployment.user,
)
task.save()
LOGGER.debug(task.msg('pending'))
state.save()
LOGGER.debug(state.msg('pending'))
# generate task items
# generate state items
for site in deployment.service.site.all():
deploy = DeploymentTaskItem(
task=task,
deploy = DeploymentStateItem(
state=state,
site=site,
user=deployment.user
)
deploy.save()
LOGGER.debug(deploy.msg('pending'))
return task
return state
def chancel_items(self):
for item in self.task_items.all():
for item in self.state_items.all():
item.chancel()
def chancel(self):
......@@ -781,12 +781,12 @@ class DeploymentTask(models.Model):
)
def msg(self, msg):
return '[DeploymentTask:{}] {}'.format(self, msg)
return '[DState:{}] {}'.format(self, msg)
def publish(self):
# mitigating circular dependencies here
from .clientapi.serializers import DeploymentTaskSerializer
msg = json.dumps(DeploymentTaskSerializer(self).data)
from .clientapi.serializers import DeploymentStateSerializer
msg = json.dumps(DeploymentStateSerializer(self).data)
RabbitMQInstance.load().publish_by_service(
self.service,
......@@ -805,11 +805,11 @@ class DeploymentTask(models.Model):
)
def try_finished(self):
if not self.task_items.exists():
if not self.state_items.exists():
# finished sends its own message
self._finished()
# maintenance after all task items are done
# maintenance after all state items are done
def _finished(self):
LOGGER.info(self.msg('done'))
......@@ -842,22 +842,22 @@ def questionnaire_default():
def credential_default():
return {}
# DeploymentTaskItem: knows:
# DeploymentStateItem: knows:
# user, service, key, action, _and_ site
class DeploymentTaskItem(models.Model):
task = models.ForeignKey(
DeploymentTask,
related_name='task_items',
class DeploymentStateItem(models.Model):
state = models.ForeignKey(
DeploymentState,
related_name='state_items',
on_delete=models.CASCADE,
)
site = models.ForeignKey(
Site,
related_name='task_items',
related_name='state_items',
on_delete=models.CASCADE,
)
user = models.ForeignKey(
User,
related_name='deployment_task_items',
related_name='deployment_state_items',
on_delete=models.CASCADE,
)
STATE_CHOICES = (
......@@ -888,40 +888,40 @@ class DeploymentTaskItem(models.Model):
@property
def service(self):
return self.task.service
return self.state.service
@property
def key(self):
return self.task.key
return self.state.key
@property
def action(self):
return self.deployment.key
# the client acked the receipt and execution of the task for his site
# the client acked the receipt and execution of the state for his site
def success(self, credentials=None):
task = self.task
state = self.state
self.credentials = credentials
self.save()
LOGGER.debug(self.msg('success'))
# TODO test: does not deleting the task item work?
# TODO test: does not deleting the state item work?
# self.delete()
task.send_state_update()
state.send_state_update()
# TODO test: does not deleting the task work?
#task.try_finished()
# TODO test: does not deleting the state work?
#state.try_finished()
# the user changed the deployment
# chancel (delete) this task item
# chancel (delete) this state item
def chancel(self):
LOGGER.debug(self.msg('chanceled'))
self.delete()
# no update on chancel
# the next task will send an update
#task.send_state_update()
# the next state will send an update
#state.send_state_update()
# the client failed to execute the item
# the client can try again later
......@@ -931,7 +931,7 @@ class DeploymentTaskItem(models.Model):
self.state = 'failed'
self.save()
self.task.send_state_update()
self.state.send_state_update()
# the client failed to execute the item
# the client needs additional information from the user to try again
......@@ -942,7 +942,7 @@ class DeploymentTaskItem(models.Model):
self.questionnaire = questionnaire
self.save()
self.task.send_state_update()
self.state.send_state_update()
def questionnaire_answered(self, answers=None):
LOGGER.debug('%s %s', self.msg('answers'), answers)
......@@ -950,14 +950,14 @@ class DeploymentTaskItem(models.Model):
self.questionnaire = answers
self.save()
# publish the task item
# publish the state item
self.publish()
# only used when we got a questionnaire_answered
def publish(self):
# mitigating circular dependencies here
from .clientapi.serializers import DeploymentTaskSerializer
data = DeploymentTaskSerializer(self.task).data
from .clientapi.serializers import DeploymentStateSerializer
data = DeploymentStateSerializer(self.state).data
data['questionnaire'] = self.questionnaire
RabbitMQInstance.load().publish_by_site(
......@@ -968,13 +968,13 @@ class DeploymentTaskItem(models.Model):
def __str__(self):
return "{}@{}#{}".format(
self.task,
self.state,
self.site,
self.id,
)
def msg(self, msg):
return '[Depl. TaskItem:{}] {}'.format(self, msg)
return '[DSItem:{}] {}'.format(self, msg)
#
......
......@@ -114,11 +114,11 @@ class DeploymentTest(TestCase):
deployment = models.Deployment.get_deployment(user, service)
self.assertIsNotNone(deployment)
# no tasks exist yet