From 20a571fdd299f18c497bbb22ab0af7ead1bc4690 Mon Sep 17 00:00:00 2001 From: Jesper Schmitz Mouridsen Date: Tue, 14 Jun 2022 21:32:18 +0000 Subject: [PATCH] Add cinder-user-facing messages for Backup This patch adds a tab for cinder user messages for volume backups. Cinder user messages show error details for cinder resources like if we are unable to create a volume backup due to some failure in cinder it will show us the reason for failure. It also updates project and admin SnapshotDetailsTabs to use DetailTabsGroup instead of TabGroup to improve top padding. Also adds the fail_reason in the detail view, if backup errored. Related-Bug https://bugs.launchpad.net/cinder/+bug/1978729 Change-Id: I4e639211043270e814fac489f915588af03f966a --- openstack_dashboard/api/cinder.py | 2 +- .../dashboards/admin/backups/tabs.py | 8 ++++- .../templates/backups/_detail_overview.html | 4 +++ .../dashboards/admin/snapshots/tabs.py | 2 +- .../dashboards/project/backups/tables.py | 18 ++++++++++ .../dashboards/project/backups/tabs.py | 26 ++++++++++++-- .../templates/backups/_detail_overview.html | 4 +++ .../dashboards/project/backups/tests.py | 36 ++++++++++++++++++- .../dashboards/project/snapshots/tabs.py | 2 +- .../test/test_data/cinder_data.py | 14 ++++++++ ...ckup-cinder-messages-2127d04da3c82033.yaml | 4 +++ 11 files changed, 113 insertions(+), 7 deletions(-) create mode 100644 releasenotes/notes/cinder-backup-cinder-messages-2127d04da3c82033.yaml diff --git a/openstack_dashboard/api/cinder.py b/openstack_dashboard/api/cinder.py index 03ac5aee34..89319cd4ac 100644 --- a/openstack_dashboard/api/cinder.py +++ b/openstack_dashboard/api/cinder.py @@ -121,7 +121,7 @@ class VolumeBackup(BaseCinderAPIResourceWrapper): _attrs = ['id', 'name', 'description', 'container', 'size', 'status', 'created_at', 'volume_id', 'availability_zone', 'snapshot_id', - 'os-backup-project-attr:project_id'] + 'os-backup-project-attr:project_id', 'fail_reason'] _volume = None _snapshot = None diff --git a/openstack_dashboard/dashboards/admin/backups/tabs.py b/openstack_dashboard/dashboards/admin/backups/tabs.py index d4631822cd..dfb40ddaa4 100644 --- a/openstack_dashboard/dashboards/admin/backups/tabs.py +++ b/openstack_dashboard/dashboards/admin/backups/tabs.py @@ -10,6 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. +from openstack_dashboard.dashboards.project.backups \ + import tables as backup_messages_tables from openstack_dashboard.dashboards.project.backups \ import tabs as project_tabs @@ -19,5 +21,9 @@ class AdminBackupOverviewTab(project_tabs.BackupOverviewTab): redirect_url = 'horizon:admin:backups:index' +class BackupMessagesTab(project_tabs.BackupMessagesTab): + table_classes = (backup_messages_tables.BackupMessagesTable,) + + class AdminBackupDetailTabs(project_tabs.BackupDetailTabs): - tabs = (AdminBackupOverviewTab,) + tabs = (AdminBackupOverviewTab, BackupMessagesTab) diff --git a/openstack_dashboard/dashboards/admin/backups/templates/backups/_detail_overview.html b/openstack_dashboard/dashboards/admin/backups/templates/backups/_detail_overview.html index 948df205bc..c878d33bf4 100644 --- a/openstack_dashboard/dashboards/admin/backups/templates/backups/_detail_overview.html +++ b/openstack_dashboard/dashboards/admin/backups/templates/backups/_detail_overview.html @@ -14,6 +14,10 @@
{{ backup.project_id|default:_("-") }}
{% trans "Status" %}
{{ backup.status|capfirst }}
+ {% if backup.status == 'error' %} +
{%trans "Fail reason"%}
+
{{ backup.fail_reason }}
+ {% endif %} {% if volume %}
{% trans "Volume" %}
diff --git a/openstack_dashboard/dashboards/admin/snapshots/tabs.py b/openstack_dashboard/dashboards/admin/snapshots/tabs.py index 940fb074f3..7ef2f5616c 100644 --- a/openstack_dashboard/dashboards/admin/snapshots/tabs.py +++ b/openstack_dashboard/dashboards/admin/snapshots/tabs.py @@ -34,6 +34,6 @@ class SnapshotMessagesTab(project_tab.SnapshotMessagesTab): table_classes = (snap_messages_tables.SnapshotMessagesTable,) -class SnapshotDetailsTabs(tabs.TabGroup): +class SnapshotDetailsTabs(tabs.DetailTabsGroup): slug = "snapshot_details" tabs = (OverviewTab, SnapshotMessagesTab) diff --git a/openstack_dashboard/dashboards/project/backups/tables.py b/openstack_dashboard/dashboards/project/backups/tables.py index 4cbf34ef8f..6c0a1c46d2 100644 --- a/openstack_dashboard/dashboards/project/backups/tables.py +++ b/openstack_dashboard/dashboards/project/backups/tables.py @@ -197,3 +197,21 @@ class BackupsTable(tables.DataTable): row_class = UpdateRow table_actions = (DeleteBackup,) row_actions = (RestoreBackup, DeleteBackup) + + +class BackupMessagesTable(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 = "backup_messages" + verbose_name = _("Messages") diff --git a/openstack_dashboard/dashboards/project/backups/tabs.py b/openstack_dashboard/dashboards/project/backups/tabs.py index d867af4852..74606fa45c 100644 --- a/openstack_dashboard/dashboards/project/backups/tabs.py +++ b/openstack_dashboard/dashboards/project/backups/tabs.py @@ -18,6 +18,8 @@ from horizon import exceptions from horizon import tabs from openstack_dashboard.api import cinder +from openstack_dashboard.dashboards.project.backups \ + import tables as backup_messages_tables class BackupOverviewTab(tabs.Tab): @@ -53,6 +55,26 @@ class BackupOverviewTab(tabs.Tab): redirect=redirect) -class BackupDetailTabs(tabs.TabGroup): +class BackupMessagesTab(tabs.TableTab): + table_classes = (backup_messages_tables.BackupMessagesTable,) + name = _("Messages") + slug = "messages_tab" + template_name = ("horizon/common/_detail_table.html") + preload = False + + def get_backup_messages_data(self): + messages = [] + backup = self.tab_group.kwargs['backup'] + backup_id = backup.id + try: + messages = cinder.message_list(self.request, search_opts={ + 'resource_type': 'volume_backup', 'resource_uuid': backup_id}) + except Exception: + exceptions.handle(self.request, _("Unable to retrieve " + "backup messages.")) + return messages + + +class BackupDetailTabs(tabs.DetailTabsGroup): slug = "backup_details" - tabs = (BackupOverviewTab,) + tabs = (BackupOverviewTab, BackupMessagesTab) diff --git a/openstack_dashboard/dashboards/project/backups/templates/backups/_detail_overview.html b/openstack_dashboard/dashboards/project/backups/templates/backups/_detail_overview.html index 26e78b5185..6567b88be8 100644 --- a/openstack_dashboard/dashboards/project/backups/templates/backups/_detail_overview.html +++ b/openstack_dashboard/dashboards/project/backups/templates/backups/_detail_overview.html @@ -12,6 +12,10 @@ {% endif %}
{% trans "Status" %}
{{ backup.status|capfirst }}
+ {% if backup.status == 'error' %} +
{%trans "Fail reason"%}
+
{{ backup.fail_reason }}
+ {% endif %} {% if volume %}
{% trans "Volume" %}
diff --git a/openstack_dashboard/dashboards/project/backups/tests.py b/openstack_dashboard/dashboards/project/backups/tests.py index 4a93d782ad..d769f10a8a 100644 --- a/openstack_dashboard/dashboards/project/backups/tests.py +++ b/openstack_dashboard/dashboards/project/backups/tests.py @@ -9,7 +9,7 @@ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # License for the specific language governing permissions and limitations # under the License. - +from unittest import mock from urllib import parse from django.conf import settings @@ -20,6 +20,8 @@ from django.utils.http import urlencode from openstack_dashboard import api from openstack_dashboard.dashboards.project.backups \ import tables as backup_tables +from openstack_dashboard.dashboards.project.backups \ + import tabs from openstack_dashboard.test import helpers as test @@ -399,6 +401,38 @@ class VolumeBackupsViewTests(test.TestCase): self.mock_volume_get.assert_called_once_with( test.IsHttpRequest(), backup.volume_id) + @test.create_mocks({api.cinder: ('volume_backup_get', + 'volume_get', + 'message_list')}) + def test_volume_backup_detail_view_with_messages_tab(self): + + backup = self.cinder_volume_backups.first() + volume = self.cinder_volumes.first() + + self.mock_volume_backup_get.return_value = backup + self.mock_volume_get.return_value = volume + messages = [msg for msg in self.cinder_messages.list() + if msg.resource_type == 'VOLUME_BACKUP'] + self.mock_message_list.return_value = messages + url = reverse('horizon:project:backups:detail', + args=[backup.id]) + detail_view = tabs.BackupDetailTabs(self.request) + messages_tab_link = "?%s=%s" % ( + detail_view.param_name, + detail_view.get_tab("messages_tab").get_id()) + url += messages_tab_link + res = self.client.get(url) + self.assertTemplateUsed(res, 'horizon/common/_detail.html') + self.assertContains(res, messages[0].user_message) + self.assertNoMessages() + self.mock_volume_backup_get.assert_has_calls([ + mock.call(test.IsHttpRequest(), backup.id), + ]) + search_opts = {'resource_type': 'volume_backup', + 'resource_uuid': backup.id} + self.mock_message_list.assert_called_once_with( + test.IsHttpRequest(), search_opts=search_opts) + @test.create_mocks({api.cinder: ('volume_list', 'volume_backup_restore')}) def test_restore_backup(self): diff --git a/openstack_dashboard/dashboards/project/snapshots/tabs.py b/openstack_dashboard/dashboards/project/snapshots/tabs.py index 5a3f63deb4..34957b8a4f 100644 --- a/openstack_dashboard/dashboards/project/snapshots/tabs.py +++ b/openstack_dashboard/dashboards/project/snapshots/tabs.py @@ -68,6 +68,6 @@ class SnapshotMessagesTab(tabs.TableTab): return messages -class SnapshotDetailTabs(tabs.TabGroup): +class SnapshotDetailTabs(tabs.DetailTabsGroup): slug = "snapshot_details" tabs = (OverviewTab, SnapshotMessagesTab) diff --git a/openstack_dashboard/test/test_data/cinder_data.py b/openstack_dashboard/test/test_data/cinder_data.py index cc2f72681d..8aa0b5e6c9 100644 --- a/openstack_dashboard/test/test_data/cinder_data.py +++ b/openstack_dashboard/test/test_data/cinder_data.py @@ -579,6 +579,20 @@ def data(TEST): 'user_message': ('schedule allocate volume:' 'Could not find any available weighted backend.'), }) + messages_4 = messages.Message( + messages.MessageManager(None), + {'created_at': '2020-09-24T11:03:31.000000', + 'event_id': 'VOLUME_VOLUME_BACKUP_001_004', + 'guaranteed_until': '2020-10-24T11:03:31.000000', + 'id': '029c84a0-5810-47ed-94b6-c2aa61c5aaaf', + 'resource_type': 'VOLUME_BACKUP', + 'resource_uuid': '3c2106cb-ebef-490e-803d-b92f061e7308', + 'message_level': 'ERROR', + 'user_message': ('create backup:' + 'Backup driver failed to create backup.'), + }) + TEST.cinder_messages.add(api.cinder.Message(messages_1)) TEST.cinder_messages.add(api.cinder.Message(messages_2)) TEST.cinder_messages.add(api.cinder.Message(messages_3)) + TEST.cinder_messages.add(api.cinder.Message(messages_4)) diff --git a/releasenotes/notes/cinder-backup-cinder-messages-2127d04da3c82033.yaml b/releasenotes/notes/cinder-backup-cinder-messages-2127d04da3c82033.yaml new file mode 100644 index 0000000000..aa52ddfce7 --- /dev/null +++ b/releasenotes/notes/cinder-backup-cinder-messages-2127d04da3c82033.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Cinder user messages are now available for volume backups in a messages tab.