Commit 844200d8 authored by Lukas Burgey's avatar Lukas Burgey
Browse files

Make service and vo deployment cooperate with each other

parent 2e2602ff
# pylint: disable=global-statement
import logging
from logging import getLogger
from json import dumps
from requests.auth import HTTPBasicAuth
from django.db import models
......@@ -10,7 +11,7 @@ from django_mysql.models import JSONField
import pika
from pika.exceptions import ConnectionClosed
LOGGER = logging.getLogger(__name__)
LOGGER = getLogger(__name__)
RABBITMQ_CONNECTION = None
......@@ -23,6 +24,14 @@ def exchanges_default():
]
def publish_to_user(user, obj):
from . import serializers
RabbitMQInstance.load().publish_to_user(
user,
dumps(serializers.UpdateSerializer(obj).data),
)
# singleton for simple configs
# https://steelkiwi.com/blog/practical-application-singleton-design-pattern/
class SingletonModel(models.Model):
......@@ -159,21 +168,6 @@ class RabbitMQInstance(SingletonModel):
msg,
)
def publish_by_service(self, service, msg):
self._publish(
'services',
service.name,
msg,
)
def publish_by_vo(self, vo, msg):
self._publish(
vo.broker_exchange,
vo.name,
msg,
)
def publish_to_user(self, user, msg):
self._publish(
'users',
......
......@@ -10,9 +10,8 @@ from polymorphic.models import PolymorphicModel
from feudal.backend.auth.v1.models.vo import VO
from . import Site, Service
from . import Site, Service, brokers
from .users import User, SSHPublicKey
from .brokers import RabbitMQInstance
LOGGER = getLogger(__name__)
......@@ -113,15 +112,7 @@ class Deployment(PolymorphicModel):
self.state_target = target
self.save()
# FIXME this is breaking things when one wants deploy and another gets a user_remove
# # set the target to all credentials
# for state in self.states.all():
# state.assure_credential_states_exist()
# for credential_state in CredentialState.objects.filter(
# target__deployments=self,
# ):
# credential_state.set_target(target)
self.publish_to_user()
# Deployment.user_deploy
def user_deploy(self):
......@@ -132,26 +123,31 @@ class Deployment(PolymorphicModel):
for item in self.states.filter(~Q(state=DEPLOYED)):
item.user_deploy()
self.publish()
self.publish_to_client()
# Deployment.user_remove
def user_remove(self):
LOGGER.debug(self.msg('user_remove'))
can_publish = False
self._set_target(NOT_DEPLOYED)
# states which are not NOT_DEPLOYED
for item in self.states.filter(~Q(state=NOT_DEPLOYED)):
if item.user_remove():
can_publish = True
# we only publish to the clients if allowed
# we are not allowed to publish a removal if another deployment for our
# DeploymentStates exists and has the target DEPLOYED
if can_publish:
self.publish()
else:
self.publish_to_user()
item.user_remove()
# we always publish to the client
# ATTENTION! This may cause removals of Services which are wanted by other deployments!
# These will be deployed again when their DepState realises the error
self.publish_to_client()
# this did not make sense
# # we only publish to the clients if allowed
# # we are not allowed to publish a removal if another deployment for our
# # DeploymentStates exists and has the target DEPLOYED
# if can_publish:
# self.publish()
# else:
# self.publish_to_user()
def user_credential_added(self, key):
for item in self.states.all():
......@@ -167,13 +163,13 @@ class Deployment(PolymorphicModel):
def publish_to_client(self):
if settings.DEBUG_PUBLISHING:
LOGGER.debug(self.msg('publish_to_client'))
LOGGER.debug(self.msg('publish_to_client: {}'.format(self.state_target)))
from .serializers import clients
data = clients.DeploymentSerializer(self).data
msg = dumps(data)
RabbitMQInstance.load().publish(
brokers.RabbitMQInstance.load().publish(
self,
msg,
)
......@@ -184,15 +180,13 @@ class Deployment(PolymorphicModel):
return
if settings.DEBUG_PUBLISHING:
LOGGER.debug(self.msg('publish_to_user'))
LOGGER.debug(self.msg('publish_to_user: {}'.format(self.state_target)))
from . import serializers
msg = dumps({
'deployment': serializers.DeploymentSerializer(self).data,
})
RabbitMQInstance.load().publish_to_user(
brokers.publish_to_user(
self.user,
msg,
{
'deployment': self,
},
)
def publish(self):
......@@ -460,7 +454,7 @@ class DeploymentState(models.Model):
# starts tracking this the credential for this item
def user_credential_added(self, credential):
if settings.DEBUG_CREDENTIALS:
LOGGER.debug('user_credential_added: %s %s', self, credential)
LOGGER.debug(self.msg('Adding credential {}'.format(credential.name)))
CredentialState.get_credential_state(
credential,
......@@ -469,7 +463,7 @@ class DeploymentState(models.Model):
def user_credential_removed(self, credential):
if settings.DEBUG_CREDENTIALS:
LOGGER.debug('user_credential_removed: %s %s', self, credential)
LOGGER.debug(self.msg('Removing credential {}'.format(credential.name)))
try:
credential_state = self.credential_states.get(credential=credential)
......@@ -496,10 +490,7 @@ class DeploymentState(models.Model):
LOGGER.debug(self.msg('State: already deployed'))
return
self._set_state(
DEPLOYMENT_PENDING,
publish=False, # the post response already contains the update
)
self._set_state(DEPLOYMENT_PENDING)
# DeploymentState.user_remove
# returns True if no other deployment needs this state_item to be deployed
......@@ -524,20 +515,17 @@ class DeploymentState(models.Model):
or self.state == REJECTED)
):
self._reset()
self._set_state(NOT_DEPLOYED, publish=False)
self._set_state(NOT_DEPLOYED)
elif (
self.state == DEPLOYMENT_PENDING
or self.state == QUESTIONNAIRE
):
self._set_state(NOT_DEPLOYED)
self._set_state(NOT_DEPLOYED, publish=True)
else:
# default: start the removal process
self._set_state(
REMOVAL_PENDING,
publish=False, # the post response already contains the update
)
self._set_state(REMOVAL_PENDING)
# True: signal the callee that a publish_to_client is permitted
return True
......@@ -549,7 +537,7 @@ class DeploymentState(models.Model):
return
self.questionnaire = answers
self._set_state(DEPLOYMENT_PENDING, publish=False)
self._set_state(DEPLOYMENT_PENDING)
self.publish_to_client()
def client_credential_states(self, credential_states):
......@@ -576,14 +564,11 @@ class DeploymentState(models.Model):
state = output.get('state', '')
LOGGER.debug(self.msg('Client response: {}'.format(state)))
self._set_state(state)
credential_states = output.get('user_credential_states', None)
if credential_states is not None:
self.client_credential_states(credential_states)
self.message = output.get('message', '')
self.save()
# update values
if state == DEPLOYED:
......@@ -605,9 +590,14 @@ class DeploymentState(models.Model):
else:
return 'unknown state "{}"'.format(state)
# FIXME this apparently causes deployment loops
# _set_state does not always save!
self.save()
# publish after we updated the values of the response
self._set_state(state, publish=True)
# is the target reached now?
if self.state_target != self.state:
if str(self.state_target) != str(self.state):
LOGGER.debug(self.msg('Target {} still not reached. Publishing again'.format(
self.state_target,
)))
......@@ -633,10 +623,14 @@ class DeploymentState(models.Model):
except CredentialState.DoesNotExist:
LOGGER.error('CredentialState.DoesNotExist in _set_state')
def _set_state(self, state, publish=True):
def _set_state(self, state, publish=False):
self._assure_credential_states_exist()
# not trans
if str(self.state) == str(state):
# publish to user (even if the state did not change!)
if publish:
self.publish_to_user()
return
LOGGER.debug(self.msg('State: {} -> {} - Target: {}'.format(self.state, state, self.state_target)))
......@@ -644,33 +638,41 @@ class DeploymentState(models.Model):
self.state = state
self.save()
# publish to user
if publish and self.deployments.exists():
if publish:
self.publish_to_user()
def publish_to_user(self):
for deployment in self.deployments.all():
deployment.publish_to_user()
if self.user is not None:
if settings.DEBUG_PUBLISHING:
LOGGER.debug(self.msg('publish_to_user'))
brokers.publish_to_user(
self.user,
{
'deployment_state': self,
},
)
def publish_to_client(self):
for deployment in self.deployments.all():
# only publish to the client using deployments with our target
for deployment in self.deployments.filter(state_target=self.state_target):
deployment.publish_to_client()
def msg(self, msg):
return '{} - {}'.format(self, msg)
return ' {} - {}'.format(self, msg)
def __str__(self):
if self.deployments.exists():
deployment_names = [str(deployment.id) for deployment in self.deployments.all()]
return 'DState: ({}:{}:{})#{}'.format(
return 'Dep-St: ({}:{}:{})#{}'.format(
','.join(deployment_names),
self.service,
self.site,
self.id,
)
return 'DState: (ORPHANED:{}:{})#{}'.format(
return 'Dep-St: (ORPHANED:{}:{})#{}'.format(
self.service,
self.site,
self.id,
......@@ -763,13 +765,10 @@ class CredentialState(models.Model):
self.state = state
self.save()
self.target.publish_to_user()
def credential_deleted(self):
if self.state == NOT_DEPLOYED:
self._delete_state()
LOGGER.debug('related credential: %s', self.credential)
self.state_target = NOT_DEPLOYED
self._credential_deleted = True
self.save()
......@@ -785,10 +784,10 @@ class CredentialState(models.Model):
credential.try_delete_key()
def msg(self, message):
return '{} - {}'.format(self, message)
return ' {} - {}'.format(self, message)
def __str__(self):
return 'CState: ({}:{})#{}'.format(
return 'Cred-St: ({}:{})#{}'.format(
self.target.id,
self.credential,
self.id,
......
......@@ -117,6 +117,7 @@ class VODeploymentSerializer(serializers.ModelSerializer):
class ServiceDeploymentSerializer(serializers.ModelSerializer):
states = DeploymentStateSerializer(many=True)
service = ServiceSerializer()
class Meta:
model = ServiceDeployment
fields = DEPLOYMENT_FIELDS + (
......@@ -132,41 +133,33 @@ class DeploymentSerializer(PolymorphicSerializer):
}
# contains properties which change less often
class UserSerializer(serializers.ModelSerializer):
vos = VOSerializer(many=True)
ssh_keys = SSHPublicKeySerializer(many=True)
class Meta:
model = User
fields = [
'id',
'profile_name',
'ssh_keys',
'userinfo',
'vos',
]
class UserStateSerializer(serializers.ModelSerializer):
vos = VOSerializer(many=True)
ssh_keys = SSHPublicKeySerializer(many=True)
deployments = DeploymentSerializer(many=True)
services = ServiceSerializer(many=True)
ssh_keys = SSHPublicKeySerializer(many=True)
states = DeploymentStateSerializer(many=True)
vos = VOSerializer(many=True)
class Meta:
model = User
fields = [
fields = (
'deployments',
'id',
'profile_name',
'services',
'ssh_keys',
'states',
'userinfo',
'vos',
]
)
class StateSerializer(serializers.Serializer):
error = serializers.CharField(allow_blank=True, required=False)
user = UserStateSerializer()
class UpdateSerializer(serializers.Serializer):
error = serializers.CharField(allow_blank=True, required=False)
deployment = DeploymentSerializer(required=False)
deployment_state = DeploymentStateSerializer(required=False)
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