diff options
author | Zuul <zuul@review.opendev.org> | 2019-09-11 14:16:09 +0000 |
---|---|---|
committer | Gerrit Code Review <review@openstack.org> | 2019-09-11 14:16:09 +0000 |
commit | bafd84b9fad75426e8f0dc3f4e751f0201a7338d (patch) | |
tree | 778f9e3133e7348b8bde081d35536e10a736de3c | |
parent | 61fec71adbcff8b62312d2e814c8af4879d169be (diff) | |
parent | 624b444226a6a5f14d1f381c1ba9301b192510d1 (diff) | |
download | python-cinderclient-bafd84b9fad75426e8f0dc3f4e751f0201a7338d.tar.gz |
Merge "Optional filters parameters should be passed only once"
-rw-r--r-- | cinderclient/shell.py | 47 | ||||
-rw-r--r-- | cinderclient/tests/unit/test_shell.py | 6 | ||||
-rw-r--r-- | cinderclient/tests/unit/v3/test_shell.py | 36 | ||||
-rw-r--r-- | cinderclient/v3/shell.py | 68 |
4 files changed, 136 insertions, 21 deletions
diff --git a/cinderclient/shell.py b/cinderclient/shell.py index 7a7d19a..d948332 100644 --- a/cinderclient/shell.py +++ b/cinderclient/shell.py @@ -54,6 +54,33 @@ V3_SHELL = 'cinderclient.v3.shell' HINT_HELP_MSG = (" [hint: use '--os-volume-api-version' flag to show help " "message for proper version]") +FILTER_CHECK = ["type-list", + "backup-list", + "get-pools", + "list", + "group-list", + "group-snapshot-list", + "message-list", + "snapshot-list", + "attachment-list"] + +RESOURCE_FILTERS = { + "list": ["name", "status", "metadata", + "bootable", "migration_status", "availability_zone", + "group_id", "size"], + "backup-list": ["name", "status", "volume_id"], + "snapshot-list": ["name", "status", "volume_id", "metadata", + "availability_zone"], + "group-list": ["name"], + "group-snapshot-list": ["name", "status", "group_id"], + "attachment-list": ["volume_id", "status", "instance_id", "attach_status"], + "message-list": ["resource_uuid", "resource_type", "event_id", + "request_id", "message_level"], + "get-pools": ["name", "volume_type"], + "type-list": ["is_public"] +} + + logging.basicConfig() logger = logging.getLogger(__name__) @@ -521,8 +548,28 @@ class OpenStackCinderShell(object): logger.warning("downgrading to %s based on server support." % discovered.get_string()) + def check_duplicate_filters(self, argv, filter): + resource = RESOURCE_FILTERS[filter] + filters = [] + for opt in range(len(argv)): + if argv[opt].startswith('--'): + if argv[opt] == '--filters': + key, __ = argv[opt + 1].split('=') + if key in resource: + filters.append(key) + elif argv[opt][2:] in resource: + filters.append(argv[opt][2:]) + + if len(set(filters)) != len(filters): + raise exc.CommandError( + "Filters are only allowed to be passed once.") + def main(self, argv): # Parse args once to find version and debug settings + for filter in FILTER_CHECK: + if filter in argv: + self.check_duplicate_filters(argv, filter) + break parser = self.get_base_parser() (options, args) = parser.parse_known_args(argv) self.setup_debugging(options.debug) diff --git a/cinderclient/tests/unit/test_shell.py b/cinderclient/tests/unit/test_shell.py index b8f120d..305a37c 100644 --- a/cinderclient/tests/unit/test_shell.py +++ b/cinderclient/tests/unit/test_shell.py @@ -201,6 +201,12 @@ class ShellTest(utils.TestCase): _shell = shell.OpenStackCinderShell() _shell.main(['list']) + def test_duplicate_filters(self): + _shell = shell.OpenStackCinderShell() + self.assertRaises(exceptions.CommandError, + _shell.main, + ['list', '--name', 'abc', '--filters', 'name=xyz']) + @unittest.skip("Skip cuz I broke it") def test_cinder_service_name(self): # Failing with 'No mock address' means we are not diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index f4aa598..8c81de0 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -145,6 +145,10 @@ class ShellTest(utils.TestCase): u'list --filters name~=Σ', 'expected': '/volumes/detail?name~=%CE%A3'}, + {'command': + u'list --filters name=abc --filters size=1', + 'expected': + '/volumes/detail?name=abc&size=1'}, # testcases for list group {'command': 'group-list --filters name=456', @@ -158,6 +162,10 @@ class ShellTest(utils.TestCase): 'group-list --filters name~=456', 'expected': '/groups/detail?name~=456'}, + {'command': + 'group-list --filters name=abc --filters status=available', + 'expected': + '/groups/detail?name=abc&status=available'}, # testcases for list group-snapshot {'command': 'group-snapshot-list --status=error --filters status=available', @@ -171,6 +179,11 @@ class ShellTest(utils.TestCase): 'group-snapshot-list --filters status~=available', 'expected': '/group_snapshots/detail?status~=available'}, + {'command': + 'group-snapshot-list --filters status=available ' + '--filters availability_zone=123', + 'expected': + '/group_snapshots/detail?availability_zone=123&status=available'}, # testcases for list message {'command': 'message-list --event_id=123 --filters event_id=456', @@ -184,6 +197,10 @@ class ShellTest(utils.TestCase): 'message-list --filters request_id~=123', 'expected': '/messages?request_id~=123'}, + {'command': + 'message-list --filters request_id=123 --filters event_id=456', + 'expected': + '/messages?event_id=456&request_id=123'}, # testcases for list attachment {'command': 'attachment-list --volume-id=123 --filters volume_id=456', @@ -197,6 +214,11 @@ class ShellTest(utils.TestCase): 'attachment-list --filters volume_id~=456', 'expected': '/attachments?volume_id~=456'}, + {'command': + 'attachment-list --filters volume_id=123 ' + '--filters mountpoint=456', + 'expected': + '/attachments?mountpoint=456&volume_id=123'}, # testcases for list backup {'command': 'backup-list --volume-id=123 --filters volume_id=456', @@ -210,6 +232,10 @@ class ShellTest(utils.TestCase): 'backup-list --filters volume_id~=456', 'expected': '/backups/detail?volume_id~=456'}, + {'command': + 'backup-list --filters volume_id=123 --filters name=456', + 'expected': + '/backups/detail?name=456&volume_id=123'}, # testcases for list snapshot {'command': 'snapshot-list --volume-id=123 --filters volume_id=456', @@ -223,6 +249,10 @@ class ShellTest(utils.TestCase): 'snapshot-list --filters volume_id~=456', 'expected': '/snapshots/detail?volume_id~=456'}, + {'command': + 'snapshot-list --filters volume_id=123 --filters name=456', + 'expected': + '/snapshots/detail?name=456&volume_id=123'}, # testcases for get pools {'command': 'get-pools --filters name=456 --detail', @@ -231,7 +261,11 @@ class ShellTest(utils.TestCase): {'command': 'get-pools --filters name=456', 'expected': - '/scheduler-stats/get_pools?name=456'} + '/scheduler-stats/get_pools?name=456'}, + {'command': + 'get-pools --filters name=456 --filters detail=True', + 'expected': + '/scheduler-stats/get_pools?detail=True&name=456'} ) @ddt.unpack def test_list_with_filters_mixed(self, command, expected): diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 436c80c..816ce9a 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -37,6 +37,14 @@ FILTER_DEPRECATED = ("This option is deprecated and will be removed in " "is introduced since 3.33 instead.") +class AppendFilters(argparse.Action): + + filters = [] + + def __call__(self, parser, namespace, values, option_string): + AppendFilters.filters.append(values[0]) + + @api_versions.wraps('3.33') @utils.arg('--resource', metavar='<resource>', @@ -52,6 +60,7 @@ def do_list_filters(cs, args): @utils.arg('--filters', + action=AppendFilters, type=six.text_type, nargs='*', start_version='3.52', @@ -66,8 +75,8 @@ def do_type_list(cs, args): # 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 AppendFilters.filters: + search_opts.update(shell_utils.extract_filters(AppendFilters.filters)) vtypes = cs.volume_types.list(search_opts=search_opts) shell_utils.print_volume_type_list(vtypes) @@ -83,6 +92,7 @@ def do_type_list(cs, args): mode="w"): for vtype in vtypes: cs.volume_types.write_to_completion_cache('name', vtype.name) + AppendFilters.filters = [] @utils.arg('--all-tenants', @@ -132,6 +142,7 @@ def do_type_list(cs, args): 'Valid keys: %s. ' 'Default=None.') % ', '.join(base.SORT_KEY_VALUES))) @utils.arg('--filters', + action=AppendFilters, type=six.text_type, nargs='*', start_version='3.33', @@ -163,8 +174,8 @@ def do_backup_list(cs, args): } # 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 AppendFilters.filters: + search_opts.update(shell_utils.extract_filters(AppendFilters.filters)) total_count = 0 if show_count: @@ -205,12 +216,14 @@ def do_backup_list(cs, args): for backup in backups: if backup.name is not None: cs.backups.write_to_completion_cache('name', backup.name) + AppendFilters.filters = [] @utils.arg('--detail', action='store_true', help='Show detailed information about pools.') @utils.arg('--filters', + action=AppendFilters, type=six.text_type, nargs='*', start_version='3.33', @@ -223,8 +236,8 @@ def do_get_pools(cs, args): # 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 AppendFilters.filters: + search_opts.update(shell_utils.extract_filters(AppendFilters.filters)) if cs.api_version >= api_versions.APIVersion("3.33"): pools = cs.volumes.get_pools(args.detail, search_opts) else: @@ -238,6 +251,7 @@ def do_get_pools(cs, args): if args.detail: backend.update(info['capabilities']) utils.print_dict(backend) + AppendFilters.filters = [] RESET_STATE_RESOURCES = {'volume': utils.find_volume, @@ -345,6 +359,7 @@ RESET_STATE_RESOURCES = {'volume': utils.find_volume, metavar='<tenant>', help='Display information from single tenant (Admin only).') @utils.arg('--filters', + action=AppendFilters, type=six.text_type, nargs='*', start_version='3.33', @@ -387,8 +402,8 @@ def do_list(cs, args): '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 AppendFilters.filters: + search_opts.update(shell_utils.extract_filters(AppendFilters.filters)) # If unavailable/non-existent fields are specified, these fields will # be removed from key_list at the print_list() during key validation. @@ -458,6 +473,7 @@ def do_list(cs, args): sortby_index=sortby_index) if show_count: print("Volume in total: %s" % total_count) + AppendFilters.filters = [] @utils.arg('entity', metavar='<entity>', nargs='+', @@ -754,6 +770,7 @@ def do_summary(cs, args): @api_versions.wraps('3.11') @utils.arg('--filters', + action=AppendFilters, type=six.text_type, nargs='*', start_version='3.52', @@ -764,10 +781,11 @@ def do_group_type_list(cs, args): """Lists available 'group types'. (Admin only will see private types)""" 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 AppendFilters.filters: + search_opts.update(shell_utils.extract_filters(AppendFilters.filters)) gtypes = cs.group_types.list(search_opts=search_opts) shell_utils.print_group_type_list(gtypes) + AppendFilters.filters = [] @api_versions.wraps('3.11') @@ -1342,6 +1360,7 @@ def do_manageable_list(cs, args): default=utils.env('ALL_TENANTS', default=None), help='Shows details for all tenants. Admin only.') @utils.arg('--filters', + action=AppendFilters, type=six.text_type, nargs='*', start_version='3.33', @@ -1355,8 +1374,8 @@ def do_group_list(cs, args): 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)) + if AppendFilters.filters: + search_opts.update(shell_utils.extract_filters(AppendFilters.filters)) groups = cs.groups.list(search_opts=search_opts) @@ -1376,6 +1395,7 @@ def do_group_list(cs, args): if group.name is None: continue cs.groups.write_to_completion_cache('name', group.name) + AppendFilters.filters = [] @api_versions.wraps('3.13') @@ -1652,6 +1672,7 @@ def do_group_list_replication_targets(cs, args): help="Filters results by a group ID. Default=None. " "%s" % FILTER_DEPRECATED) @utils.arg('--filters', + action=AppendFilters, type=six.text_type, nargs='*', start_version='3.33', @@ -1669,13 +1690,14 @@ def do_group_snapshot_list(cs, args): '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)) + if AppendFilters.filters: + search_opts.update(shell_utils.extract_filters(AppendFilters.filters)) group_snapshots = cs.group_snapshots.list(search_opts=search_opts) columns = ['ID', 'Status', 'Name'] utils.print_list(group_snapshots, columns) + AppendFilters.filters = [] @api_versions.wraps('3.14') @@ -1902,6 +1924,7 @@ def do_revert_to_snapshot(cs, args): help="Filters results by the message level. Default=None. " "%s" % FILTER_DEPRECATED) @utils.arg('--filters', + action=AppendFilters, type=six.text_type, nargs='*', start_version='3.33', @@ -1918,8 +1941,8 @@ def do_message_list(cs, args): '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 AppendFilters.filters: + search_opts.update(shell_utils.extract_filters(AppendFilters.filters)) if args.resource_type: search_opts['resource_type'] = args.resource_type.upper() if args.level: @@ -1941,6 +1964,7 @@ def do_message_list(cs, args): else: sortby_index = 0 utils.print_list(messages, columns, sortby_index=sortby_index) + AppendFilters.filters = [] @api_versions.wraps("3.3") @@ -2040,6 +2064,7 @@ def do_message_delete(cs, args): "volume api version >=3.22. Default=None. " "%s" % FILTER_DEPRECATED) @utils.arg('--filters', + action=AppendFilters, type=six.text_type, nargs='*', start_version='3.33', @@ -2085,8 +2110,8 @@ def do_snapshot_list(cs, args): } # 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 AppendFilters.filters: + search_opts.update(shell_utils.extract_filters(AppendFilters.filters)) total_count = 0 if show_count: @@ -2122,6 +2147,7 @@ def do_snapshot_list(cs, args): mode='w'): for snapshot in snapshots: cs.volume_snapshots.write_to_completion_cache('uuid', snapshot.id) + AppendFilters.filters = [] @api_versions.wraps('3.27') @@ -2167,6 +2193,7 @@ def do_snapshot_list(cs, args): metavar='<tenant>', help='Display information from single tenant (Admin only).') @utils.arg('--filters', + action=AppendFilters, type=six.text_type, nargs='*', start_version='3.33', @@ -2184,8 +2211,8 @@ def do_attachment_list(cs, args): '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)) + if AppendFilters.filters: + search_opts.update(shell_utils.extract_filters(AppendFilters.filters)) attachments = cs.attachments.list(search_opts=search_opts, marker=args.marker, @@ -2199,6 +2226,7 @@ def do_attachment_list(cs, args): else: sortby_index = 0 utils.print_list(attachments, columns, sortby_index=sortby_index) + AppendFilters.filters = [] @api_versions.wraps('3.27') |