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
This commit is contained in:
Vivek Agrawal
2017-01-06 13:29:40 -08:00
parent 93d09ebc06
commit 056cf5c059
3 changed files with 109 additions and 0 deletions

View File

@@ -26,6 +26,8 @@ from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v3 import fakes from cinderclient.tests.unit.v3 import fakes
from cinderclient.tests.unit.fixture_data import keystone_client from cinderclient.tests.unit.fixture_data import keystone_client
from six.moves.urllib import parse
@ddt.ddt @ddt.ddt
@mock.patch.object(client, 'Client', fakes.FakeClient) @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.run_command('--os-volume-api-version 3.3 message-list')
self.assert_called('GET', '/messages') 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',), @ddt.data(('resource_type',), ('event_id',), ('resource_uuid',),
('level', 'message_level'), ('request_id',)) ('level', 'message_level'), ('request_id',))
def test_list_messages_with_filters(self, filter): def test_list_messages_with_filters(self, filter):

View File

@@ -20,6 +20,8 @@ from cinderclient.tests.unit import utils
from cinderclient.tests.unit.v3 import fakes from cinderclient.tests.unit.v3 import fakes
from cinderclient.v3 import volumes from cinderclient.v3 import volumes
from six.moves.urllib import parse
cs = fakes.FakeClient() cs = fakes.FakeClient()
@@ -84,3 +86,10 @@ class VolumesTest(utils.TestCase):
cs = fakes.FakeClient(api_versions.APIVersion('3.8')) cs = fakes.FakeClient(api_versions.APIVersion('3.8'))
cs.volume_snapshots.list_manageable('host1', detailed=True) cs.volume_snapshots.list_manageable('host1', detailed=True)
cs.assert_called('GET', '/manageable_snapshots/detail?host=host1') 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)

View File

@@ -1210,3 +1210,94 @@ def do_message_delete(cs, args):
if failure_count == len(args.message): if failure_count == len(args.message):
raise exceptions.CommandError("Unable to delete any of the specified " raise exceptions.CommandError("Unable to delete any of the specified "
"messages.") "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='<name>',
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='<status>',
default=None,
help='Filters results by a status. Default=None.')
@utils.arg('--volume-id',
metavar='<volume-id>',
default=None,
help='Filters results by a volume ID. Default=None.')
@utils.arg('--volume_id',
help=argparse.SUPPRESS)
@utils.arg('--marker',
metavar='<marker>',
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='<limit>',
default=None,
help='Maximum number of snapshots to return. Default=None.')
@utils.arg('--sort',
metavar='<key>[:<direction>]',
default=None,
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('--tenant',
type=str,
dest='tenant',
nargs='?',
metavar='<tenant>',
help='Display information from single tenant (Admin only).')
@utils.arg('--metadata',
nargs='*',
metavar='<key=value>',
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)