Add commands for user messages
Allows listing, showing and deleting of user messages. Change-Id: I5ffb840a271c518f62ee1accfd8e20a97f45594d Partially-implements: blueprint user-messages Depends-On: Ia0cc524e0bfb2ca5e495e575e17e9911c746690b
This commit is contained in:
parent
757bb793a8
commit
8bf5f9b7e0
|
@ -27,7 +27,7 @@ from manilaclient import utils
|
||||||
|
|
||||||
LOG = logging.getLogger(__name__)
|
LOG = logging.getLogger(__name__)
|
||||||
|
|
||||||
MAX_VERSION = '2.33'
|
MAX_VERSION = '2.37'
|
||||||
MIN_VERSION = '2.0'
|
MIN_VERSION = '2.0'
|
||||||
DEPRECATED_VERSION = '1.0'
|
DEPRECATED_VERSION = '1.0'
|
||||||
_VERSIONED_METHOD_MAP = {}
|
_VERSIONED_METHOD_MAP = {}
|
||||||
|
|
|
@ -81,3 +81,8 @@ V2_SERVICE_TYPE = 'sharev2'
|
||||||
SERVICE_TYPES = {'1': V1_SERVICE_TYPE, '2': V2_SERVICE_TYPE}
|
SERVICE_TYPES = {'1': V1_SERVICE_TYPE, '2': V2_SERVICE_TYPE}
|
||||||
|
|
||||||
EXTENSION_PLUGIN_NAMESPACE = 'manilaclient.common.apiclient.auth'
|
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'
|
||||||
|
)
|
||||||
|
|
|
@ -17,6 +17,7 @@ import traceback
|
||||||
|
|
||||||
from oslo_log import log
|
from oslo_log import log
|
||||||
from tempest.lib.cli import base
|
from tempest.lib.cli import base
|
||||||
|
from tempest.lib.common.utils import data_utils
|
||||||
from tempest.lib import exceptions as lib_exc
|
from tempest.lib import exceptions as lib_exc
|
||||||
|
|
||||||
from manilaclient import config
|
from manilaclient import config
|
||||||
|
@ -324,3 +325,40 @@ class BaseTestCase(base.ClientTestBase):
|
||||||
if wait_for_creation:
|
if wait_for_creation:
|
||||||
client.wait_for_snapshot_status(snapshot['id'], 'available')
|
client.wait_for_snapshot_status(snapshot['id'], 'available')
|
||||||
return snapshot
|
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
|
||||||
|
|
|
@ -29,6 +29,7 @@ from manilaclient.tests.functional import exceptions
|
||||||
from manilaclient.tests.functional import utils
|
from manilaclient.tests.functional import utils
|
||||||
|
|
||||||
CONF = config.CONF
|
CONF = config.CONF
|
||||||
|
MESSAGE = 'message'
|
||||||
SHARE = 'share'
|
SHARE = 'share'
|
||||||
SHARE_TYPE = 'share_type'
|
SHARE_TYPE = 'share_type'
|
||||||
SHARE_NETWORK = 'share_network'
|
SHARE_NETWORK = 'share_network'
|
||||||
|
@ -133,6 +134,8 @@ class ManilaCLIClient(base.CLIClient):
|
||||||
func = self.is_share_deleted
|
func = self.is_share_deleted
|
||||||
elif res_type == SNAPSHOT:
|
elif res_type == SNAPSHOT:
|
||||||
func = self.is_snapshot_deleted
|
func = self.is_snapshot_deleted
|
||||||
|
elif res_type == MESSAGE:
|
||||||
|
func = self.is_message_deleted
|
||||||
else:
|
else:
|
||||||
raise exceptions.InvalidResource(message=res_type)
|
raise exceptions.InvalidResource(message=res_type)
|
||||||
|
|
||||||
|
@ -1363,3 +1366,70 @@ class ManilaCLIClient(base.CLIClient):
|
||||||
self.wait_for_resource_deletion(
|
self.wait_for_resource_deletion(
|
||||||
SHARE_SERVER, res_id=share_server, interval=3, timeout=60,
|
SHARE_SERVER, res_id=share_server, interval=3, timeout=60,
|
||||||
microversion=microversion)
|
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)
|
||||||
|
|
|
@ -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'])
|
|
@ -1095,6 +1095,35 @@ class FakeHTTPClient(fakes.FakeHTTPClient):
|
||||||
}
|
}
|
||||||
return 200, {}, sg_type_access
|
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):
|
def fake_create(url, body, response_key):
|
||||||
return {'url': url, 'body': body, 'resp_key': response_key}
|
return {'url': url, 'body': body, 'resp_key': response_key}
|
||||||
|
@ -1151,3 +1180,16 @@ class ShareGroupSnapshot(object):
|
||||||
share_network_id = ShareNetwork().id
|
share_network_id = ShareNetwork().id
|
||||||
name = 'fake name'
|
name = 'fake name'
|
||||||
description = 'fake description'
|
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'
|
||||||
|
|
|
@ -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('<Message: fake_id>', 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)
|
|
@ -30,6 +30,7 @@ from manilaclient import exceptions
|
||||||
from manilaclient import shell
|
from manilaclient import shell
|
||||||
from manilaclient.tests.unit import utils as test_utils
|
from manilaclient.tests.unit import utils as test_utils
|
||||||
from manilaclient.tests.unit.v2 import fakes
|
from manilaclient.tests.unit.v2 import fakes
|
||||||
|
from manilaclient.v2 import messages
|
||||||
from manilaclient.v2 import security_services
|
from manilaclient.v2 import security_services
|
||||||
from manilaclient.v2 import share_instances
|
from manilaclient.v2 import share_instances
|
||||||
from manilaclient.v2 import share_networks
|
from manilaclient.v2 import share_networks
|
||||||
|
@ -2623,3 +2624,51 @@ class ShellTest(test_utils.TestCase):
|
||||||
self.assert_called_anytime(
|
self.assert_called_anytime(
|
||||||
'DELETE', '/share-servers/%s' % server.id,
|
'DELETE', '/share-servers/%s' % server.id,
|
||||||
clear_callstack=False)
|
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)
|
||||||
|
|
|
@ -23,6 +23,7 @@ from manilaclient.common import httpclient
|
||||||
from manilaclient import exceptions
|
from manilaclient import exceptions
|
||||||
from manilaclient.v2 import availability_zones
|
from manilaclient.v2 import availability_zones
|
||||||
from manilaclient.v2 import limits
|
from manilaclient.v2 import limits
|
||||||
|
from manilaclient.v2 import messages
|
||||||
from manilaclient.v2 import quota_classes
|
from manilaclient.v2 import quota_classes
|
||||||
from manilaclient.v2 import quotas
|
from manilaclient.v2 import quotas
|
||||||
from manilaclient.v2 import scheduler_stats
|
from manilaclient.v2 import scheduler_stats
|
||||||
|
@ -212,6 +213,7 @@ class Client(object):
|
||||||
self.availability_zones = availability_zones.AvailabilityZoneManager(
|
self.availability_zones = availability_zones.AvailabilityZoneManager(
|
||||||
self)
|
self)
|
||||||
self.limits = limits.LimitsManager(self)
|
self.limits = limits.LimitsManager(self)
|
||||||
|
self.messages = messages.MessageManager(self)
|
||||||
self.services = services.ServiceManager(self)
|
self.services = services.ServiceManager(self)
|
||||||
self.security_services = security_services.SecurityServiceManager(self)
|
self.security_services = security_services.SecurityServiceManager(self)
|
||||||
self.share_networks = share_networks.ShareNetworkManager(self)
|
self.share_networks = share_networks.ShareNetworkManager(self)
|
||||||
|
|
|
@ -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 "<Message: %s>" % 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)
|
|
@ -269,6 +269,11 @@ def _find_share_server(cs, share_server):
|
||||||
return apiclient_utils.find_resource(cs.share_servers, 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):
|
def _translate_keys(collection, convert):
|
||||||
for item in collection:
|
for item in collection:
|
||||||
keys = item.__dict__
|
keys = item.__dict__
|
||||||
|
@ -4650,3 +4655,170 @@ def do_share_replica_resync(cs, args):
|
||||||
"""
|
"""
|
||||||
replica = _find_share_replica(cs, args.replica)
|
replica = _find_share_replica(cs, args.replica)
|
||||||
cs.share_replicas.resync(replica)
|
cs.share_replicas.resync(replica)
|
||||||
|
|
||||||
|
|
||||||
|
##############################################################################
|
||||||
|
#
|
||||||
|
# User Messages
|
||||||
|
#
|
||||||
|
##############################################################################
|
||||||
|
|
||||||
|
|
||||||
|
@api_versions.wraps("2.37")
|
||||||
|
@cliutils.arg(
|
||||||
|
'--resource_id',
|
||||||
|
'--resource-id',
|
||||||
|
'--resource',
|
||||||
|
metavar='<resource_id>',
|
||||||
|
default=None,
|
||||||
|
action='single_alias',
|
||||||
|
help='Filters results by a resource uuid. Default=None.')
|
||||||
|
@cliutils.arg(
|
||||||
|
'--resource_type',
|
||||||
|
'--resource-type',
|
||||||
|
metavar='<type>',
|
||||||
|
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='<id>',
|
||||||
|
default=None,
|
||||||
|
action='single_alias',
|
||||||
|
help='Filters results by action id. Default=None.')
|
||||||
|
@cliutils.arg(
|
||||||
|
'--detail_id',
|
||||||
|
'--detail-id',
|
||||||
|
'--detail',
|
||||||
|
metavar='<id>',
|
||||||
|
default=None,
|
||||||
|
action='single_alias',
|
||||||
|
help='Filters results by detail id. Default=None.')
|
||||||
|
@cliutils.arg(
|
||||||
|
'--request_id',
|
||||||
|
'--request-id',
|
||||||
|
'--request',
|
||||||
|
metavar='<request_id>',
|
||||||
|
default=None,
|
||||||
|
action='single_alias',
|
||||||
|
help='Filters results by request id. Default=None.')
|
||||||
|
@cliutils.arg(
|
||||||
|
'--level',
|
||||||
|
'--message_level',
|
||||||
|
'--message-level',
|
||||||
|
metavar='<level>',
|
||||||
|
default=None,
|
||||||
|
action='single_alias',
|
||||||
|
help='Filters results by the message level. Default=None. '
|
||||||
|
'Example: "manila message-list --level ERROR".')
|
||||||
|
@cliutils.arg(
|
||||||
|
'--limit',
|
||||||
|
metavar='<limit>',
|
||||||
|
type=int,
|
||||||
|
default=None,
|
||||||
|
help='Maximum number of messages to return. (Default=None)')
|
||||||
|
@cliutils.arg(
|
||||||
|
'--offset',
|
||||||
|
metavar="<offset>",
|
||||||
|
default=None,
|
||||||
|
help='Start position of message listing.')
|
||||||
|
@cliutils.arg(
|
||||||
|
'--sort-key', '--sort_key',
|
||||||
|
metavar='<sort_key>',
|
||||||
|
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='<sort_dir>',
|
||||||
|
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='<columns>',
|
||||||
|
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='<message>',
|
||||||
|
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='<message>',
|
||||||
|
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)
|
||||||
|
|
|
@ -0,0 +1,3 @@
|
||||||
|
---
|
||||||
|
features:
|
||||||
|
- Added new subcommands message-list, message-show and message-delete.
|
Loading…
Reference in New Issue