Commit 5c0672bc by Őry Máté

Merge branch 'nostate-operation' into 'master'

Nostate Operation
parents 46a12f1e 865a8ba5
...@@ -536,7 +536,7 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -536,7 +536,7 @@ class VmDetailTest(LoginMixin, TestCase):
with patch.object(WakeUpOperation, 'async') as mock_method: with patch.object(WakeUpOperation, 'async') as mock_method:
inst = Instance.objects.get(pk=1) inst = Instance.objects.get(pk=1)
mock_method.side_effect = inst.wake_up mock_method.side_effect = inst.wake_up
inst.manual_state_change('RUNNING') inst.status = 'RUNNING'
inst.set_level(self.u2, 'owner') inst.set_level(self.u2, 'owner')
with patch('dashboard.views.messages') as msg: with patch('dashboard.views.messages') as msg:
c.post("/dashboard/vm/1/op/wake_up/") c.post("/dashboard/vm/1/op/wake_up/")
...@@ -554,7 +554,7 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -554,7 +554,7 @@ class VmDetailTest(LoginMixin, TestCase):
inst = Instance.objects.get(pk=1) inst = Instance.objects.get(pk=1)
new_wake_up.side_effect = inst.wake_up new_wake_up.side_effect = inst.wake_up
inst.get_remote_queue_name = Mock(return_value='test') inst.get_remote_queue_name = Mock(return_value='test')
inst.manual_state_change('SUSPENDED') inst.status = 'SUSPENDED'
inst.set_level(self.u2, 'owner') inst.set_level(self.u2, 'owner')
with patch('dashboard.views.messages') as msg: with patch('dashboard.views.messages') as msg:
response = c.post("/dashboard/vm/1/op/wake_up/") response = c.post("/dashboard/vm/1/op/wake_up/")
...@@ -568,12 +568,10 @@ class VmDetailTest(LoginMixin, TestCase): ...@@ -568,12 +568,10 @@ class VmDetailTest(LoginMixin, TestCase):
c = Client() c = Client()
self.login(c, "user2") self.login(c, "user2")
inst = Instance.objects.get(pk=1) inst = Instance.objects.get(pk=1)
inst.manual_state_change('SUSPENDED') inst.status = 'SUSPENDED'
inst.set_level(self.u2, 'user') inst.set_level(self.u2, 'user')
response = c.post("/dashboard/vm/1/op/wake_up/") response = c.post("/dashboard/vm/1/op/wake_up/")
self.assertEqual(response.status_code, 403) self.assertEqual(response.status_code, 403)
inst = Instance.objects.get(pk=1)
self.assertEqual(inst.status, 'SUSPENDED')
def test_non_existing_template_get(self): def test_non_existing_template_get(self):
c = Client() c = Client()
......
...@@ -335,6 +335,7 @@ class VmDetailView(CheckedDetailView): ...@@ -335,6 +335,7 @@ class VmDetailView(CheckedDetailView):
for k, v in options.iteritems(): for k, v in options.iteritems():
if request.POST.get(k) is not None: if request.POST.get(k) is not None:
return v(request) return v(request)
raise Http404()
raise Http404() raise Http404()
...@@ -866,6 +867,8 @@ vm_ops = OrderedDict([ ...@@ -866,6 +867,8 @@ vm_ops = OrderedDict([
op='shut_off', icon='ban', effect='warning')), op='shut_off', icon='ban', effect='warning')),
('recover', VmOperationView.factory( ('recover', VmOperationView.factory(
op='recover', icon='medkit', effect='warning')), op='recover', icon='medkit', effect='warning')),
('nostate', VmOperationView.factory(
op='emergency_change_state', icon='legal', effect='danger')),
('destroy', VmOperationView.factory( ('destroy', VmOperationView.factory(
extra_bases=[TokenOperationView], extra_bases=[TokenOperationView],
op='destroy', icon='times', effect='danger')), op='destroy', icon='times', effect='danger')),
......
...@@ -30,7 +30,7 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop ...@@ -30,7 +30,7 @@ from django.utils.translation import ugettext_lazy as _, ugettext_noop
from common.models import ( from common.models import (
ActivityModel, activitycontextimpl, create_readable, join_activity_code, ActivityModel, activitycontextimpl, create_readable, join_activity_code,
HumanReadableObject, HumanReadableObject, HumanReadableException,
) )
from manager.mancelery import celery from manager.mancelery import celery
...@@ -39,16 +39,17 @@ from manager.mancelery import celery ...@@ -39,16 +39,17 @@ from manager.mancelery import celery
logger = getLogger(__name__) logger = getLogger(__name__)
class ActivityInProgressError(Exception): class ActivityInProgressError(HumanReadableException):
def __init__(self, activity, message=None): @classmethod
if message is None: def create(cls, activity):
message = ("Another activity is currently in progress: '%s'." obj = super(ActivityInProgressError, cls).create(
% activity.activity_code) ugettext_noop("%(activity)s activity is currently in progress."),
ugettext_noop("%(activity)s (%(pk)s) activity is currently "
Exception.__init__(self, message) "in progress."),
activity=activity.readable_name, pk=activity.pk)
self.activity = activity obj.activity = activity
return obj
def _normalize_readable_name(name, default=None): def _normalize_readable_name(name, default=None):
...@@ -95,7 +96,7 @@ class InstanceActivity(ActivityModel): ...@@ -95,7 +96,7 @@ class InstanceActivity(ActivityModel):
# Check for concurrent activities # Check for concurrent activities
active_activities = instance.activity_log.filter(finished__isnull=True) active_activities = instance.activity_log.filter(finished__isnull=True)
if concurrency_check and active_activities.exists(): if concurrency_check and active_activities.exists():
raise ActivityInProgressError(active_activities[0]) raise ActivityInProgressError.create(active_activities[0])
activity_code = join_activity_code(cls.ACTIVITY_CODE_BASE, code_suffix) activity_code = join_activity_code(cls.ACTIVITY_CODE_BASE, code_suffix)
act = cls(activity_code=activity_code, instance=instance, parent=None, act = cls(activity_code=activity_code, instance=instance, parent=None,
...@@ -112,7 +113,7 @@ class InstanceActivity(ActivityModel): ...@@ -112,7 +113,7 @@ class InstanceActivity(ActivityModel):
# Check for concurrent activities # Check for concurrent activities
active_children = self.children.filter(finished__isnull=True) active_children = self.children.filter(finished__isnull=True)
if concurrency_check and active_children.exists(): if concurrency_check and active_children.exists():
raise ActivityInProgressError(active_children[0]) raise ActivityInProgressError.create(active_children[0])
act = InstanceActivity( act = InstanceActivity(
activity_code=join_activity_code(self.activity_code, code_suffix), activity_code=join_activity_code(self.activity_code, code_suffix),
......
...@@ -271,6 +271,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -271,6 +271,7 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
('create_vm', _('Can create a new VM.')), ('create_vm', _('Can create a new VM.')),
('config_ports', _('Can configure port forwards.')), ('config_ports', _('Can configure port forwards.')),
('recover', _('Can recover a destroyed VM.')), ('recover', _('Can recover a destroyed VM.')),
('emergency_change_state', _('Can change VM state to NOSTATE.')),
) )
verbose_name = _('instance') verbose_name = _('instance')
verbose_name_plural = _('instances') verbose_name_plural = _('instances')
...@@ -444,27 +445,12 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin, ...@@ -444,27 +445,12 @@ class Instance(AclBase, VirtualMachineDescModel, StatusModel, OperatedMixin,
self.time_of_suspend, self.time_of_delete = self.get_renew_times() self.time_of_suspend, self.time_of_delete = self.get_renew_times()
super(Instance, self).clean(*args, **kwargs) super(Instance, self).clean(*args, **kwargs)
def manual_state_change(self, new_state="NOSTATE", reason=None, user=None):
""" Manually change state of an Instance.
Can be used to recover VM after administrator fixed problems.
"""
# TODO cancel concurrent activity (if exists)
act = InstanceActivity.create(
code_suffix='manual_state_change', instance=self, user=user,
readable_name=create_readable(ugettext_noop(
"force %(state)s state"), state=new_state))
act.finished = act.started
act.result = reason
act.resultant_state = new_state
act.succeeded = True
act.save()
def vm_state_changed(self, new_state): def vm_state_changed(self, new_state):
# log state change # log state change
try: try:
act = InstanceActivity.create(code_suffix='vm_state_changed', act = InstanceActivity.create(code_suffix='vm_state_changed',
instance=self) instance=self,
readable_name="vm state changed")
except ActivityInProgressError: except ActivityInProgressError:
pass # discard state change if another activity is in progress. pass # discard state change if another activity is in progress.
else: else:
......
...@@ -722,6 +722,21 @@ class RenewOperation(InstanceOperation): ...@@ -722,6 +722,21 @@ class RenewOperation(InstanceOperation):
register_operation(RenewOperation) register_operation(RenewOperation)
class ChangeStateOperation(InstanceOperation):
activity_code_suffix = 'emergency_change_state'
id = 'emergency_change_state'
name = _("emergency change state")
description = _("Change the virtual machine state to NOSTATE")
acl_level = "owner"
required_perms = ('vm.emergency_change_state', )
def _operation(self, user, activity, new_state="NOSTATE"):
activity.resultant_state = new_state
register_operation(ChangeStateOperation)
class NodeOperation(Operation): class NodeOperation(Operation):
async_operation = abortable_async_node_operation async_operation = abortable_async_node_operation
host_cls = Node host_cls = Node
......
...@@ -66,8 +66,8 @@ class InstanceTestCase(TestCase): ...@@ -66,8 +66,8 @@ class InstanceTestCase(TestCase):
inst = MagicMock(spec=Instance, node=node, vnc_port=port) inst = MagicMock(spec=Instance, node=node, vnc_port=port)
inst.save.side_effect = AssertionError inst.save.side_effect = AssertionError
with patch('vm.models.instance.InstanceActivity') as ia: with patch('vm.models.instance.InstanceActivity') as ia:
ia.create.side_effect = ActivityInProgressError(MagicMock()) ia.create.side_effect = ActivityInProgressError.create(MagicMock())
Instance.vm_state_changed(inst, 'STOPPED') Instance.status = 'STOPPED'
self.assertEquals(inst.node, node) self.assertEquals(inst.node, node)
self.assertEquals(inst.vnc_port, port) self.assertEquals(inst.vnc_port, port)
......
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 sign in to comment