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
This commit is contained in:
Jesper Schmitz Mouridsen 2022-06-14 21:32:18 +00:00
parent f044c4b0a3
commit 20a571fdd2
11 changed files with 113 additions and 7 deletions

View File

@ -121,7 +121,7 @@ class VolumeBackup(BaseCinderAPIResourceWrapper):
_attrs = ['id', 'name', 'description', 'container', 'size', 'status', _attrs = ['id', 'name', 'description', 'container', 'size', 'status',
'created_at', 'volume_id', 'availability_zone', 'snapshot_id', 'created_at', 'volume_id', 'availability_zone', 'snapshot_id',
'os-backup-project-attr:project_id'] 'os-backup-project-attr:project_id', 'fail_reason']
_volume = None _volume = None
_snapshot = None _snapshot = None

View File

@ -10,6 +10,8 @@
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from openstack_dashboard.dashboards.project.backups \
import tables as backup_messages_tables
from openstack_dashboard.dashboards.project.backups \ from openstack_dashboard.dashboards.project.backups \
import tabs as project_tabs import tabs as project_tabs
@ -19,5 +21,9 @@ class AdminBackupOverviewTab(project_tabs.BackupOverviewTab):
redirect_url = 'horizon:admin:backups:index' redirect_url = 'horizon:admin:backups:index'
class BackupMessagesTab(project_tabs.BackupMessagesTab):
table_classes = (backup_messages_tables.BackupMessagesTable,)
class AdminBackupDetailTabs(project_tabs.BackupDetailTabs): class AdminBackupDetailTabs(project_tabs.BackupDetailTabs):
tabs = (AdminBackupOverviewTab,) tabs = (AdminBackupOverviewTab, BackupMessagesTab)

View File

@ -14,6 +14,10 @@
<dd>{{ backup.project_id|default:_("-") }}</dd> <dd>{{ backup.project_id|default:_("-") }}</dd>
<dt>{% trans "Status" %}</dt> <dt>{% trans "Status" %}</dt>
<dd>{{ backup.status|capfirst }}</dd> <dd>{{ backup.status|capfirst }}</dd>
{% if backup.status == 'error' %}
<dt>{%trans "Fail reason"%}</dt>
<dd>{{ backup.fail_reason }} <dd>
{% endif %}
{% if volume %} {% if volume %}
<dt>{% trans "Volume" %}</dt> <dt>{% trans "Volume" %}</dt>
<dd> <dd>

View File

@ -34,6 +34,6 @@ class SnapshotMessagesTab(project_tab.SnapshotMessagesTab):
table_classes = (snap_messages_tables.SnapshotMessagesTable,) table_classes = (snap_messages_tables.SnapshotMessagesTable,)
class SnapshotDetailsTabs(tabs.TabGroup): class SnapshotDetailsTabs(tabs.DetailTabsGroup):
slug = "snapshot_details" slug = "snapshot_details"
tabs = (OverviewTab, SnapshotMessagesTab) tabs = (OverviewTab, SnapshotMessagesTab)

View File

@ -197,3 +197,21 @@ class BackupsTable(tables.DataTable):
row_class = UpdateRow row_class = UpdateRow
table_actions = (DeleteBackup,) table_actions = (DeleteBackup,)
row_actions = (RestoreBackup, 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")

View File

@ -18,6 +18,8 @@ from horizon import exceptions
from horizon import tabs from horizon import tabs
from openstack_dashboard.api import cinder from openstack_dashboard.api import cinder
from openstack_dashboard.dashboards.project.backups \
import tables as backup_messages_tables
class BackupOverviewTab(tabs.Tab): class BackupOverviewTab(tabs.Tab):
@ -53,6 +55,26 @@ class BackupOverviewTab(tabs.Tab):
redirect=redirect) 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" slug = "backup_details"
tabs = (BackupOverviewTab,) tabs = (BackupOverviewTab, BackupMessagesTab)

View File

@ -12,6 +12,10 @@
{% endif %} {% endif %}
<dt>{% trans "Status" %}</dt> <dt>{% trans "Status" %}</dt>
<dd>{{ backup.status|capfirst }}</dd> <dd>{{ backup.status|capfirst }}</dd>
{% if backup.status == 'error' %}
<dt>{%trans "Fail reason"%}</dt>
<dd>{{ backup.fail_reason }} <dd>
{% endif %}
{% if volume %} {% if volume %}
<dt>{% trans "Volume" %}</dt> <dt>{% trans "Volume" %}</dt>
<dd> <dd>

View File

@ -9,7 +9,7 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations # License for the specific language governing permissions and limitations
# under the License. # under the License.
from unittest import mock
from urllib import parse from urllib import parse
from django.conf import settings from django.conf import settings
@ -20,6 +20,8 @@ from django.utils.http import urlencode
from openstack_dashboard import api from openstack_dashboard import api
from openstack_dashboard.dashboards.project.backups \ from openstack_dashboard.dashboards.project.backups \
import tables as backup_tables import tables as backup_tables
from openstack_dashboard.dashboards.project.backups \
import tabs
from openstack_dashboard.test import helpers as test from openstack_dashboard.test import helpers as test
@ -399,6 +401,38 @@ class VolumeBackupsViewTests(test.TestCase):
self.mock_volume_get.assert_called_once_with( self.mock_volume_get.assert_called_once_with(
test.IsHttpRequest(), backup.volume_id) 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', @test.create_mocks({api.cinder: ('volume_list',
'volume_backup_restore')}) 'volume_backup_restore')})
def test_restore_backup(self): def test_restore_backup(self):

View File

@ -68,6 +68,6 @@ class SnapshotMessagesTab(tabs.TableTab):
return messages return messages
class SnapshotDetailTabs(tabs.TabGroup): class SnapshotDetailTabs(tabs.DetailTabsGroup):
slug = "snapshot_details" slug = "snapshot_details"
tabs = (OverviewTab, SnapshotMessagesTab) tabs = (OverviewTab, SnapshotMessagesTab)

View File

@ -579,6 +579,20 @@ def data(TEST):
'user_message': ('schedule allocate volume:' 'user_message': ('schedule allocate volume:'
'Could not find any available weighted backend.'), '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_1))
TEST.cinder_messages.add(api.cinder.Message(messages_2)) 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_3))
TEST.cinder_messages.add(api.cinder.Message(messages_4))

View File

@ -0,0 +1,4 @@
---
features:
- |
Cinder user messages are now available for volume backups in a messages tab.