summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorZuul <zuul@review.opendev.org>2022-02-17 07:16:32 +0000
committerGerrit Code Review <review@openstack.org>2022-02-17 07:16:32 +0000
commit5abb12ad926420b53f7304973b71a467a58741a3 (patch)
tree14c0530411e5d89402701f2be7b706ba2bb58518
parente716a4fe1ee86f6eddf219809271c6761959f919 (diff)
parentd269b1640f49e13aa1693a175083d66a3eaf5386 (diff)
downloadhorizon-5abb12ad926420b53f7304973b71a467a58741a3.tar.gz
Merge "Fix for "Resize instance" button"
-rw-r--r--openstack_dashboard/dashboards/project/instances/tables.py8
-rw-r--r--openstack_dashboard/dashboards/project/instances/tests.py73
-rw-r--r--openstack_dashboard/dashboards/project/instances/utils.py39
-rw-r--r--openstack_dashboard/dashboards/project/instances/views.py18
-rw-r--r--openstack_dashboard/dashboards/project/instances/workflows/resize_instance.py6
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: