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.