diff options
author | Zuul <zuul@review.opendev.org> | 2022-03-23 16:24:55 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2022-03-23 16:24:55 +0000 |
commit | 314558ef5e876f96751f7f0aca48ab40dd1a07ad (patch) | |
tree | cc7e86b23744f6d826b58937c92802fb0e3537ae | |
parent | 7a5432de8d68eab5772dd3a23951d0953ba99ddb (diff) | |
parent | 41cb811865a335626e7a14282e5223fa8d09379a (diff) | |
download | horizon-314558ef5e876f96751f7f0aca48ab40dd1a07ad.tar.gz |
Merge "Fix for "Resize instance" button" into stable/victoria
5 files changed, 104 insertions, 22 deletions
diff --git a/openstack_dashboard/dashboards/project/instances/tables.py b/openstack_dashboard/dashboards/project/instances/tables.py index 56af9b8f3..97a5d10f4 100644 --- a/openstack_dashboard/dashboards/project/instances/tables.py +++ b/openstack_dashboard/dashboards/project/instances/tables.py @@ -39,6 +39,8 @@ from horizon.utils import filters from openstack_dashboard import api from openstack_dashboard.dashboards.project.floating_ips import workflows from openstack_dashboard.dashboards.project.instances import tabs +from openstack_dashboard.dashboards.project.instances \ + import utils as instance_utils from openstack_dashboard.dashboards.project.instances.workflows \ import resize_instance from openstack_dashboard.dashboards.project.instances.workflows \ @@ -799,8 +801,8 @@ class UpdateRow(tables.Row): def get_data(self, request, instance_id): instance = api.nova.server_get(request, instance_id) try: - instance.full_flavor = api.nova.flavor_get(request, - instance.flavor["id"]) + instance.full_flavor = instance_utils.resolve_flavor(request, + instance) except Exception: exceptions.handle(request, _('Unable to retrieve flavor information ' @@ -1055,7 +1057,7 @@ def get_flavor(instance): "size_disk": size_disk, "size_ram": size_ram, "vcpus": instance.full_flavor.vcpus, - "flavor_id": instance.full_flavor.id + "flavor_id": getattr(instance.full_flavor, 'id', None) } return template.loader.render_to_string(template_name, context) return _("Not available") diff --git a/openstack_dashboard/dashboards/project/instances/tests.py b/openstack_dashboard/dashboards/project/instances/tests.py index b7ef8cac8..722ca8668 100644 --- a/openstack_dashboard/dashboards/project/instances/tests.py +++ b/openstack_dashboard/dashboards/project/instances/tests.py @@ -4602,12 +4602,26 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): def test_disassociate_floating_ip_with_release(self): self._test_disassociate_floating_ip(is_release=True) + def _populate_server_flavor_nova_api_ge_2_47(self, server): + flavor_id = server.flavor['id'] + flavor = self.flavors.get(id=flavor_id) + server.flavor = { + 'original_name': flavor.name, + 'vcpus': flavor.vcpus, + 'ram': flavor.ram, + 'swap': flavor.swap, + 'disk': flavor.disk, + 'ephemeral': flavor.ephemeral, + 'extra_specs': flavor.extra_specs, + } + return server + @helpers.create_mocks({api.nova: ('server_get', 'flavor_list', 'tenant_absolute_limits', 'is_feature_available', 'extension_supported')}) - def test_instance_resize_get(self): + def _test_instance_resize_get(self, server, nova_api_lt_2_47=False): server = self.servers.first() self.mock_server_get.return_value = server self.mock_flavor_list.return_value = self.flavors.list() @@ -4618,14 +4632,35 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): url = reverse('horizon:project:instances:resize', args=[server.id]) res = self.client.get(url) + workflow = res.context['workflow'] self.assertTemplateUsed(res, views.WorkflowView.template_name) + self.assertEqual(res.context['workflow'].name, + workflows.ResizeInstance.name) + self.assertContains(res, 'Disk Partition') config_drive_field_label = 'Configuration Drive' self.assertNotContains(res, config_drive_field_label) + step = workflow.get_step("flavor_choice") + self.assertEqual(step.action.initial['old_flavor_name'], + self.flavors.first().name) + + step = workflow.get_step("setadvancedaction") + self.assertEqual(step.action.fields['disk_config'].label, + 'Disk Partition') + self.assertQuerysetEqual(workflow.steps, + ['<SetFlavorChoice: flavor_choice>', + '<SetAdvanced: setadvancedaction>']) option = '<option value="%s">%s</option>' + + def is_original_flavor(server, flavor, nova_api_lt_2_47): + if nova_api_lt_2_47: + return flavor.id == server.flavor['id'] + else: + return flavor.name == server.flavor['original_name'] + for flavor in self.flavors.list(): - if flavor.id == server.flavor['id']: + if is_original_flavor(server, flavor, nova_api_lt_2_47): self.assertNotContains(res, option % (flavor.id, flavor.name)) else: self.assertContains(res, option % (flavor.id, flavor.name)) @@ -4640,6 +4675,15 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): self._check_extension_supported({'DiskConfig': 1, 'ServerGroups': 1}) + def test_instance_resize_get_nova_api_lt_2_47(self): + server = self.servers.first() + self._test_instance_resize_get(server, nova_api_lt_2_47=True) + + def test_instance_resize_get_nova_api_ge_2_47(self): + server = self.servers.first() + self._populate_server_flavor_nova_api_ge_2_47(server) + self._test_instance_resize_get(server) + @helpers.create_mocks({api.nova: ('server_get',)}) def test_instance_resize_get_server_get_exception(self): server = self.servers.first() @@ -4655,10 +4699,9 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): helpers.IsHttpRequest(), server.id) @helpers.create_mocks({api.nova: ('server_get', - 'flavor_list',)}) + 'flavor_list')}) def test_instance_resize_get_flavor_list_exception(self): server = self.servers.first() - self.mock_server_get.return_value = server self.mock_flavor_list.side_effect = self.exceptions.nova @@ -4672,6 +4715,8 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): server.id) self.mock_flavor_list.assert_called_once_with(helpers.IsHttpRequest()) + # TODO(amotoki): This is requred only when nova API <=2.46 is used. + # Once server_get() uses nova API >=2.47 only, this test can be droppped. @helpers.create_mocks({api.nova: ('server_get', 'flavor_list', 'flavor_get', diff --git a/openstack_dashboard/dashboards/project/instances/utils.py b/openstack_dashboard/dashboards/project/instances/utils.py index 11bd5dc68..0639690db 100644 --- a/openstack_dashboard/dashboards/project/instances/utils.py +++ b/openstack_dashboard/dashboards/project/instances/utils.py @@ -10,6 +10,7 @@ # License for the specific language governing permissions and limitations # under the License. +from collections import namedtuple import logging from operator import itemgetter @@ -232,3 +233,45 @@ def server_group_field_data(request): return [("", _("Select Server Group")), ] + server_groups_list return [("", _("No server groups available")), ] + + +def resolve_flavor(request, instance, flavors=None, **kwargs): + """Resolves name of instance flavor independent of API microversion + + :param request: django http request object + :param instance: api._nova.Server instance to resolve flavor + :param flavors: dict of flavors already retrieved + :param kwargs: flavor parameters to return if hit some flavor discrepancy + :return: flavor name or default placeholder + """ + def flavor_from_dict(flavor_dict): + """Creates flavor-like objects from dictionary + + :param flavor_dict: dictionary contains vcpu, ram, name, etc. values + :return: novaclient.v2.flavors.Flavor like object + """ + return namedtuple('Flavor', flavor_dict.keys())(*flavor_dict.values()) + + if flavors is None: + flavors = {} + flavor_id = instance.flavor.get('id') + if flavor_id: # Nova API <=2.46 + if flavor_id in flavors: + return flavors[flavor_id] + try: + return api.nova.flavor_get(request, flavor_id) + except Exception: + msg = _('Unable to retrieve flavor information ' + 'for instance "%s".') % instance.id + exceptions.handle(request, msg, ignore=True) + fallback_flavor = { + 'vcpus': 0, 'ram': 0, 'disk': 0, 'ephemeral': 0, 'swap': 0, + 'name': _('Not available'), + 'original_name': _('Not available'), + 'extra_specs': {}, + } + fallback_flavor.update(kwargs) + return flavor_from_dict(fallback_flavor) + else: + instance.flavor['name'] = instance.flavor['original_name'] + return flavor_from_dict(instance.flavor) diff --git a/openstack_dashboard/dashboards/project/instances/views.py b/openstack_dashboard/dashboards/project/instances/views.py index 6e7a76295..cdc4d59b3 100644 --- a/openstack_dashboard/dashboards/project/instances/views.py +++ b/openstack_dashboard/dashboards/project/instances/views.py @@ -51,6 +51,8 @@ from openstack_dashboard.dashboards.project.instances \ from openstack_dashboard.dashboards.project.instances \ import tabs as project_tabs from openstack_dashboard.dashboards.project.instances \ + import utils as instance_utils +from openstack_dashboard.dashboards.project.instances \ import workflows as project_workflows from openstack_dashboard.dashboards.project.networks.ports \ import views as port_views @@ -599,19 +601,9 @@ class ResizeView(workflows.WorkflowView): redirect = reverse("horizon:project:instances:index") msg = _('Unable to retrieve instance details.') exceptions.handle(self.request, msg, redirect=redirect) - flavor_id = instance.flavor['id'] flavors = self.get_flavors() - if flavor_id in flavors: - instance.flavor_name = flavors[flavor_id].name - else: - try: - flavor = api.nova.flavor_get(self.request, flavor_id) - instance.flavor_name = flavor.name - except Exception: - msg = _('Unable to retrieve flavor information for instance ' - '"%s".') % instance_id - exceptions.handle(self.request, msg, ignore=True) - instance.flavor_name = _("Not available") + flavor = instance_utils.resolve_flavor(self.request, instance, flavors) + instance.flavor_name = flavor.name return instance @memoized.memoized_method @@ -632,7 +624,6 @@ class ResizeView(workflows.WorkflowView): initial.update( {'instance_id': self.kwargs['instance_id'], 'name': getattr(_object, 'name', None), - 'old_flavor_id': _object.flavor['id'], 'old_flavor_name': getattr(_object, 'flavor_name', ''), 'flavors': self.get_flavors()}) return initial diff --git a/openstack_dashboard/dashboards/project/instances/workflows/resize_instance.py b/openstack_dashboard/dashboards/project/instances/workflows/resize_instance.py index 1fb4869a5..8634842f0 100644 --- a/openstack_dashboard/dashboards/project/instances/workflows/resize_instance.py +++ b/openstack_dashboard/dashboards/project/instances/workflows/resize_instance.py @@ -47,11 +47,12 @@ class SetFlavorChoiceAction(workflows.Action): "_flavors_and_quotas.html") def populate_flavor_choices(self, request, context): - old_flavor_id = context.get('old_flavor_id') + old_flavor_name = context.get('old_flavor_name') flavors = context.get('flavors').values() # Remove current flavor from the list of flavor choices - flavors = [flavor for flavor in flavors if flavor.id != old_flavor_id] + flavors = [flavor for flavor in flavors + if flavor.name != old_flavor_name] if flavors: if len(flavors) > 1: |