Add v3 user messages with pagination
GET /messages GET /messages/{id} DELETE /message/{id} Partially-Implements: blueprint summarymessage Depends-On: I398cbd02b61f30918a427291d1d3ae00435e0f4c Change-Id: Ic057ab521c048a376d2a6bed513b8eb8118810d1
This commit is contained in:
parent
29099bbc68
commit
1c87b6fa71
cinderclient
releasenotes/notes
@ -38,7 +38,11 @@ SORT_KEY_VALUES = ('id', 'status', 'size', 'availability_zone', 'name',
|
||||
# Mapping of client keys to actual sort keys
|
||||
SORT_KEY_MAPPINGS = {'name': 'display_name'}
|
||||
# Additional sort keys for resources
|
||||
SORT_KEY_ADD_VALUES = {'backups': ('data_timestamp', ), }
|
||||
SORT_KEY_ADD_VALUES = {
|
||||
'backups': ('data_timestamp', ),
|
||||
'messages': ('resource_type', 'event_id', 'resource_uuid',
|
||||
'message_level', 'guaranteed_until', 'request_id'),
|
||||
}
|
||||
|
||||
Resource = common_base.Resource
|
||||
|
||||
|
@ -639,7 +639,8 @@ def _construct_http_client(username=None, password=None, project_id=None,
|
||||
cacert=cacert,
|
||||
auth_system=auth_system,
|
||||
auth_plugin=auth_plugin,
|
||||
logger=logger
|
||||
logger=logger,
|
||||
api_version=api_version
|
||||
)
|
||||
|
||||
|
||||
|
@ -289,7 +289,6 @@ class FakeHTTPClient(fake_v2.FakeHTTPClient):
|
||||
#
|
||||
# Groups
|
||||
#
|
||||
|
||||
def get_groups_detail(self, **kw):
|
||||
return (200, {}, {"groups": [
|
||||
_stub_group(id='1234'),
|
||||
@ -414,3 +413,50 @@ class FakeHTTPClient(fake_v2.FakeHTTPClient):
|
||||
"source_identifier": "myvol", "size": 5,
|
||||
"extra_info": "qos_setting:low", "reason_not_safe": None}]
|
||||
return (200, {}, {"manageable-snapshots": snaps})
|
||||
|
||||
#
|
||||
# Messages
|
||||
#
|
||||
def get_messages(self, **kw):
|
||||
return 200, {}, {'messages': [
|
||||
{
|
||||
'id': '1234',
|
||||
'event_id': 'VOLUME_000002',
|
||||
'user_message': 'Fake Message',
|
||||
'created_at': '2012-08-27T00:00:00.000000',
|
||||
'guaranteed_until': "2013-11-12T21:00:00.000000",
|
||||
},
|
||||
{
|
||||
'id': '12345',
|
||||
'event_id': 'VOLUME_000002',
|
||||
'user_message': 'Fake Message',
|
||||
'created_at': '2012-08-27T00:00:00.000000',
|
||||
'guaranteed_until': "2013-11-12T21:00:00.000000",
|
||||
}
|
||||
]}
|
||||
|
||||
def delete_messages_1234(self, **kw):
|
||||
return 204, {}, None
|
||||
|
||||
def delete_messages_12345(self, **kw):
|
||||
return 204, {}, None
|
||||
|
||||
def get_messages_1234(self, **kw):
|
||||
message = {
|
||||
'id': '1234',
|
||||
'event_id': 'VOLUME_000002',
|
||||
'user_message': 'Fake Message',
|
||||
'created_at': '2012-08-27T00:00:00.000000',
|
||||
'guaranteed_until': "2013-11-12T21:00:00.000000",
|
||||
}
|
||||
return 200, {}, {'message': message}
|
||||
|
||||
def get_messages_12345(self, **kw):
|
||||
message = {
|
||||
'id': '12345',
|
||||
'event_id': 'VOLUME_000002',
|
||||
'user_message': 'Fake Message',
|
||||
'created_at': '2012-08-27T00:00:00.000000',
|
||||
'guaranteed_until': "2013-11-12T21:00:00.000000",
|
||||
}
|
||||
return 200, {}, {'message': message}
|
||||
|
55
cinderclient/tests/unit/v3/test_messages.py
Normal file
55
cinderclient/tests/unit/v3/test_messages.py
Normal file
@ -0,0 +1,55 @@
|
||||
# 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 six.moves.urllib import parse
|
||||
|
||||
from cinderclient.tests.unit import utils
|
||||
from cinderclient.tests.unit.v3 import fakes
|
||||
|
||||
cs = fakes.FakeClient()
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class MessagesTest(utils.TestCase):
|
||||
|
||||
def test_list_messages(self):
|
||||
cs.messages.list()
|
||||
cs.assert_called('GET', '/messages')
|
||||
|
||||
@ddt.data('id', 'id:asc', 'id:desc', 'resource_type', 'event_id',
|
||||
'resource_uuid', 'message_level', 'guaranteed_until',
|
||||
'request_id')
|
||||
def test_list_messages_with_sort(self, sort_string):
|
||||
cs.messages.list(sort=sort_string)
|
||||
cs.assert_called('GET', '/messages?sort=%s' % parse.quote(sort_string))
|
||||
|
||||
@ddt.data('id', 'resource_type', 'event_id', 'resource_uuid',
|
||||
'message_level', 'guaranteed_until', 'request_id')
|
||||
def test_list_messages_with_filters(self, filter_string):
|
||||
cs.messages.list(search_opts={filter_string: 'value'})
|
||||
cs.assert_called('GET', '/messages?%s=value' % parse.quote(
|
||||
filter_string))
|
||||
|
||||
@ddt.data('fake', 'fake:asc', 'fake:desc')
|
||||
def test_list_messages_with_invalid_sort(self, sort_string):
|
||||
self.assertRaises(ValueError, cs.messages.list, sort=sort_string)
|
||||
|
||||
def test_get_messages(self):
|
||||
fake_id = '1234'
|
||||
cs.messages.get(fake_id)
|
||||
cs.assert_called('GET', '/messages/%s' % fake_id)
|
||||
|
||||
def test_delete_messages(self):
|
||||
fake_id = '1234'
|
||||
cs.messages.delete(fake_id)
|
||||
cs.assert_called('DELETE', '/messages/%s' % fake_id)
|
@ -14,7 +14,6 @@
|
||||
# under the License.
|
||||
|
||||
import ddt
|
||||
|
||||
import fixtures
|
||||
import mock
|
||||
from requests_mock.contrib import fixture as requests_mock_fixture
|
||||
@ -339,3 +338,41 @@ class ShellTest(utils.TestCase):
|
||||
self.run_command('--os-volume-api-version 3.8 '
|
||||
'snapshot-manageable-list fakehost --detailed False')
|
||||
self.assert_called('GET', '/manageable_snapshots?host=fakehost')
|
||||
|
||||
def test_list_messages(self):
|
||||
self.run_command('--os-volume-api-version 3.3 message-list')
|
||||
self.assert_called('GET', '/messages')
|
||||
|
||||
@ddt.data(('resource_type',), ('event_id',), ('resource_uuid',),
|
||||
('level', 'message_level'), ('request_id',))
|
||||
def test_list_messages_with_filters(self, filter):
|
||||
self.run_command('--os-volume-api-version 3.5 message-list --%s=TEST'
|
||||
% filter[0])
|
||||
self.assert_called('GET', '/messages?%s=TEST' % filter[-1])
|
||||
|
||||
def test_list_messages_with_sort(self):
|
||||
self.run_command('--os-volume-api-version 3.5 '
|
||||
'message-list --sort=id:asc')
|
||||
self.assert_called('GET', '/messages?sort=id%3Aasc')
|
||||
|
||||
def test_list_messages_with_limit(self):
|
||||
self.run_command('--os-volume-api-version 3.5 message-list --limit=1')
|
||||
self.assert_called('GET', '/messages?limit=1')
|
||||
|
||||
def test_list_messages_with_marker(self):
|
||||
self.run_command('--os-volume-api-version 3.5 message-list --marker=1')
|
||||
self.assert_called('GET', '/messages?marker=1')
|
||||
|
||||
def test_show_message(self):
|
||||
self.run_command('--os-volume-api-version 3.5 message-show 1234')
|
||||
self.assert_called('GET', '/messages/1234')
|
||||
|
||||
def test_delete_message(self):
|
||||
self.run_command('--os-volume-api-version 3.5 message-delete 1234')
|
||||
self.assert_called('DELETE', '/messages/1234')
|
||||
|
||||
def test_delete_messages(self):
|
||||
self.run_command(
|
||||
'--os-volume-api-version 3.3 message-delete 1234 12345')
|
||||
self.assert_called_anytime('DELETE', '/messages/1234')
|
||||
self.assert_called_anytime('DELETE', '/messages/12345')
|
||||
|
@ -26,6 +26,7 @@ from cinderclient.v3 import groups
|
||||
from cinderclient.v3 import group_snapshots
|
||||
from cinderclient.v3 import group_types
|
||||
from cinderclient.v3 import limits
|
||||
from cinderclient.v3 import messages
|
||||
from cinderclient.v3 import pools
|
||||
from cinderclient.v3 import qos_specs
|
||||
from cinderclient.v3 import quota_classes
|
||||
@ -68,6 +69,7 @@ class Client(object):
|
||||
password = api_key
|
||||
self.version = '3.0'
|
||||
self.limits = limits.LimitsManager(self)
|
||||
self.api_version = api_version or api_versions.APIVersion(self.version)
|
||||
|
||||
# extensions
|
||||
self.volumes = volumes.VolumeManager(self)
|
||||
@ -82,6 +84,7 @@ class Client(object):
|
||||
self.quota_classes = quota_classes.QuotaClassSetManager(self)
|
||||
self.quotas = quotas.QuotaSetManager(self)
|
||||
self.backups = volume_backups.VolumeBackupManager(self)
|
||||
self.messages = messages.MessageManager(self)
|
||||
self.restores = volume_backups_restore.VolumeBackupRestoreManager(self)
|
||||
self.transfers = volume_transfers.VolumeTransferManager(self)
|
||||
self.services = services.ServiceManager(self)
|
||||
@ -95,7 +98,6 @@ class Client(object):
|
||||
availability_zones.AvailabilityZoneManager(self)
|
||||
self.pools = pools.PoolManager(self)
|
||||
self.capabilities = capabilities.CapabilitiesManager(self)
|
||||
self.api_version = api_version or api_versions.APIVersion(self.version)
|
||||
|
||||
# Add in any extensions...
|
||||
if extensions:
|
||||
|
77
cinderclient/v3/messages.py
Normal file
77
cinderclient/v3/messages.py
Normal file
@ -0,0 +1,77 @@
|
||||
# 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.
|
||||
|
||||
"""Message interface (v3 extension)."""
|
||||
|
||||
from cinderclient import base
|
||||
from cinderclient import api_versions
|
||||
|
||||
|
||||
class Message(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('3.3')
|
||||
def get(self, message_id):
|
||||
"""Get a message.
|
||||
|
||||
:param message_id: The ID of the message to get.
|
||||
:rtype: :class:`Message`
|
||||
"""
|
||||
return self._get("/messages/%s" % message_id, "message")
|
||||
|
||||
@api_versions.wraps('3.3', '3.4')
|
||||
def list(self, **kwargs):
|
||||
"""Lists all messages.
|
||||
|
||||
:rtype: list of :class:`Message`
|
||||
"""
|
||||
|
||||
resource_type = "messages"
|
||||
url = self._build_list_url(resource_type, detailed=False)
|
||||
return self._list(url, resource_type)
|
||||
|
||||
@api_versions.wraps('3.5')
|
||||
def list(self, search_opts=None, marker=None, limit=None, sort=None):
|
||||
"""Lists all messages.
|
||||
|
||||
:param search_opts: Search options to filter out volumes.
|
||||
:param marker: Begin returning volumes that appear later in the volume
|
||||
list than that represented by this volume id.
|
||||
:param limit: Maximum number of volumes to return.
|
||||
:param sort: Sort information
|
||||
:rtype: list of :class:`Message`
|
||||
"""
|
||||
resource_type = "messages"
|
||||
url = self._build_list_url(resource_type, detailed=False,
|
||||
search_opts=search_opts, marker=marker,
|
||||
limit=limit, sort=sort)
|
||||
return self._list(url, resource_type, limit=limit)
|
||||
|
||||
@api_versions.wraps('3.3')
|
||||
def delete(self, message):
|
||||
"""Delete a message."""
|
||||
|
||||
loc = "/messages/%s" % base.getid(message)
|
||||
|
||||
return self._delete(loc)
|
@ -113,6 +113,11 @@ def _find_qos_specs(cs, qos_specs):
|
||||
return utils.find_resource(cs.qos_specs, qos_specs)
|
||||
|
||||
|
||||
def _find_message(cs, message):
|
||||
"""Gets a message by ID."""
|
||||
return utils.find_resource(cs.messages, message)
|
||||
|
||||
|
||||
def _print_volume_snapshot(snapshot):
|
||||
utils.print_dict(snapshot._info)
|
||||
|
||||
@ -3352,11 +3357,11 @@ def do_thaw_host(cs, args):
|
||||
cs.services.thaw_host(args.host)
|
||||
|
||||
|
||||
@utils.service_type('volumev3')
|
||||
@utils.arg('host', metavar='<hostname>', help='Host name.')
|
||||
@utils.arg('--backend_id',
|
||||
metavar='<backend-id>',
|
||||
help='ID of backend to failover to (Default=None)')
|
||||
@utils.service_type('volumev3')
|
||||
def do_failover_host(cs, args):
|
||||
"""Failover a replicating cinder-volume host."""
|
||||
cs.services.failover_host(args.host, args.backend_id)
|
||||
@ -3369,3 +3374,108 @@ def do_api_version(cs, args):
|
||||
columns = ['ID', 'Status', 'Version', 'Min_version']
|
||||
response = cs.services.server_api_version()
|
||||
utils.print_list(response, columns)
|
||||
|
||||
|
||||
@utils.service_type('volumev3')
|
||||
@api_versions.wraps("3.3")
|
||||
@utils.arg('--marker',
|
||||
metavar='<marker>',
|
||||
default=None,
|
||||
start_version='3.5',
|
||||
help='Begin returning message that appear later in the message '
|
||||
'list than that represented by this id. '
|
||||
'Default=None.')
|
||||
@utils.arg('--limit',
|
||||
metavar='<limit>',
|
||||
default=None,
|
||||
start_version='3.5',
|
||||
help='Maximum number of messages to return. Default=None.')
|
||||
@utils.arg('--sort',
|
||||
metavar='<key>[:<direction>]',
|
||||
default=None,
|
||||
start_version='3.5',
|
||||
help=(('Comma-separated list of sort keys and directions in the '
|
||||
'form of <key>[:<asc|desc>]. '
|
||||
'Valid keys: %s. '
|
||||
'Default=None.') % ', '.join(base.SORT_KEY_VALUES)))
|
||||
@utils.arg('--resource_uuid',
|
||||
metavar='<resource_uuid>',
|
||||
default=None,
|
||||
help='Filters results by a resource uuid. Default=None.')
|
||||
@utils.arg('--resource_type',
|
||||
metavar='<type>',
|
||||
default=None,
|
||||
help='Filters results by a resource type. Default=None.')
|
||||
@utils.arg('--event_id',
|
||||
metavar='<id>',
|
||||
default=None,
|
||||
help='Filters results by event id. Default=None.')
|
||||
@utils.arg('--request_id',
|
||||
metavar='<request_id>',
|
||||
default=None,
|
||||
help='Filters results by request id. Default=None.')
|
||||
@utils.arg('--level',
|
||||
metavar='<level>',
|
||||
default=None,
|
||||
help='Filters results by the message level. Default=None.')
|
||||
def do_message_list(cs, args):
|
||||
"""Lists all messages."""
|
||||
search_opts = {
|
||||
'resource_uuid': args.resource_uuid,
|
||||
'event_id': args.event_id,
|
||||
'request_id': args.request_id,
|
||||
}
|
||||
if args.resource_type:
|
||||
search_opts['resource_type'] = args.resource_type.upper()
|
||||
if args.level:
|
||||
search_opts['message_level'] = args.level.upper()
|
||||
|
||||
marker = args.marker if hasattr(args, 'marker') else None
|
||||
limit = args.limit if hasattr(args, 'limit') else None
|
||||
sort = args.sort if hasattr(args, 'sort') else None
|
||||
|
||||
messages = cs.messages.list(search_opts=search_opts,
|
||||
marker=marker,
|
||||
limit=limit,
|
||||
sort=sort)
|
||||
|
||||
columns = ['ID', 'Resource Type', 'Resource UUID', 'Event ID',
|
||||
'User Message']
|
||||
if sort:
|
||||
sortby_index = None
|
||||
else:
|
||||
sortby_index = 0
|
||||
utils.print_list(messages, columns, sortby_index=sortby_index)
|
||||
|
||||
|
||||
@utils.service_type('volumev3')
|
||||
@api_versions.wraps("3.3")
|
||||
@utils.arg('message',
|
||||
metavar='<message>',
|
||||
help='ID of message.')
|
||||
def do_message_show(cs, args):
|
||||
"""Shows message details."""
|
||||
info = dict()
|
||||
message = _find_message(cs, args.message)
|
||||
info.update(message._info)
|
||||
info.pop('links', None)
|
||||
utils.print_dict(info)
|
||||
|
||||
|
||||
@utils.service_type('volumev3')
|
||||
@api_versions.wraps("3.3")
|
||||
@utils.arg('message',
|
||||
metavar='<message>', nargs='+',
|
||||
help='ID of one or more message to be deleted.')
|
||||
def do_message_delete(cs, args):
|
||||
"""Removes one or more messages."""
|
||||
failure_count = 0
|
||||
for message in args.message:
|
||||
try:
|
||||
_find_message(cs, message).delete()
|
||||
except Exception as e:
|
||||
failure_count += 1
|
||||
print("Delete for message %s failed: %s" % (message, e))
|
||||
if failure_count == len(args.message):
|
||||
raise exceptions.CommandError("Unable to delete any of the specified "
|
||||
"messages.")
|
||||
|
11
releasenotes/notes/messages-v3-api-3da81f4f66bf5903.yaml
Normal file
11
releasenotes/notes/messages-v3-api-3da81f4f66bf5903.yaml
Normal file
@ -0,0 +1,11 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Add support for /messages API
|
||||
|
||||
GET /messages
|
||||
cinder --os-volume-api-version 3.3 message-list
|
||||
GET /messages/{id}
|
||||
cinder --os-volume-api-version 3.3 message-show {id}
|
||||
DELETE /message/{id}
|
||||
cinder --os-volume-api-version 3.3 message-delete {id}
|
Loading…
x
Reference in New Issue
Block a user