Show volume's messages in details view
Cinder can now show asynchronous error messages that are related to a volume. However, this requires microversions support. This patch cleans up the version and endpoint selection logic too. For compatibility with setups that don't follow good practices, it also checks for a "volume" endpoint. Change-Id: Ifdb7537b5208683bb0a81da9ac504d58beaedc89 Closes-Bug: 1352516
This commit is contained in:
parent
665a2cd874
commit
938ee35d94
|
@ -26,6 +26,8 @@ from django.conf import settings
|
|||
from django.utils.translation import pgettext_lazy
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
|
||||
from cinderclient import api_versions
|
||||
from cinderclient import client as cinder_client
|
||||
from cinderclient import exceptions as cinder_exception
|
||||
from cinderclient.v2.contrib import list_extensions as cinder_list_extensions
|
||||
|
||||
|
@ -35,6 +37,7 @@ from horizon.utils.memoized import memoized
|
|||
from horizon.utils.memoized import memoized_with_request
|
||||
|
||||
from openstack_dashboard.api import base
|
||||
from openstack_dashboard.api import microversions
|
||||
from openstack_dashboard.api import nova
|
||||
from openstack_dashboard.contrib.developer.profiler import api as profiler
|
||||
|
||||
|
@ -52,12 +55,12 @@ CONSUMER_CHOICES = (
|
|||
('both', pgettext_lazy('Both of front-end and back-end', u'both')),
|
||||
)
|
||||
|
||||
VERSIONS = base.APIVersionManager("volume", preferred_version=2)
|
||||
VERSIONS = base.APIVersionManager("volume", preferred_version='2')
|
||||
|
||||
try:
|
||||
from cinderclient.v2 import client as cinder_client_v2
|
||||
VERSIONS.load_supported_version(2, {"client": cinder_client_v2,
|
||||
"version": 2})
|
||||
VERSIONS.load_supported_version('2', {"client": cinder_client_v2,
|
||||
"version": '2'})
|
||||
except ImportError:
|
||||
pass
|
||||
|
||||
|
@ -167,51 +170,81 @@ class VolumePool(base.APIResourceWrapper):
|
|||
|
||||
|
||||
def get_auth_params_from_request(request):
|
||||
api_version = VERSIONS.get_active_version()
|
||||
cinder_url = ""
|
||||
auth_url = base.url_for(request, 'identity')
|
||||
try:
|
||||
# The cinder client assumes that the v2 endpoint type will be
|
||||
# 'volumev2'.
|
||||
if api_version['version'] == 2:
|
||||
try:
|
||||
cinder_url = base.url_for(request, 'volumev2')
|
||||
except exceptions.ServiceCatalogException:
|
||||
LOG.warning("Cinder v2 requested but no 'volumev2' service "
|
||||
"type available in Keystone catalog.")
|
||||
except exceptions.ServiceCatalogException:
|
||||
LOG.debug('no volume service configured.')
|
||||
raise
|
||||
|
||||
cinder_urls = []
|
||||
for service_name in ('volume', 'volumev2', 'volumev3'):
|
||||
try:
|
||||
cinder_url = base.url_for(request, service_name)
|
||||
cinder_urls.append((service_name, cinder_url))
|
||||
except exceptions.ServiceCatalogException:
|
||||
pass
|
||||
if not cinder_urls:
|
||||
raise exceptions.ServiceCatalogException(
|
||||
"no volume service configured")
|
||||
cinder_urls = tuple(cinder_urls) # need to make it cacheable
|
||||
return(
|
||||
request.user.username,
|
||||
request.user.token.id,
|
||||
request.user.tenant_id,
|
||||
cinder_url,
|
||||
cinder_urls,
|
||||
auth_url,
|
||||
)
|
||||
|
||||
|
||||
@memoized_with_request(get_auth_params_from_request)
|
||||
def cinderclient(request_auth_params):
|
||||
api_version = VERSIONS.get_active_version()
|
||||
def cinderclient(request_auth_params, version=None):
|
||||
if version is None:
|
||||
api_version = VERSIONS.get_active_version()
|
||||
version = api_version['version']
|
||||
insecure = getattr(settings, 'OPENSTACK_SSL_NO_VERIFY', False)
|
||||
cacert = getattr(settings, 'OPENSTACK_SSL_CACERT', None)
|
||||
|
||||
username, token_id, tenant_id, cinder_url, auth_url =\
|
||||
request_auth_params
|
||||
c = api_version['client'].Client(username,
|
||||
token_id,
|
||||
project_id=tenant_id,
|
||||
auth_url=auth_url,
|
||||
insecure=insecure,
|
||||
cacert=cacert,
|
||||
http_log_debug=settings.DEBUG)
|
||||
username, token_id, tenant_id, cinder_urls, auth_url = request_auth_params
|
||||
version = base.Version(version)
|
||||
if version == 2:
|
||||
service_names = ('volumev2', 'volume')
|
||||
elif version == 3:
|
||||
service_names = ('volumev3', 'volume')
|
||||
else:
|
||||
service_names = ('volume',)
|
||||
for name, cinder_url in cinder_urls:
|
||||
if name in service_names:
|
||||
break
|
||||
else:
|
||||
raise exceptions.ServiceCatalogException(
|
||||
"Cinder {version} requested but no '{service}' service "
|
||||
"type available in Keystone catalog.".format(version=version,
|
||||
service=service_names)
|
||||
)
|
||||
c = cinder_client.Client(
|
||||
version,
|
||||
username,
|
||||
token_id,
|
||||
project_id=tenant_id,
|
||||
auth_url=auth_url,
|
||||
insecure=insecure,
|
||||
cacert=cacert,
|
||||
http_log_debug=settings.DEBUG,
|
||||
)
|
||||
c.client.auth_token = token_id
|
||||
c.client.management_url = cinder_url
|
||||
return c
|
||||
|
||||
|
||||
def get_microversion(request, feature):
|
||||
for service_name in ('volume', 'volumev2', 'volumev3'):
|
||||
try:
|
||||
cinder_url = base.url_for(request, service_name)
|
||||
break
|
||||
except exceptions.ServiceCatalogException:
|
||||
continue
|
||||
else:
|
||||
return None
|
||||
min_ver, max_ver = cinder_client.get_server_version(cinder_url)
|
||||
return (microversions.get_microversion_for_feature(
|
||||
'cinder', feature, api_versions.APIVersion, min_ver, max_ver))
|
||||
|
||||
|
||||
def _replace_v2_parameters(data):
|
||||
if VERSIONS.active < 2:
|
||||
data['display_name'] = data['name']
|
||||
|
@ -1024,10 +1057,21 @@ def pool_list(request, detailed=False):
|
|||
detailed=detailed)]
|
||||
|
||||
|
||||
@profiler.trace
|
||||
def message_list(request, search_opts=None):
|
||||
version = get_microversion(request, 'message_list')
|
||||
if version is None:
|
||||
LOG.warning("insufficient microversion for message_list")
|
||||
return []
|
||||
c_client = cinderclient(request, version=version)
|
||||
return c_client.messages.list(search_opts)
|
||||
|
||||
|
||||
def is_volume_service_enabled(request):
|
||||
return bool(
|
||||
base.is_service_enabled(request, 'volume') or
|
||||
base.is_service_enabled(request, 'volumev2')
|
||||
base.is_service_enabled(request, 'volumev2') or
|
||||
base.is_service_enabled(request, 'volumev3')
|
||||
)
|
||||
|
||||
|
||||
|
|
|
@ -97,4 +97,22 @@
|
|||
<dd>{{ volume.transfer.created_at|parse_date }}</dd>
|
||||
</dl>
|
||||
{% endif %}
|
||||
|
||||
{% if volume.messages %}
|
||||
<h4>{% trans "Messages" %}</h4>
|
||||
<hr class="header_rule">
|
||||
<div>
|
||||
{% for m in volume.messages %}
|
||||
<div class="alert
|
||||
{% if m.message_level == 'ERROR' %}alert-danger
|
||||
{% elif m.message_level == 'WARNING' %}alert-warning
|
||||
{% elif m.message_level == 'INFO' %}alert-info
|
||||
{% else %}alert-success
|
||||
{% endif %}
|
||||
">
|
||||
{{ m.user_message }}
|
||||
</div>
|
||||
{% endfor %}
|
||||
</div>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
|
|
@ -1392,7 +1392,8 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
|
|||
|
||||
@test.create_stubs({cinder: ('tenant_absolute_limits',
|
||||
'volume_get',
|
||||
'volume_snapshot_list'),
|
||||
'volume_snapshot_list',
|
||||
'message_list'),
|
||||
api.nova: ('server_get',)})
|
||||
def test_detail_view(self):
|
||||
volume = self.cinder_volumes.first()
|
||||
|
@ -1408,6 +1409,13 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
|
|||
api.nova.server_get(IsA(http.HttpRequest), server.id).AndReturn(server)
|
||||
cinder.tenant_absolute_limits(IsA(http.HttpRequest))\
|
||||
.AndReturn(self.cinder_limits['absolute'])
|
||||
cinder.message_list(
|
||||
IsA(http.HttpRequest),
|
||||
{
|
||||
'resource_uuid': '11023e92-8008-4c8b-8059-7f2293ff3887',
|
||||
'resource_type': 'volume',
|
||||
},
|
||||
).AndReturn([])
|
||||
|
||||
self.mox.ReplayAll()
|
||||
|
||||
|
@ -1420,7 +1428,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
|
|||
self.assertNoMessages()
|
||||
|
||||
@test.create_stubs({cinder: ('volume_get',
|
||||
'volume_get_encryption_metadata'), })
|
||||
'volume_get_encryption_metadata')})
|
||||
def test_encryption_detail_view_encrypted(self):
|
||||
enc_meta = self.cinder_volume_encryption.first()
|
||||
volume = self.cinder_volumes.get(name='my_volume2')
|
||||
|
|
|
@ -173,6 +173,18 @@ class DetailView(tabs.TabView):
|
|||
exceptions.handle(self.request,
|
||||
_('Unable to retrieve volume details.'),
|
||||
redirect=redirect)
|
||||
try:
|
||||
volume.messages = cinder.message_list(
|
||||
self.request,
|
||||
{'resource_type': 'volume', 'resource_uuid': volume.id},
|
||||
)
|
||||
except Exception:
|
||||
volume.messages = []
|
||||
exceptions.handle(
|
||||
self.request,
|
||||
_('Unable to retrieve volume messages.'),
|
||||
ignore=True,
|
||||
)
|
||||
return volume
|
||||
|
||||
def get_redirect_url(self):
|
||||
|
|
Loading…
Reference in New Issue