Add cinder-user-facing messages

This patch adds a tab for cinder user messages for volumes and
snapshots. Cinder user messages shows error details for cinder
resources like if we are unable to create a volume due to some
failure in cinder it will shows us the reason of failure.

Implements blueprint cinder-user-facing-messages

Change-Id: I6f1539ffebdf2dfd0a470009e9171e868c2a9ad3
This commit is contained in:
manchandavishal 2020-06-08 17:34:53 +00:00
parent fbeaefcd66
commit 79ff0d45c4
14 changed files with 156 additions and 62 deletions

View File

@ -187,6 +187,10 @@ class VolumePool(base.APIResourceWrapper):
'storage_protocol', 'extra_specs']
class Message(base.APIResourceWrapper):
_attrs = ['id', 'event_id', 'created_at', 'resource_type', 'user_message']
class Group(base.APIResourceWrapper):
_attrs = ['id', 'status', 'availability_zone', 'created_at', 'name',
'description', 'group_type', 'volume_types',

View File

@ -16,10 +16,12 @@ from django.utils.translation import ugettext_lazy as _
from horizon import tabs
from openstack_dashboard.dashboards.project.snapshots \
import tabs as overview_tab
import tables as snap_messages_tables
from openstack_dashboard.dashboards.project.snapshots \
import tabs as project_tab
class OverviewTab(overview_tab.OverviewTab):
class OverviewTab(project_tab.OverviewTab):
name = _("Overview")
slug = "overview"
template_name = ("project/snapshots/_detail_overview.html")
@ -28,6 +30,10 @@ class OverviewTab(overview_tab.OverviewTab):
return reverse('horizon:admin:snapshots:index')
class SnapshotMessagesTab(project_tab.SnapshotMessagesTab):
table_classes = (snap_messages_tables.SnapshotMessagesTable,)
class SnapshotDetailsTabs(tabs.TabGroup):
slug = "snapshot_details"
tabs = (OverviewTab,)
tabs = (OverviewTab, SnapshotMessagesTab)

View File

@ -11,6 +11,8 @@
# under the License.
from openstack_dashboard.dashboards.admin.snapshots import tables
from openstack_dashboard.dashboards.project.volumes \
import tables as vol_messages_tables
from openstack_dashboard.dashboards.project.volumes import tabs as project_tabs
@ -34,5 +36,9 @@ class SnapshotTab(project_tabs.SnapshotTab):
table_classes = (tables.VolumeDetailsSnapshotsTable,)
class VolumeMessagesTab(project_tabs.VolumeMessagesTab):
table_classes = (vol_messages_tables.VolumeMessagesTable,)
class VolumeDetailTabs(project_tabs.VolumeDetailTabs):
tabs = (OverviewTab, SnapshotTab)
tabs = (OverviewTab, SnapshotTab, VolumeMessagesTab)

View File

@ -380,7 +380,7 @@ class VolumeTests(test.BaseAdminViewTests):
@test.create_mocks({
api.nova: ['server_get'],
api.cinder: ['tenant_absolute_limits', 'volume_get',
'volume_snapshot_list', 'message_list']})
'volume_snapshot_list']})
def test_detail_view_snapshot_tab(self):
volume = self.cinder_volumes.first()
server = self.servers.first()
@ -394,7 +394,6 @@ class VolumeTests(test.BaseAdminViewTests):
self.mock_tenant_absolute_limits.return_value = volume_limits
self.mock_volume_get.return_value = volume
self.mock_volume_snapshot_list.return_value = this_volume_snapshots
self.mock_message_list.return_value = []
url = (reverse(DETAIL_URL, args=[volume.id]) + '?' +
'='.join(['tab', 'volume_details__snapshots_tab']))
@ -414,9 +413,3 @@ class VolumeTests(test.BaseAdminViewTests):
self.mock_volume_snapshot_list.assert_called_once_with(
test.IsHttpRequest(),
search_opts={'volume_id': volume.id, 'all_tenants': True})
self.mock_message_list.assert_called_once_with(
test.IsHttpRequest(),
{
'resource_uuid': volume.id,
'resource_type': 'volume'
})

View File

@ -263,3 +263,21 @@ class VolumeSnapshotsTable(VolumeDetailsSnapshotsTable):
class Meta(VolumeDetailsSnapshotsTable.Meta):
pass
class SnapshotMessagesTable(tables.DataTable):
message_id = tables.Column("id", verbose_name=_("ID"))
message_level = tables.Column("message_level",
verbose_name=_("Message Level"))
event_id = tables.Column("event_id",
verbose_name=_("Event Id"))
user_message = tables.Column("user_message",
verbose_name=_("User Message"))
created_at = tables.Column("created_at",
verbose_name=_("Created At"))
guaranteed_until = tables.Column("guaranteed_until",
verbose_name=_("Guaranteed Until"))
class Meta(object):
name = "snapshot_messages"
verbose_name = _("Messages")

View File

@ -19,6 +19,8 @@ from horizon import exceptions
from horizon import tabs
from openstack_dashboard.api import cinder
from openstack_dashboard.dashboards.project.snapshots \
import tables as snap_messages_tables
class OverviewTab(tabs.Tab):
@ -43,6 +45,29 @@ class OverviewTab(tabs.Tab):
return reverse('horizon:project:snapshots:index')
class SnapshotMessagesTab(tabs.TableTab):
table_classes = (snap_messages_tables.SnapshotMessagesTable,)
name = _("Messages")
slug = "messages_tab"
template_name = ("horizon/common/_detail_table.html")
preload = False
def get_snapshot_messages_data(self):
messages = []
snapshot = self.tab_group.kwargs['snapshot']
snap_id = snapshot.id
try:
snap_msgs = cinder.message_list(self.request, search_opts={
'resource_type': 'volume_snapshot', 'resource_uuid': snap_id})
for snap_msg in snap_msgs:
messages.append(snap_msg)
except Exception:
exceptions.handle(self.request, _("Unable to retrieve "
"snapshot messages."))
return messages
class SnapshotDetailTabs(tabs.TabGroup):
slug = "snapshot_details"
tabs = (OverviewTab,)
tabs = (OverviewTab, SnapshotMessagesTab)

View File

@ -656,3 +656,21 @@ class AttachmentsTable(tables.DataTable):
verbose_name = _("Attachments")
table_actions = (DetachVolume,)
row_actions = (DetachVolume,)
class VolumeMessagesTable(tables.DataTable):
message_id = tables.Column("id", verbose_name=_("ID"))
message_level = tables.Column("message_level",
verbose_name=_("Message Level"))
event_id = tables.Column("event_id",
verbose_name=_("Event Id"))
user_message = tables.Column("user_message",
verbose_name=_("User Message"))
created_at = tables.Column("created_at",
verbose_name=_("Created At"))
guaranteed_until = tables.Column("guaranteed_until",
verbose_name=_("Guaranteed Until"))
class Meta(object):
name = "volume_messages"
verbose_name = _("Messages")

View File

@ -19,6 +19,8 @@ from horizon import tabs
from openstack_dashboard.api import cinder
from openstack_dashboard.dashboards.project.snapshots import tables
from openstack_dashboard.dashboards.project.volumes \
import tables as vol_messages_tables
class OverviewTab(tabs.Tab):
@ -77,6 +79,28 @@ class SnapshotTab(tabs.TableTab):
return snapshots
class VolumeMessagesTab(tabs.TableTab):
table_classes = (vol_messages_tables.VolumeMessagesTable,)
name = _("Messages")
slug = "messages_tab"
template_name = ("horizon/common/_detail_table.html")
preload = False
def get_volume_messages_data(self):
messages = []
volume = self.tab_group.kwargs['volume'].id
try:
vol_msgs = cinder.message_list(self.request, search_opts={
'resource_type': 'volume', 'resource_uuid': volume})
for vol_msg in vol_msgs:
messages.append(vol_msg)
except Exception:
exceptions.handle(self.request, _("Unable to retrieve "
"volume messages."))
return messages
class VolumeDetailTabs(tabs.DetailTabsGroup):
slug = "volume_details"
tabs = (OverviewTab, SnapshotTab)
tabs = (OverviewTab, SnapshotTab, VolumeMessagesTab)

View File

@ -105,22 +105,4 @@
<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>

View File

@ -1404,8 +1404,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
@test.create_mocks({
api.nova: ['server_get'],
cinder: ['message_list',
'volume_snapshot_list',
cinder: ['volume_snapshot_list',
'volume_get',
'tenant_absolute_limits'],
})
@ -1422,7 +1421,6 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mock_server_get.return_value = server
self.mock_tenant_absolute_limits.return_value = \
self.cinder_limits['absolute']
self.mock_message_list.return_value = []
url = reverse('horizon:project:volumes:detail',
args=[volume.id])
@ -1438,12 +1436,6 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mock_server_get.assert_called_once_with(test.IsHttpRequest(),
server.id)
self.mock_tenant_absolute_limits.assert_called_once()
self.mock_message_list.assert_called_once_with(
test.IsHttpRequest(),
{
'resource_uuid': '11023e92-8008-4c8b-8059-7f2293ff3887',
'resource_type': 'volume',
})
@mock.patch.object(cinder, 'volume_get_encryption_metadata')
@mock.patch.object(cinder, 'volume_get')
@ -1530,8 +1522,7 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
api.nova: ['server_get'],
cinder: ['tenant_absolute_limits',
'volume_get',
'volume_snapshot_list',
'message_list'],
'volume_snapshot_list'],
})
def test_detail_view_snapshot_tab(self):
volume = self.cinder_volumes.first()
@ -1546,7 +1537,6 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mock_server_get.return_value = server
self.mock_tenant_absolute_limits.return_value = \
self.cinder_limits['absolute']
self.mock_message_list.return_value = []
self.mock_volume_snapshot_list.return_value = this_volume_snapshots
url = '?'.join([reverse(DETAIL_URL, args=[volume.id]),
@ -1564,12 +1554,6 @@ class VolumeViewTests(test.ResetImageAPIVersionMixin, test.TestCase):
self.mock_volume_snapshot_list.assert_called_once_with(
test.IsHttpRequest(), search_opts={'volume_id': volume.id})
self.mock_tenant_absolute_limits.assert_called_once()
self.mock_message_list.assert_called_once_with(
test.IsHttpRequest(),
{
'resource_uuid': volume.id,
'resource_type': 'volume'
})
@mock.patch.object(cinder, 'volume_get')
def test_detail_view_with_exception(self, mock_get):

View File

@ -223,18 +223,6 @@ class DetailView(tabs.TabbedTableView):
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, snapshots
def get_redirect_url(self):

View File

@ -28,6 +28,7 @@ from cinderclient.v2 import volumes
from cinderclient.v3 import group_snapshots
from cinderclient.v3 import group_types
from cinderclient.v3 import groups
from cinderclient.v3 import messages
from openstack_dashboard import api
from openstack_dashboard.api import cinder as cinder_api
@ -36,6 +37,7 @@ from openstack_dashboard.usage import quotas as usage_quotas
def data(TEST):
TEST.cinder_messages = utils.TestDataContainer()
TEST.cinder_services = utils.TestDataContainer()
TEST.cinder_volumes = utils.TestDataContainer()
TEST.cinder_volume_backups = utils.TestDataContainer()
@ -547,3 +549,27 @@ def data(TEST):
TEST.cinder_volume_snapshots_with_groups.add(
api.cinder.VolumeSnapshot(snapshot5))
# Cinder Messages
messages_1 = messages.Message(
messages.MessageManager(None),
{'created_at': '2020-07-08T17:12:06.000000',
'event_id': 'VOLUME_VOLUME_001_001',
'guaranteed_until': '2020-08-07T17:12:06.000000',
'id': '2d2bb0d7-af28-4566-9a65-6d987c19093c',
'resource_type': 'VOLUME',
'resource_uuid': '6d53d143-e10f-440a-a65f-16a6b6d068f7',
'user_message': 'schedule allocate volume:An unknown error occurred.'
})
messages_2 = messages.Message(
messages.MessageManager(None),
{'created_at': '2020-07-12T12:56:43.000000',
'event_id': 'VOLUME_VOLUME_SNAPSHOT_009_015',
'guaranteed_until': '2020-08-11T12:56:43.000000',
'id': 'd360b4e2-bda5-4289-b673-714a90cde80b',
'resource_type': 'VOLUME_SNAPSHOT',
'resource_uuid': '761634b0-fa1c-4e59-b8ad-d720807cb355',
'user_message': 'create snapshot:Snapshot is busy.'})
TEST.cinder_messages.add(api.cinder.Message(messages_1))
TEST.cinder_messages.add(api.cinder.Message(messages_2))

View File

@ -447,6 +447,19 @@ class CinderApiTests(test.APIMockTestCase):
self.assertEqual(default_volume_type, volume_type)
cinderclient.volume_types.default.assert_called_once()
@test.create_mocks({
api.cinder: [('_cinderclient_with_features', 'cinderclient'), ]})
def test_cinder_message_list(self):
search_opts = {'resource_type': 'VOLUME',
'resource_uuid': '6d53d143-e10f-440a-a65f-16a6b6d068f7'}
messages = self.cinder_messages.list()
cinderclient = self.mock_cinderclient.return_value
messages_mock = cinderclient.messages.list
messages_mock.return_value = messages
api.cinder.message_list(self.request, search_opts=search_opts)
messages_mock.assert_called_once_with(search_opts)
class CinderApiVersionTests(test.TestCase):

View File

@ -0,0 +1,7 @@
---
features:
- |
[`cinder-user-facing messages <https://blueprints.launchpad.net/horizon/+spec/cinder-user-facing-messages>`_]
This bp add a new tab "Messages" in volume/snapshot detail pages where User can see failure summary messages
for corresponding volume and snapshot resources. Before this bp it only shows user messages and message-level
info. for a specific volume in the detail page but now more parameters will be displayed for that volume.