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.