diff --git a/contrib/tempest/tempest/api/share/admin/test_shares_actions.py b/contrib/tempest/tempest/api/share/admin/test_shares_actions.py new file mode 100644 index 0000000000..8a7672cc83 --- /dev/null +++ b/contrib/tempest/tempest/api/share/admin/test_shares_actions.py @@ -0,0 +1,373 @@ +# Copyright 2014 Mirantis Inc. +# All Rights Reserved. +# +# 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. + +from tempest.api.share import base +from tempest.common.utils import data_utils +from tempest import config_share as config +from tempest import test + +CONF = config.CONF + + +class SharesActionsAdminTest(base.BaseSharesAdminTest): + """Covers share functionality, that doesn't related to share type.""" + + @classmethod + def setUpClass(cls): + super(SharesActionsAdminTest, cls).setUpClass() + + # create volume type for share filtering purposes + cls.vt_name = data_utils.rand_name("tempest-vt-name") + cls.extra_specs = {'storage_protocol': CONF.share.storage_protocol} + __, cls.vt = cls.create_volume_type( + name=cls.vt_name, + cleanup_in_class=True, + extra_specs=cls.extra_specs, + ) + + # create share + cls.share_name = data_utils.rand_name("tempest-share-name") + cls.share_desc = data_utils.rand_name("tempest-share-description") + cls.metadata = { + 'foo_key_share_1': 'foo_value_share_1', + 'bar_key_share_1': 'foo_value_share_1', + } + cls.share_size = 1 + __, cls.share = cls.create_share( + name=cls.share_name, + description=cls.share_desc, + size=cls.share_size, + metadata=cls.metadata, + volume_type_id=cls.vt['id'], + ) + + # create snapshot + cls.snap_name = data_utils.rand_name("tempest-snapshot-name") + cls.snap_desc = data_utils.rand_name("tempest-snapshot-description") + __, cls.snap = cls.create_snapshot_wait_for_active(cls.share["id"], + cls.snap_name, + cls.snap_desc) + + # create second share from snapshot for purposes of sorting and + # snapshot filtering + cls.share_name2 = data_utils.rand_name("tempest-share-name") + cls.share_desc2 = data_utils.rand_name("tempest-share-description") + cls.metadata2 = { + 'foo_key_share_2': 'foo_value_share_2', + 'bar_key_share_2': 'foo_value_share_2', + } + __, cls.share2 = cls.create_share( + name=cls.share_name2, + description=cls.share_desc2, + size=cls.share_size, + metadata=cls.metadata2, + snapshot_id=cls.snap['id'], + ) + + @test.attr(type=["gate", ]) + def test_get_share(self): + + # get share + resp, share = self.shares_client.get_share(self.share['id']) + + # verify response + self.assertIn(int(resp["status"]), test.HTTP_SUCCESS) + + # verify keys + expected_keys = ["status", "description", "links", "availability_zone", + "created_at", "export_location", "share_proto", + "name", "snapshot_id", "id", "size"] + actual_keys = share.keys() + [self.assertIn(key, actual_keys) for key in expected_keys] + + # verify values + msg = "Expected name: '%s', actual name: '%s'" % (self.share_name, + share["name"]) + self.assertEqual(self.share_name, str(share["name"]), msg) + + msg = "Expected description: '%s', "\ + "actual description: '%s'" % (self.share_desc, + share["description"]) + self.assertEqual(self.share_desc, str(share["description"]), msg) + + msg = "Expected size: '%s', actual size: '%s'" % (self.share_size, + share["size"]) + self.assertEqual(self.share_size, int(share["size"]), msg) + + @test.attr(type=["gate", ]) + def test_list_shares(self): + + # list shares + resp, shares = self.shares_client.list_shares() + + # verify response + self.assertIn(int(resp["status"]), test.HTTP_SUCCESS) + + # verify keys + keys = ["name", "id", "links"] + [self.assertIn(key, sh.keys()) for sh in shares for key in keys] + + # our share id in list and have no duplicates + for share_id in [self.share["id"], self.share2["id"]]: + gen = [sid["id"] for sid in shares if sid["id"] in share_id] + msg = "expected id lists %s times in share list" % (len(gen)) + self.assertEqual(1, len(gen), msg) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail(self): + + # list shares + resp, shares = self.shares_client.list_shares_with_detail() + + # verify response + self.assertIn(int(resp["status"]), test.HTTP_SUCCESS) + + # verify keys + keys = [ + "status", "description", "links", "availability_zone", + "created_at", "export_location", "share_proto", "host", + "name", "snapshot_id", "id", "size", "project_id", + ] + [self.assertIn(key, sh.keys()) for sh in shares for key in keys] + + # our shares in list and have no duplicates + for share_id in [self.share["id"], self.share2["id"]]: + gen = [sid["id"] for sid in shares if sid["id"] in share_id] + msg = "expected id lists %s times in share list" % (len(gen)) + self.assertEqual(1, len(gen), msg) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_metadata(self): + filters = {'metadata': self.metadata} + + # list shares + __, shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + for share in shares: + self.assertDictContainsSubset( + filters['metadata'], share['metadata']) + self.assertFalse(self.share2['id'] in [s['id'] for s in shares]) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_extra_specs(self): + filters = {"extra_specs": self.extra_specs} + + # list shares + __, shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + shares_ids = [s["id"] for s in shares] + self.assertTrue(self.share["id"] in shares_ids) + self.assertFalse(self.share2["id"] in shares_ids) + for share in shares: + __, vts = self.shares_client.list_volume_types() + # find its name or id, get id + vt_id = None + for vt in vts: + if share["volume_type"] in [vt["id"], vt["name"]]: + vt_id = vt["id"] + break + if vt_id is None: + raise ValueError( + "Share '%(s_id)s' listed with extra_specs filter has " + "nonexistent volume type '%(vt)s'." % { + "s_id": share["id"], "vt": share["volume_type"]} + ) + __, extra_specs = self.shares_client.list_volume_types_extra_specs( + vt_id) + self.assertDictContainsSubset(filters["extra_specs"], extra_specs) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_volume_type_id(self): + filters = {'volume_type_id': self.vt['id']} + + # list shares + __, shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + for share in shares: + __, vts = self.shares_client.list_volume_types() + # find its name or id, get id + vt_id = None + for vt in vts: + if share["volume_type"] in [vt["id"], vt["name"]]: + vt_id = vt["id"] + break + if vt_id is None: + raise ValueError( + "Share '%(s_id)s' listed with volume_type_id filter has " + "nonexistent volume type '%(vt)s'." % { + "s_id": share["id"], "vt": share["volume_type"]} + ) + self.assertEqual( + filters['volume_type_id'], vt_id) + self.assertFalse(self.share2['id'] in [s['id'] for s in shares]) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_host(self): + __, base_share = self.shares_client.get_share(self.share['id']) + filters = {'host': base_share['host']} + + # list shares + __, shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + for share in shares: + self.assertEqual(filters['host'], share['host']) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_share_network_id(self): + __, base_share = self.shares_client.get_share(self.share['id']) + filters = {'share_network_id': base_share['share_network_id']} + + # list shares + __, shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 1) + for share in shares: + self.assertEqual( + filters['share_network_id'], share['share_network_id']) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_snapshot_id(self): + filters = {'snapshot_id': self.snap['id']} + + # list shares + __, shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + for share in shares: + self.assertEqual(filters['snapshot_id'], share['snapshot_id']) + self.assertFalse(self.share['id'] in [s['id'] for s in shares]) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_with_asc_sorting(self): + filters = {'sort_key': 'created_at', 'sort_dir': 'asc'} + + # list shares + __, shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + sorted_list = [share['created_at'] for share in shares] + self.assertEqual(sorted_list, sorted(sorted_list)) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_existed_name(self): + # list shares by name, at least one share is expected + params = {"name": self.share_name} + resp, shares = self.shares_client.list_shares_with_detail(params) + self.assertIn(int(resp["status"]), test.HTTP_SUCCESS) + self.assertEqual(shares[0]["name"], self.share_name) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_fake_name(self): + # list shares by fake name, no shares are expected + params = {"name": data_utils.rand_name("fake-nonexistent-name")} + resp, shares = self.shares_client.list_shares_with_detail(params) + self.assertIn(int(resp["status"]), test.HTTP_SUCCESS) + self.assertEqual(0, len(shares)) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_active_status(self): + # list shares by active status, at least one share is expected + params = {"status": "available"} + resp, shares = self.shares_client.list_shares_with_detail(params) + self.assertIn(int(resp["status"]), test.HTTP_SUCCESS) + self.assertTrue(len(shares) > 0) + for share in shares: + self.assertEqual(share["status"], params["status"]) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_fake_status(self): + # list shares by fake status, no shares are expected + params = {"status": 'fake'} + resp, shares = self.shares_client.list_shares_with_detail(params) + self.assertIn(int(resp["status"]), test.HTTP_SUCCESS) + self.assertEqual(0, len(shares)) + + @test.attr(type=["gate", ]) + def test_get_snapshot(self): + + # get snapshot + resp, get = self.shares_client.get_snapshot(self.snap["id"]) + + # verify data + self.assertIn(int(resp["status"]), test.HTTP_SUCCESS) + + # verify keys + expected_keys = ["status", "links", "share_id", "name", + "export_location", "share_proto", "created_at", + "description", "id", "share_size"] + actual_keys = get.keys() + [self.assertIn(key, actual_keys) for key in expected_keys] + + # verify data + msg = "Expected name: '%s', actual name: '%s'" % (self.snap_name, + get["name"]) + self.assertEqual(self.snap_name, get["name"], msg) + + msg = "Expected description: '%s', "\ + "actual description: '%s'" % (self.snap_desc, get["description"]) + self.assertEqual(self.snap_desc, get["description"], msg) + + msg = "Expected share_id: '%s', "\ + "actual share_id: '%s'" % (self.share["id"], get["share_id"]) + self.assertEqual(self.share["id"], get["share_id"], msg) + + @test.attr(type=["gate", ]) + def test_list_snapshots(self): + + # list share snapshots + resp, snaps = self.shares_client.list_snapshots() + + # verify response + self.assertIn(int(resp["status"]), test.HTTP_SUCCESS) + + # verify keys + keys = ["id", "name", "links"] + [self.assertIn(key, sn.keys()) for sn in snaps for key in keys] + + # our share id in list and have no duplicates + gen = [sid["id"] for sid in snaps if sid["id"] in self.snap["id"]] + msg = "expected id lists %s times in share list" % (len(gen)) + self.assertEqual(1, len(gen), msg) + + @test.attr(type=["gate", ]) + def test_list_snapshots_with_detail(self): + + # list share snapshots + resp, snaps = self.shares_client.list_snapshots_with_detail() + + # verify response + self.assertIn(int(resp["status"]), test.HTTP_SUCCESS) + + # verify keys + keys = ["status", "links", "share_id", "name", + "export_location", "share_proto", "created_at", + "description", "id", "share_size"] + [self.assertIn(key, sn.keys()) for sn in snaps for key in keys] + + # our share id in list and have no duplicates + gen = [sid["id"] for sid in snaps if sid["id"] in self.snap["id"]] + msg = "expected id lists %s times in share list" % (len(gen)) + self.assertEqual(1, len(gen), msg) diff --git a/contrib/tempest/tempest/api/share/test_shares_actions.py b/contrib/tempest/tempest/api/share/test_shares_actions.py index fe23ae3897..5bfbb808f7 100644 --- a/contrib/tempest/tempest/api/share/test_shares_actions.py +++ b/contrib/tempest/tempest/api/share/test_shares_actions.py @@ -1,4 +1,4 @@ -# Copyright 2014 mirantis Inc. +# Copyright 2014 Mirantis Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -21,25 +21,49 @@ from tempest import test CONF = config.CONF -class SharesTest(base.BaseSharesTest): +class SharesActionsTest(base.BaseSharesTest): """Covers share functionality, that doesn't related to share type.""" @classmethod def setUpClass(cls): - super(SharesTest, cls).setUpClass() + super(SharesActionsTest, cls).setUpClass() + # create share cls.share_name = data_utils.rand_name("tempest-share-name") cls.share_desc = data_utils.rand_name("tempest-share-description") + cls.metadata = { + 'foo_key_share_1': 'foo_value_share_1', + 'bar_key_share_1': 'foo_value_share_1', + } cls.share_size = 1 - __, cls.share = cls.create_share(name=cls.share_name, - description=cls.share_desc, - size=cls.share_size) + __, cls.share = cls.create_share( + name=cls.share_name, + description=cls.share_desc, + size=cls.share_size, + metadata=cls.metadata, + ) + # create snapshot cls.snap_name = data_utils.rand_name("tempest-snapshot-name") cls.snap_desc = data_utils.rand_name("tempest-snapshot-description") - __, cls.snap = cls.create_snapshot_wait_for_active(cls.share["id"], - cls.snap_name, - cls.snap_desc) + __, cls.snap = cls.create_snapshot_wait_for_active( + cls.share["id"], cls.snap_name, cls.snap_desc) + + # create second share from snapshot for purposes of sorting and + # snapshot filtering + cls.share_name2 = data_utils.rand_name("tempest-share-name") + cls.share_desc2 = data_utils.rand_name("tempest-share-description") + cls.metadata2 = { + 'foo_key_share_2': 'foo_value_share_2', + 'bar_key_share_2': 'foo_value_share_2', + } + __, cls.share2 = cls.create_share( + name=cls.share_name2, + description=cls.share_desc2, + size=cls.share_size, + metadata=cls.metadata2, + snapshot_id=cls.snap['id'], + ) @test.attr(type=["gate", ]) def test_get_share(self): @@ -85,9 +109,10 @@ class SharesTest(base.BaseSharesTest): [self.assertIn(key, sh.keys()) for sh in shares for key in keys] # our share id in list and have no duplicates - gen = [sid["id"] for sid in shares if sid["id"] in self.share["id"]] - msg = "expected id lists %s times in share list" % (len(gen)) - self.assertEqual(len(gen), 1, msg) + for share_id in [self.share["id"], self.share2["id"]]: + gen = [sid["id"] for sid in shares if sid["id"] in share_id] + msg = "expected id lists %s times in share list" % (len(gen)) + self.assertEqual(1, len(gen), msg) @test.attr(type=["gate", ]) def test_list_shares_with_detail(self): @@ -106,10 +131,77 @@ class SharesTest(base.BaseSharesTest): ] [self.assertIn(key, sh.keys()) for sh in shares for key in keys] - # our share id in list and have no duplicates - gen = [sid["id"] for sid in shares if sid["id"] in self.share["id"]] - msg = "expected id lists %s times in share list" % (len(gen)) - self.assertEqual(len(gen), 1, msg) + # our shares in list and have no duplicates + for share_id in [self.share["id"], self.share2["id"]]: + gen = [sid["id"] for sid in shares if sid["id"] in share_id] + msg = "expected id lists %s times in share list" % (len(gen)) + self.assertEqual(1, len(gen), msg) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_metadata(self): + filters = {'metadata': self.metadata} + + # list shares + __, shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + for share in shares: + self.assertDictContainsSubset( + filters['metadata'], share['metadata']) + self.assertFalse(self.share2['id'] in [s['id'] for s in shares]) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_host(self): + __, base_share = self.shares_client.get_share(self.share['id']) + filters = {'host': base_share['host']} + + # list shares + __, shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + for share in shares: + self.assertEqual(filters['host'], share['host']) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_share_network_id(self): + __, base_share = self.shares_client.get_share(self.share['id']) + filters = {'share_network_id': base_share['share_network_id']} + + # list shares + __, shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 1) + for share in shares: + self.assertEqual( + filters['share_network_id'], share['share_network_id']) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_filter_by_snapshot_id(self): + filters = {'snapshot_id': self.snap['id']} + + # list shares + __, shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + for share in shares: + self.assertEqual(filters['snapshot_id'], share['snapshot_id']) + self.assertFalse(self.share['id'] in [s['id'] for s in shares]) + + @test.attr(type=["gate", ]) + def test_list_shares_with_detail_with_asc_sorting(self): + filters = {'sort_key': 'created_at', 'sort_dir': 'asc'} + + # list shares + __, shares = self.shares_client.list_shares_with_detail(params=filters) + + # verify response + self.assertTrue(len(shares) > 0) + sorted_list = [share['created_at'] for share in shares] + self.assertEqual(sorted_list, sorted(sorted_list)) @test.attr(type=["gate", ]) def test_list_shares_with_detail_filter_by_existed_name(self): diff --git a/contrib/tempest/tempest/services/share/json/shares_client.py b/contrib/tempest/tempest/services/share/json/shares_client.py index 95e4094e10..7cb08e7626 100644 --- a/contrib/tempest/tempest/services/share/json/shares_client.py +++ b/contrib/tempest/tempest/services/share/json/shares_client.py @@ -80,17 +80,16 @@ class SharesClient(rest_client.RestClient): def delete_share(self, share_id): return self.delete("shares/%s" % share_id) - def list_shares(self): - resp, body = self.get("shares") + def list_shares(self, detailed=False, params=None): + """Get list of shares w/o filters.""" + uri = 'shares/detail' if detailed else 'shares' + uri += '?%s' % urllib.urlencode(params) if params else '' + resp, body = self.get(uri) return resp, self._parse_resp(body) def list_shares_with_detail(self, params=None): - """List the details of all shares.""" - uri = 'shares/detail' - if params: - uri += '?%s' % urllib.urlencode(params) - resp, body = self.get(uri) - return resp, self._parse_resp(body) + """Get detailed list of shares w/o filters.""" + return self.list_shares(detailed=True, params=params) def get_share(self, share_id): resp, body = self.get("shares/%s" % share_id) diff --git a/manila/api/v1/shares.py b/manila/api/v1/shares.py index 169406c0b0..5ee1ac121f 100644 --- a/manila/api/v1/shares.py +++ b/manila/api/v1/shares.py @@ -15,6 +15,8 @@ """The shares api.""" +import ast + import six import webob from webob import exc @@ -110,15 +112,33 @@ class ShareController(wsgi.Controller): search_opts = {} search_opts.update(req.GET) - # NOTE(rushiagr): v2 API allows name instead of display_name + # Remove keys that are not related to share attrs + search_opts.pop('limit', None) + search_opts.pop('offset', None) + sort_key = search_opts.pop('sort_key', 'created_at') + sort_dir = search_opts.pop('sort_dir', 'desc') + + # Deserialize dicts + if 'metadata' in search_opts: + search_opts['metadata'] = ast.literal_eval(search_opts['metadata']) + if 'extra_specs' in search_opts: + search_opts['extra_specs'] = ast.literal_eval( + search_opts['extra_specs']) + + # NOTE(vponomaryov): Manila stores in DB key 'display_name', but + # allows to use both keys 'name' and 'display_name'. It is leftover + # from Cinder v1 and v2 APIs. if 'name' in search_opts: - search_opts['display_name'] = search_opts['name'] - del search_opts['name'] + search_opts['display_name'] = search_opts.pop('name') + if sort_key == 'name': + sort_key = 'display_name' common.remove_invalid_options( context, search_opts, self._get_share_search_options()) - shares = self.share_api.get_all(context, search_opts=search_opts) + shares = self.share_api.get_all( + context, search_opts=search_opts, sort_key=sort_key, + sort_dir=sort_dir) limited_list = common.limited(shares, req) @@ -132,7 +152,13 @@ class ShareController(wsgi.Controller): """Return share search options allowed by non-admin.""" # NOTE(vponomaryov): share_server_id depends on policy, allow search # by it for non-admins in case policy changed. - return ('display_name', 'status', 'share_server_id', ) + # Also allow search by extra_specs in case policy + # for it allows non-admin access. + return ( + 'display_name', 'status', 'share_server_id', 'volume_type_id', + 'snapshot_id', 'host', 'share_network_id', + 'metadata', 'extra_specs', 'sort_key', 'sort_dir', + ) @wsgi.serializers(xml=ShareTemplate) def update(self, req, id, body): diff --git a/manila/db/api.py b/manila/db/api.py index b5dcbba70f..48f3810df9 100644 --- a/manila/db/api.py +++ b/manila/db/api.py @@ -294,24 +294,37 @@ def share_get(context, share_id): return IMPL.share_get(context, share_id) -def share_get_all(context): +def share_get_all(context, filters=None, sort_key=None, sort_dir=None): """Get all shares.""" - return IMPL.share_get_all(context) + return IMPL.share_get_all( + context, filters=filters, sort_key=sort_key, sort_dir=sort_dir, + ) -def share_get_all_by_host(context, host): +def share_get_all_by_host(context, host, filters=None, sort_key=None, + sort_dir=None): """Returns all shares with given host.""" - return IMPL.share_get_all_by_host(context, host) + return IMPL.share_get_all_by_host( + context, host, filters=filters, sort_key=sort_key, sort_dir=sort_dir, + ) -def share_get_all_by_project(context, project_id): +def share_get_all_by_project(context, project_id, filters=None, sort_key=None, + sort_dir=None): """Returns all shares with given project ID.""" - return IMPL.share_get_all_by_project(context, project_id) + return IMPL.share_get_all_by_project( + context, project_id, filters=filters, sort_key=sort_key, + sort_dir=sort_dir, + ) -def share_get_all_by_share_server(context, share_server_id): - """Returns all shares with given share server.""" - return IMPL.share_get_all_by_share_server(context, share_server_id) +def share_get_all_by_share_server(context, share_server_id, filters=None, + sort_key=None, sort_dir=None): + """Returns all shares with given share server ID.""" + return IMPL.share_get_all_by_share_server( + context, share_server_id, filters=filters, sort_key=sort_key, + sort_dir=sort_dir, + ) def share_delete(context, share_id): diff --git a/manila/db/sqlalchemy/api.py b/manila/db/sqlalchemy/api.py index 4b8d6cb024..a00ed571f5 100644 --- a/manila/db/sqlalchemy/api.py +++ b/manila/db/sqlalchemy/api.py @@ -1,6 +1,7 @@ # Copyright (c) 2011 X.commerce, a business unit of eBay Inc. # Copyright 2010 United States Government as represented by the # Administrator of the National Aeronautics and Space Administration. +# Copyright (c) 2014 Mirantis, Inc. # All Rights Reserved. # # Licensed under the Apache License, Version 2.0 (the "License"); you may @@ -1164,28 +1165,109 @@ def share_get(context, share_id, session=None): return result -@require_admin_context -def share_get_all(context): - return _share_get_query(context).all() +@require_context +def _share_get_all_with_filters(context, project_id=None, share_server_id=None, + host=None, filters=None, + sort_key=None, sort_dir=None): + """Returns sorted list of shares that satisfies filters. - -@require_admin_context -def share_get_all_by_host(context, host): + :param context: context to query under + :param project_id: project id that owns shares + :param share_server_id: share server that hosts shares + :param host: host name where shares [and share servers] are located + :param filters: dict of filters to specify share selection + :param sort_key: key of models.Share to be used for sorting + :param sort_dir: desired direction of sorting, can be 'asc' and 'desc' + :returns: list -- models.Share + :raises: exception.InvalidInput + """ + if not sort_key: + sort_key = 'created_at' + if not sort_dir: + sort_dir = 'desc' query = _share_get_query(context) - return query.filter_by(host=host).all() + if project_id: + query = query.filter_by(project_id=project_id) + if share_server_id: + query = query.filter_by(share_server_id=share_server_id) + if host: + query = query.filter_by(host=host) + + # Apply filters + if not filters: + filters = {} + if 'metadata' in filters: + for k, v in filters['metadata'].items(): + query = query.filter( + or_(models.Share.share_metadata.any( # pylint: disable=E1101 + key=k, value=v))) + if 'extra_specs' in filters: + query = query.join( + models.VolumeTypeExtraSpecs, + models.VolumeTypeExtraSpecs.volume_type_id == + models.Share.volume_type_id) + for k, v in filters['extra_specs'].items(): + query = query.filter(or_(models.VolumeTypeExtraSpecs.key == k, + models.VolumeTypeExtraSpecs.value == v)) + + # Apply sorting + try: + attr = getattr(models.Share, sort_key) + except AttributeError: + msg = _("Wrong sorting key provided - '%s'.") % sort_key + raise exception.InvalidInput(reason=msg) + if sort_dir.lower() == 'desc': + query = query.order_by(attr.desc()) + elif sort_dir.lower() == 'asc': + query = query.order_by(attr.asc()) + else: + msg = _("Wrong sorting data provided: sort key is '%(sort_key)s' " + "and sort direction is '%(sort_dir)s'.") % { + "sort_key": sort_key, "sort_dir": sort_dir} + raise exception.InvalidInput(reason=msg) + + # Returns list of shares that satisfy filters. + query = query.all() + return query + + +@require_admin_context +def share_get_all(context, filters=None, sort_key=None, sort_dir=None): + query = _share_get_all_with_filters( + context, filters=filters, sort_key=sort_key, sort_dir=sort_dir) + return query + + +@require_admin_context +def share_get_all_by_host(context, host, filters=None, + sort_key=None, sort_dir=None): + query = _share_get_all_with_filters( + context, host=host, filters=filters, + sort_key=sort_key, sort_dir=sort_dir, + ) + return query @require_context -def share_get_all_by_project(context, project_id): +def share_get_all_by_project(context, project_id, filters=None, + sort_key=None, sort_dir=None): """Returns list of shares with given project ID.""" - return _share_get_query(context).filter_by(project_id=project_id).all() + query = _share_get_all_with_filters( + context, project_id=project_id, filters=filters, + sort_key=sort_key, sort_dir=sort_dir, + ) + return query @require_context -def share_get_all_by_share_server(context, share_server_id): +def share_get_all_by_share_server(context, share_server_id, filters=None, + sort_key=None, sort_dir=None): """Returns list of shares with given share server.""" - return _share_get_query(context).filter_by( - share_server_id=share_server_id).all() + query = _share_get_all_with_filters( + context, share_server_id=share_server_id, filters=filters, + sort_key=sort_key, sort_dir=sort_dir, + ) + return query @require_context diff --git a/manila/share/api.py b/manila/share/api.py index 92c5c8113a..0f5f50a486 100644 --- a/manila/share/api.py +++ b/manila/share/api.py @@ -21,6 +21,7 @@ Handles all requests relating to shares. from oslo.config import cfg import six +from manila.api import extensions from manila.db import base from manila import exception from manila.openstack.common import excutils @@ -337,24 +338,60 @@ class API(base.Base): policy.check_policy(context, 'share', 'get', rv) return rv - def get_all(self, context, search_opts=None): + def get_all(self, context, search_opts=None, sort_key='created_at', + sort_dir='desc'): policy.check_policy(context, 'share', 'get_all') + if search_opts is None: search_opts = {} + + LOG.debug("Searching for shares by: %s", six.text_type(search_opts)) + + # Prepare filters + filters = {} + if 'metadata' in search_opts: + filters['metadata'] = search_opts.pop('metadata') + if not isinstance(filters['metadata'], dict): + msg = _("Wrong metadata filter provided: " + "%s.") % six.text_type(filters['metadata']) + raise exception.InvalidInput(reason=msg) + if 'extra_specs' in search_opts: + # Verify policy for extra-specs access + extensions.extension_authorizer( + 'share', 'types_extra_specs')(context) + filters['extra_specs'] = search_opts.pop('extra_specs') + if not isinstance(filters['extra_specs'], dict): + msg = _("Wrong extra specs filter provided: " + "%s.") % six.text_type(filters['extra_specs']) + raise exception.InvalidInput(reason=msg) + if not (isinstance(sort_key, six.string_types) and sort_key): + msg = _("Wrong sort_key filter provided: " + "'%s'.") % six.text_type(sort_key) + raise exception.InvalidInput(reason=msg) + if not (isinstance(sort_dir, six.string_types) and sort_dir): + msg = _("Wrong sort_dir filter provided: " + "'%s'.") % six.text_type(sort_dir) + raise exception.InvalidInput(reason=msg) + + # Get filtered list of shares if 'share_server_id' in search_opts: # NOTE(vponomaryov): this is project_id independent policy.check_policy(context, 'share', 'list_by_share_server_id') shares = self.db.share_get_all_by_share_server( - context, search_opts.pop('share_server_id')) + context, search_opts.pop('share_server_id'), filters=filters, + sort_key=sort_key, sort_dir=sort_dir) elif (context.is_admin and 'all_tenants' in search_opts): - shares = self.db.share_get_all(context) + shares = self.db.share_get_all( + context, filters=filters, sort_key=sort_key, sort_dir=sort_dir) else: - shares = self.db.share_get_all_by_project(context, - context.project_id) + shares = self.db.share_get_all_by_project( + context, project_id=context.project_id, filters=filters, + sort_key=sort_key, sort_dir=sort_dir) + # NOTE(vponomaryov): we do not need 'all_tenants' opt anymore search_opts.pop('all_tenants', None) + if search_opts: - LOG.debug("Searching for shares by: %s", str(search_opts)) results = [] for s in shares: # values in search_opts can be only strings diff --git a/manila/tests/api/contrib/stubs.py b/manila/tests/api/contrib/stubs.py index d8993db601..e280f00691 100644 --- a/manila/tests/api/contrib/stubs.py +++ b/manila/tests/api/contrib/stubs.py @@ -86,7 +86,8 @@ def stub_snapshot_update(self, context, *args, **param): return share -def stub_share_get_all_by_project(self, context, search_opts=None): +def stub_share_get_all_by_project(self, context, sort_key=None, sort_dir=None, + search_opts={}): return [stub_share_get(self, context, '1')] diff --git a/manila/tests/api/v1/test_shares.py b/manila/tests/api/v1/test_shares.py index 12426b4b26..79141d9093 100644 --- a/manila/tests/api/v1/test_shares.py +++ b/manila/tests/api/v1/test_shares.py @@ -322,50 +322,67 @@ class ShareApiTest(test.TestCase): req, 1) - def test_share_list_summary_with_search_opts_by_non_admin(self): + def _share_list_summary_with_search_opts(self, use_admin_context): + search_opts = { + 'name': 'fake_name', + 'status': 'available', + 'share_server_id': 'fake_share_server_id', + 'volume_type_id': 'fake_volume_type_id', + 'snapshot_id': 'fake_snapshot_id', + 'host': 'fake_host', + 'share_network_id': 'fake_share_network_id', + 'metadata': '%7B%27k1%27%3A+%27v1%27%7D', # serialized k1=v1 + 'extra_specs': '%7B%27k2%27%3A+%27v2%27%7D', # serialized k2=v2 + 'sort_key': 'fake_sort_key', + 'sort_dir': 'fake_sort_dir', + 'limit': '1', + 'offset': '1', + } # fake_key should be filtered for non-admin - fake_key = 'fake_value' - name = 'fake_name' - status = 'available' - share_server_id = 'fake_share_server_id' - req = fakes.HTTPRequest.blank( - '/shares?fake_key=%s&name=%s&share_server_id=%s&' - 'status=%s' % (fake_key, name, share_server_id, status), - use_admin_context=False, - ) - self.stubs.Set(share_api.API, 'get_all', mock.Mock(return_value=[])) - self.controller.index(req) + url = '/shares?fake_key=fake_value' + for k, v in search_opts.items(): + url = url + '&' + k + '=' + v + req = fakes.HTTPRequest.blank(url, use_admin_context=use_admin_context) + + shares = [ + {'id': 'id1', 'display_name': 'n1'}, + {'id': 'id2', 'display_name': 'n2'}, + {'id': 'id3', 'display_name': 'n3'}, + ] + self.stubs.Set(share_api.API, 'get_all', + mock.Mock(return_value=shares)) + + result = self.controller.index(req) + + search_opts_expected = { + 'display_name': search_opts['name'], + 'status': search_opts['status'], + 'share_server_id': search_opts['share_server_id'], + 'volume_type_id': search_opts['volume_type_id'], + 'snapshot_id': search_opts['snapshot_id'], + 'host': search_opts['host'], + 'share_network_id': search_opts['share_network_id'], + 'metadata': {'k1': 'v1'}, + 'extra_specs': {'k2': 'v2'}, + } + if use_admin_context: + search_opts_expected.update({'fake_key': 'fake_value'}) share_api.API.get_all.assert_called_once_with( req.environ['manila.context'], - search_opts={ - 'display_name': name, - 'share_server_id': share_server_id, - 'status': status, - }, + sort_key=search_opts['sort_key'], + sort_dir=search_opts['sort_dir'], + search_opts=search_opts_expected, ) + self.assertEqual(1, len(result['shares'])) + self.assertEqual(shares[1]['id'], result['shares'][0]['id']) + self.assertEqual( + shares[1]['display_name'], result['shares'][0]['name']) + + def test_share_list_summary_with_search_opts_by_non_admin(self): + self._share_list_summary_with_search_opts(use_admin_context=False) def test_share_list_summary_with_search_opts_by_admin(self): - # none of search_opts should be filtered for admin - fake_key = 'fake_value' - name = 'fake_name' - status = 'available' - share_server_id = 'fake_share_server_id' - req = fakes.HTTPRequest.blank( - '/shares?fake_key=%s&name=%s&share_server_id=%s&' - 'status=%s' % (fake_key, name, share_server_id, status), - use_admin_context=True, - ) - self.stubs.Set(share_api.API, 'get_all', mock.Mock(return_value=[])) - self.controller.index(req) - share_api.API.get_all.assert_called_once_with( - req.environ['manila.context'], - search_opts={ - 'display_name': name, - 'fake_key': fake_key, - 'share_server_id': share_server_id, - 'status': status, - }, - ) + self._share_list_summary_with_search_opts(use_admin_context=True) def test_share_list_summary(self): self.stubs.Set(share_api.API, 'get_all', @@ -392,50 +409,89 @@ class ShareApiTest(test.TestCase): } self.assertEqual(res_dict, expected) - def test_share_list_detail_with_search_opts_by_non_admin(self): + def _share_list_detail_with_search_opts(self, use_admin_context): + search_opts = { + 'name': 'fake_name', + 'status': 'available', + 'share_server_id': 'fake_share_server_id', + 'volume_type_id': 'fake_volume_type_id', + 'snapshot_id': 'fake_snapshot_id', + 'host': 'fake_host', + 'share_network_id': 'fake_share_network_id', + 'metadata': '%7B%27k1%27%3A+%27v1%27%7D', # serialized k1=v1 + 'extra_specs': '%7B%27k2%27%3A+%27v2%27%7D', # serialized k2=v2 + 'sort_key': 'fake_sort_key', + 'sort_dir': 'fake_sort_dir', + 'limit': '1', + 'offset': '1', + } # fake_key should be filtered for non-admin - fake_key = 'fake_value' - name = 'fake_name' - status = 'available' - share_server_id = 'fake_share_server_id' - req = fakes.HTTPRequest.blank( - '/shares?fake_key=%s&name=%s&share_server_id=%s&' - 'status=%s' % (fake_key, name, share_server_id, status), - use_admin_context=False, - ) - self.stubs.Set(share_api.API, 'get_all', mock.Mock(return_value=[])) - self.controller.detail(req) + url = '/shares/detail?fake_key=fake_value' + for k, v in search_opts.items(): + url = url + '&' + k + '=' + v + req = fakes.HTTPRequest.blank(url, use_admin_context=use_admin_context) + + shares = [ + {'id': 'id1', 'display_name': 'n1'}, + { + 'id': 'id2', + 'display_name': 'n2', + 'status': 'available', + 'snapshot_id': 'fake_snapshot_id', + 'volume_type_id': 'fake_volume_type_id', + 'snapshot_id': 'fake_snapshot_id', + 'host': 'fake_host', + 'share_network_id': 'fake_share_network_id', + }, + {'id': 'id3', 'display_name': 'n3'}, + ] + self.stubs.Set(share_api.API, 'get_all', + mock.Mock(return_value=shares)) + + result = self.controller.detail(req) + + search_opts_expected = { + 'display_name': search_opts['name'], + 'status': search_opts['status'], + 'share_server_id': search_opts['share_server_id'], + 'volume_type_id': search_opts['volume_type_id'], + 'snapshot_id': search_opts['snapshot_id'], + 'host': search_opts['host'], + 'share_network_id': search_opts['share_network_id'], + 'metadata': {'k1': 'v1'}, + 'extra_specs': {'k2': 'v2'}, + } + if use_admin_context: + search_opts_expected.update({'fake_key': 'fake_value'}) share_api.API.get_all.assert_called_once_with( req.environ['manila.context'], - search_opts={ - 'display_name': name, - 'share_server_id': share_server_id, - 'status': status, - }, + sort_key=search_opts['sort_key'], + sort_dir=search_opts['sort_dir'], + search_opts=search_opts_expected, ) + self.assertEqual(1, len(result['shares'])) + self.assertEqual(shares[1]['id'], result['shares'][0]['id']) + self.assertEqual( + shares[1]['display_name'], result['shares'][0]['name']) + self.assertEqual( + shares[1]['snapshot_id'], result['shares'][0]['snapshot_id']) + self.assertEqual( + shares[1]['status'], result['shares'][0]['status']) + self.assertEqual( + shares[1]['volume_type_id'], result['shares'][0]['volume_type']) + self.assertEqual( + shares[1]['snapshot_id'], result['shares'][0]['snapshot_id']) + self.assertEqual( + shares[1]['host'], result['shares'][0]['host']) + self.assertEqual( + shares[1]['share_network_id'], + result['shares'][0]['share_network_id']) + + def test_share_list_detail_with_search_opts_by_non_admin(self): + self._share_list_detail_with_search_opts(use_admin_context=False) def test_share_list_detail_with_search_opts_by_admin(self): - # none of search_opts should be filtered for admin - fake_key = 'fake_value' - name = 'fake_name' - status = 'available' - share_server_id = 'fake_share_server_id' - req = fakes.HTTPRequest.blank( - '/shares?fake_key=%s&name=%s&share_server_id=%s&' - 'status=%s' % (fake_key, name, share_server_id, status), - use_admin_context=True, - ) - self.stubs.Set(share_api.API, 'get_all', mock.Mock(return_value=[])) - self.controller.detail(req) - share_api.API.get_all.assert_called_once_with( - req.environ['manila.context'], - search_opts={ - 'display_name': name, - 'fake_key': fake_key, - 'share_server_id': share_server_id, - 'status': status, - }, - ) + self._share_list_detail_with_search_opts(use_admin_context=True) def test_share_list_detail(self): self.stubs.Set(share_api.API, 'get_all', diff --git a/manila/tests/share/test_api.py b/manila/tests/share/test_api.py index 4980bd5db2..c0e21665d6 100644 --- a/manila/tests/share/test_api.py +++ b/manila/tests/share/test_api.py @@ -158,7 +158,9 @@ class ShareAPITestCase(test.TestCase): share_api.policy.check_policy.assert_called_once_with( ctx, 'share', 'get_all') db_driver.share_get_all_by_project.assert_called_once_with( - ctx, 'fake_pid_1') + ctx, sort_dir='desc', sort_key='created_at', + project_id='fake_pid_1', filters={}, + ) self.assertEqual(shares, _FAKE_LIST_OF_ALL_SHARES[0]) def test_get_all_admin_filter_by_all_tenants(self): @@ -168,7 +170,8 @@ class ShareAPITestCase(test.TestCase): shares = self.api.get_all(ctx, {'all_tenants': 1}) share_api.policy.check_policy.assert_called_once_with( ctx, 'share', 'get_all') - db_driver.share_get_all.assert_called_once_with(ctx) + db_driver.share_get_all.assert_called_once_with( + ctx, sort_dir='desc', sort_key='created_at', filters={}) self.assertEqual(shares, _FAKE_LIST_OF_ALL_SHARES) def test_get_all_non_admin_filter_by_share_server(self): @@ -206,7 +209,9 @@ class ShareAPITestCase(test.TestCase): mock.call(ctx, 'share', 'list_by_share_server_id'), ]) db_driver.share_get_all_by_share_server.assert_called_once_with( - ctx, 'fake_server_3') + ctx, 'fake_server_3', sort_dir='desc', sort_key='created_at', + filters={}, + ) db_driver.share_get_all_by_project.assert_has_calls([]) db_driver.share_get_all.assert_has_calls([]) self.assertEqual(shares, _FAKE_LIST_OF_ALL_SHARES[2:]) @@ -220,7 +225,9 @@ class ShareAPITestCase(test.TestCase): mock.call(ctx, 'share', 'get_all'), ]) db_driver.share_get_all_by_project.assert_called_once_with( - ctx, 'fake_pid_2') + ctx, sort_dir='desc', sort_key='created_at', + project_id='fake_pid_2', filters={}, + ) self.assertEqual(shares, _FAKE_LIST_OF_ALL_SHARES[1::2]) def test_get_all_admin_filter_by_name_and_all_tenants(self): @@ -231,7 +238,8 @@ class ShareAPITestCase(test.TestCase): share_api.policy.check_policy.assert_has_calls([ mock.call(ctx, 'share', 'get_all'), ]) - db_driver.share_get_all.assert_called_once_with(ctx) + db_driver.share_get_all.assert_called_once_with( + ctx, sort_dir='desc', sort_key='created_at', filters={}) self.assertEqual(shares, _FAKE_LIST_OF_ALL_SHARES[::2]) def test_get_all_admin_filter_by_status(self): @@ -243,7 +251,9 @@ class ShareAPITestCase(test.TestCase): mock.call(ctx, 'share', 'get_all'), ]) db_driver.share_get_all_by_project.assert_called_once_with( - ctx, 'fake_pid_2') + ctx, sort_dir='desc', sort_key='created_at', + project_id='fake_pid_2', filters={} + ) self.assertEqual(shares, _FAKE_LIST_OF_ALL_SHARES[2::4]) def test_get_all_admin_filter_by_status_and_all_tenants(self): @@ -254,7 +264,8 @@ class ShareAPITestCase(test.TestCase): share_api.policy.check_policy.assert_has_calls([ mock.call(ctx, 'share', 'get_all'), ]) - db_driver.share_get_all.assert_called_once_with(ctx) + db_driver.share_get_all.assert_called_once_with( + ctx, sort_dir='desc', sort_key='created_at', filters={}) self.assertEqual(shares, _FAKE_LIST_OF_ALL_SHARES[1::2]) def test_get_all_non_admin_filter_by_all_tenants(self): @@ -267,7 +278,9 @@ class ShareAPITestCase(test.TestCase): mock.call(ctx, 'share', 'get_all'), ]) db_driver.share_get_all_by_project.assert_called_once_with( - ctx, 'fake_pid_2') + ctx, sort_dir='desc', sort_key='created_at', + project_id='fake_pid_2', filters={}, + ) self.assertEqual(shares, _FAKE_LIST_OF_ALL_SHARES[1:]) def test_get_all_non_admin_with_name_and_status_filters(self): @@ -279,7 +292,9 @@ class ShareAPITestCase(test.TestCase): mock.call(ctx, 'share', 'get_all'), ]) db_driver.share_get_all_by_project.assert_called_once_with( - ctx, 'fake_pid_2') + ctx, sort_dir='desc', sort_key='created_at', + project_id='fake_pid_2', filters={}, + ) # two items expected, one filtered self.assertEqual(shares, _FAKE_LIST_OF_ALL_SHARES[1::2]) @@ -292,10 +307,86 @@ class ShareAPITestCase(test.TestCase): mock.call(ctx, 'share', 'get_all'), ]) db_driver.share_get_all_by_project.assert_has_calls([ - mock.call(ctx, 'fake_pid_2'), - mock.call(ctx, 'fake_pid_2'), + mock.call(ctx, sort_dir='desc', sort_key='created_at', + project_id='fake_pid_2', filters={}), + mock.call(ctx, sort_dir='desc', sort_key='created_at', + project_id='fake_pid_2', filters={}), ]) + def test_get_all_with_sorting_valid(self): + self.stubs.Set(db_driver, 'share_get_all_by_project', + mock.Mock(return_value=_FAKE_LIST_OF_ALL_SHARES[0])) + ctx = context.RequestContext('fake_uid', 'fake_pid_1', is_admin=False) + shares = self.api.get_all(ctx, sort_key='status', sort_dir='asc') + share_api.policy.check_policy.assert_called_once_with( + ctx, 'share', 'get_all') + db_driver.share_get_all_by_project.assert_called_once_with( + ctx, sort_dir='asc', sort_key='status', + project_id='fake_pid_1', filters={}, + ) + self.assertEqual(_FAKE_LIST_OF_ALL_SHARES[0], shares) + + def test_get_all_sort_key_invalid(self): + self.stubs.Set(db_driver, 'share_get_all_by_project', + mock.Mock(return_value=_FAKE_LIST_OF_ALL_SHARES[0])) + ctx = context.RequestContext('fake_uid', 'fake_pid_1', is_admin=False) + self.assertRaises( + exception.InvalidInput, + self.api.get_all, + ctx, + sort_key=1, + ) + share_api.policy.check_policy.assert_called_once_with( + ctx, 'share', 'get_all') + + def test_get_all_sort_dir_invalid(self): + self.stubs.Set(db_driver, 'share_get_all_by_project', + mock.Mock(return_value=_FAKE_LIST_OF_ALL_SHARES[0])) + ctx = context.RequestContext('fake_uid', 'fake_pid_1', is_admin=False) + self.assertRaises( + exception.InvalidInput, + self.api.get_all, + ctx, + sort_dir=1, + ) + share_api.policy.check_policy.assert_called_once_with( + ctx, 'share', 'get_all') + + def _get_all_filter_metadata_or_extra_specs_valid(self, key): + self.stubs.Set(db_driver, 'share_get_all_by_project', + mock.Mock(return_value=_FAKE_LIST_OF_ALL_SHARES[0])) + ctx = context.RequestContext('fake_uid', 'fake_pid_1', is_admin=False) + search_opts = {key: {'foo1': 'bar1', 'foo2': 'bar2'}} + shares = self.api.get_all(ctx, search_opts=search_opts.copy()) + share_api.policy.check_policy.assert_called_once_with( + ctx, 'share', 'get_all') + db_driver.share_get_all_by_project.assert_called_once_with( + ctx, sort_dir='desc', sort_key='created_at', + project_id='fake_pid_1', filters=search_opts) + self.assertEqual(_FAKE_LIST_OF_ALL_SHARES[0], shares) + + def test_get_all_filter_by_metadata(self): + self._get_all_filter_metadata_or_extra_specs_valid(key='metadata') + + def test_get_all_filter_by_extra_specs(self): + self._get_all_filter_metadata_or_extra_specs_valid(key='extra_specs') + + def _get_all_filter_metadata_or_extra_specs_invalid(self, key): + self.stubs.Set(db_driver, 'share_get_all_by_project', + mock.Mock(return_value=_FAKE_LIST_OF_ALL_SHARES[0])) + ctx = context.RequestContext('fake_uid', 'fake_pid_1', is_admin=False) + search_opts = {key: "{'foo': 'bar'}"} + self.assertRaises(exception.InvalidInput, self.api.get_all, ctx, + search_opts=search_opts) + share_api.policy.check_policy.assert_called_once_with( + ctx, 'share', 'get_all') + + def test_get_all_filter_by_invalid_metadata(self): + self._get_all_filter_metadata_or_extra_specs_invalid(key='metadata') + + def test_get_all_filter_by_invalid_extra_specs(self): + self._get_all_filter_metadata_or_extra_specs_invalid(key='extra_specs') + def test_create(self): date = datetime.datetime(1, 1, 1, 1, 1, 1) self.mock_utcnow.return_value = date