diff --git a/openstack_dashboard/api/nova.py b/openstack_dashboard/api/nova.py
index 4eb4973447..56652a2e7e 100644
--- a/openstack_dashboard/api/nova.py
+++ b/openstack_dashboard/api/nova.py
@@ -493,15 +493,21 @@ def server_delete(request, instance_id):
novaclient(request).servers.delete(instance_id)
+def get_novaclient_with_locked_status(request):
+ microversion = get_microversion(request, "locked_attribute")
+ return novaclient(request, version=microversion)
+
+
@profiler.trace
def server_get(request, instance_id):
- return Server(novaclient(request).servers.get(instance_id), request)
+ return Server(get_novaclient_with_locked_status(request).servers.get(
+ instance_id), request)
@profiler.trace
def server_list(request, search_opts=None, all_tenants=False, detailed=True):
+ nova_client = get_novaclient_with_locked_status(request)
page_size = utils.get_page_size(request)
- c = novaclient(request)
paginate = False
if search_opts is None:
search_opts = {}
@@ -515,7 +521,7 @@ def server_list(request, search_opts=None, all_tenants=False, detailed=True):
else:
search_opts['project_id'] = request.user.tenant_id
servers = [Server(s, request)
- for s in c.servers.list(detailed, search_opts)]
+ for s in nova_client.servers.list(detailed, search_opts)]
has_more_data = False
if paginate and len(servers) > page_size:
diff --git a/openstack_dashboard/dashboards/admin/instances/tables.py b/openstack_dashboard/dashboards/admin/instances/tables.py
index 3791fc5df6..28c6476335 100644
--- a/openstack_dashboard/dashboards/admin/instances/tables.py
+++ b/openstack_dashboard/dashboards/admin/instances/tables.py
@@ -152,6 +152,9 @@ class AdminInstancesTable(tables.DataTable):
status=True,
status_choices=STATUS_CHOICES,
display_choices=project_tables.STATUS_DISPLAY_CHOICES)
+ locked = tables.Column(project_tables.render_locked,
+ verbose_name=_(" "),
+ sortable=False)
task = tables.Column("OS-EXT-STS:task_state",
verbose_name=_("Task"),
empty_value=project_tables.TASK_DISPLAY_NONE,
diff --git a/openstack_dashboard/dashboards/project/instances/tables.py b/openstack_dashboard/dashboards/project/instances/tables.py
index 3f52f6cc4f..f2bf1eebb8 100644
--- a/openstack_dashboard/dashboards/project/instances/tables.py
+++ b/openstack_dashboard/dashboards/project/instances/tables.py
@@ -22,6 +22,7 @@ from django import shortcuts
from django import template
from django.template.defaultfilters import title
from django.utils.http import urlencode
+from django.utils.safestring import mark_safe
from django.utils.translation import npgettext_lazy
from django.utils.translation import pgettext_lazy
from django.utils.translation import string_concat
@@ -1183,6 +1184,23 @@ class InstancesFilterAction(tables.FilterAction):
filter_choices = INSTANCE_FILTER_CHOICES
+def render_locked(instance):
+ if not hasattr(instance, 'locked'):
+ return ""
+ if instance.locked:
+ icon_classes = "fa fa-fw fa-lock"
+ help_tooltip = _("This instance is currently locked. To enable more "
+ "actions on it, please unlock it by selecting Unlock "
+ "Instance from the actions menu.")
+ else:
+ icon_classes = "fa fa-fw fa-unlock text-muted"
+ help_tooltip = _("This instance is unlocked.")
+
+ locked_status = (''
+ '').format(help_tooltip, icon_classes)
+ return mark_safe(locked_status)
+
+
class InstancesTable(tables.DataTable):
TASK_STATUS_CHOICES = (
(None, True),
@@ -1216,6 +1234,9 @@ class InstancesTable(tables.DataTable):
status=True,
status_choices=STATUS_CHOICES,
display_choices=STATUS_DISPLAY_CHOICES)
+ locked = tables.Column(render_locked,
+ verbose_name="",
+ sortable=False)
az = tables.Column("availability_zone",
verbose_name=_("Availability Zone"))
task = tables.Column("OS-EXT-STS:task_state",
diff --git a/openstack_dashboard/dashboards/project/instances/templates/instances/_detail_overview.html b/openstack_dashboard/dashboards/project/instances/templates/instances/_detail_overview.html
index fce3b05994..6b1500fd2e 100644
--- a/openstack_dashboard/dashboards/project/instances/templates/instances/_detail_overview.html
+++ b/openstack_dashboard/dashboards/project/instances/templates/instances/_detail_overview.html
@@ -8,6 +8,10 @@
{{ instance.id }}
{% trans "Status" %}
{{ instance.status_label|title }}
+ {% if instance.locked != None %}
+ {% trans "Locked" %}
+ {{ instance.locked }}
+ {% endif %}
{% trans "Availability Zone" %}
{{ instance.availability_zone|default:_("-") }}
{% trans "Created" %}
diff --git a/openstack_dashboard/test/api_tests/neutron_tests.py b/openstack_dashboard/test/api_tests/neutron_tests.py
index 0e288e4dea..2854f5d0c2 100644
--- a/openstack_dashboard/test/api_tests/neutron_tests.py
+++ b/openstack_dashboard/test/api_tests/neutron_tests.py
@@ -1093,6 +1093,8 @@ class NeutronApiFloatingIpTests(NeutronApiTestBase):
servers = self.servers.list()
novaclient = self.stub_novaclient()
novaclient.servers = self.mox.CreateMockAnything()
+ novaclient.versions = self.mox.CreateMockAnything()
+ novaclient.versions.get_current().AndReturn("2.45")
search_opts = {'project_id': self.request.user.tenant_id}
novaclient.servers.list(False, search_opts).AndReturn(servers)
diff --git a/openstack_dashboard/test/api_tests/nova_tests.py b/openstack_dashboard/test/api_tests/nova_tests.py
index 2583dee9ee..1c5eefa7dc 100644
--- a/openstack_dashboard/test/api_tests/nova_tests.py
+++ b/openstack_dashboard/test/api_tests/nova_tests.py
@@ -141,6 +141,8 @@ class ComputeApiTests(test.APITestCase):
novaclient = self.stub_novaclient()
novaclient.servers = self.mox.CreateMockAnything()
+ novaclient.versions = self.mox.CreateMockAnything()
+ novaclient.versions.get_current().AndReturn("2.45")
novaclient.servers.list(True, {'all_tenants': True}).AndReturn(servers)
self.mox.ReplayAll()
@@ -154,6 +156,8 @@ class ComputeApiTests(test.APITestCase):
servers = self.servers.list()
novaclient = self.stub_novaclient()
novaclient.servers = self.mox.CreateMockAnything()
+ novaclient.versions = self.mox.CreateMockAnything()
+ novaclient.versions.get_current().AndReturn("2.45")
novaclient.servers.list(True,
{'all_tenants': True,
'marker': None,
@@ -174,6 +178,8 @@ class ComputeApiTests(test.APITestCase):
servers = self.servers.list()
novaclient = self.stub_novaclient()
novaclient.servers = self.mox.CreateMockAnything()
+ novaclient.versions = self.mox.CreateMockAnything()
+ novaclient.versions.get_current().AndReturn("2.45")
novaclient.servers.list(True,
{'all_tenants': True,
'marker': None,
@@ -267,6 +273,8 @@ class ComputeApiTests(test.APITestCase):
server = self.servers.first()
novaclient = self.stub_novaclient()
+ novaclient.versions = self.mox.CreateMockAnything()
+ novaclient.versions.get_current().AndReturn("2.45")
novaclient.servers = self.mox.CreateMockAnything()
novaclient.servers.get(server.id).AndReturn(server)
self.mox.ReplayAll()
@@ -385,6 +393,8 @@ class ComputeApiTests(test.APITestCase):
novaclient = self.stub_novaclient()
server_uuid = hypervisor.servers[0]["uuid"]
+ novaclient.versions = self.mox.CreateMockAnything()
+ novaclient.versions.get_current().AndReturn("2.45")
novaclient.hypervisors = self.mox.CreateMockAnything()
novaclient.hypervisors.search('host', True).AndReturn([hypervisor])
@@ -405,6 +415,8 @@ class ComputeApiTests(test.APITestCase):
novaclient = self.stub_novaclient()
server_uuid = hypervisor.servers[0]["uuid"]
+ novaclient.versions = self.mox.CreateMockAnything()
+ novaclient.versions.get_current().AndReturn("2.45")
novaclient.hypervisors = self.mox.CreateMockAnything()
novaclient.hypervisors.search('host', True).AndReturn([hypervisor])
@@ -425,6 +437,8 @@ class ComputeApiTests(test.APITestCase):
novaclient = self.stub_novaclient()
server_uuid = hypervisor.servers[0]["uuid"]
+ novaclient.versions = self.mox.CreateMockAnything()
+ novaclient.versions.get_current().AndReturn("2.45")
novaclient.hypervisors = self.mox.CreateMockAnything()
novaclient.hypervisors.search('host', True).AndReturn([hypervisor])
diff --git a/openstack_dashboard/test/helpers.py b/openstack_dashboard/test/helpers.py
index df5be0cfba..a775743050 100644
--- a/openstack_dashboard/test/helpers.py
+++ b/openstack_dashboard/test/helpers.py
@@ -417,6 +417,9 @@ class APITestCase(TestCase):
"""
return self.stub_glanceclient()
+ def fake_novaclient(request, version=None):
+ return self.stub_novaclient()
+
# Store the original clients
self._original_glanceclient = api.glance.glanceclient
self._original_keystoneclient = api.keystone.keystoneclient
@@ -428,7 +431,7 @@ class APITestCase(TestCase):
# Replace the clients with our stubs.
api.glance.glanceclient = fake_glanceclient
api.keystone.keystoneclient = fake_keystoneclient
- api.nova.novaclient = lambda request: self.stub_novaclient()
+ api.nova.novaclient = fake_novaclient
api.neutron.neutronclient = lambda request: self.stub_neutronclient()
api.cinder.cinderclient = lambda request: self.stub_cinderclient()
api.heat.heatclient = (lambda request, password=None:
diff --git a/releasenotes/notes/bug-1593903-8fb8721dc2449f71.yaml b/releasenotes/notes/bug-1593903-8fb8721dc2449f71.yaml
new file mode 100644
index 0000000000..9f0403ea50
--- /dev/null
+++ b/releasenotes/notes/bug-1593903-8fb8721dc2449f71.yaml
@@ -0,0 +1,6 @@
+---
+features:
+ - |
+ Added a locked status column on admin/project instances table. It will
+ show a locked or unlocked icon if nova 2.9 or above is used. The locked
+ status is also available on instance details panel.