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:
parent
fbeaefcd66
commit
79ff0d45c4
@ -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',
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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'
|
||||
})
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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")
|
||||
|
@ -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)
|
||||
|
@ -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>
|
||||
|
@ -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):
|
||||
|
@ -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):
|
||||
|
@ -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))
|
||||
|
@ -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):
|
||||
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user