diff options
author | Zuul <zuul@review.opendev.org> | 2022-02-17 07:16:32 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2022-02-17 07:16:32 +0000 |
commit | 5abb12ad926420b53f7304973b71a467a58741a3 (patch) | |
tree | 14c0530411e5d89402701f2be7b706ba2bb58518 | |
parent | e716a4fe1ee86f6eddf219809271c6761959f919 (diff) | |
parent | d269b1640f49e13aa1693a175083d66a3eaf5386 (diff) | |
download | horizon-5abb12ad926420b53f7304973b71a467a58741a3.tar.gz |
Merge "Fix for "Resize instance" button"
5 files changed, 113 insertions, 31 deletions
diff --git a/openstack_dashboard/dashboards/project/instances/tables.py b/openstack_dashboard/dashboards/project/instances/tables.py index 4351e1c51..e4b07ce7b 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 \ @@ -782,8 +784,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 ' @@ -1034,7 +1036,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 075beea34..e01abd3ca 100644 --- a/openstack_dashboard/dashboards/project/instances/tests.py +++ b/openstack_dashboard/dashboards/project/instances/tests.py @@ -2155,16 +2155,30 @@ 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')}) - def test_instance_resize_get(self): - server = self.servers.first() - flavor = self.flavors.first() + 'is_feature_available', + 'flavor_get')}) + def _test_instance_resize_get(self, server, nova_api_lt_2_47=False): self.mock_server_get.return_value = server self.mock_flavor_list.return_value = self.flavors.list() self.mock_tenant_absolute_limits.return_value = self.limits['absolute'] + self.mock_flavor_get.return_value = self.flavors.first() url = reverse('horizon:project:instances:resize', args=[server.id]) res = self.client.get(url) @@ -2179,7 +2193,8 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): self.assertNotContains(res, config_drive_field_label) step = workflow.get_step("flavor_choice") - self.assertEqual(step.action.initial['old_flavor_id'], flavor.id) + 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, @@ -2188,8 +2203,15 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): ['<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)) @@ -2201,6 +2223,20 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): mock.call(helpers.IsHttpRequest())) self.mock_tenant_absolute_limits.assert_called_once_with( helpers.IsHttpRequest(), reserved=True) + if nova_api_lt_2_47: + self.mock_flavor_get.assert_called_once_with( + helpers.IsHttpRequest(), server.flavor['id']) + else: + self.mock_flavor_get.assert_not_called() + + 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): @@ -2217,10 +2253,10 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): helpers.IsHttpRequest(), server.id) @helpers.create_mocks({api.nova: ('server_get', - 'flavor_list',)}) - def test_instance_resize_get_flavor_list_exception(self): - server = self.servers.first() - + 'flavor_list', + 'flavor_get')}) + def _test_instance_resize_get_flavor_list_exception( + self, server, nova_api_lt_2_47=False): self.mock_server_get.return_value = server self.mock_flavor_list.side_effect = self.exceptions.nova @@ -2233,7 +2269,24 @@ class InstanceTests2(InstanceTestBase, InstanceTableTestMixin): self.mock_server_get.assert_called_once_with(helpers.IsHttpRequest(), server.id) self.mock_flavor_list.assert_called_once_with(helpers.IsHttpRequest()) + if nova_api_lt_2_47: + self.mock_flavor_get.assert_called_once_with( + helpers.IsHttpRequest(), server.flavor['id']) + else: + self.mock_flavor_get.assert_not_called() + + def test_instance_resize_get_flavor_list_exception_nova_api_lt_2_47(self): + server = self.servers.first() + self._test_instance_resize_get_flavor_list_exception( + server, nova_api_lt_2_47=True) + + def test_instance_resize_get_flavor_list_exception(self): + server = self.servers.first() + self._populate_server_flavor_nova_api_ge_2_47(server) + self._test_instance_resize_get_flavor_list_exception(server) + # 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..60fbe5d34 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 @@ -17,7 +18,6 @@ from django.conf import settings from django.utils.translation import ugettext_lazy as _ from horizon import exceptions - from openstack_dashboard import api LOG = logging.getLogger(__name__) @@ -232,3 +232,40 @@ def server_group_field_data(request): return [("", _("Select Server Group")), ] + server_groups_list return [("", _("No server groups available")), ] + + +def resolve_flavor(request, instance, **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 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()) + + flavor_id = instance.flavor.get('id') + if flavor_id: # Nova API <=2.46 + 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 ee8b1636c..3e1099cda 100644 --- a/openstack_dashboard/dashboards/project/instances/views.py +++ b/openstack_dashboard/dashboards/project/instances/views.py @@ -50,6 +50,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 @@ -586,19 +588,8 @@ 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") + instance.flavor_name = instance_utils.resolve_flavor(self.request, + instance).name return instance @memoized.memoized_method @@ -619,7 +610,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 fd9bf7adc..e1f063877 100644 --- a/openstack_dashboard/dashboards/project/instances/workflows/resize_instance.py +++ b/openstack_dashboard/dashboards/project/instances/workflows/resize_instance.py @@ -62,11 +62,11 @@ 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: |