diff --git a/openstack_dashboard/api/nova.py b/openstack_dashboard/api/nova.py index 973b96f513f..d2f16e84631 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 1fe4dcf08d6..0d263f31bf7 100644 --- a/openstack_dashboard/dashboards/project/instances/tables.py +++ b/openstack_dashboard/dashboards/project/instances/tables.py @@ -709,6 +709,68 @@ def action(self, request, obj_id): 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 Meta: 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 272bf132c3e..af79f0d66f4 100644 --- a/openstack_dashboard/dashboards/project/instances/tests.py +++ b/openstack_dashboard/dashboards/project/instances/tests.py @@ -653,6 +653,115 @@ def test_resume_instance_exception(self): 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",