summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorThomas Goirand <zigo@debian.org>2022-02-17 10:36:31 +0100
committerTatiana Ovchinnikova <t.v.ovtchinnikova@gmail.com>2022-04-19 16:59:54 +0000
commit724005ca275e820d2850feba3139c058d7d07958 (patch)
tree3d10e292f2b115a89682d2f41f8c93e00196df1c
parent3153a5bca88548f1b14efd8844d9b722b5dfeafd (diff)
downloadhorizon-724005ca275e820d2850feba3139c058d7d07958.tar.gz
Fix for "Resize instance" button
Currently, "Resize instance" widget is not working because it relies on legacy Nova API v2.46, obsoleted in Pike release. Proposed patch make Horizon use current Nova API (>=2.47). Note: It also cherry-picks a7956cd004 from the master branch which avoids the extra call of flavor_get in resize server form. Closes-Bug: #1940834 Co-Authored-By: Akihiro Motoki <amotoki@gmail.com> Change-Id: Id2f38acfc27cdf93cc4341422873e512aaff716a (cherry picked from commit d269b1640f49e13aa1693a175083d66a3eaf5386) (cherry picked from commit 920cbb82d20be33074eec52abc6022f5ff97e569)
-rw-r--r--openstack_dashboard/dashboards/project/instances/tables.py8
-rw-r--r--openstack_dashboard/dashboards/project/instances/tests.py53
-rw-r--r--openstack_dashboard/dashboards/project/instances/utils.py43
-rw-r--r--openstack_dashboard/dashboards/project/instances/views.py17
-rw-r--r--openstack_dashboard/dashboards/project/instances/workflows/resize_instance.py5
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 d12c8bebf..8cfce2b6e 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 \
@@ -785,8 +787,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 '
@@ -1041,7 +1043,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 b4e69cbde..9ac8b3375 100644
--- a/openstack_dashboard/dashboards/project/instances/tests.py
+++ b/openstack_dashboard/dashboards/project/instances/tests.py
@@ -4604,12 +4604,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()
@@ -4620,14 +4634,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))
@@ -4642,6 +4677,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()
@@ -4657,10 +4701,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
@@ -4674,6 +4717,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 2b2352ef1..ce91be7b5 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
@@ -233,3 +234,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: