summaryrefslogtreecommitdiff
path: root/openstackclient
diff options
context:
space:
mode:
Diffstat (limited to 'openstackclient')
-rw-r--r--openstackclient/tests/unit/volume/v3/fakes.py68
-rw-r--r--openstackclient/tests/unit/volume/v3/test_volume_message.py324
-rw-r--r--openstackclient/volume/v3/volume_message.py165
3 files changed, 557 insertions, 0 deletions
diff --git a/openstackclient/tests/unit/volume/v3/fakes.py b/openstackclient/tests/unit/volume/v3/fakes.py
index fb3b1b74..45cad8c1 100644
--- a/openstackclient/tests/unit/volume/v3/fakes.py
+++ b/openstackclient/tests/unit/volume/v3/fakes.py
@@ -32,6 +32,8 @@ class FakeVolumeClient(object):
self.attachments = mock.Mock()
self.attachments.resource_class = fakes.FakeResource(None, {})
+ self.messages = mock.Mock()
+ self.messages.resource_class = fakes.FakeResource(None, {})
self.volumes = mock.Mock()
self.volumes.resource_class = fakes.FakeResource(None, {})
@@ -59,6 +61,72 @@ class TestVolume(utils.TestCommand):
FakeVolume = volume_v2_fakes.FakeVolume
+class FakeVolumeMessage:
+ """Fake one or more volume messages."""
+
+ @staticmethod
+ def create_one_volume_message(attrs=None):
+ """Create a fake message.
+
+ :param attrs: A dictionary with all attributes of message
+ :return: A FakeResource object with id, name, status, etc.
+ """
+ attrs = attrs or {}
+
+ # Set default attribute
+ message_info = {
+ 'created_at': '2016-02-11T11:17:37.000000',
+ 'event_id': f'VOLUME_{random.randint(1, 999999):06d}',
+ 'guaranteed_until': '2016-02-11T11:17:37.000000',
+ 'id': uuid.uuid4().hex,
+ 'message_level': 'ERROR',
+ 'request_id': f'req-{uuid.uuid4().hex}',
+ 'resource_type': 'VOLUME',
+ 'resource_uuid': uuid.uuid4().hex,
+ 'user_message': f'message-{uuid.uuid4().hex}',
+ }
+
+ # Overwrite default attributes if there are some attributes set
+ message_info.update(attrs)
+
+ message = fakes.FakeResource(
+ None,
+ message_info,
+ loaded=True)
+ return message
+
+ @staticmethod
+ def create_volume_messages(attrs=None, count=2):
+ """Create multiple fake messages.
+
+ :param attrs: A dictionary with all attributes of message
+ :param count: The number of messages to be faked
+ :return: A list of FakeResource objects
+ """
+ messages = []
+ for n in range(0, count):
+ messages.append(FakeVolumeMessage.create_one_volume_message(attrs))
+
+ return messages
+
+ @staticmethod
+ def get_volume_messages(messages=None, count=2):
+ """Get an iterable MagicMock object with a list of faked messages.
+
+ If messages list is provided, then initialize the Mock object with the
+ list. Otherwise create one.
+
+ :param messages: A list of FakeResource objects faking messages
+ :param count: The number of messages to be faked
+ :return An iterable Mock object with side_effect set to a list of faked
+ messages
+ """
+ if messages is None:
+ messages = FakeVolumeMessage.create_messages(count)
+
+ return mock.Mock(side_effect=messages)
+
+
class FakeVolumeAttachment:
"""Fake one or more volume attachments."""
diff --git a/openstackclient/tests/unit/volume/v3/test_volume_message.py b/openstackclient/tests/unit/volume/v3/test_volume_message.py
new file mode 100644
index 00000000..68becf44
--- /dev/null
+++ b/openstackclient/tests/unit/volume/v3/test_volume_message.py
@@ -0,0 +1,324 @@
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# 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.mock import call
+
+from cinderclient import api_versions
+from osc_lib import exceptions
+
+from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes
+from openstackclient.tests.unit.volume.v3 import fakes as volume_fakes
+from openstackclient.volume.v3 import volume_message
+
+
+class TestVolumeMessage(volume_fakes.TestVolume):
+
+ def setUp(self):
+ super().setUp()
+
+ self.projects_mock = self.app.client_manager.identity.projects
+ self.projects_mock.reset_mock()
+
+ self.volume_messages_mock = self.app.client_manager.volume.messages
+ self.volume_messages_mock.reset_mock()
+
+
+class TestVolumeMessageDelete(TestVolumeMessage):
+
+ fake_messages = volume_fakes.FakeVolumeMessage.create_volume_messages(
+ count=2)
+
+ def setUp(self):
+ super().setUp()
+
+ self.volume_messages_mock.get = \
+ volume_fakes.FakeVolumeMessage.get_volume_messages(
+ self.fake_messages)
+ self.volume_messages_mock.delete.return_value = None
+
+ # Get the command object to mock
+ self.cmd = volume_message.DeleteMessage(self.app, None)
+
+ def test_message_delete(self):
+ self.app.client_manager.volume.api_version = \
+ api_versions.APIVersion('3.3')
+
+ arglist = [
+ self.fake_messages[0].id,
+ ]
+ verifylist = [
+ ('message_ids', [self.fake_messages[0].id]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ result = self.cmd.take_action(parsed_args)
+
+ self.volume_messages_mock.delete.assert_called_with(
+ self.fake_messages[0].id)
+ self.assertIsNone(result)
+
+ def test_message_delete_multiple_messages(self):
+ self.app.client_manager.volume.api_version = \
+ api_versions.APIVersion('3.3')
+
+ arglist = [
+ self.fake_messages[0].id,
+ self.fake_messages[1].id,
+ ]
+ verifylist = [
+ ('message_ids', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ result = self.cmd.take_action(parsed_args)
+
+ calls = []
+ for m in self.fake_messages:
+ calls.append(call(m.id))
+ self.volume_messages_mock.delete.assert_has_calls(calls)
+ self.assertIsNone(result)
+
+ def test_message_delete_multiple_messages_with_exception(self):
+ self.app.client_manager.volume.api_version = \
+ api_versions.APIVersion('3.3')
+
+ arglist = [
+ self.fake_messages[0].id,
+ 'invalid_message',
+ ]
+ verifylist = [
+ ('message_ids', arglist),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ self.volume_messages_mock.delete.side_effect = [
+ self.fake_messages[0], exceptions.CommandError]
+
+ exc = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action, parsed_args)
+ self.assertEqual('Failed to delete 1 of 2 messages.', str(exc))
+
+ self.volume_messages_mock.delete.assert_any_call(
+ self.fake_messages[0].id)
+ self.volume_messages_mock.delete.assert_any_call('invalid_message')
+
+ self.assertEqual(2, self.volume_messages_mock.delete.call_count)
+
+ def test_message_delete_pre_v33(self):
+ self.app.client_manager.volume.api_version = \
+ api_versions.APIVersion('3.2')
+
+ arglist = [
+ self.fake_messages[0].id,
+ ]
+ verifylist = [
+ ('message_ids', [self.fake_messages[0].id]),
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ exc = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-volume-api-version 3.3 or greater is required',
+ str(exc))
+
+
+class TestVolumeMessageList(TestVolumeMessage):
+
+ fake_project = identity_fakes.FakeProject.create_one_project()
+ fake_messages = volume_fakes.FakeVolumeMessage.create_volume_messages(
+ count=3)
+
+ columns = (
+ 'ID',
+ 'Event ID',
+ 'Resource Type',
+ 'Resource UUID',
+ 'Message Level',
+ 'User Message',
+ 'Request ID',
+ 'Created At',
+ 'Guaranteed Until',
+ )
+ data = []
+ for fake_message in fake_messages:
+ data.append((
+ fake_message.id,
+ fake_message.event_id,
+ fake_message.resource_type,
+ fake_message.resource_uuid,
+ fake_message.message_level,
+ fake_message.user_message,
+ fake_message.request_id,
+ fake_message.created_at,
+ fake_message.guaranteed_until,
+ ))
+
+ def setUp(self):
+ super().setUp()
+
+ self.projects_mock.get.return_value = self.fake_project
+ self.volume_messages_mock.list.return_value = self.fake_messages
+ # Get the command to test
+ self.cmd = volume_message.ListMessages(self.app, None)
+
+ def test_message_list(self):
+ self.app.client_manager.volume.api_version = \
+ api_versions.APIVersion('3.3')
+
+ arglist = []
+ verifylist = [
+ ('project', None),
+ ('marker', None),
+ ('limit', None),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ search_opts = {
+ 'project_id': None,
+ }
+ self.volume_messages_mock.list.assert_called_with(
+ search_opts=search_opts,
+ marker=None,
+ limit=None,
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertItemsEqual(self.data, list(data))
+
+ def test_message_list_with_options(self):
+ self.app.client_manager.volume.api_version = \
+ api_versions.APIVersion('3.3')
+
+ arglist = [
+ '--project', self.fake_project.name,
+ '--marker', self.fake_messages[0].id,
+ '--limit', '3',
+ ]
+ verifylist = [
+ ('project', self.fake_project.name),
+ ('marker', self.fake_messages[0].id),
+ ('limit', 3),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+ columns, data = self.cmd.take_action(parsed_args)
+
+ search_opts = {
+ 'project_id': self.fake_project.id,
+ }
+ self.volume_messages_mock.list.assert_called_with(
+ search_opts=search_opts,
+ marker=self.fake_messages[0].id,
+ limit=3,
+ )
+ self.assertEqual(self.columns, columns)
+ self.assertItemsEqual(self.data, list(data))
+
+ def test_message_list_pre_v33(self):
+ self.app.client_manager.volume.api_version = \
+ api_versions.APIVersion('3.2')
+
+ arglist = []
+ verifylist = [
+ ('project', None),
+ ('marker', None),
+ ('limit', None),
+ ]
+
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ exc = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-volume-api-version 3.3 or greater is required',
+ str(exc))
+
+
+class TestVolumeMessageShow(TestVolumeMessage):
+
+ fake_message = volume_fakes.FakeVolumeMessage.create_one_volume_message()
+
+ columns = (
+ 'created_at',
+ 'event_id',
+ 'guaranteed_until',
+ 'id',
+ 'message_level',
+ 'request_id',
+ 'resource_type',
+ 'resource_uuid',
+ 'user_message',
+ )
+ data = (
+ fake_message.created_at,
+ fake_message.event_id,
+ fake_message.guaranteed_until,
+ fake_message.id,
+ fake_message.message_level,
+ fake_message.request_id,
+ fake_message.resource_type,
+ fake_message.resource_uuid,
+ fake_message.user_message,
+ )
+
+ def setUp(self):
+ super().setUp()
+
+ self.volume_messages_mock.get.return_value = self.fake_message
+ # Get the command object to test
+ self.cmd = volume_message.ShowMessage(self.app, None)
+
+ def test_message_show(self):
+ self.app.client_manager.volume.api_version = \
+ api_versions.APIVersion('3.3')
+
+ arglist = [
+ self.fake_message.id
+ ]
+ verifylist = [
+ ('message_id', self.fake_message.id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ columns, data = self.cmd.take_action(parsed_args)
+ self.volume_messages_mock.get.assert_called_with(self.fake_message.id)
+
+ self.assertEqual(self.columns, columns)
+ self.assertEqual(self.data, data)
+
+ def test_message_show_pre_v33(self):
+ self.app.client_manager.volume.api_version = \
+ api_versions.APIVersion('3.2')
+
+ arglist = [
+ self.fake_message.id
+ ]
+ verifylist = [
+ ('message_id', self.fake_message.id)
+ ]
+ parsed_args = self.check_parser(self.cmd, arglist, verifylist)
+
+ exc = self.assertRaises(
+ exceptions.CommandError,
+ self.cmd.take_action,
+ parsed_args)
+ self.assertIn(
+ '--os-volume-api-version 3.3 or greater is required',
+ str(exc))
diff --git a/openstackclient/volume/v3/volume_message.py b/openstackclient/volume/v3/volume_message.py
new file mode 100644
index 00000000..4fe5ae92
--- /dev/null
+++ b/openstackclient/volume/v3/volume_message.py
@@ -0,0 +1,165 @@
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+#
+
+"""Volume V3 Messages implementations"""
+
+import logging as LOG
+
+from cinderclient import api_versions
+from osc_lib.command import command
+from osc_lib import exceptions
+from osc_lib import utils
+
+from openstackclient.i18n import _
+from openstackclient.identity import common as identity_common
+
+
+class DeleteMessage(command.Command):
+ _description = _('Delete a volume failure message')
+
+ def get_parser(self, prog_name):
+ parser = super().get_parser(prog_name)
+ parser.add_argument(
+ 'message_ids',
+ metavar='<message-id>',
+ nargs='+',
+ help=_('Message(s) to delete (ID)')
+ )
+
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+
+ if volume_client.api_version < api_versions.APIVersion('3.3'):
+ msg = _(
+ "--os-volume-api-version 3.3 or greater is required to "
+ "support the 'volume message delete' command"
+ )
+ raise exceptions.CommandError(msg)
+
+ errors = 0
+ for message_id in parsed_args.message_ids:
+ try:
+ volume_client.messages.delete(message_id)
+ except Exception:
+ LOG.error(_('Failed to delete message: %s'), message_id)
+ errors += 1
+
+ if errors > 0:
+ total = len(parsed_args.message_ids)
+ msg = _('Failed to delete %(errors)s of %(total)s messages.') % {
+ 'errors': errors, 'total': total,
+ }
+ raise exceptions.CommandError(msg)
+
+
+class ListMessages(command.Lister):
+ _description = _('List volume failure messages')
+
+ def get_parser(self, prog_name):
+ parser = super().get_parser(prog_name)
+
+ parser.add_argument(
+ '--project',
+ metavar='<project>',
+ help=_('Filter results by project (name or ID) (admin only)'),
+ )
+ identity_common.add_project_domain_option_to_parser(parser)
+ parser.add_argument(
+ '--marker',
+ metavar='<message-id>',
+ help=_('The last message ID of the previous page'),
+ default=None,
+ )
+ parser.add_argument(
+ '--limit',
+ type=int,
+ metavar='<limit>',
+ help=_('Maximum number of messages to display'),
+ default=None,
+ )
+
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+ identity_client = self.app.client_manager.identity
+
+ if volume_client.api_version < api_versions.APIVersion('3.3'):
+ msg = _(
+ "--os-volume-api-version 3.3 or greater is required to "
+ "support the 'volume message list' command"
+ )
+ raise exceptions.CommandError(msg)
+
+ column_headers = (
+ 'ID',
+ 'Event ID',
+ 'Resource Type',
+ 'Resource UUID',
+ 'Message Level',
+ 'User Message',
+ 'Request ID',
+ 'Created At',
+ 'Guaranteed Until',
+ )
+
+ project_id = None
+ if parsed_args.project:
+ project_id = identity_common.find_project(
+ identity_client,
+ parsed_args.project,
+ parsed_args.project_domain).id
+
+ search_opts = {
+ 'project_id': project_id,
+ }
+ data = volume_client.messages.list(
+ search_opts=search_opts,
+ marker=parsed_args.marker,
+ limit=parsed_args.limit)
+
+ return (
+ column_headers,
+ (utils.get_item_properties(s, column_headers) for s in data)
+ )
+
+
+class ShowMessage(command.ShowOne):
+ _description = _('Show a volume failure message')
+
+ def get_parser(self, prog_name):
+ parser = super(ShowMessage, self).get_parser(prog_name)
+ parser.add_argument(
+ 'message_id',
+ metavar='<message-id>',
+ help=_('Message to show (ID).')
+ )
+
+ return parser
+
+ def take_action(self, parsed_args):
+ volume_client = self.app.client_manager.volume
+
+ if volume_client.api_version < api_versions.APIVersion('3.3'):
+ msg = _(
+ "--os-volume-api-version 3.3 or greater is required to "
+ "support the 'volume message show' command"
+ )
+ raise exceptions.CommandError(msg)
+
+ message = volume_client.messages.get(parsed_args.message_id)
+
+ return zip(*sorted(message._info.items()))