Commit faaace05 by Őry Máté

Merge branch 'feature-vm-tasks' into 'master'

Sub- and Remote Operations

closes #304

requires [vmdriver!4](vmdriver!4)

See merge request !220
parents 664f2527 5254e2ad
......@@ -26,6 +26,17 @@ from .models import activity_context, has_suffix, humanize_exception
logger = getLogger(__name__)
class SubOperationMixin(object):
required_perms = ()
def create_activity(self, parent, user, kwargs):
if not parent:
raise TypeError("SubOperation can only be called with "
"parent_activity specified.")
return super(SubOperationMixin, self).create_activity(
parent, user, kwargs)
class Operation(object):
"""Base class for VM operations.
"""
......@@ -36,6 +47,10 @@ class Operation(object):
abortable = False
has_percentage = False
@classmethod
def get_activity_code_suffix(cls):
return cls.id
def __call__(self, **kwargs):
return self.call(**kwargs)
......@@ -232,7 +247,7 @@ class OperatedMixin(object):
operation could be found.
"""
for op in getattr(self, operation_registry_name, {}).itervalues():
if has_suffix(activity_code, op.activity_code_suffix):
if has_suffix(activity_code, op.get_activity_code_suffix()):
return op(self)
else:
return None
......
......@@ -27,9 +27,7 @@ class OperationTestCase(TestCase):
class AbortEx(Exception):
pass
op = Operation(MagicMock())
op.activity_code_suffix = 'test'
op.id = 'test'
op = TestOp(MagicMock())
op.async_operation = MagicMock(
apply_async=MagicMock(side_effect=AbortEx))
......@@ -44,9 +42,7 @@ class OperationTestCase(TestCase):
class AbortEx(Exception):
pass
op = Operation(MagicMock())
op.activity_code_suffix = 'test'
op.id = 'test'
op = TestOp(MagicMock())
with patch.object(Operation, 'create_activity', side_effect=AbortEx):
with patch.object(Operation, 'check_precond') as chk_pre:
try:
......@@ -55,9 +51,7 @@ class OperationTestCase(TestCase):
self.assertTrue(chk_pre.called)
def test_auth_check_on_non_system_call(self):
op = Operation(MagicMock())
op.activity_code_suffix = 'test'
op.id = 'test'
op = TestOp(MagicMock())
user = MagicMock()
with patch.object(Operation, 'check_auth') as check_auth:
with patch.object(Operation, 'check_precond'), \
......@@ -67,9 +61,7 @@ class OperationTestCase(TestCase):
check_auth.assert_called_with(user)
def test_no_auth_check_on_system_call(self):
op = Operation(MagicMock())
op.activity_code_suffix = 'test'
op.id = 'test'
op = TestOp(MagicMock())
with patch.object(Operation, 'check_auth', side_effect=AssertionError):
with patch.object(Operation, 'check_precond'), \
patch.object(Operation, 'create_activity'), \
......@@ -77,39 +69,25 @@ class OperationTestCase(TestCase):
op.call(system=True)
def test_no_exception_for_more_arguments_when_operation_takes_kwargs(self):
class KwargOp(Operation):
activity_code_suffix = 'test'
id = 'test'
def _operation(self, **kwargs):
pass
op = KwargOp(MagicMock())
with patch.object(KwargOp, 'create_activity'), \
patch.object(KwargOp, '_exec_op'):
op = TestOp(MagicMock())
with patch.object(TestOp, 'create_activity'), \
patch.object(TestOp, '_exec_op'):
op.call(system=True, foo=42)
def test_exception_for_unexpected_arguments(self):
class TestOp(Operation):
activity_code_suffix = 'test'
id = 'test'
def _operation(self):
pass
op = TestOp(MagicMock())
with patch.object(TestOp, 'create_activity'), \
patch.object(TestOp, '_exec_op'):
self.assertRaises(TypeError, op.call, system=True, foo=42)
self.assertRaises(TypeError, op.call, system=True, bar=42)
def test_exception_for_missing_arguments(self):
class TestOp(Operation):
activity_code_suffix = 'test'
id = 'test'
def _operation(self, foo):
pass
op = TestOp(MagicMock())
with patch.object(TestOp, 'create_activity'):
self.assertRaises(TypeError, op.call, system=True)
class TestOp(Operation):
id = 'test'
def _operation(self, foo):
pass
......@@ -512,20 +512,20 @@ class VmDetailTest(LoginMixin, TestCase):
self.login(c, "user2")
with patch.object(Instance, 'select_node', return_value=None), \
patch.object(WakeUpOperation, 'async') as new_wake_up, \
patch('vm.tasks.vm_tasks.wake_up.apply_async') as wuaa, \
patch.object(Instance.WrongStateError, 'send_message') as wro:
inst = Instance.objects.get(pk=1)
new_wake_up.side_effect = inst.wake_up
inst._wake_up_vm = Mock()
inst.get_remote_queue_name = Mock(return_value='test')
inst.status = 'SUSPENDED'
inst.set_level(self.u2, 'owner')
with patch('dashboard.views.messages') as msg:
response = c.post("/dashboard/vm/1/op/wake_up/")
assert not msg.error.called
assert inst._wake_up_vm.called
self.assertEqual(response.status_code, 302)
self.assertEqual(inst.status, 'RUNNING')
assert new_wake_up.called
assert wuaa.called
assert not wro.called
def test_unpermitted_wake_up(self):
......
......@@ -7,7 +7,6 @@ from .common import BaseResourceConfigModel
from .common import Lease
from .common import NamedBaseResourceConfig
from .common import Trait
from .instance import InstanceActiveManager
from .instance import VirtualMachineDescModel
from .instance import InstanceTemplate
from .instance import Instance
......@@ -19,7 +18,7 @@ from .network import Interface
from .node import Node
__all__ = [
'InstanceActivity', 'InstanceActiveManager', 'BaseResourceConfigModel',
'InstanceActivity', 'BaseResourceConfigModel',
'NamedBaseResourceConfig', 'VirtualMachineDescModel', 'InstanceTemplate',
'Instance', 'instance_activity', 'post_state_changed', 'pre_state_changed',
'InterfaceTemplate', 'Interface', 'Trait', 'Node', 'NodeActivity', 'Lease',
......
......@@ -29,7 +29,8 @@ from ..models import (
)
from ..models.instance import find_unused_port, ActivityInProgressError
from ..operations import (
DeployOperation, DestroyOperation, FlushOperation, MigrateOperation,
RemoteOperationMixin, DeployOperation, DestroyOperation, FlushOperation,
MigrateOperation,
)
......@@ -89,7 +90,7 @@ class InstanceTestCase(TestCase):
self.assertFalse(inst.save.called)
def test_destroy_sets_destroyed(self):
inst = Mock(destroyed_at=None, spec=Instance,
inst = Mock(destroyed_at=None, spec=Instance, _delete_vm=Mock(),
InstanceDestroyedError=Instance.InstanceDestroyedError)
inst.node = MagicMock(spec=Node)
inst.disks.all.return_value = []
......@@ -105,7 +106,8 @@ class InstanceTestCase(TestCase):
inst.node = MagicMock(spec=Node)
inst.status = 'RUNNING'
migrate_op = MigrateOperation(inst)
with patch('vm.models.instance.vm_tasks.migrate') as migr:
with patch('vm.operations.vm_tasks.migrate') as migr, \
patch.object(RemoteOperationMixin, "_operation"):
act = MagicMock()
with patch.object(MigrateOperation, 'create_activity',
return_value=act):
......@@ -121,7 +123,8 @@ class InstanceTestCase(TestCase):
inst.node = MagicMock(spec=Node)
inst.status = 'RUNNING'
migrate_op = MigrateOperation(inst)
with patch('vm.models.instance.vm_tasks.migrate') as migr:
with patch('vm.operations.vm_tasks.migrate') as migr, \
patch.object(RemoteOperationMixin, "_operation"):
inst.select_node.side_effect = AssertionError
act = MagicMock()
with patch.object(MigrateOperation, 'create_activity',
......@@ -138,19 +141,22 @@ class InstanceTestCase(TestCase):
inst.status = 'RUNNING'
e = Exception('abc')
setattr(e, 'libvirtError', '')
inst.migrate_vm.side_effect = e
migrate_op = MigrateOperation(inst)
with patch('vm.models.instance.vm_tasks.migrate') as migr:
migrate_op.rollback = Mock()
with patch('vm.operations.vm_tasks.migrate') as migr, \
patch.object(RemoteOperationMixin, '_operation') as remop:
act = MagicMock()
remop.side_effect = e
with patch.object(MigrateOperation, 'create_activity',
return_value=act):
self.assertRaises(Exception, migrate_op, system=True)
remop.assert_called()
migr.apply_async.assert_called()
self.assertIn(call.sub_activity(
u'rollback_net', readable_name=u'redeploy network (rollback)'),
act.mock_calls)
inst.allocate_node.assert_called()
u'scheduling', readable_name=u'schedule'), act.mock_calls)
migrate_op.rollback.assert_called()
inst.select_node.assert_called()
def test_status_icon(self):
inst = MagicMock(spec=Instance)
......
......@@ -52,15 +52,14 @@ class MigrateOperationTestCase(TestCase):
inst = MagicMock(spec=Instance)
act = MagicMock(spec=InstanceActivity)
inst.migrate_vm = MagicMock(side_effect=MigrateException())
op = MigrateOperation(inst)
op._get_remote_args = MagicMock(side_effect=MigrateException())
inst.select_node = MagicMock(return_value='test')
inst.reallocate_node = (
lambda act: Instance.reallocate_node(inst, act))
self.assertRaises(
MigrateException, MigrateOperation(inst)._operation,
MigrateException, op._operation,
act, to_node=None)
assert inst.select_node.called
inst.migrate_vm.assert_called_once_with(to_node='test', timeout=120)
op._get_remote_args.assert_called_once_with(to_node='test')
class RebootOperationTestCase(TestCase):
......
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