From 056cf5c0591465dadd5017be9349d8f272fdddd0 Mon Sep 17 00:00:00 2001 From: Vivek Agrawal Date: Fri, 6 Jan 2017 13:29:40 -0800 Subject: Metadata based snapshop filtering The snpapshot-list API for cinder gives a list of snapshots based on certain criteria to the user. From microversion 3.22 onwards the snapshot-list API has been enhanced to support snapshot list filtering based on metadata of snapshots. The metadata is stored as key-value pair for every snapshot. With this commit cinder will be queried based on metadata key and value specified in the API snaphot-list. All the snapshots which match the key, value provided by the user along with any other filter criteria will be returned. Added the test cases for the CLI and web requests. DocImpact: "Filters results by a metadata key and value pair. Default=None." on cinder snapshot-list APIImpact Closes-bug: #1569554 Change-Id: Idec0d0d02e7956843f202508e32c023c3cafbb0f --- cinderclient/tests/unit/v3/test_shell.py | 9 +++ cinderclient/tests/unit/v3/test_volumes.py | 9 +++ cinderclient/v3/shell.py | 91 ++++++++++++++++++++++++++++++ 3 files changed, 109 insertions(+) diff --git a/cinderclient/tests/unit/v3/test_shell.py b/cinderclient/tests/unit/v3/test_shell.py index e6a1eca..e3b2617 100644 --- a/cinderclient/tests/unit/v3/test_shell.py +++ b/cinderclient/tests/unit/v3/test_shell.py @@ -26,6 +26,8 @@ from cinderclient.tests.unit import utils from cinderclient.tests.unit.v3 import fakes from cinderclient.tests.unit.fixture_data import keystone_client +from six.moves.urllib import parse + @ddt.ddt @mock.patch.object(client, 'Client', fakes.FakeClient) @@ -344,6 +346,13 @@ class ShellTest(utils.TestCase): self.run_command('--os-volume-api-version 3.3 message-list') self.assert_called('GET', '/messages') + def test_snapshot_list_with_metadata(self): + self.run_command('--os-volume-api-version 3.22 ' + 'snapshot-list --metadata key1=val1') + expected = ("/snapshots/detail?metadata=%s" + % parse.quote_plus("{'key1': 'val1'}")) + self.assert_called('GET', expected) + @ddt.data(('resource_type',), ('event_id',), ('resource_uuid',), ('level', 'message_level'), ('request_id',)) def test_list_messages_with_filters(self, filter): diff --git a/cinderclient/tests/unit/v3/test_volumes.py b/cinderclient/tests/unit/v3/test_volumes.py index 22ffa75..fe27a51 100644 --- a/cinderclient/tests/unit/v3/test_volumes.py +++ b/cinderclient/tests/unit/v3/test_volumes.py @@ -20,6 +20,8 @@ from cinderclient.tests.unit import utils from cinderclient.tests.unit.v3 import fakes from cinderclient.v3 import volumes +from six.moves.urllib import parse + cs = fakes.FakeClient() @@ -84,3 +86,10 @@ class VolumesTest(utils.TestCase): cs = fakes.FakeClient(api_versions.APIVersion('3.8')) cs.volume_snapshots.list_manageable('host1', detailed=True) cs.assert_called('GET', '/manageable_snapshots/detail?host=host1') + + def test_snapshot_list_with_metadata(self): + cs = fakes.FakeClient(api_versions.APIVersion('3.22')) + cs.volume_snapshots.list(search_opts={'metadata': {'key1': 'val1'}}) + expected = ("/snapshots/detail?metadata=%s" + % parse.quote_plus("{'key1': 'val1'}")) + cs.assert_called('GET', expected) diff --git a/cinderclient/v3/shell.py b/cinderclient/v3/shell.py index 6a5d355..d89e10c 100644 --- a/cinderclient/v3/shell.py +++ b/cinderclient/v3/shell.py @@ -1210,3 +1210,94 @@ def do_message_delete(cs, args): if failure_count == len(args.message): raise exceptions.CommandError("Unable to delete any of the specified " "messages.") + + +@utils.arg('--all-tenants', + dest='all_tenants', + metavar='<0|1>', + 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.') +@utils.arg('--display-name', + help=argparse.SUPPRESS) +@utils.arg('--display_name', + help=argparse.SUPPRESS) +@utils.arg('--status', + metavar='', + default=None, + help='Filters results by a status. Default=None.') +@utils.arg('--volume-id', + metavar='', + default=None, + help='Filters results by a volume ID. Default=None.') +@utils.arg('--volume_id', + help=argparse.SUPPRESS) +@utils.arg('--marker', + metavar='', + default=None, + help='Begin returning snapshots that appear later in the snapshot ' + 'list than that represented by this id. ' + 'Default=None.') +@utils.arg('--limit', + metavar='', + default=None, + help='Maximum number of snapshots 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('--tenant', + type=str, + dest='tenant', + nargs='?', + metavar='', + help='Display information from single tenant (Admin only).') +@utils.arg('--metadata', + nargs='*', + 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.') +@utils.service_type('volumev3') +def do_snapshot_list(cs, args): + """Lists all snapshots.""" + all_tenants = (1 if args.tenant else + int(os.environ.get("ALL_TENANTS", args.all_tenants))) + + if args.display_name is not None: + args.name = args.display_name + + search_opts = { + 'all_tenants': all_tenants, + 'name': args.name, + 'status': args.status, + 'volume_id': args.volume_id, + 'project_id': args.tenant, + 'metadata': shell_utils.extract_metadata(args) + if args.metadata else None, + } + + snapshots = cs.volume_snapshots.list(search_opts=search_opts, + marker=args.marker, + limit=args.limit, + sort=args.sort) + shell_utils.translate_volume_snapshot_keys(snapshots) + sortby_index = None if args.sort else 0 + utils.print_list(snapshots, + ['ID', 'Volume ID', 'Status', 'Name', 'Size'], + sortby_index=sortby_index) \ No newline at end of file -- cgit v1.2.1