summaryrefslogtreecommitdiff
path: root/cinderclient
diff options
context:
space:
mode:
authorJenkins <jenkins@review.openstack.org>2016-09-01 14:32:40 +0000
committerGerrit Code Review <review@openstack.org>2016-09-01 14:32:40 +0000
commit2a9eb37891eeeeb6eea9d8cd492a3a0941ad6066 (patch)
treedd78fb4d232f239ecb16460937f5ec4cd7064a9f /cinderclient
parent6c43bd99d0852f1141280fcb837534dfc22b0a6e (diff)
parent1c87b6fa71a4414831fc4b56e3543c1131081496 (diff)
downloadpython-cinderclient-2a9eb37891eeeeb6eea9d8cd492a3a0941ad6066.tar.gz
Merge "Add v3 user messages with pagination"
Diffstat (limited to 'cinderclient')
-rw-r--r--cinderclient/base.py6
-rw-r--r--cinderclient/client.py3
-rw-r--r--cinderclient/tests/unit/v3/fakes.py48
-rw-r--r--cinderclient/tests/unit/v3/test_messages.py55
-rw-r--r--cinderclient/tests/unit/v3/test_shell.py39
-rw-r--r--cinderclient/v3/client.py4
-rw-r--r--cinderclient/v3/messages.py77
-rw-r--r--cinderclient/v3/shell.py112
8 files changed, 338 insertions, 6 deletions
diff --git a/cinderclient/base.py b/cinderclient/base.py
index 2374c5c..613a066 100644
--- a/cinderclient/base.py
+++ b/cinderclient/base.py
@@ -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
diff --git a/cinderclient/client.py b/cinderclient/client.py
index aef00ef..4ebc88f 100644
--- a/cinderclient/client.py
+++ b/cinderclient/client.py
@@ -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
)
diff --git a/cinderclient/tests/unit/v3/fakes.py b/cinderclient/tests/unit/v3/fakes.py
index e61228e..060b697 100644
--- a/cinderclient/tests/unit/v3/fakes.py
+++ b/cinderclient/tests/unit/v3/fakes.py
@@ -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}
diff --git a/cinderclient/tests/unit/v3/test_messages.py b/cinderclient/tests/unit/v3/test_messages.py
new file mode 100644
index 0000000..31ccdb6
--- /dev/null
+++ b/cinderclient/tests/unit/v3/test_messages.py
@@ -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)
diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py
index 7640b95..a3718fa 100644
--- a/cinderclient/tests/unit/v3/test_shell.py
+++ b/cinderclient/tests/unit/v3/test_shell.py
@@ -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')
diff --git a/cinderclient/v3/client.py b/cinderclient/v3/client.py
index 363dad4..79bb110 100644
--- a/cinderclient/v3/client.py
+++ b/cinderclient/v3/client.py
@@ -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:
diff --git a/cinderclient/v3/messages.py b/cinderclient/v3/messages.py
new file mode 100644
index 0000000..8efd088
--- /dev/null
+++ b/cinderclient/v3/messages.py
@@ -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)
diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py
index 1ff8c42..13097d2 100644
--- a/cinderclient/v3/shell.py
+++ b/cinderclient/v3/shell.py
@@ -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.")