activity.py 7.14 KB
Newer Older
1
from __future__ import absolute_import, unicode_literals
Őry Máté committed
2 3 4
from contextlib import contextmanager
from logging import getLogger

Kálmán Viktor committed
5 6
from celery.contrib.abortable import AbortableAsyncResult

7
from django.core.urlresolvers import reverse
8
from django.db.models import CharField, ForeignKey
Őry Máté committed
9 10 11
from django.utils import timezone
from django.utils.translation import ugettext_lazy as _

12
from common.models import (
13
    ActivityModel, activitycontextimpl, join_activity_code, split_activity_code
14 15
)

Kálmán Viktor committed
16 17
from manager.mancelery import celery

18

Őry Máté committed
19 20 21
logger = getLogger(__name__)


22 23 24 25 26 27 28 29 30 31 32 33
class ActivityInProgressError(Exception):

        def __init__(self, activity, message=None):
            if message is None:
                message = ("Another activity is currently in progress: '%s'."
                           % activity.activity_code)

            Exception.__init__(self, message)

            self.activity = activity


Őry Máté committed
34
class InstanceActivity(ActivityModel):
35
    ACTIVITY_CODE_BASE = join_activity_code('vm', 'Instance')
Őry Máté committed
36 37 38
    instance = ForeignKey('Instance', related_name='activity_log',
                          help_text=_('Instance this activity works on.'),
                          verbose_name=_('instance'))
39
    resultant_state = CharField(blank=True, max_length=20, null=True)
Őry Máté committed
40 41 42 43

    class Meta:
        app_label = 'vm'
        db_table = 'vm_instanceactivity'
44
        ordering = ['-finished', '-started', 'instance', '-id']
Őry Máté committed
45 46 47 48 49 50 51 52 53 54

    def __unicode__(self):
        if self.parent:
            return '{}({})->{}'.format(self.parent.activity_code,
                                       self.instance,
                                       self.activity_code)
        else:
            return '{}({})'.format(self.activity_code,
                                   self.instance)

Dudás Ádám committed
55 56
    def abort(self):
        AbortableAsyncResult(self.task_uuid, backend=celery.backend).abort()
57

Őry Máté committed
58
    @classmethod
59 60
    def create(cls, code_suffix, instance, task_uuid=None, user=None,
               concurrency_check=True):
61 62
        # Check for concurrent activities
        active_activities = instance.activity_log.filter(finished__isnull=True)
63
        if concurrency_check and active_activities.exists():
64 65
            raise ActivityInProgressError(active_activities[0])

66 67 68 69
        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(),
                  task_uuid=task_uuid, user=user)
Őry Máté committed
70 71 72
        act.save()
        return act

73
    def create_sub(self, code_suffix, task_uuid=None, concurrency_check=True):
74 75
        # Check for concurrent activities
        active_children = self.children.filter(finished__isnull=True)
76
        if concurrency_check and active_children.exists():
77 78
            raise ActivityInProgressError(active_children[0])

Őry Máté committed
79
        act = InstanceActivity(
80
            activity_code=join_activity_code(self.activity_code, code_suffix),
81 82
            instance=self.instance, parent=self, resultant_state=None,
            started=timezone.now(), task_uuid=task_uuid, user=self.user)
Őry Máté committed
83 84 85
        act.save()
        return act

Dudás Ádám committed
86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105
    def get_absolute_url(self):
        return reverse('dashboard.views.vm-activity', args=[self.pk])

    def get_readable_name(self):
        activity_code_last_suffix = split_activity_code(self.activity_code)[-1]
        return activity_code_last_suffix.replace('_', ' ').capitalize()

    def get_status_id(self):
        if self.succeeded is None:
            return 'wait'
        elif self.succeeded:
            return 'success'
        else:
            return 'failed'

    @property
    def is_abortable(self):
        """Can the activity be aborted?

        :returns: True if the activity can be aborted; otherwise, False.
106
        """
Dudás Ádám committed
107 108
        op = self.instance.get_operation_from_activity_code(self.activity_code)
        return self.task_uuid and op and op.abortable and not self.finished
Őry Máté committed
109

110 111 112 113 114 115 116
    def is_abortable_for(self, user):
        """Can the given user abort the activity?
        """

        return self.is_abortable and (
            user.is_superuser or user in (self.instance.owner, self.user))

117 118 119 120 121 122 123 124 125
    @property
    def is_aborted(self):
        """Has the activity been aborted?

        :returns: True if the activity has been aborted; otherwise, False.
        """
        return self.task_uuid and AbortableAsyncResult(self.task_uuid
                                                       ).is_aborted()

126 127 128 129 130
    def save(self, *args, **kwargs):
        ret = super(InstanceActivity, self).save(*args, **kwargs)
        self.instance._update_status()
        return ret

Dudás Ádám committed
131 132 133 134 135 136 137
    @contextmanager
    def sub_activity(self, code_suffix, on_abort=None, on_commit=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)
        return activitycontextimpl(act, on_abort=on_abort, on_commit=on_commit)
Kálmán Viktor committed
138

Őry Máté committed
139 140

@contextmanager
141
def instance_activity(code_suffix, instance, on_abort=None, on_commit=None,
142
                      task_uuid=None, user=None, concurrency_check=True):
143 144
    """Create a transactional context for an instance activity.
    """
145 146
    act = InstanceActivity.create(code_suffix, instance, task_uuid, user,
                                  concurrency_check)
147
    return activitycontextimpl(act, on_abort=on_abort, on_commit=on_commit)
148 149 150


class NodeActivity(ActivityModel):
151
    ACTIVITY_CODE_BASE = join_activity_code('vm', 'Node')
152 153 154 155 156 157 158 159
    node = ForeignKey('Node', related_name='activity_log',
                      help_text=_('Node this activity works on.'),
                      verbose_name=_('node'))

    class Meta:
        app_label = 'vm'
        db_table = 'vm_nodeactivity'

160 161 162 163 164 165 166 167 168 169 170 171
    def __unicode__(self):
        if self.parent:
            return '{}({})->{}'.format(self.parent.activity_code,
                                       self.node,
                                       self.activity_code)
        else:
            return '{}({})'.format(self.activity_code,
                                   self.node)

    def get_readable_name(self):
        return self.activity_code.split('.')[-1].replace('_', ' ').capitalize()

172 173
    @classmethod
    def create(cls, code_suffix, node, task_uuid=None, user=None):
174 175 176
        activity_code = join_activity_code(cls.ACTIVITY_CODE_BASE, code_suffix)
        act = cls(activity_code=activity_code, node=node, parent=None,
                  started=timezone.now(), task_uuid=task_uuid, user=user)
177 178 179 180 181
        act.save()
        return act

    def create_sub(self, code_suffix, task_uuid=None):
        act = NodeActivity(
182
            activity_code=join_activity_code(self.activity_code, code_suffix),
183 184 185 186 187 188 189 190 191 192 193 194 195
            node=self.node, parent=self, started=timezone.now(),
            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)
        return activitycontextimpl(act)


@contextmanager
def node_activity(code_suffix, node, task_uuid=None, user=None):
196
    act = NodeActivity.create(code_suffix, node, task_uuid, user)
197
    return activitycontextimpl(act)