diff --git a/circle/common/operations.py b/circle/common/operations.py index b05dd6d..da985ac 100644 --- a/circle/common/operations.py +++ b/circle/common/operations.py @@ -74,7 +74,8 @@ class Operation(object): self.check_auth(user) self.check_precond() - activity = self.create_activity(parent=parent_activity, user=user) + activity = self.create_activity( + parent=parent_activity, user=user, kwargs=kwargs) return activity, allargs, auxargs @@ -150,7 +151,7 @@ class Operation(object): raise PermissionDenied("%s doesn't have the required permissions." % user) - def create_activity(self, parent, user): + def create_activity(self, parent, user, kwargs): raise NotImplementedError def on_abort(self, activity, error): @@ -159,6 +160,18 @@ class Operation(object): """ pass + def get_activity_name(self, kwargs): + try: + return self.activity_name + except AttributeError: + try: + return self.name._proxy____args[0] # ewww! + except AttributeError: + raise ImproperlyConfigured( + "Set Operation.activity_name to an ugettext_nooped " + "string or a create_readable call, or override " + "get_activity_name to create a name dynamically") + def on_commit(self, activity): """This method is called when the operation executes successfully. """ diff --git a/circle/vm/models/activity.py b/circle/vm/models/activity.py index 8752bd2..9ee9458 100644 --- a/circle/vm/models/activity.py +++ b/circle/vm/models/activity.py @@ -18,6 +18,7 @@ from __future__ import absolute_import, unicode_literals from contextlib import contextmanager from logging import getLogger +from warnings import warn from celery.signals import worker_ready from celery.contrib.abortable import AbortableAsyncResult @@ -28,7 +29,8 @@ from django.utils import timezone from django.utils.translation import ugettext_lazy as _, ugettext_noop from common.models import ( - ActivityModel, activitycontextimpl, create_readable, join_activity_code + ActivityModel, activitycontextimpl, create_readable, join_activity_code, + HumanReadableObject, ) from manager.mancelery import celery @@ -49,6 +51,18 @@ class ActivityInProgressError(Exception): self.activity = activity +def _normalize_readable_name(name, default=None): + if name is None: + warn("Set readable_name to a HumanReadableObject", + DeprecationWarning, 3) + name = default.replace(".", " ") + + if not isinstance(name, HumanReadableObject): + name = create_readable(name) + + return name + + class InstanceActivity(ActivityModel): ACTIVITY_CODE_BASE = join_activity_code('vm', 'Instance') instance = ForeignKey('Instance', related_name='activity_log', @@ -75,7 +89,9 @@ class InstanceActivity(ActivityModel): @classmethod def create(cls, code_suffix, instance, task_uuid=None, user=None, - concurrency_check=True): + concurrency_check=True, readable_name=None): + + readable_name = _normalize_readable_name(readable_name, code_suffix) # Check for concurrent activities active_activities = instance.activity_log.filter(finished__isnull=True) if concurrency_check and active_activities.exists(): @@ -84,11 +100,15 @@ class InstanceActivity(ActivityModel): activity_code = join_activity_code(cls.ACTIVITY_CODE_BASE, code_suffix) act = cls(activity_code=activity_code, instance=instance, parent=None, resultant_state=None, started=timezone.now(), + readable_name_data=readable_name.to_dict(), task_uuid=task_uuid, user=user) act.save() return act - def create_sub(self, code_suffix, task_uuid=None, concurrency_check=True): + def create_sub(self, code_suffix, task_uuid=None, concurrency_check=True, + readable_name=None): + + readable_name = _normalize_readable_name(readable_name, code_suffix) # Check for concurrent activities active_children = self.children.filter(finished__isnull=True) if concurrency_check and active_children.exists(): @@ -97,7 +117,8 @@ class InstanceActivity(ActivityModel): act = InstanceActivity( activity_code=join_activity_code(self.activity_code, code_suffix), instance=self.instance, parent=self, resultant_state=None, - started=timezone.now(), task_uuid=task_uuid, user=self.user) + readable_name_data=readable_name.to_dict(), started=timezone.now(), + task_uuid=task_uuid, user=self.user) act.save() return act @@ -158,10 +179,12 @@ class InstanceActivity(ActivityModel): @contextmanager def sub_activity(self, code_suffix, on_abort=None, on_commit=None, - task_uuid=None, concurrency_check=True): + readable_name=None, task_uuid=None, + concurrency_check=True): """Create a transactional context for a nested instance activity. """ - act = self.create_sub(code_suffix, task_uuid, concurrency_check) + act = self.create_sub(code_suffix, task_uuid, concurrency_check, + readable_name=readable_name) return activitycontextimpl(act, on_abort=on_abort, on_commit=on_commit) @@ -195,24 +218,32 @@ class NodeActivity(ActivityModel): self.node) @classmethod - def create(cls, code_suffix, node, task_uuid=None, user=None): + def create(cls, code_suffix, node, task_uuid=None, user=None, + readable_name=None): + + readable_name = _normalize_readable_name(readable_name, code_suffix) activity_code = join_activity_code(cls.ACTIVITY_CODE_BASE, code_suffix) act = cls(activity_code=activity_code, node=node, parent=None, + readable_name_data=readable_name.to_dict(), started=timezone.now(), task_uuid=task_uuid, user=user) act.save() return act - def create_sub(self, code_suffix, task_uuid=None): + def create_sub(self, code_suffix, task_uuid=None, readable_name=None): + + readable_name = _normalize_readable_name(readable_name, code_suffix) act = NodeActivity( activity_code=join_activity_code(self.activity_code, code_suffix), node=self.node, parent=self, started=timezone.now(), - task_uuid=task_uuid, user=self.user) + readable_name_data=readable_name.to_dict(), task_uuid=task_uuid, + user=self.user) act.save() return act @contextmanager - def sub_activity(self, code_suffix, task_uuid=None): - act = self.create_sub(code_suffix, task_uuid) + def sub_activity(self, code_suffix, task_uuid=None, readable_name=None): + act = self.create_sub(code_suffix, task_uuid, + readable_name=readable_name) return activitycontextimpl(act) diff --git a/circle/vm/operations.py b/circle/vm/operations.py index d83ac77..878609c 100644 --- a/circle/vm/operations.py +++ b/circle/vm/operations.py @@ -21,10 +21,11 @@ from re import search from django.core.exceptions import PermissionDenied from django.utils import timezone -from django.utils.translation import ugettext_lazy as _ +from django.utils.translation import ugettext_lazy as _, ugettext_noop from celery.exceptions import TimeLimitExceeded +from common.models import create_readable from common.operations import Operation, register_operation from .tasks.local_tasks import ( abortable_async_instance_operation, abortable_async_node_operation, @@ -59,7 +60,8 @@ class InstanceOperation(Operation): super(InstanceOperation, self).check_auth(user=user) - def create_activity(self, parent, user): + def create_activity(self, parent, user, kwargs): + name = self.get_activity_name(kwargs) if parent: if parent.instance != self.instance: raise ValueError("The instance associated with the specified " @@ -70,11 +72,13 @@ class InstanceOperation(Operation): "parent activity does not match the user " "provided as parameter.") - return parent.create_sub(code_suffix=self.activity_code_suffix) + return parent.create_sub(code_suffix=self.activity_code_suffix, + readable_name=name) else: return InstanceActivity.create( code_suffix=self.activity_code_suffix, instance=self.instance, - user=user, concurrency_check=self.concurrency_check) + readable_name=name, user=user, + concurrency_check=self.concurrency_check) def is_preferred(self): """If this is the recommended op in the current state of the instance. @@ -102,6 +106,10 @@ class AddInterfaceOperation(InstanceOperation): return net + def get_activity_name(self, kwargs): + return create_readable(ugettext_noop("add %(vlan)s interface"), + vlan=kwargs['vlan']) + register_operation(AddInterfaceOperation) @@ -646,7 +654,8 @@ class NodeOperation(Operation): super(NodeOperation, self).__init__(subject=node) self.node = node - def create_activity(self, parent, user): + def create_activity(self, parent, user, kwargs): + name = self.get_activity_name(kwargs) if parent: if parent.node != self.node: raise ValueError("The node associated with the specified " @@ -657,10 +666,12 @@ class NodeOperation(Operation): "parent activity does not match the user " "provided as parameter.") - return parent.create_sub(code_suffix=self.activity_code_suffix) + return parent.create_sub(code_suffix=self.activity_code_suffix, + readable_name=name) else: return NodeActivity.create(code_suffix=self.activity_code_suffix, - node=self.node, user=user) + node=self.node, user=user, + readable_name=name) class FlushOperation(NodeOperation):