diff --git a/manilaclient/api_versions.py b/manilaclient/api_versions.py index 76817789b..456588c0b 100644 --- a/manilaclient/api_versions.py +++ b/manilaclient/api_versions.py @@ -27,7 +27,7 @@ from manilaclient import utils LOG = logging.getLogger(__name__) -MAX_VERSION = '2.33' +MAX_VERSION = '2.37' MIN_VERSION = '2.0' DEPRECATED_VERSION = '1.0' _VERSIONED_METHOD_MAP = {} diff --git a/manilaclient/common/constants.py b/manilaclient/common/constants.py index 5249f83b5..975c4ca0d 100644 --- a/manilaclient/common/constants.py +++ b/manilaclient/common/constants.py @@ -81,3 +81,8 @@ V2_SERVICE_TYPE = 'sharev2' SERVICE_TYPES = {'1': V1_SERVICE_TYPE, '2': V2_SERVICE_TYPE} EXTENSION_PLUGIN_NAMESPACE = 'manilaclient.common.apiclient.auth' +MESSAGE_SORT_KEY_VALUES = ( + 'id', 'project_id', 'request_id', 'resource_type', 'action_id', + 'detail_id', 'resource_id', 'message_level', 'expires_at', + 'request_id', 'created_at' +) diff --git a/manilaclient/tests/functional/base.py b/manilaclient/tests/functional/base.py index 92d3dc359..f468b2da0 100644 --- a/manilaclient/tests/functional/base.py +++ b/manilaclient/tests/functional/base.py @@ -17,6 +17,7 @@ import traceback from oslo_log import log from tempest.lib.cli import base +from tempest.lib.common.utils import data_utils from tempest.lib import exceptions as lib_exc from manilaclient import config @@ -324,3 +325,40 @@ class BaseTestCase(base.ClientTestBase): if wait_for_creation: client.wait_for_snapshot_status(snapshot['id'], 'available') return snapshot + + @classmethod + def create_message(cls, client=None, wait_for_creation=True, + cleanup_in_class=False, microversion=None): + """Trigger a 'no valid host' situation to generate a message.""" + if client is None: + client = cls.get_admin_client() + + extra_specs = { + 'vendor_name': 'foobar', + } + share_type_name = data_utils.rand_name("share-type") + cls.create_share_type( + name=share_type_name, extra_specs=extra_specs, + driver_handles_share_servers=False, client=client, + cleanup_in_class=cleanup_in_class, microversion=microversion) + + share_name = data_utils.rand_name("share") + share = cls.create_share( + name=share_name, share_type=share_type_name, + cleanup_in_class=cleanup_in_class, microversion=microversion, + wait_for_creation=False, client=client) + + client.wait_for_share_status(share['id'], "error") + message = client.wait_for_message(share['id']) + + resource = { + "type": "message", + "id": message["ID"], + "client": client, + "microversion": microversion, + } + if cleanup_in_class: + cls.class_resources.insert(0, resource) + else: + cls.method_resources.insert(0, resource) + return message diff --git a/manilaclient/tests/functional/client.py b/manilaclient/tests/functional/client.py index f846e6121..9c1b90a17 100644 --- a/manilaclient/tests/functional/client.py +++ b/manilaclient/tests/functional/client.py @@ -29,6 +29,7 @@ from manilaclient.tests.functional import exceptions from manilaclient.tests.functional import utils CONF = config.CONF +MESSAGE = 'message' SHARE = 'share' SHARE_TYPE = 'share_type' SHARE_NETWORK = 'share_network' @@ -133,6 +134,8 @@ class ManilaCLIClient(base.CLIClient): func = self.is_share_deleted elif res_type == SNAPSHOT: func = self.is_snapshot_deleted + elif res_type == MESSAGE: + func = self.is_message_deleted else: raise exceptions.InvalidResource(message=res_type) @@ -1363,3 +1366,70 @@ class ManilaCLIClient(base.CLIClient): self.wait_for_resource_deletion( SHARE_SERVER, res_id=share_server, interval=3, timeout=60, microversion=microversion) + + # user messages + + def wait_for_message(self, resource_id): + """Waits until a message for a resource with given id exists""" + start = int(time.time()) + message = None + + while not message: + time.sleep(self.build_interval) + for msg in self.list_messages(): + if msg['Resource ID'] == resource_id: + return msg + + if int(time.time()) - start >= self.build_timeout: + message = ('No message for resource with id %s was created in' + ' the required time (%s s).' % + (resource_id, self.build_timeout)) + raise tempest_lib_exc.TimeoutException(message) + + def list_messages(self, columns=None, microversion=None): + """List messages. + + :param columns: str -- comma separated string of columns. + Example, "--columns id,resource_id". + :param microversion: API microversion to be used for request. + """ + cmd = "message-list" + if columns is not None: + cmd += " --columns " + columns + messages_raw = self.manila(cmd, microversion=microversion) + messages = utils.listing(messages_raw) + return messages + + @not_found_wrapper + def get_message(self, message, microversion=None): + """Returns share server by its Name or ID.""" + message_raw = self.manila( + 'message-show %s' % message, microversion=microversion) + message = output_parser.details(message_raw) + return message + + @not_found_wrapper + def delete_message(self, message, microversion=None): + """Deletes message by its ID.""" + return self.manila('message-delete %s' % message, + microversion=microversion) + + def is_message_deleted(self, message, microversion=None): + """Indicates whether message is deleted or not. + + :param message: str -- ID of message + """ + try: + self.get_message(message, microversion=microversion) + return False + except tempest_lib_exc.NotFound: + return True + + def wait_for_message_deletion(self, message, microversion=None): + """Wait for message deletion by its ID. + + :param message: text -- ID of message + """ + self.wait_for_resource_deletion( + MESSAGE, res_id=message, interval=3, timeout=60, + microversion=microversion) diff --git a/manilaclient/tests/functional/test_messages.py b/manilaclient/tests/functional/test_messages.py new file mode 100644 index 000000000..7e0aba180 --- /dev/null +++ b/manilaclient/tests/functional/test_messages.py @@ -0,0 +1,71 @@ +# 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. + +import ddt + +from manilaclient.tests.functional import base + + +@ddt.ddt +class MessagesReadOnlyTest(base.BaseTestCase): + + @ddt.data( + ("admin", "2.37"), + ("user", "2.37"), + ) + @ddt.unpack + def test_message_list(self, role, microversion): + self.skip_if_microversion_not_supported(microversion) + self.clients[role].manila("message-list", microversion=microversion) + + +@ddt.ddt +class MessagesReadWriteTest(base.BaseTestCase): + + @classmethod + def setUpClass(cls): + super(MessagesReadWriteTest, cls).setUpClass() + cls.message = cls.create_message(cleanup_in_class=True) + + def test_list_messages(self): + self.skip_if_microversion_not_supported('2.37') + messages = self.admin_client.list_messages() + self.assertTrue(any(m['ID'] is not None for m in messages)) + self.assertTrue(any(m['User Message'] is not None for m in messages)) + self.assertTrue(any(m['Resource ID'] is not None for m in messages)) + self.assertTrue(any(m['Action ID'] is not None for m in messages)) + self.assertTrue(any(m['Detail ID'] is not None for m in messages)) + self.assertTrue(any(m['Resource Type'] is not None for m in messages)) + + @ddt.data( + 'id', 'action_id', 'resource_id', 'action_id', 'detail_id', + 'resource_type', 'created_at', 'action_id,detail_id,resource_id', + ) + def test_list_share_type_select_column(self, columns): + self.skip_if_microversion_not_supported('2.37') + self.admin_client.list_messages(columns=columns) + + def test_get_message(self): + self.skip_if_microversion_not_supported('2.37') + message = self.admin_client.get_message(self.message['ID']) + expected_keys = ( + 'id', 'action_id', 'resource_id', 'action_id', 'detail_id', + 'resource_type', 'created_at', 'created_at', + ) + for key in expected_keys: + self.assertIn(key, message) + + def test_delete_message(self): + self.skip_if_microversion_not_supported('2.37') + message = self.create_message(cleanup_in_class=False) + self.admin_client.delete_message(message['ID']) + self.admin_client.wait_for_message_deletion(message['ID']) diff --git a/manilaclient/tests/unit/v2/fakes.py b/manilaclient/tests/unit/v2/fakes.py index 0a582b398..16258c621 100644 --- a/manilaclient/tests/unit/v2/fakes.py +++ b/manilaclient/tests/unit/v2/fakes.py @@ -1095,6 +1095,35 @@ class FakeHTTPClient(fakes.FakeHTTPClient): } return 200, {}, sg_type_access + fake_message = { + 'id': 'fake message id', + 'action_id': '001', + 'detail_id': '002', + 'user_message': 'user message', + 'message_level': 'ERROR', + 'resource_type': 'SHARE', + 'resource_id': 'resource id', + 'created_at': '2015-08-27T09:49:58-05:00', + 'expires_at': '2015-09-27T09:49:58-05:00', + 'request_id': 'req-936666d2-4c8f-4e41-9ac9-237b43f8b848', + } + + def get_messages(self, **kw): + messages = { + 'messages': [self.fake_message], + } + return 200, {}, messages + + def get_messages_1234(self, **kw): + message = {'message': self.fake_message} + return 200, {}, message + + def delete_messages_1234(self, **kw): + return 202, {}, None + + def delete_messages_5678(self, **kw): + return 202, {}, None + def fake_create(url, body, response_key): return {'url': url, 'body': body, 'resp_key': response_key} @@ -1151,3 +1180,16 @@ class ShareGroupSnapshot(object): share_network_id = ShareNetwork().id name = 'fake name' description = 'fake description' + + +class Message(object): + id = 'fake message id' + action_id = '001' + detail_id = '002' + user_message = 'user message' + message_level = 'ERROR' + resource_type = 'SHARE' + resource_id = 'resource id' + created_at = '2015-08-27T09:49:58-05:00' + expires_at = '2015-09-27T09:49:58-05:00' + request_id = 'req-936666d2-4c8f-4e41-9ac9-237b43f8b848' diff --git a/manilaclient/tests/unit/v2/test_messages.py b/manilaclient/tests/unit/v2/test_messages.py new file mode 100644 index 000000000..2477b1880 --- /dev/null +++ b/manilaclient/tests/unit/v2/test_messages.py @@ -0,0 +1,132 @@ +# Copyright 2017 Red Hat +# All Rights Reserved. +# +# 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. + +import mock + +import ddt +import six + +from manilaclient.tests.unit import utils +from manilaclient.tests.unit.v2 import fakes as fake +from manilaclient.v2 import messages + + +class MessageTest(utils.TestCase): + + def setUp(self): + super(MessageTest, self).setUp() + self.manager = messages.MessageManager(fake.FakeClient()) + self.message = messages.Message( + self.manager, {'id': 'fake_id'}) + self.fake_kwargs = {'key': 'value'} + + def test_repr(self): + result = six.text_type(self.message) + + self.assertEqual('', result) + + def test_delete(self): + mock_manager_delete = self.mock_object(self.manager, 'delete') + + self.message.delete() + + mock_manager_delete.assert_called_once_with(self.message) + + +@ddt.ddt +class MessageManagerTest(utils.TestCase): + + def setUp(self): + super(MessageManagerTest, self).setUp() + self.manager = messages.MessageManager(fake.FakeClient()) + + def test_get(self): + fake_message = fake.Message() + mock_get = self.mock_object( + self.manager, '_get', mock.Mock(return_value=fake_message)) + + result = self.manager.get(fake.Message.id) + + self.assertIs(fake_message, result) + mock_get.assert_called_once_with( + messages.RESOURCE_PATH % fake.Message.id, + messages.RESOURCE_NAME) + + def test_list(self): + fake_message = fake.Message() + mock_list = self.mock_object( + self.manager, '_list', mock.Mock(return_value=[fake_message])) + + result = self.manager.list() + + self.assertEqual([fake_message], result) + mock_list.assert_called_once_with( + messages.RESOURCES_PATH, + messages.RESOURCES_NAME) + + @ddt.data( + ({'action_id': 1, 'resource_type': 'share'}, + '?action_id=1&resource_type=share'), + ({'action_id': 1}, '?action_id=1'), + ) + @ddt.unpack + def test_list_with_filters(self, filters, filters_path): + fake_message = fake.Message() + mock_list = self.mock_object( + self.manager, '_list', mock.Mock(return_value=[fake_message])) + + result = self.manager.list(search_opts=filters) + + self.assertEqual([fake_message], result) + expected_path = (messages.RESOURCES_PATH + filters_path) + mock_list.assert_called_once_with( + expected_path, messages.RESOURCES_NAME) + + @ddt.data('id', 'project_id', 'request_id', 'resource_type', 'action_id', + 'detail_id', 'resource_id', 'message_level', 'expires_at', + 'request_id', 'created_at') + def test_list_with_sorting(self, key): + fake_message = fake.Message() + mock_list = self.mock_object( + self.manager, '_list', mock.Mock(return_value=[fake_message])) + + result = self.manager.list(sort_dir='asc', sort_key=key) + + self.assertEqual([fake_message], result) + expected_path = ( + messages.RESOURCES_PATH + '?sort_dir=asc&sort_key=' + + key) + mock_list.assert_called_once_with( + expected_path, messages.RESOURCES_NAME) + + @ddt.data( + ('name', 'invalid'), + ('invalid', 'asc'), + ) + @ddt.unpack + def test_list_with_invalid_sorting(self, sort_key, sort_dir): + self.assertRaises( + ValueError, + self.manager.list, sort_dir=sort_dir, sort_key=sort_key) + + def test_delete(self): + mock_delete = self.mock_object(self.manager, '_delete') + mock_post = self.mock_object(self.manager.api.client, 'post') + + self.manager.delete(fake.Message()) + + mock_delete.assert_called_once_with( + messages.RESOURCE_PATH % fake.Message.id) + self.assertFalse(mock_post.called) diff --git a/manilaclient/tests/unit/v2/test_shell.py b/manilaclient/tests/unit/v2/test_shell.py index 9f0dc12ac..ec11b23f5 100644 --- a/manilaclient/tests/unit/v2/test_shell.py +++ b/manilaclient/tests/unit/v2/test_shell.py @@ -30,6 +30,7 @@ from manilaclient import exceptions from manilaclient import shell from manilaclient.tests.unit import utils as test_utils from manilaclient.tests.unit.v2 import fakes +from manilaclient.v2 import messages from manilaclient.v2 import security_services from manilaclient.v2 import share_instances from manilaclient.v2 import share_networks @@ -2623,3 +2624,51 @@ class ShellTest(test_utils.TestCase): self.assert_called_anytime( 'DELETE', '/share-servers/%s' % server.id, clear_callstack=False) + + @mock.patch.object(cliutils, 'print_list', mock.Mock()) + def test_message_list(self): + self.run_command('message-list') + + self.assert_called('GET', '/messages') + cliutils.print_list.assert_called_once_with( + mock.ANY, fields=['ID', 'Resource Type', 'Resource ID', + 'Action ID', 'User Message', 'Detail ID', + 'Created At'], sortby_index=None) + + @mock.patch.object(cliutils, 'print_list', mock.Mock()) + def test_message_list_select_column(self): + self.run_command('message-list --columns id,resource_type') + + self.assert_called('GET', '/messages') + cliutils.print_list.assert_called_once_with( + mock.ANY, fields=['Id', 'Resource_Type'], sortby_index=None) + + def test_message_list_with_filters(self): + self.run_command('message-list --limit 10 --offset 0') + + self.assert_called( + 'GET', '/messages?limit=10&offset=0') + + def test_message_show(self): + self.run_command('message-show 1234') + + self.assert_called('GET', '/messages/1234') + + @ddt.data(('1234', ), ('1234', '5678')) + def test_message_delete(self, ids): + fake_messages = [ + messages.Message('fake', {'id': mid}, True) for mid in ids + ] + self.mock_object( + shell_v2, '_find_message', + mock.Mock(side_effect=fake_messages)) + + self.run_command('message-delete %s' % ' '.join(ids)) + + shell_v2._find_message.assert_has_calls([ + mock.call(self.shell.cs, mid) for mid in ids + ]) + for fake_message in fake_messages: + self.assert_called_anytime( + 'DELETE', '/messages/%s' % fake_message.id, + clear_callstack=False) diff --git a/manilaclient/v2/client.py b/manilaclient/v2/client.py index 8e55ef9a0..033edae79 100644 --- a/manilaclient/v2/client.py +++ b/manilaclient/v2/client.py @@ -23,6 +23,7 @@ from manilaclient.common import httpclient from manilaclient import exceptions from manilaclient.v2 import availability_zones from manilaclient.v2 import limits +from manilaclient.v2 import messages from manilaclient.v2 import quota_classes from manilaclient.v2 import quotas from manilaclient.v2 import scheduler_stats @@ -212,6 +213,7 @@ class Client(object): self.availability_zones = availability_zones.AvailabilityZoneManager( self) self.limits = limits.LimitsManager(self) + self.messages = messages.MessageManager(self) self.services = services.ServiceManager(self) self.security_services = security_services.SecurityServiceManager(self) self.share_networks = share_networks.ShareNetworkManager(self) diff --git a/manilaclient/v2/messages.py b/manilaclient/v2/messages.py new file mode 100644 index 000000000..b7696d4c9 --- /dev/null +++ b/manilaclient/v2/messages.py @@ -0,0 +1,96 @@ +# 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. + +"""Asynchronous User Message interface.""" + +try: + from urllib import urlencode # noqa +except ImportError: + from urllib.parse import urlencode # noqa + +from manilaclient import api_versions +from manilaclient import base +from manilaclient.common.apiclient import base as common_base +from manilaclient.common import constants + +RESOURCES_PATH = '/messages' +RESOURCE_PATH = '/messages/%s' +RESOURCES_NAME = 'messages' +RESOURCE_NAME = 'message' + + +class Message(common_base.Resource): + NAME_ATTR = 'id' + + def __repr__(self): + return "" % self.id + + def delete(self): + """Delete this message.""" + return self.manager.delete(self) + + +class MessageManager(base.ManagerWithFind): + """Manage :class:`Message` resources.""" + resource_class = Message + + @api_versions.wraps('2.37') + def get(self, message_id): + """Get a message. + + :param message_id: The ID of the message to get. + :rtype: :class:`Message` + """ + return self._get(RESOURCE_PATH % message_id, RESOURCE_NAME) + + @api_versions.wraps('2.37') + def list(self, search_opts=None, sort_key=None, sort_dir=None): + """Lists all messages. + + :param search_opts: Search options to filter out messages. + :rtype: list of :class:`Message` + """ + search_opts = search_opts or {} + + if sort_key is not None: + if sort_key in constants.MESSAGE_SORT_KEY_VALUES: + search_opts['sort_key'] = sort_key + else: + raise ValueError( + 'sort_key must be one of the following: %s.' + % ', '.join(constants.MESSAGE_SORT_KEY_VALUES)) + + if sort_dir is not None: + if sort_dir in constants.SORT_DIR_VALUES: + search_opts['sort_dir'] = sort_dir + else: + raise ValueError('sort_dir must be one of the following: %s.' + % ', '.join(constants.SORT_DIR_VALUES)) + + if search_opts: + query_string = urlencode( + sorted([(k, v) for (k, v) in list(search_opts.items()) if v])) + if query_string: + query_string = "?%s" % (query_string,) + else: + query_string = '' + + path = RESOURCES_PATH + query_string + return self._list(path, RESOURCES_NAME) + + @api_versions.wraps('2.37') + def delete(self, message): + """Delete a message.""" + + loc = RESOURCE_PATH % common_base.getid(message) + + return self._delete(loc) diff --git a/manilaclient/v2/shell.py b/manilaclient/v2/shell.py index a25b0692a..04e482157 100644 --- a/manilaclient/v2/shell.py +++ b/manilaclient/v2/shell.py @@ -269,6 +269,11 @@ def _find_share_server(cs, share_server): return apiclient_utils.find_resource(cs.share_servers, share_server) +def _find_message(cs, message): + """Get a message by ID.""" + return apiclient_utils.find_resource(cs.messages, message) + + def _translate_keys(collection, convert): for item in collection: keys = item.__dict__ @@ -4650,3 +4655,170 @@ def do_share_replica_resync(cs, args): """ replica = _find_share_replica(cs, args.replica) cs.share_replicas.resync(replica) + + +############################################################################## +# +# User Messages +# +############################################################################## + + +@api_versions.wraps("2.37") +@cliutils.arg( + '--resource_id', + '--resource-id', + '--resource', + metavar='', + default=None, + action='single_alias', + help='Filters results by a resource uuid. Default=None.') +@cliutils.arg( + '--resource_type', + '--resource-type', + metavar='', + default=None, + action='single_alias', + help='Filters results by a resource type. Default=None. ' + 'Example: "manila message-list --resource_type share"') +@cliutils.arg( + '--action_id', + '--action-id', + '--action', + metavar='', + default=None, + action='single_alias', + help='Filters results by action id. Default=None.') +@cliutils.arg( + '--detail_id', + '--detail-id', + '--detail', + metavar='', + default=None, + action='single_alias', + help='Filters results by detail id. Default=None.') +@cliutils.arg( + '--request_id', + '--request-id', + '--request', + metavar='', + default=None, + action='single_alias', + help='Filters results by request id. Default=None.') +@cliutils.arg( + '--level', + '--message_level', + '--message-level', + metavar='', + default=None, + action='single_alias', + help='Filters results by the message level. Default=None. ' + 'Example: "manila message-list --level ERROR".') +@cliutils.arg( + '--limit', + metavar='', + type=int, + default=None, + help='Maximum number of messages to return. (Default=None)') +@cliutils.arg( + '--offset', + metavar="", + default=None, + help='Start position of message listing.') +@cliutils.arg( + '--sort-key', '--sort_key', + metavar='', + type=str, + default=None, + action='single_alias', + help='Key to be sorted, available keys are %(keys)s. Default=desc.' % { + 'keys': constants.MESSAGE_SORT_KEY_VALUES}) +@cliutils.arg( + '--sort-dir', '--sort_dir', + metavar='', + type=str, + default=None, + action='single_alias', + help='Sort direction, available values are %(values)s. ' + 'OPTIONAL: Default=None.' % {'values': constants.SORT_DIR_VALUES}) +@cliutils.arg( + '--columns', + metavar='', + type=str, + default=None, + help='Comma separated list of columns to be displayed ' + 'example --columns "resource_id,user_message".') +def do_message_list(cs, args): + """Lists all messages.""" + if args.columns is not None: + list_of_keys = _split_columns(columns=args.columns) + else: + list_of_keys = ['ID', 'Resource Type', 'Resource ID', 'Action ID', + 'User Message', 'Detail ID', 'Created At'] + + search_opts = { + 'offset': args.offset, + 'limit': args.limit, + 'request_id': args.request_id, + 'resource_type': args.resource_type, + 'resource_id': args.resource_id, + 'action_id': args.action_id, + 'detail_id': args.detail_id, + 'message_level': args.level + } + messages = cs.messages.list( + search_opts=search_opts, sort_key=args.sort_key, + sort_dir=args.sort_dir) + cliutils.print_list(messages, fields=list_of_keys, sortby_index=None) + + +@cliutils.arg( + 'message', + metavar='', + help='ID of the message.') +@api_versions.wraps("2.37") +def do_message_show(cs, args): + """Show details about a message.""" + + message = cs.messages.get(args.message) + _print_message(message) + + +@api_versions.wraps("2.37") +@cliutils.arg( + 'message', + metavar='', + nargs='+', + help='ID of the message(s).') +def do_message_delete(cs, args): + """Remove one or more messages.""" + failure_count = 0 + + for message in args.message: + try: + message_ref = _find_message(cs, message) + cs.messages.delete(message_ref) + except Exception as e: + failure_count += 1 + print("Delete for message %s failed: %s" % (message, e), + file=sys.stderr) + + if failure_count == len(args.message): + raise exceptions.CommandError("Unable to delete any of the specified " + "messages.") + + +def _print_message(message): + message_dict = { + 'id': message.id, + 'resource_type': message.resource_type, + 'resource_id': message.resource_id, + 'action_id': message.action_id, + 'user_message': message.user_message, + 'message_level': message.message_level, + 'detail_id': message.detail_id, + 'created_at': message.created_at, + 'expires_at': message.expires_at, + 'request_id': message.request_id, + } + cliutils.print_dict(message_dict) diff --git a/releasenotes/notes/add-message-list-and-delete-41b3323edd63d894.yaml b/releasenotes/notes/add-message-list-and-delete-41b3323edd63d894.yaml new file mode 100644 index 000000000..39945a54a --- /dev/null +++ b/releasenotes/notes/add-message-list-and-delete-41b3323edd63d894.yaml @@ -0,0 +1,3 @@ +--- +features: + - Added new subcommands message-list, message-show and message-delete.