From 9bee1c103c751fd0a939c67cbc833d20c0935f1b Mon Sep 17 00:00:00 2001 From: Cedric Brandily Date: Sun, 6 Sep 2015 20:24:04 +0200 Subject: [PATCH] Enable compute shelve/unshelve commands This change enables shelve/unshelve commands in user/admin views. Closes-Bug: #1285389 Implements: blueprint horizon-shelving-command Change-Id: I15e1e5d2fdb3d246a7f3e7e676c4b364694410fb --- openstack_dashboard/api/nova.py | 8 + .../dashboards/admin/instances/tables.py | 1 + .../dashboards/admin/instances/tests.py | 12 ++ .../dashboards/project/instances/tables.py | 71 ++++++++- .../dashboards/project/instances/tests.py | 145 ++++++++++++++++++ 5 files changed, 236 insertions(+), 1 deletion(-) diff --git a/openstack_dashboard/api/nova.py b/openstack_dashboard/api/nova.py index d61cf73013..01d075cb60 100644 --- a/openstack_dashboard/api/nova.py +++ b/openstack_dashboard/api/nova.py @@ -650,6 +650,14 @@ def server_resume(request, instance_id): novaclient(request).servers.resume(instance_id) +def server_shelve(request, instance_id): + novaclient(request).servers.shelve(instance_id) + + +def server_unshelve(request, instance_id): + novaclient(request).servers.unshelve(instance_id) + + def server_reboot(request, instance_id, soft_reboot=False): hardness = nova_servers.REBOOT_HARD if soft_reboot: diff --git a/openstack_dashboard/dashboards/admin/instances/tables.py b/openstack_dashboard/dashboards/admin/instances/tables.py index ec7b624f00..32832b1897 100644 --- a/openstack_dashboard/dashboards/admin/instances/tables.py +++ b/openstack_dashboard/dashboards/admin/instances/tables.py @@ -182,6 +182,7 @@ class AdminInstancesTable(tables.DataTable): project_tables.CreateSnapshot, project_tables.TogglePause, project_tables.ToggleSuspend, + project_tables.ToggleShelve, MigrateInstance, LiveMigrateInstance, project_tables.SoftRebootInstance, diff --git a/openstack_dashboard/dashboards/admin/instances/tests.py b/openstack_dashboard/dashboards/admin/instances/tests.py index 777d964ef3..0937fe4929 100644 --- a/openstack_dashboard/dashboards/admin/instances/tests.py +++ b/openstack_dashboard/dashboards/admin/instances/tests.py @@ -39,6 +39,8 @@ class InstanceViewTest(test.BaseAdminViewTests): tenants = self.tenants.list() api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \ .MultipleTimes().AndReturn(True) + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) api.keystone.tenant_list(IsA(http.HttpRequest)).\ AndReturn([tenants, False]) search_opts = {'marker': None, 'paginate': True} @@ -73,6 +75,8 @@ class InstanceViewTest(test.BaseAdminViewTests): all_tenants=True) api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \ .MultipleTimes().AndReturn(True) + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) api.nova.flavor_list(IsA(http.HttpRequest)). \ AndRaise(self.exceptions.nova) api.keystone.tenant_list(IsA(http.HttpRequest)).\ @@ -109,6 +113,8 @@ class InstanceViewTest(test.BaseAdminViewTests): all_tenants=True) api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \ .MultipleTimes().AndReturn(True) + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) api.nova.flavor_list(IsA(http.HttpRequest)). \ AndReturn(flavors) api.keystone.tenant_list(IsA(http.HttpRequest)).\ @@ -150,6 +156,8 @@ class InstanceViewTest(test.BaseAdminViewTests): api.nova.server_get(IsA(http.HttpRequest), server.id).AndReturn(server) api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \ .MultipleTimes().AndReturn(True) + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) api.nova.flavor_get(IsA(http.HttpRequest), server.flavor['id']).AndReturn(flavor) api.keystone.tenant_get(IsA(http.HttpRequest), @@ -191,6 +199,8 @@ class InstanceViewTest(test.BaseAdminViewTests): all_tenants=True) api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \ .MultipleTimes().AndReturn(True) + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) api.nova.flavor_list(IsA(http.HttpRequest)).\ AndReturn(self.flavors.list()) self.mox.ReplayAll() @@ -215,6 +225,8 @@ class InstanceViewTest(test.BaseAdminViewTests): search_opts = {'marker': None, 'paginate': True} api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \ .MultipleTimes().AndReturn(True) + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) api.nova.server_list(IsA(http.HttpRequest), all_tenants=True, search_opts=search_opts) \ .AndReturn([servers, False]) diff --git a/openstack_dashboard/dashboards/project/instances/tables.py b/openstack_dashboard/dashboards/project/instances/tables.py index 3720cae6a6..e2ca1b85c6 100644 --- a/openstack_dashboard/dashboards/project/instances/tables.py +++ b/openstack_dashboard/dashboards/project/instances/tables.py @@ -70,6 +70,8 @@ PAUSE = 0 UNPAUSE = 1 SUSPEND = 0 RESUME = 1 +SHELVE = 0 +UNSHELVE = 1 def is_deleting(instance): @@ -304,6 +306,73 @@ class ToggleSuspend(tables.BatchAction): self.current_past_action = SUSPEND +class ToggleShelve(tables.BatchAction): + name = "shelve" + icon = "shelve" + + @staticmethod + def action_present(count): + return ( + ungettext_lazy( + u"Shelve Instance", + u"Shelve Instances", + count + ), + ungettext_lazy( + u"Unshelve Instance", + u"Unshelve Instances", + count + ), + ) + + @staticmethod + def action_past(count): + return ( + ungettext_lazy( + u"Shelved Instance", + u"Shelved Instances", + count + ), + ungettext_lazy( + u"Unshelved Instance", + u"Unshelved Instances", + count + ), + ) + + def allowed(self, request, instance=None): + if not api.nova.extension_supported('Shelve', request): + return False + if not instance: + return False + self.shelved = instance.status == "SHELVED_OFFLOADED" + if self.shelved: + self.current_present_action = UNSHELVE + policy = (("compute", "compute_extension:unshelve"),) + else: + self.current_present_action = SHELVE + policy = (("compute", "compute_extension:shelve"),) + + has_permission = True + policy_check = getattr(settings, "POLICY_CHECK_FUNCTION", None) + if policy_check: + has_permission = policy_check( + policy, request, + target={'project_id': getattr(instance, 'tenant_id', None)}) + + return (has_permission + and (instance.status in ACTIVE_STATES or self.shelved) + and not is_deleting(instance)) + + def action(self, request, obj_id): + if self.shelved: + api.nova.server_unshelve(request, obj_id) + self.current_past_action = UNSHELVE + else: + api.nova.server_shelve(request, obj_id) + self.current_past_action = SHELVE + + class LaunchLink(tables.LinkAction): name = "launch" verbose_name = _("Launch Instance") @@ -1109,6 +1178,6 @@ class InstancesTable(tables.DataTable): DetachInterface, EditInstance, DecryptInstancePassword, EditInstanceSecurityGroups, ConsoleLink, LogLink, TogglePause, ToggleSuspend, - ResizeLink, LockInstance, UnlockInstance, + ToggleShelve, 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 6473a94167..b28010137e 100644 --- a/openstack_dashboard/dashboards/project/instances/tests.py +++ b/openstack_dashboard/dashboards/project/instances/tests.py @@ -75,6 +75,8 @@ class InstanceTests(helpers.TestCase): api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \ .MultipleTimes().AndReturn(True) + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) api.nova.flavor_list(IsA(http.HttpRequest)) \ .AndReturn(self.flavors.list()) api.glance.image_list_detailed(IgnoreArg()) \ @@ -138,6 +140,8 @@ class InstanceTests(helpers.TestCase): api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \ .MultipleTimes().AndReturn(True) + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) api.nova.server_list(IsA(http.HttpRequest), search_opts=search_opts) \ .AndReturn([servers, False]) api.network.servers_update_addresses(IsA(http.HttpRequest), servers) @@ -182,6 +186,8 @@ class InstanceTests(helpers.TestCase): api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \ .MultipleTimes().AndReturn(True) + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) api.nova.flavor_list(IsA(http.HttpRequest)) \ .AndReturn(self.flavors.list()) api.glance.image_list_detailed(IgnoreArg()) \ @@ -611,6 +617,129 @@ class InstanceTests(helpers.TestCase): self.assertRedirectsNoFollow(res, INDEX_URL) + @helpers.create_stubs({api.nova: ('server_shelve', + 'server_list', + 'flavor_list', + 'extension_supported',), + api.glance: ('image_list_detailed',), + api.network: ('servers_update_addresses',)}) + def test_shelve_instance(self): + servers = self.servers.list() + server = servers[0] + + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) + api.nova.flavor_list(IsA(http.HttpRequest)) \ + .AndReturn(self.flavors.list()) + 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_shelve(IsA(http.HttpRequest), six.text_type(server.id)) + + self.mox.ReplayAll() + + formData = {'action': 'instances__shelve__%s' % server.id} + res = self.client.post(INDEX_URL, formData) + + self.assertRedirectsNoFollow(res, INDEX_URL) + + @helpers.create_stubs({api.nova: ('server_shelve', + 'server_list', + 'flavor_list', + 'extension_supported',), + api.glance: ('image_list_detailed',), + api.network: ('servers_update_addresses',)}) + def test_shelve_instance_exception(self): + servers = self.servers.list() + server = servers[0] + + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) + api.nova.flavor_list(IsA(http.HttpRequest)) \ + .AndReturn(self.flavors.list()) + 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_shelve(IsA(http.HttpRequest), + six.text_type(server.id)) \ + .AndRaise(self.exceptions.nova) + + self.mox.ReplayAll() + + formData = {'action': 'instances__shelve__%s' % server.id} + res = self.client.post(INDEX_URL, formData) + + self.assertRedirectsNoFollow(res, INDEX_URL) + + @helpers.create_stubs({api.nova: ('server_unshelve', + 'server_list', + 'flavor_list', + 'extension_supported',), + api.glance: ('image_list_detailed',), + api.network: ('servers_update_addresses',)}) + def test_unshelve_instance(self): + servers = self.servers.list() + server = servers[0] + server.status = "SHELVED_OFFLOADED" + + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) + api.nova.flavor_list(IsA(http.HttpRequest)) \ + .AndReturn(self.flavors.list()) + 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_unshelve(IsA(http.HttpRequest), + six.text_type(server.id)) + + self.mox.ReplayAll() + + formData = {'action': 'instances__shelve__%s' % server.id} + res = self.client.post(INDEX_URL, formData) + + self.assertRedirectsNoFollow(res, INDEX_URL) + + @helpers.create_stubs({api.nova: ('server_unshelve', + 'server_list', + 'flavor_list', + 'extension_supported',), + api.glance: ('image_list_detailed',), + api.network: ('servers_update_addresses',)}) + def test_unshelve_instance_exception(self): + servers = self.servers.list() + server = servers[0] + server.status = "SHELVED_OFFLOADED" + + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) + api.nova.flavor_list(IsA(http.HttpRequest)) \ + .AndReturn(self.flavors.list()) + 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_unshelve(IsA(http.HttpRequest), + six.text_type(server.id)) \ + .AndRaise(self.exceptions.nova) + + self.mox.ReplayAll() + + formData = {'action': 'instances__shelve__%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',), @@ -771,6 +900,8 @@ class InstanceTests(helpers.TestCase): .MultipleTimes().AndReturn(True) api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \ .MultipleTimes().AndReturn(True) + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) self.mox.ReplayAll() @@ -1140,6 +1271,8 @@ class InstanceTests(helpers.TestCase): api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \ .MultipleTimes().AndReturn(True) + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) api.nova.flavor_list(IsA(http.HttpRequest)) \ .AndReturn(self.flavors.list()) api.glance.image_list_detailed(IgnoreArg()) \ @@ -3298,6 +3431,8 @@ class InstanceTests(helpers.TestCase): api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \ .MultipleTimes().AndReturn(True) + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) api.nova.flavor_list(IsA(http.HttpRequest)) \ .AndReturn(self.flavors.list()) api.glance.image_list_detailed(IgnoreArg()) \ @@ -3470,6 +3605,8 @@ class InstanceTests(helpers.TestCase): api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \ .MultipleTimes().AndReturn(True) + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) api.nova.flavor_list(IsA(http.HttpRequest)) \ .AndReturn(self.flavors.list()) api.glance.image_list_detailed(IgnoreArg()) \ @@ -4024,6 +4161,8 @@ class InstanceTests(helpers.TestCase): api.nova.extension_supported('AdminActions', IsA(http.HttpRequest)) \ .MultipleTimes().AndReturn(True) + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) api.nova.flavor_list(IsA(http.HttpRequest)) \ .MultipleTimes().AndReturn(self.flavors.list()) api.glance.image_list_detailed(IgnoreArg()) \ @@ -4170,6 +4309,8 @@ class InstanceAjaxTests(helpers.TestCase): api.nova.extension_supported('AdminActions', IsA(http.HttpRequest))\ .MultipleTimes().AndReturn(True) + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'security-group')\ .MultipleTimes().AndReturn(True) @@ -4212,6 +4353,8 @@ class InstanceAjaxTests(helpers.TestCase): api.nova.extension_supported('AdminActions', IsA(http.HttpRequest))\ .MultipleTimes().AndReturn(True) + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'security-group')\ .MultipleTimes().AndReturn(True) @@ -4250,6 +4393,8 @@ class InstanceAjaxTests(helpers.TestCase): api.nova.extension_supported('AdminActions', IsA(http.HttpRequest))\ .MultipleTimes().AndReturn(True) + api.nova.extension_supported('Shelve', IsA(http.HttpRequest)) \ + .MultipleTimes().AndReturn(True) api.neutron.is_extension_supported(IsA(http.HttpRequest), 'security-group')\ .MultipleTimes().AndReturn(True)