diff --git a/cinderclient/api_versions.py b/cinderclient/api_versions.py index dee7c78..d1bb1e1 100644 --- a/cinderclient/api_versions.py +++ b/cinderclient/api_versions.py @@ -29,7 +29,7 @@ LOG = logging.getLogger(__name__) # key is a deprecated version and value is an alternative version. DEPRECATED_VERSIONS = {"1": "2"} DEPRECATED_VERSION = "2.0" -MAX_VERSION = "3.28" +MAX_VERSION = "3.33" MIN_VERSION = "3.0" _SUBSTITUTIONS = {} diff --git a/cinderclient/shell_utils.py b/cinderclient/shell_utils.py index e386558..949f7f7 100644 --- a/cinderclient/shell_utils.py +++ b/cinderclient/shell_utils.py @@ -143,6 +143,26 @@ def translate_availability_zone_keys(collection): translate_keys(collection, convert) +def extract_filters(args): + filters = {} + for f in args: + if '=' in f: + (key, value) = f.split('=', 1) + if value.startswith('{') and value.endswith('}'): + value = _build_internal_dict(value[1:-1]) + filters[key] = value + + return filters + + +def _build_internal_dict(content): + result = {} + for pair in content.split(','): + k, v = pair.split(':', 1) + result.update({k.strip(): v.strip()}) + return result + + def extract_metadata(args, type='user_metadata'): metadata = {} if type == 'image_metadata': @@ -169,6 +189,11 @@ def print_group_type_list(gtypes): utils.print_list(gtypes, ['ID', 'Name', 'Description']) +def print_resource_filter_list(filters): + formatter = {'Filters': lambda resource: ', '.join(resource.filters)} + utils.print_list(filters, ['Resource', 'Filters'], formatters=formatter) + + def quota_show(quotas): quotas_info_dict = utils.unicode_key_value_to_string(quotas._info) quota_dict = {} diff --git a/cinderclient/tests/unit/test_utils.py b/cinderclient/tests/unit/test_utils.py index a62425e..eeb4800 100644 --- a/cinderclient/tests/unit/test_utils.py +++ b/cinderclient/tests/unit/test_utils.py @@ -12,6 +12,7 @@ # limitations under the License. import collections +import ddt import sys import mock @@ -21,6 +22,7 @@ import six from cinderclient import api_versions from cinderclient.apiclient import base as common_base from cinderclient import exceptions +from cinderclient import shell_utils from cinderclient import utils from cinderclient import base from cinderclient.tests.unit import utils as test_utils @@ -187,6 +189,21 @@ class BuildQueryParamTestCase(test_utils.TestCase): self.assertFalse(result_2) +@ddt.ddt +class ExtractFilterTestCase(test_utils.TestCase): + + @ddt.data({'content': ['key1=value1'], + 'expected': {'key1': 'value1'}}, + {'content': ['key1={key2:value2}'], + 'expected': {'key1': {'key2': 'value2'}}}, + {'content': ['key1=value1', 'key2={key22:value22}'], + 'expected': {'key1': 'value1', 'key2': {'key22': 'value22'}}}) + @ddt.unpack + def test_extract_filters(self, content, expected): + result = shell_utils.extract_filters(content) + self.assertEqual(expected, result) + + class PrintListTestCase(test_utils.TestCase): def test_print_list_with_list(self): diff --git a/cinderclient/tests/unit/v3/fakes.py b/cinderclient/tests/unit/v3/fakes.py index 6a62477..25a8151 100644 --- a/cinderclient/tests/unit/v3/fakes.py +++ b/cinderclient/tests/unit/v3/fakes.py @@ -544,6 +544,12 @@ class FakeHTTPClient(fake_v2.FakeHTTPClient): } return 200, {}, {'message': message} + # + # resource filters + # + def get_resource_filters(self, **kw): + return 200, {}, {'resource_filters': []} + def fake_request_get(): versions = {'versions': [{'id': 'v1.0', diff --git a/cinderclient/tests/unit/v3/test_resource_filters.py b/cinderclient/tests/unit/v3/test_resource_filters.py new file mode 100644 index 0000000..3b14124 --- /dev/null +++ b/cinderclient/tests/unit/v3/test_resource_filters.py @@ -0,0 +1,32 @@ +# 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 cinderclient.tests.unit import utils +from cinderclient.tests.unit.v3 import fakes + +cs = fakes.FakeClient() + + +@ddt.ddt +class ResourceFilterTests(utils.TestCase): + @ddt.data({'resource': None, 'query_url': None}, + {'resource': 'volume', 'query_url': '?resource=volume'}, + {'resource': 'group', 'query_url': '?resource=group'}) + @ddt.unpack + def test_list_messages(self, resource, query_url): + cs.resource_filters.list(resource) + url = '/resource_filters' + if resource is not None: + url += query_url + cs.assert_called('GET', url) diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index 08bd2c2..c13cdb2 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -66,6 +66,104 @@ class ShellTest(utils.TestCase): return self.shell.cs.assert_called(method, url, body, partial_body, **kwargs) + @ddt.data({'resource': None, 'query_url': None}, + {'resource': 'volume', 'query_url': '?resource=volume'}, + {'resource': 'group', 'query_url': '?resource=group'}) + @ddt.unpack + def test_list_filters(self, resource, query_url): + url = '/resource_filters' + if resource is not None: + url += query_url + self.run_command('--os-volume-api-version 3.33 ' + 'list-filters --resource=%s' % resource) + else: + self.run_command('--os-volume-api-version 3.33 list-filters') + + self.assert_called('GET', url) + + @ddt.data( + # testcases for list volume + {'command': + 'list --name=123 --filters name=456', + 'expected': + '/volumes/detail?name=456'}, + {'command': + 'list --filters name=123', + 'expected': + '/volumes/detail?name=123'}, + {'command': + 'list --filters metadata={key1:value1}', + 'expected': + '/volumes/detail?metadata=%7B%27key1%27%3A+%27value1%27%7D'}, + # testcases for list group + {'command': + 'group-list --filters name=456', + 'expected': + '/groups/detail?name=456'}, + {'command': + 'group-list --filters status=available', + 'expected': + '/groups/detail?status=available'}, + # testcases for list group-snapshot + {'command': + 'group-snapshot-list --status=error --filters status=available', + 'expected': + '/group_snapshots/detail?status=available'}, + {'command': + 'group-snapshot-list --filters availability_zone=123', + 'expected': + '/group_snapshots/detail?availability_zone=123'}, + # testcases for list message + {'command': + 'message-list --event_id=123 --filters event_id=456', + 'expected': + '/messages?event_id=456'}, + {'command': + 'message-list --filters request_id=123', + 'expected': + '/messages?request_id=123'}, + # testcases for list attachment + {'command': + 'attachment-list --volume-id=123 --filters volume_id=456', + 'expected': + '/attachments?volume_id=456'}, + {'command': + 'attachment-list --filters mountpoint=123', + 'expected': + '/attachments?mountpoint=123'}, + # testcases for list backup + {'command': + 'backup-list --volume-id=123 --filters volume_id=456', + 'expected': + '/backups/detail?volume_id=456'}, + {'command': + 'backup-list --filters name=123', + 'expected': + '/backups/detail?name=123'}, + # testcases for list snapshot + {'command': + 'snapshot-list --volume-id=123 --filters volume_id=456', + 'expected': + '/snapshots/detail?volume_id=456'}, + {'command': + 'snapshot-list --filters name=123', + 'expected': + '/snapshots/detail?name=123'}, + # testcases for get pools + {'command': + 'get-pools --filters name=456 --detail', + 'expected': + '/scheduler-stats/get_pools?detail=True&name=456'}, + {'command': + 'get-pools --filters name=456', + 'expected': + '/scheduler-stats/get_pools?name=456'} + ) + @ddt.unpack + def test_list_with_filters_mixed(self, command, expected): + self.run_command('--os-volume-api-version 3.33 %s' % command) + self.assert_called('GET', expected) + def test_list(self): self.run_command('list') # NOTE(jdg): we default to detail currently diff --git a/cinderclient/tests/unit/v3/test_volumes.py b/cinderclient/tests/unit/v3/test_volumes.py index 4613f37..f6164a5 100644 --- a/cinderclient/tests/unit/v3/test_volumes.py +++ b/cinderclient/tests/unit/v3/test_volumes.py @@ -15,6 +15,8 @@ # License for the specific language governing permissions and limitations # under the License. +import ddt + from cinderclient import api_versions from cinderclient.tests.unit import utils from cinderclient.tests.unit.v3 import fakes @@ -25,6 +27,7 @@ from six.moves.urllib import parse cs = fakes.FakeClient() +@ddt.data class VolumesTest(utils.TestCase): def test_volume_manager_upload_to_image(self): @@ -100,3 +103,13 @@ class VolumesTest(utils.TestCase): expected = ("/volumes/detail?glance_metadata=%s" % parse.quote_plus("{'key1': 'val1'}")) cs.assert_called('GET', expected) + + @ddt.data(True, False) + def test_get_pools_filter_by_name(self, detail): + cs = fakes.FakeClient(api_version=api_versions.APIVersion('3.33')) + vol = cs.volumes.get_pools(detail, 'pool1') + request_url = '/scheduler-stats/get_pools?name=pool1' + if detail: + request_url = '/scheduler-stats/get_pools?detail=True&name=pool1' + cs.assert_called('GET', request_url) + self._assert_request_id(vol) diff --git a/cinderclient/v3/client.py b/cinderclient/v3/client.py index 0d4bb86..ef242f4 100644 --- a/cinderclient/v3/client.py +++ b/cinderclient/v3/client.py @@ -32,6 +32,7 @@ from cinderclient.v3 import pools from cinderclient.v3 import qos_specs from cinderclient.v3 import quota_classes from cinderclient.v3 import quotas +from cinderclient.v3 import resource_filters from cinderclient.v3 import services from cinderclient.v3 import volumes from cinderclient.v3 import volume_snapshots @@ -85,6 +86,7 @@ class Client(object): self.quotas = quotas.QuotaSetManager(self) self.backups = volume_backups.VolumeBackupManager(self) self.messages = messages.MessageManager(self) + self.resource_filters = resource_filters.ResourceFilterManager(self) self.restores = volume_backups_restore.VolumeBackupRestoreManager(self) self.transfers = volume_transfers.VolumeTransferManager(self) self.services = services.ServiceManager(self) diff --git a/cinderclient/v3/resource_filters.py b/cinderclient/v3/resource_filters.py new file mode 100644 index 0000000..c726f8c --- /dev/null +++ b/cinderclient/v3/resource_filters.py @@ -0,0 +1,37 @@ +# 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. + +"""Resource filters interface.""" + +from cinderclient import base +from cinderclient import api_versions + + +class ResourceFilter(base.Resource): + NAME_ATTR = 'resource' + + def __repr__(self): + return "" % self.resource + + +class ResourceFilterManager(base.ManagerWithFind): + """Manage :class:`ResourceFilter` resources.""" + + resource_class = ResourceFilter + + @api_versions.wraps('3.33') + def list(self, resource=None): + """List all resource filters.""" + url = '/resource_filters' + if resource is not None: + url += '?resource=%s' % resource + return self._list(url, "resource_filters") diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 0f88f14..e1c6e90 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -32,6 +32,136 @@ from cinderclient import utils from cinderclient.v2.shell import * # flake8: noqa +FILTER_DEPRECATED = ("This option is deprecated and will be removed in " + "newer release. Please use '--filters' option which " + "is introduced since 3.33 instead.") + + +@api_versions.wraps('3.33') +@utils.arg('--resource', + metavar='', + default=None, + help='Show enabled filters for specified resource. Default=None.') +def do_list_filters(cs, args): + filters = cs.resource_filters.list(resource=args.resource) + shell_utils.print_resource_filter_list(filters) + + +@utils.arg('--all-tenants', + metavar='', + nargs='?', + type=int, + const=1, + default=0, + help='Shows details for all tenants. Admin only.') +@utils.arg('--all_tenants', + nargs='?', + type=int, + const=1, + help=argparse.SUPPRESS) +@utils.arg('--name', + metavar='', + default=None, + help="Filters results by a name. Default=None. " + "%s" % FILTER_DEPRECATED) +@utils.arg('--status', + metavar='', + default=None, + help="Filters results by a status. Default=None. " + "%s" % FILTER_DEPRECATED) +@utils.arg('--volume-id', + metavar='', + default=None, + help="Filters results by a volume ID. Default=None. " + "%s" % FILTER_DEPRECATED) +@utils.arg('--volume_id', + help=argparse.SUPPRESS) +@utils.arg('--marker', + metavar='', + default=None, + help='Begin returning backups that appear later in the backup ' + 'list than that represented by this id. ' + 'Default=None.') +@utils.arg('--limit', + metavar='', + default=None, + help='Maximum number of backups to return. Default=None.') +@utils.arg('--sort', + metavar='[:]', + default=None, + help=(('Comma-separated list of sort keys and directions in the ' + 'form of [:]. ' + 'Valid keys: %s. ' + 'Default=None.') % ', '.join(base.SORT_KEY_VALUES))) +@utils.arg('--filters', + type=str, + nargs='*', + start_version='3.33', + metavar='', + default=None, + help="Filter key and value pairs. Please use 'cinder list-filters' " + "to check enabled filters from server, Default=None.") +def do_backup_list(cs, args): + """Lists all backups.""" + # pylint: disable=function-redefined + + search_opts = { + 'all_tenants': args.all_tenants, + 'name': args.name, + 'status': args.status, + 'volume_id': args.volume_id, + } + + # Update search option with `filters` + if hasattr(args, 'filters') and args.filters is not None: + search_opts.update(shell_utils.extract_filters(args.filters)) + + backups = cs.backups.list(search_opts=search_opts, + marker=args.marker, + limit=args.limit, + sort=args.sort) + shell_utils.translate_volume_snapshot_keys(backups) + columns = ['ID', 'Volume ID', 'Status', 'Name', 'Size', 'Object Count', + 'Container'] + if args.sort: + sortby_index = None + else: + sortby_index = 0 + utils.print_list(backups, columns, sortby_index=sortby_index) + + +@utils.arg('--detail', + action='store_true', + help='Show detailed information about pools.') +@utils.arg('--filters', + type=str, + nargs='*', + start_version='3.33', + metavar='', + default=None, + help="Filter key and value pairs. Please use 'cinder list-filters' " + "to check enabled filters from server, Default=None.") +def do_get_pools(cs, args): + """Show pool information for backends. Admin only.""" + # pylint: disable=function-redefined + search_opts = {} + # Update search option with `filters` + if hasattr(args, 'filters') and args.filters is not None: + search_opts.update(shell_utils.extract_filters(args.filters)) + if cs.api_version >= api_versions.APIVersion("3.33"): + pools = cs.volumes.get_pools(args.detail, search_opts) + else: + pools = cs.volumes.get_pools(args.detail) + infos = dict() + infos.update(pools._info) + + for info in infos['pools']: + backend = dict() + backend['name'] = info['name'] + if args.detail: + backend.update(info['capabilities']) + utils.print_dict(backend) + RESET_STATE_RESOURCES = {'volume': utils.find_volume, 'backup': shell_utils.find_backup, @@ -43,7 +173,8 @@ RESET_STATE_RESOURCES = {'volume': utils.find_volume, @utils.arg('--group_id', metavar='', default=None, - help='Filters results by a group_id. Default=None.', + help="Filters results by a group_id. Default=None." + "%s" % FILTER_DEPRECATED, start_version='3.10') @utils.arg('--all-tenants', dest='all_tenants', @@ -61,37 +192,43 @@ RESET_STATE_RESOURCES = {'volume': utils.find_volume, @utils.arg('--name', metavar='', default=None, - help='Filters results by a name. Default=None.') + help="Filters results by a name. Default=None. " + "%s" % FILTER_DEPRECATED) @utils.arg('--display-name', help=argparse.SUPPRESS) @utils.arg('--status', metavar='', default=None, - help='Filters results by a status. Default=None.') + help="Filters results by a status. Default=None. " + "%s" % FILTER_DEPRECATED) @utils.arg('--bootable', metavar='', const=True, nargs='?', choices=['True', 'true', 'False', 'false'], - help='Filters results by bootable status. Default=None.') + help="Filters results by bootable status. Default=None. " + "%s" % FILTER_DEPRECATED) @utils.arg('--migration_status', metavar='', default=None, - help='Filters results by a migration status. Default=None. ' - 'Admin only.') + help="Filters results by a migration status. Default=None. " + "Admin only. " + "%s" % FILTER_DEPRECATED) @utils.arg('--metadata', nargs='*', metavar='', default=None, - help='Filters results by a metadata key and value pair. ' - 'Default=None.') + help="Filters results by a metadata key and value pair. " + "Default=None. " + "%s" % FILTER_DEPRECATED) @utils.arg('--image_metadata', nargs='*', metavar='', default=None, start_version='3.4', - help='Filters results by a image metadata key and value pair. ' - 'Require volume api version >=3.4. Default=None.') + help="Filters results by a image metadata key and value pair. " + "Require volume api version >=3.4. Default=None." + "%s" % FILTER_DEPRECATED) @utils.arg('--marker', metavar='', default=None, @@ -130,8 +267,17 @@ RESET_STATE_RESOURCES = {'volume': utils.find_volume, nargs='?', metavar='', help='Display information from single tenant (Admin only).') +@utils.arg('--filters', + type=str, + nargs='*', + start_version='3.33', + metavar='', + default=None, + help="Filter key and value pairs. Please use 'cinder list-filters' " + "to check enabled filters from server, Default=None.") def do_list(cs, args): """Lists all volumes.""" + # pylint: disable=function-redefined # NOTE(thingee): Backwards-compatibility with v1 args if args.display_name is not None: args.name = args.display_name @@ -152,6 +298,9 @@ def do_list(cs, args): if hasattr(args, 'image_metadata') and args.image_metadata else None, 'group_id': getattr(args, 'group_id', None), } + # Update search option with `filters` + if hasattr(args, 'filters') and args.filters is not None: + search_opts.update(shell_utils.extract_filters(args.filters)) # If unavailable/non-existent fields are specified, these fields will # be removed from key_list at the print_list() during key validation. @@ -820,10 +969,22 @@ def do_manageable_list(cs, args): const=1, default=utils.env('ALL_TENANTS', default=0), help='Shows details for all tenants. Admin only.') +@utils.arg('--filters', + type=str, + nargs='*', + start_version='3.33', + metavar='', + default=None, + help="Filter key and value pairs. Please use 'cinder list-filters' " + "to check enabled filters from server, Default=None.") def do_group_list(cs, args): """Lists all groups.""" search_opts = {'all_tenants': args.all_tenants} + # Update search option with `filters` + if hasattr(args, 'filters') and args.filters is not None: + search_opts.update(shell_utils.extract_filters(args.filters)) + groups = cs.groups.list(search_opts=search_opts) columns = ['ID', 'Status', 'Name'] @@ -1003,11 +1164,21 @@ def do_group_update(cs, args): @utils.arg('--status', metavar='', default=None, - help='Filters results by a status. Default=None.') + help="Filters results by a status. Default=None. " + "%s" % FILTER_DEPRECATED) @utils.arg('--group-id', metavar='', default=None, - help='Filters results by a group ID. Default=None.') + help="Filters results by a group ID. Default=None. " + "%s" % FILTER_DEPRECATED) +@utils.arg('--filters', + type=str, + nargs='*', + start_version='3.33', + metavar='', + default=None, + help="Filter key and value pairs. Please use 'cinder list-filters' " + "to check enabled filters from server, Default=None.") def do_group_snapshot_list(cs, args): """Lists all group snapshots.""" @@ -1018,6 +1189,9 @@ def do_group_snapshot_list(cs, args): 'status': args.status, 'group_id': args.group_id, } + # Update search option with `filters` + if hasattr(args, 'filters') and args.filters is not None: + search_opts.update(shell_utils.extract_filters(args.filters)) group_snapshots = cs.group_snapshots.list(search_opts=search_opts) @@ -1200,23 +1374,36 @@ def do_api_version(cs, args): @utils.arg('--resource_uuid', metavar='', default=None, - help='Filters results by a resource uuid. Default=None.') + help="Filters results by a resource uuid. Default=None. " + "%s" % FILTER_DEPRECATED) @utils.arg('--resource_type', metavar='', default=None, - help='Filters results by a resource type. Default=None.') + help="Filters results by a resource type. Default=None. " + "%s" % FILTER_DEPRECATED) @utils.arg('--event_id', metavar='', default=None, - help='Filters results by event id. Default=None.') + help="Filters results by event id. Default=None. " + "%s" % FILTER_DEPRECATED) @utils.arg('--request_id', metavar='', default=None, - help='Filters results by request id. Default=None.') + help="Filters results by request id. Default=None. " + "%s" % FILTER_DEPRECATED) @utils.arg('--level', metavar='', default=None, - help='Filters results by the message level. Default=None.') + help="Filters results by the message level. Default=None. " + "%s" % FILTER_DEPRECATED) +@utils.arg('--filters', + type=str, + nargs='*', + start_version='3.33', + metavar='', + default=None, + help="Filter key and value pairs. Please use 'cinder list-filters' " + "to check enabled filters from server, Default=None.") def do_message_list(cs, args): """Lists all messages.""" search_opts = { @@ -1224,6 +1411,9 @@ def do_message_list(cs, args): 'event_id': args.event_id, 'request_id': args.request_id, } + # Update search option with `filters` + if hasattr(args, 'filters') and args.filters is not None: + search_opts.update(shell_utils.extract_filters(args.filters)) if args.resource_type: search_opts['resource_type'] = args.resource_type.upper() if args.level: @@ -1294,7 +1484,8 @@ def do_message_delete(cs, args): @utils.arg('--name', metavar='', default=None, - help='Filters results by a name. Default=None.') + help="Filters results by a name. Default=None. " + "%s" % FILTER_DEPRECATED) @utils.arg('--display-name', help=argparse.SUPPRESS) @utils.arg('--display_name', @@ -1302,11 +1493,13 @@ def do_message_delete(cs, args): @utils.arg('--status', metavar='', default=None, - help='Filters results by a status. Default=None.') + help="Filters results by a status. Default=None. " + "%s" % FILTER_DEPRECATED) @utils.arg('--volume-id', metavar='', default=None, - help='Filters results by a volume ID. Default=None.') + help="Filters results by a volume ID. Default=None. " + "%s" % FILTER_DEPRECATED) @utils.arg('--volume_id', help=argparse.SUPPRESS) @utils.arg('--marker', @@ -1337,10 +1530,20 @@ def do_message_delete(cs, args): metavar='', default=None, start_version='3.22', - help='Filters results by a metadata key and value pair. Require ' - 'volume api version >=3.22. Default=None.') + help="Filters results by a metadata key and value pair. Require " + "volume api version >=3.22. Default=None. " + "%s" % FILTER_DEPRECATED) +@utils.arg('--filters', + type=str, + nargs='*', + start_version='3.33', + metavar='', + default=None, + help="Filter key and value pairs. Please use 'cinder list-filters' " + "to check enabled filters from server, Default=None.") def do_snapshot_list(cs, args): """Lists all snapshots.""" + # pylint: disable=function-redefined all_tenants = (1 if args.tenant else int(os.environ.get("ALL_TENANTS", args.all_tenants))) @@ -1363,6 +1566,10 @@ def do_snapshot_list(cs, args): 'metadata': metadata } + # Update search option with `filters` + if hasattr(args, 'filters') and args.filters is not None: + search_opts.update(shell_utils.extract_filters(args.filters)) + snapshots = cs.volume_snapshots.list(search_opts=search_opts, marker=args.marker, limit=args.limit, @@ -1386,11 +1593,13 @@ def do_snapshot_list(cs, args): @utils.arg('--volume-id', metavar='', default=None, - help='Filters results by a volume ID. Default=None.') + help="Filters results by a volume ID. Default=None. " + "%s" % FILTER_DEPRECATED) @utils.arg('--status', metavar='', default=None, - help='Filters results by a status. Default=None.') + help="Filters results by a status. Default=None. " + "%s" % FILTER_DEPRECATED) @utils.arg('--marker', metavar='', default=None, @@ -1414,6 +1623,14 @@ def do_snapshot_list(cs, args): nargs='?', metavar='', help='Display information from single tenant (Admin only).') +@utils.arg('--filters', + type=str, + nargs='*', + start_version='3.33', + metavar='', + default=None, + help="Filter key and value pairs. Please use 'cinder list-filters' " + "to check enabled filters from server, Default=None.") def do_attachment_list(cs, args): """Lists all attachments.""" search_opts = { @@ -1422,6 +1639,9 @@ def do_attachment_list(cs, args): 'status': args.status, 'volume_id': args.volume_id, } + # Update search option with `filters` + if hasattr(args, 'filters') and args.filters is not None: + search_opts.update(shell_utils.extract_filters(args.filters)) attachments = cs.attachments.list(search_opts=search_opts, marker=args.marker, diff --git a/cinderclient/v3/volumes.py b/cinderclient/v3/volumes.py index c786adb..2224489 100644 --- a/cinderclient/v3/volumes.py +++ b/cinderclient/v3/volumes.py @@ -176,3 +176,23 @@ class VolumeManager(volumes.VolumeManager): search_opts={'host': host}, marker=marker, limit=limit, offset=offset, sort=sort) return self._list(url, "manageable-volumes") + + @api_versions.wraps("2.0", "3.32") + def get_pools(self, detail): + """Show pool information for backends.""" + query_string = "" + if detail: + query_string = "?detail=True" + + return self._get('/scheduler-stats/get_pools%s' % query_string, None) + + @api_versions.wraps("3.33") + def get_pools(self, detail, search_opts): + """Show pool information for backends.""" + # pylint: disable=function-redefined + options = {'detail': detail} + options.update(search_opts) + url = self._build_list_url('scheduler-stats/get_pools', detailed=False, + search_opts=options) + + return self._get(url, None) diff --git a/releasenotes/notes/support-generialized-resource-filter-8yf6w23f66bf5903.yaml b/releasenotes/notes/support-generialized-resource-filter-8yf6w23f66bf5903.yaml new file mode 100644 index 0000000..fdfb4c0 --- /dev/null +++ b/releasenotes/notes/support-generialized-resource-filter-8yf6w23f66bf5903.yaml @@ -0,0 +1,14 @@ +--- +features: + - | + Added new command ``list-filters`` to retrieve enabled resource filters, + Added new option ``--filters`` to these list commands: + + - list + - snapshot-list + - backup-list + - group-list + - group-snapshot-list + - attachment-list + - message-list + - get-pools diff --git a/tools/lintstack.py b/tools/lintstack.py index acde2b0..34ca056 100755 --- a/tools/lintstack.py +++ b/tools/lintstack.py @@ -43,6 +43,9 @@ ignore_messages = [ # six.moves "Instance of '_MovedItems' has no 'builtins' member", + + # This error message is for code [E1101] + "Instance of 'ResourceFilterManager' has no '_list' member", ] ignore_modules = ["cinderclient/tests/"]