From 5840d041db4557ff318d0b4b3ec4c1cbde2b9c4c Mon Sep 17 00:00:00 2001 From: Ana Krivokapic Date: Wed, 12 Nov 2014 17:12:49 +0100 Subject: [PATCH] Add support for locking and unlocking servers Partially implements: blueprint lock-unlock-server Change-Id: I6d261cc7fb314bda60f0732f7a7bb3b23ba7c179 --- openstack_dashboard/api/nova.py | 8 ++ .../dashboards/project/instances/tables.py | 65 ++++++++++- .../dashboards/project/instances/tests.py | 109 ++++++++++++++++++ 3 files changed, 181 insertions(+), 1 deletion(-) diff --git a/openstack_dashboard/api/nova.py b/openstack_dashboard/api/nova.py index 973b96f513..d2f16e8463 100644 --- a/openstack_dashboard/api/nova.py +++ b/openstack_dashboard/api/nova.py @@ -656,6 +656,14 @@ def server_stop(request, instance_id): novaclient(request).servers.stop(instance_id) +def server_lock(request, instance_id): + novaclient(request).servers.lock(instance_id) + + +def server_unlock(request, instance_id): + novaclient(request).servers.unlock(instance_id) + + def tenant_quota_get(request, tenant_id): return base.QuotaSet(novaclient(request).quotas.get(tenant_id)) diff --git a/openstack_dashboard/dashboards/project/instances/tables.py b/openstack_dashboard/dashboards/project/instances/tables.py index 1fe4dcf08d..0d263f31bf 100644 --- a/openstack_dashboard/dashboards/project/instances/tables.py +++ b/openstack_dashboard/dashboards/project/instances/tables.py @@ -709,6 +709,68 @@ class StopInstance(policy.PolicyTargetMixin, tables.BatchAction): api.nova.server_stop(request, obj_id) +class LockInstance(policy.PolicyTargetMixin, tables.BatchAction): + name = "lock" + policy_rules = (("compute", "compute_extension:admin_actions:lock"),) + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Lock Instance", + u"Lock Instances", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Locked Instance", + u"Locked Instances", + count + ) + + # TODO(akrivoka): When the lock status is added to nova, revisit this + # to only allow unlocked instances to be locked + def allowed(self, request, instance): + if not api.nova.extension_supported('AdminActions', request): + return False + return True + + def action(self, request, obj_id): + api.nova.server_lock(request, obj_id) + + +class UnlockInstance(policy.PolicyTargetMixin, tables.BatchAction): + name = "unlock" + policy_rules = (("compute", "compute_extension:admin_actions:unlock"),) + + @staticmethod + def action_present(count): + return ungettext_lazy( + u"Unlock Instance", + u"Unlock Instances", + count + ) + + @staticmethod + def action_past(count): + return ungettext_lazy( + u"Unlocked Instance", + u"Unlocked Instances", + count + ) + + # TODO(akrivoka): When the lock status is added to nova, revisit this + # to only allow locked instances to be unlocked + def allowed(self, request, instance): + if not api.nova.extension_supported('AdminActions', request): + return False + return True + + def action(self, request, obj_id): + api.nova.server_unlock(request, obj_id) + + def get_ips(instance): template_name = 'project/instances/_instance_ips.html' context = {"instance": instance} @@ -930,5 +992,6 @@ class InstancesTable(tables.DataTable): SimpleDisassociateIP, EditInstance, DecryptInstancePassword, EditInstanceSecurityGroups, ConsoleLink, LogLink, TogglePause, ToggleSuspend, - ResizeLink, SoftRebootInstance, RebootInstance, + ResizeLink, LockInstance, UnlockInstance, + SoftRebootInstance, RebootInstance, StopInstance, RebuildInstance, TerminateInstance) diff --git a/openstack_dashboard/dashboards/project/instances/tests.py b/openstack_dashboard/dashboards/project/instances/tests.py index 272bf132c3..af79f0d66f 100644 --- a/openstack_dashboard/dashboards/project/instances/tests.py +++ b/openstack_dashboard/dashboards/project/instances/tests.py @@ -653,6 +653,115 @@ class InstanceTests(helpers.TestCase): self.assertRedirectsNoFollow(res, INDEX_URL) + @helpers.create_stubs({api.nova: ('server_lock', + 'server_list', + 'extension_supported',), + api.glance: ('image_list_detailed',), + api.network: ('servers_update_addresses',)}) + def test_lock_instance(self): + servers = self.servers.list() + server = servers[0] + + api.nova.extension_supported('AdminActions', IsA( + http.HttpRequest)).MultipleTimes().AndReturn(True) + api.glance.image_list_detailed(IgnoreArg()).AndReturn(( + self.images.list(), False, False)) + search_opts = {'marker': None, 'paginate': True} + api.nova.server_list( + IsA(http.HttpRequest), + search_opts=search_opts).AndReturn([servers, False]) + api.network.servers_update_addresses(IsA(http.HttpRequest), servers) + api.nova.server_lock(IsA(http.HttpRequest), server.id) + + self.mox.ReplayAll() + + formData = {'action': 'instances__lock__%s' % server.id} + res = self.client.post(INDEX_URL, formData) + + self.assertRedirectsNoFollow(res, INDEX_URL) + + @helpers.create_stubs({api.nova: ('server_lock', + 'server_list', + 'extension_supported',), + api.glance: ('image_list_detailed',), + api.network: ('servers_update_addresses',)}) + def test_lock_instance_exception(self): + servers = self.servers.list() + server = servers[0] + + api.nova.extension_supported('AdminActions', IsA( + http.HttpRequest)).MultipleTimes().AndReturn(True) + api.glance.image_list_detailed(IgnoreArg()).AndReturn(( + self.images.list(), False, False)) + search_opts = {'marker': None, 'paginate': True} + api.nova.server_list( + IsA(http.HttpRequest), + search_opts=search_opts).AndReturn([servers, False]) + api.network.servers_update_addresses(IsA(http.HttpRequest), servers) + api.nova.server_lock(IsA(http.HttpRequest), server.id).AndRaise( + self.exceptions.nova) + + self.mox.ReplayAll() + + formData = {'action': 'instances__lock__%s' % server.id} + res = self.client.post(INDEX_URL, formData) + + self.assertRedirectsNoFollow(res, INDEX_URL) + + @helpers.create_stubs({api.nova: ('server_unlock', + 'server_list', + 'extension_supported',), + api.glance: ('image_list_detailed',), + api.network: ('servers_update_addresses',)}) + def test_unlock_instance(self): + servers = self.servers.list() + server = servers[0] + api.nova.extension_supported('AdminActions', IsA( + http.HttpRequest)).MultipleTimes().AndReturn(True) + api.glance.image_list_detailed(IgnoreArg()).AndReturn(( + self.images.list(), False, False)) + search_opts = {'marker': None, 'paginate': True} + api.nova.server_list( + IsA(http.HttpRequest), + search_opts=search_opts).AndReturn([servers, False]) + api.network.servers_update_addresses(IsA(http.HttpRequest), servers) + api.nova.server_unlock(IsA(http.HttpRequest), server.id) + + self.mox.ReplayAll() + + formData = {'action': 'instances__unlock__%s' % server.id} + res = self.client.post(INDEX_URL, formData) + + self.assertRedirectsNoFollow(res, INDEX_URL) + + @helpers.create_stubs({api.nova: ('server_unlock', + 'server_list', + 'extension_supported',), + api.glance: ('image_list_detailed',), + api.network: ('servers_update_addresses',)}) + def test_unlock_instance_exception(self): + servers = self.servers.list() + server = servers[0] + + api.nova.extension_supported('AdminActions', IsA( + http.HttpRequest)).MultipleTimes().AndReturn(True) + api.glance.image_list_detailed(IgnoreArg()).AndReturn(( + self.images.list(), False, False)) + search_opts = {'marker': None, 'paginate': True} + api.nova.server_list( + IsA(http.HttpRequest), + search_opts=search_opts).AndReturn([servers, False]) + api.network.servers_update_addresses(IsA(http.HttpRequest), servers) + api.nova.server_unlock(IsA(http.HttpRequest), server.id).AndRaise( + self.exceptions.nova) + + self.mox.ReplayAll() + + formData = {'action': 'instances__unlock__%s' % server.id} + res = self.client.post(INDEX_URL, formData) + + self.assertRedirectsNoFollow(res, INDEX_URL) + @helpers.create_stubs({ api.nova: ( "server_get",