From 21699451f18a6badc16945cb2c30681f7c2f74fa Mon Sep 17 00:00:00 2001 From: Valeriy Ponomaryov Date: Wed, 15 Mar 2017 19:06:23 +0300 Subject: [PATCH] [Share groups] Add scheduler filter ConsistentSnapshotFilter That will be used for scheduling share groups based on their possibility to create consistent snapshots. Also apply following tempest plugin changes: - Add new 'capability_sg_consistent_snapshot_support' tempest config option, that will be used for creation of new share group types and used to prove that scheduling works as expected. - Fix some share group test attributes from 'only API involved' to 'API and Backend are involved', because it is so indeed. Change-Id: I05553c308ae40c4ddc2c6469ff1c1a3da36a87da Partially-Implements BP manila-share-groups --- contrib/ci/post_test_hook.sh | 1 + manila/scheduler/drivers/filter.py | 10 ++- .../filters/share_group_filters/__init__.py | 0 .../consistent_snapshot.py | 31 ++++++++ manila/scheduler/host_manager.py | 20 ++++- manila/scheduler/utils.py | 9 ++- manila/share/driver.py | 10 ++- manila/share/manager.py | 16 ++-- manila/tests/scheduler/test_host_manager.py | 11 +++ .../share/drivers/dell_emc/test_driver.py | 2 +- manila/tests/share/drivers/dummy.py | 4 +- .../glusterfs/test_glusterfs_native.py | 4 +- .../share/drivers/hpe/test_hpe_3par_driver.py | 12 ++- .../share/drivers/huawei/test_huawei_nas.py | 2 +- .../share/drivers/zfsonlinux/test_driver.py | 2 +- manila/tests/share/test_driver.py | 11 +-- manila/tests/share/test_manager.py | 75 ++++++++++++++----- manila_tempest_tests/config.py | 5 ++ .../tests/api/admin/test_share_group_types.py | 11 ++- .../tests/api/admin/test_share_groups.py | 24 +++++- .../api/admin/test_share_groups_negative.py | 56 ++++++++++++++ manila_tempest_tests/tests/api/base.py | 5 ++ setup.cfg | 4 + 23 files changed, 271 insertions(+), 54 deletions(-) create mode 100644 manila/scheduler/filters/share_group_filters/__init__.py create mode 100644 manila/scheduler/filters/share_group_filters/consistent_snapshot.py create mode 100644 manila_tempest_tests/tests/api/admin/test_share_groups_negative.py diff --git a/contrib/ci/post_test_hook.sh b/contrib/ci/post_test_hook.sh index 4bb094f652..043ea5822c 100755 --- a/contrib/ci/post_test_hook.sh +++ b/contrib/ci/post_test_hook.sh @@ -232,6 +232,7 @@ elif [[ "$DRIVER" == "dummy" ]]; then iniset $TEMPEST_CONFIG share build_timeout 180 iniset $TEMPEST_CONFIG share share_creation_retry_number 0 iniset $TEMPEST_CONFIG share capability_storage_protocol 'NFS_CIFS' + iniset $TEMPEST_CONFIG share capability_sg_consistent_snapshot_support 'pool' iniset $TEMPEST_CONFIG share enable_protocols 'nfs,cifs' iniset $TEMPEST_CONFIG share suppress_errors_in_cleanup False iniset $TEMPEST_CONFIG share multitenancy_enabled True diff --git a/manila/scheduler/drivers/filter.py b/manila/scheduler/drivers/filter.py index 4094305a09..70fe6d28e7 100644 --- a/manila/scheduler/drivers/filter.py +++ b/manila/scheduler/drivers/filter.py @@ -15,9 +15,9 @@ # under the License. """ -The FilterScheduler is for creating shares. -You can customize this scheduler by specifying your own share Filters and -Weighing Functions. +The FilterScheduler is for scheduling of share and share group creation. +You can customize this scheduler by specifying your own share/share group +filters and weighing functions. """ from oslo_config import cfg @@ -392,7 +392,9 @@ class FilterScheduler(base.Scheduler): } hosts = self.host_manager.get_filtered_hosts( - all_hosts, filter_properties) + all_hosts, + filter_properties, + CONF.scheduler_default_share_group_filters) if not hosts: return [] diff --git a/manila/scheduler/filters/share_group_filters/__init__.py b/manila/scheduler/filters/share_group_filters/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/manila/scheduler/filters/share_group_filters/consistent_snapshot.py b/manila/scheduler/filters/share_group_filters/consistent_snapshot.py new file mode 100644 index 0000000000..9c9506a8f9 --- /dev/null +++ b/manila/scheduler/filters/share_group_filters/consistent_snapshot.py @@ -0,0 +1,31 @@ +# 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 manila.scheduler.filters import base_host + + +class ConsistentSnapshotFilter(base_host.BaseHostFilter): + """Filters hosts based on possibility to create consistent SG snapshots.""" + + def host_passes(self, host_state, filter_properties): + """Return True if host will work with desired share group.""" + + cs_group_spec = filter_properties['share_group_type'].get( + 'group_specs', {}).get('consistent_snapshot_support') + + # NOTE(vpoomaryov): if 'consistent_snapshot_support' group spec + # is not set, then we assume that share group owner do not care about + # it, which means any host should pass this filter. + if cs_group_spec is None: + return True + return cs_group_spec == host_state.sg_consistent_snapshot_support diff --git a/manila/scheduler/host_manager.py b/manila/scheduler/host_manager.py index d3ebd1f005..0c5e8cdd6a 100644 --- a/manila/scheduler/host_manager.py +++ b/manila/scheduler/host_manager.py @@ -56,7 +56,14 @@ host_manager_opts = [ 'CapacityWeigher', 'GoodnessWeigher', ], - help='Which weigher class names to use for weighing hosts.') + help='Which weigher class names to use for weighing hosts.'), + cfg.ListOpt( + 'scheduler_default_share_group_filters', + default=[ + 'ConsistentSnapshotFilter', + ], + help='Which filter class names to use for filtering hosts ' + 'creating share group when not specified in the request.'), ] CONF = cfg.CONF @@ -140,6 +147,9 @@ class HostState(object): self.pools = {} self.updated = None + # Share Group capabilities + self.sg_consistent_snapshot_support = None + def update_capabilities(self, capabilities=None, service=None): # Read-only capability dicts @@ -317,6 +327,10 @@ class HostState(object): if not pool_cap.get('replication_domain'): pool_cap['replication_domain'] = self.replication_domain + if 'sg_consistent_snapshot_support' not in pool_cap: + pool_cap['sg_consistent_snapshot_support'] = ( + self.sg_consistent_snapshot_support) + def update_backend(self, capability): self.share_backend_name = capability.get('share_backend_name') self.vendor_name = capability.get('vendor_name') @@ -334,6 +348,8 @@ class HostState(object): self.updated = capability['timestamp'] self.replication_type = capability.get('replication_type') self.replication_domain = capability.get('replication_domain') + self.sg_consistent_snapshot_support = capability.get( + 'share_group_stats', {}).get('consistent_snapshot_support') def consume_from_share(self, share): """Incrementally update host state from an share.""" @@ -419,6 +435,8 @@ class PoolState(HostState): 'replication_type', self.replication_type) self.replication_domain = capability.get( 'replication_domain') + self.sg_consistent_snapshot_support = capability.get( + 'sg_consistent_snapshot_support') def update_pools(self, capability): # Do nothing, since we don't have pools within pool, yet diff --git a/manila/scheduler/utils.py b/manila/scheduler/utils.py index a73c0f13f4..10bfaadd62 100644 --- a/manila/scheduler/utils.py +++ b/manila/scheduler/utils.py @@ -53,6 +53,8 @@ def generate_stats(host_state, properties): 'pools': host_state.pools, 'max_over_subscription_ratio': host_state.max_over_subscription_ratio, + 'sg_consistent_snapshot_support': ( + host_state.sg_consistent_snapshot_support), } host_caps = host_state.capabilities @@ -60,15 +62,20 @@ def generate_stats(host_state, properties): share_type = properties.get('share_type', {}) extra_specs = share_type.get('extra_specs', {}) + share_group_type = properties.get('share_group_type', {}) + group_specs = share_group_type.get('group_specs', {}) + request_spec = properties.get('request_spec', {}) share_stats = request_spec.get('resource_properties', {}) stats = { 'host_stats': host_stats, 'host_caps': host_caps, + 'share_type': share_type, 'extra_specs': extra_specs, 'share_stats': share_stats, - 'share_type': share_type, + 'share_group_type': share_group_type, + 'group_specs': group_specs, } return stats diff --git a/manila/share/driver.py b/manila/share/driver.py index 37ce2b3f71..465ae91bc4 100644 --- a/manila/share/driver.py +++ b/manila/share/driver.py @@ -1112,7 +1112,6 @@ class ShareDriver(object): create_share_from_snapshot_support=( self.creating_shares_from_snapshots_is_supported), revert_to_snapshot_support=False, - share_group_snapshot_support=self.snapshots_are_supported, mount_snapshot_support=False, replication_domain=self.replication_domain, filter_function=self.get_filter_function(), @@ -1120,6 +1119,13 @@ class ShareDriver(object): ) if isinstance(data, dict): common.update(data) + + sg_stats = data.get('share_group_stats', {}) if data else {} + common['share_group_stats'] = { + 'consistent_snapshot_support': sg_stats.get( + 'consistent_snapshot_support'), + } + self._stats = common def get_share_server_pools(self, share_server): @@ -1357,7 +1363,7 @@ class ShareDriver(object): snap_dict['id']) snapshot_members = snap_dict.get('share_group_snapshot_members', []) - if not self._stats.get('share_group_snapshot_support'): + if not self._stats.get('snapshot_support'): raise exception.ShareGroupSnapshotNotSupported( share_group=snap_dict['share_group_id']) elif not snapshot_members: diff --git a/manila/share/manager.py b/manila/share/manager.py index 6fb3f23da3..0ad0c76fb5 100644 --- a/manila/share/manager.py +++ b/manila/share/manager.py @@ -3522,7 +3522,10 @@ class ShareManager(manager.SchedulerDependentManager): self.db.share_group_update( context, share_group_ref['id'], - {'status': constants.STATUS_ERROR}) + {'status': constants.STATUS_ERROR, + 'consistent_snapshot_support': self.driver._stats[ + 'share_group_stats'].get( + 'consistent_snapshot_support')}) for share in shares: self.db.share_instance_update( context, share['id'], @@ -3533,10 +3536,13 @@ class ShareManager(manager.SchedulerDependentManager): for share in shares: self.db.share_instance_update( context, share['id'], {'status': constants.STATUS_AVAILABLE}) - self.db.share_group_update(context, - share_group_ref['id'], - {'status': status, - 'created_at': now}) + self.db.share_group_update( + context, + share_group_ref['id'], + {'status': status, + 'created_at': now, + 'consistent_snapshot_support': self.driver._stats[ + 'share_group_stats'].get('consistent_snapshot_support')}) LOG.info("Share group %s: created successfully", share_group_id) # TODO(ameade): Add notification for create.end diff --git a/manila/tests/scheduler/test_host_manager.py b/manila/tests/scheduler/test_host_manager.py index 67e5901f74..576d0e8c13 100644 --- a/manila/tests/scheduler/test_host_manager.py +++ b/manila/tests/scheduler/test_host_manager.py @@ -217,6 +217,7 @@ class HostManagerTestCase(test.TestCase): 'compression': False, 'replication_type': None, 'replication_domain': None, + 'sg_consistent_snapshot_support': None, }, }, { 'name': 'host2@back1#BBB', @@ -244,6 +245,7 @@ class HostManagerTestCase(test.TestCase): 'compression': False, 'replication_type': None, 'replication_domain': None, + 'sg_consistent_snapshot_support': None, }, }, { 'name': 'host2@back2#CCC', @@ -271,6 +273,7 @@ class HostManagerTestCase(test.TestCase): 'compression': False, 'replication_type': None, 'replication_domain': None, + 'sg_consistent_snapshot_support': None, }, }, ] @@ -320,6 +323,7 @@ class HostManagerTestCase(test.TestCase): 'compression': False, 'replication_type': None, 'replication_domain': None, + 'sg_consistent_snapshot_support': None, }, }, { 'name': 'host2@BBB#pool2', @@ -348,6 +352,7 @@ class HostManagerTestCase(test.TestCase): 'compression': False, 'replication_type': None, 'replication_domain': None, + 'sg_consistent_snapshot_support': None, }, }, { 'name': 'host3@CCC#pool3', @@ -376,6 +381,7 @@ class HostManagerTestCase(test.TestCase): 'compression': False, 'replication_type': None, 'replication_domain': None, + 'sg_consistent_snapshot_support': None, }, }, { 'name': 'host4@DDD#pool4a', @@ -404,6 +410,7 @@ class HostManagerTestCase(test.TestCase): 'compression': False, 'replication_type': None, 'replication_domain': None, + 'sg_consistent_snapshot_support': None, }, }, { 'name': 'host4@DDD#pool4b', @@ -432,6 +439,7 @@ class HostManagerTestCase(test.TestCase): 'compression': False, 'replication_type': None, 'replication_domain': None, + 'sg_consistent_snapshot_support': None, }, }, ] @@ -493,6 +501,7 @@ class HostManagerTestCase(test.TestCase): 'compression': False, 'replication_type': None, 'replication_domain': None, + 'sg_consistent_snapshot_support': None, }, }, { 'name': 'host2@back1#BBB', @@ -520,6 +529,7 @@ class HostManagerTestCase(test.TestCase): 'compression': False, 'replication_type': None, 'replication_domain': None, + 'sg_consistent_snapshot_support': None, }, }, ] @@ -575,6 +585,7 @@ class HostManagerTestCase(test.TestCase): 'compression': False, 'replication_type': None, 'replication_domain': None, + 'sg_consistent_snapshot_support': None, }, }, ] diff --git a/manila/tests/share/drivers/dell_emc/test_driver.py b/manila/tests/share/drivers/dell_emc/test_driver.py index aa5f03a514..f3837b64fd 100644 --- a/manila/tests/share/drivers/dell_emc/test_driver.py +++ b/manila/tests/share/drivers/dell_emc/test_driver.py @@ -126,7 +126,7 @@ class EMCShareFrameworkTestCase(test.TestCase): data['snapshot_support'] = True data['create_share_from_snapshot_support'] = True data['revert_to_snapshot_support'] = False - data['share_group_snapshot_support'] = True + data['share_group_stats'] = {'consistent_snapshot_support': None} data['mount_snapshot_support'] = False data['replication_domain'] = None data['filter_function'] = None diff --git a/manila/tests/share/drivers/dummy.py b/manila/tests/share/drivers/dummy.py index c63b7cffbd..1722dad7e5 100644 --- a/manila/tests/share/drivers/dummy.py +++ b/manila/tests/share/drivers/dummy.py @@ -380,13 +380,15 @@ class DummyDriver(driver.ShareDriver): "storage_protocol": "NFS_CIFS", "reserved_percentage": self.configuration.reserved_share_percentage, - "consistency_group_support": "pool", "snapshot_support": True, "create_share_from_snapshot_support": True, "revert_to_snapshot_support": True, "mount_snapshot_support": True, "driver_name": "Dummy", "pools": self._get_pools_info(), + "share_group_stats": { + "consistent_snapshot_support": "pool", + } } if self.configuration.replication_domain: data["replication_type"] = "readable" diff --git a/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py b/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py index b86b8fe065..103d76da0c 100644 --- a/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py +++ b/manila/tests/share/drivers/glusterfs/test_glusterfs_native.py @@ -258,8 +258,10 @@ class GlusterfsNativeShareDriverTestCase(test.TestCase): 'snapshot_support': True, 'create_share_from_snapshot_support': True, 'revert_to_snapshot_support': False, - 'share_group_snapshot_support': True, 'mount_snapshot_support': False, + 'share_group_stats': { + 'consistent_snapshot_support': None, + }, 'replication_domain': None, 'filter_function': None, 'goodness_function': None, diff --git a/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py b/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py index ddeaaa4aac..d23ed365b7 100644 --- a/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py +++ b/manila/tests/share/drivers/hpe/test_hpe_3par_driver.py @@ -735,8 +735,10 @@ class HPE3ParDriverTestCase(test.TestCase): 'snapshot_support': True, 'create_share_from_snapshot_support': True, 'revert_to_snapshot_support': False, - 'share_group_snapshot_support': True, 'mount_snapshot_support': False, + 'share_group_stats': { + 'consistent_snapshot_support': None, + }, 'storage_protocol': 'NFS_CIFS', 'thin_provisioning': True, 'total_capacity_gb': 0, @@ -813,8 +815,10 @@ class HPE3ParDriverTestCase(test.TestCase): 'snapshot_support': True, 'create_share_from_snapshot_support': True, 'revert_to_snapshot_support': False, - 'share_group_snapshot_support': True, 'mount_snapshot_support': False, + 'share_group_stats': { + 'consistent_snapshot_support': None, + }, 'replication_domain': None, 'filter_function': None, 'goodness_function': None, @@ -853,8 +857,10 @@ class HPE3ParDriverTestCase(test.TestCase): 'snapshot_support': True, 'create_share_from_snapshot_support': True, 'revert_to_snapshot_support': False, - 'share_group_snapshot_support': True, 'mount_snapshot_support': False, + 'share_group_stats': { + 'consistent_snapshot_support': None, + }, 'replication_domain': None, 'filter_function': None, 'goodness_function': None, diff --git a/manila/tests/share/drivers/huawei/test_huawei_nas.py b/manila/tests/share/drivers/huawei/test_huawei_nas.py index a521483c25..61529c78db 100644 --- a/manila/tests/share/drivers/huawei/test_huawei_nas.py +++ b/manila/tests/share/drivers/huawei/test_huawei_nas.py @@ -2425,12 +2425,12 @@ class HuaweiShareDriverTestCase(test.TestCase): "snapshot_support": snapshot_support, "create_share_from_snapshot_support": snapshot_support, "revert_to_snapshot_support": False, - "share_group_snapshot_support": True, "mount_snapshot_support": False, "replication_domain": None, "filter_function": None, "goodness_function": None, "pools": [], + "share_group_stats": {"consistent_snapshot_support": None}, } if replication_support: diff --git a/manila/tests/share/drivers/zfsonlinux/test_driver.py b/manila/tests/share/drivers/zfsonlinux/test_driver.py index 4081cf1192..eb4b048e05 100644 --- a/manila/tests/share/drivers/zfsonlinux/test_driver.py +++ b/manila/tests/share/drivers/zfsonlinux/test_driver.py @@ -352,10 +352,10 @@ class ZFSonLinuxShareDriverTestCase(test.TestCase): 'replication_domain': replication_domain, 'reserved_percentage': 0, 'share_backend_name': self.driver.backend_name, + 'share_group_stats': {'consistent_snapshot_support': None}, 'snapshot_support': True, 'create_share_from_snapshot_support': True, 'revert_to_snapshot_support': False, - 'share_group_snapshot_support': True, 'mount_snapshot_support': False, 'storage_protocol': 'NFS', 'total_capacity_gb': 'unknown', diff --git a/manila/tests/share/test_driver.py b/manila/tests/share/test_driver.py index 83a26480d6..4626a228ce 100644 --- a/manila/tests/share/test_driver.py +++ b/manila/tests/share/test_driver.py @@ -690,9 +690,6 @@ class ShareDriverTestCase(test.TestCase): self.assertEqual( snapshots_are_supported, child_class_instance._stats["snapshot_support"]) - self.assertEqual( - snapshots_are_supported, - child_class_instance._stats["share_group_snapshot_support"]) self.assertTrue(child_class_instance.configuration.safe_get.called) def test_create_share_group_from_share_group_snapshot(self): @@ -843,7 +840,7 @@ class ShareDriverTestCase(test.TestCase): 'name': None } share_driver = self._instantiate_share_driver(None, False) - share_driver._stats['share_group_snapshot_support'] = True + share_driver._stats['snapshot_support'] = True mock_create_snap = self.mock_object( share_driver, 'create_snapshot', mock.Mock(side_effect=lambda *args, **kwargs: { @@ -917,7 +914,7 @@ class ShareDriverTestCase(test.TestCase): expected_exception = exception.ManilaException share_driver = self._instantiate_share_driver(None, False) - share_driver._stats['share_group_snapshot_support'] = True + share_driver._stats['snapshot_support'] = True mock_create_snap = self.mock_object( share_driver, 'create_snapshot', mock.Mock(side_effect=[None, expected_exception])) @@ -985,7 +982,7 @@ class ShareDriverTestCase(test.TestCase): 'name': None } share_driver = self._instantiate_share_driver(None, False) - share_driver._stats['share_group_snapshot_support'] = False + share_driver._stats['snapshot_support'] = False self.assertRaises( exception.ShareGroupSnapshotNotSupported, @@ -1006,7 +1003,7 @@ class ShareDriverTestCase(test.TestCase): 'name': None } share_driver = self._instantiate_share_driver(None, False) - share_driver._stats['share_group_snapshot_support'] = True + share_driver._stats['snapshot_support'] = True share_group_snapshot_update, member_update_list = ( share_driver.create_share_group_snapshot( diff --git a/manila/tests/share/test_manager.py b/manila/tests/share/test_manager.py index fe8ec7acc4..1f53b27ec3 100644 --- a/manila/tests/share/test_manager.py +++ b/manila/tests/share/test_manager.py @@ -89,6 +89,9 @@ class ShareManagerTestCase(test.TestCase): "manila.share.manager.ShareManager") self.mock_object(self.share_manager.driver, 'do_setup') self.mock_object(self.share_manager.driver, 'check_for_setup_error') + self.share_manager.driver._stats = { + 'share_group_stats': {'consistent_snapshot_support': None}, + } self.context = context.get_admin_context() self.share_manager.driver.initialized = True mock.patch.object( @@ -3226,10 +3229,13 @@ class ShareManagerTestCase(test.TestCase): self.share_manager.create_share_group(self.context, "fake_id") - self.share_manager.db.share_group_update.\ - assert_called_once_with(mock.ANY, 'fake_id', - {'status': constants.STATUS_AVAILABLE, - 'created_at': mock.ANY}) + self.share_manager.db.share_group_update.assert_called_once_with( + mock.ANY, 'fake_id', { + 'status': constants.STATUS_AVAILABLE, + 'created_at': mock.ANY, + 'consistent_snapshot_support': None, + } + ) def test_create_cg_with_share_network_driver_not_handles_servers(self): manager.CONF.set_default('driver_handles_share_servers', False) @@ -3281,8 +3287,12 @@ class ShareManagerTestCase(test.TestCase): self.share_manager.create_share_group(self.context, "fake_id") self.share_manager.db.share_group_update.assert_called_once_with( - mock.ANY, 'fake_id', - {'status': constants.STATUS_AVAILABLE, 'created_at': mock.ANY}) + mock.ANY, 'fake_id', { + 'status': constants.STATUS_AVAILABLE, + 'created_at': mock.ANY, + 'consistent_snapshot_support': None, + } + ) def test_create_share_group_with_update(self): fake_group = {'id': 'fake_id'} @@ -3298,10 +3308,13 @@ class ShareManagerTestCase(test.TestCase): self.share_manager.db.share_group_update.\ assert_any_call(mock.ANY, 'fake_id', {'foo': 'bar'}) - self.share_manager.db.share_group_update.\ - assert_any_call(mock.ANY, 'fake_id', - {'status': constants.STATUS_AVAILABLE, - 'created_at': mock.ANY}) + self.share_manager.db.share_group_update.assert_any_call( + mock.ANY, 'fake_id', { + 'status': constants.STATUS_AVAILABLE, + 'created_at': mock.ANY, + 'consistent_snapshot_support': None, + } + ) def test_create_share_group_with_error(self): fake_group = {'id': 'fake_id'} @@ -3318,7 +3331,11 @@ class ShareManagerTestCase(test.TestCase): self.context, "fake_id") self.share_manager.db.share_group_update.assert_called_once_with( - mock.ANY, 'fake_id', {'status': constants.STATUS_ERROR}) + mock.ANY, 'fake_id', { + 'status': constants.STATUS_ERROR, + 'consistent_snapshot_support': None, + } + ) def test_create_share_group_from_sg_snapshot(self): fake_group = { @@ -3348,7 +3365,9 @@ class ShareManagerTestCase(test.TestCase): self.share_manager.db.share_group_update.assert_called_once_with( mock.ANY, 'fake_id', - {'status': constants.STATUS_AVAILABLE, 'created_at': mock.ANY}) + {'status': constants.STATUS_AVAILABLE, + 'created_at': mock.ANY, + 'consistent_snapshot_support': None}) self.share_manager.db.share_server_get(mock.ANY, 'fake_ss_id') mock_create_sg_from_sg_snap.assert_called_once_with( mock.ANY, fake_group, fake_snap, share_server=fake_ss) @@ -3415,7 +3434,9 @@ class ShareManagerTestCase(test.TestCase): self.share_manager.db.share_group_update.assert_called_once_with( mock.ANY, 'fake_id', - {'status': constants.STATUS_AVAILABLE, 'created_at': mock.ANY}) + {'status': constants.STATUS_AVAILABLE, + 'created_at': mock.ANY, + 'consistent_snapshot_support': None}) def test_create_share_group_from_share_group_snapshot_with_update(self): fake_group = { @@ -3439,8 +3460,12 @@ class ShareManagerTestCase(test.TestCase): self.share_manager.db.share_group_update.assert_any_call( mock.ANY, 'fake_id', {'foo': 'bar'}) self.share_manager.db.share_group_update.assert_any_call( - mock.ANY, 'fake_id', - {'status': constants.STATUS_AVAILABLE, 'created_at': mock.ANY}) + mock.ANY, 'fake_id', { + 'status': constants.STATUS_AVAILABLE, + 'created_at': mock.ANY, + 'consistent_snapshot_support': None, + } + ) def test_create_share_group_from_sg_snapshot_with_share_update(self): fake_share = {'id': 'fake_share_id'} @@ -3474,8 +3499,12 @@ class ShareManagerTestCase(test.TestCase): self.share_manager.db.share_export_locations_update.assert_any_call( mock.ANY, 'fake_share_id', fake_export_locations) self.share_manager.db.share_group_update.assert_any_call( - mock.ANY, 'fake_id', - {'status': constants.STATUS_AVAILABLE, 'created_at': mock.ANY}) + mock.ANY, 'fake_id', { + 'status': constants.STATUS_AVAILABLE, + 'created_at': mock.ANY, + 'consistent_snapshot_support': None, + } + ) def test_create_share_group_from_sg_snapshot_with_error(self): fake_group = { @@ -3502,7 +3531,11 @@ class ShareManagerTestCase(test.TestCase): self.context, "fake_id") self.share_manager.db.share_group_update.assert_called_once_with( - mock.ANY, 'fake_id', {'status': constants.STATUS_ERROR}) + mock.ANY, 'fake_id', { + 'status': constants.STATUS_ERROR, + 'consistent_snapshot_support': None, + } + ) def test_create_share_group_from_sg_snapshot_with_share_error(self): fake_share = {'id': 'fake_share_id'} @@ -3532,7 +3565,11 @@ class ShareManagerTestCase(test.TestCase): self.share_manager.db.share_instance_update.assert_any_call( mock.ANY, 'fake_share_id', {'status': constants.STATUS_ERROR}) self.share_manager.db.share_group_update.assert_called_once_with( - mock.ANY, 'fake_id', {'status': constants.STATUS_ERROR}) + mock.ANY, 'fake_id', { + 'status': constants.STATUS_ERROR, + 'consistent_snapshot_support': None, + } + ) def test_delete_share_group(self): fake_group = {'id': 'fake_id'} diff --git a/manila_tempest_tests/config.py b/manila_tempest_tests/config.py index 7c5eeb2515..3be873f64a 100644 --- a/manila_tempest_tests/config.py +++ b/manila_tempest_tests/config.py @@ -112,6 +112,11 @@ ShareGroup = [ "capability called 'revert_to_snapshot_support' " "and will be used for setting up custom share type. " "Defaults to the value of run_revert_to_snapshot_tests."), + cfg.StrOpt("capability_sg_consistent_snapshot_support", + choices=["host", "pool", None], + help="Backend capability to create consistent snapshots of " + "share group members. Will be used with creation " + "of new share group types as group spec."), cfg.StrOpt("share_network_id", default="", help="Some backend drivers requires share network " diff --git a/manila_tempest_tests/tests/api/admin/test_share_group_types.py b/manila_tempest_tests/tests/api/admin/test_share_group_types.py index 532e773ff0..189ab34328 100644 --- a/manila_tempest_tests/tests/api/admin/test_share_group_types.py +++ b/manila_tempest_tests/tests/api/admin/test_share_group_types.py @@ -136,13 +136,16 @@ class ShareGroupTypesTest(base.BaseSharesAdminTest): self.assertDictMatch(group_specs, sg_type['group_specs']) - group_specs = {'key1': 'value3', 'key2': 'value2'} + group_specs = {'key1': 'value1', 'key2': 'value2'} self.shares_v2_client.update_share_group_type_spec( sg_type['id'], 'key1', 'value3') sg_type = self.shares_v2_client.get_share_group_type(sg_type['id']) - self.assertDictMatch(group_specs, sg_type['group_specs']) + self.assertIn('key1', sg_type['group_specs']) + self.assertIn('key2', sg_type['group_specs']) + self.assertEqual('value3', sg_type['group_specs']['key1']) + self.assertEqual(group_specs['key2'], sg_type['group_specs']['key2']) @tc.attr(base.TAG_POSITIVE, base.TAG_API) def test_update_all_share_group_type_specs_min(self): @@ -164,7 +167,9 @@ class ShareGroupTypesTest(base.BaseSharesAdminTest): sg_type['id'], group_specs) sg_type = self.shares_v2_client.get_share_group_type(sg_type['id']) - self.assertDictMatch(group_specs, sg_type['group_specs']) + for k, v in group_specs.items(): + self.assertIn(k, sg_type['group_specs']) + self.assertEqual(v, sg_type['group_specs'][k]) @tc.attr(base.TAG_POSITIVE, base.TAG_API) def test_delete_single_share_group_type_spec_min(self): diff --git a/manila_tempest_tests/tests/api/admin/test_share_groups.py b/manila_tempest_tests/tests/api/admin/test_share_groups.py index e289005971..dc1f2aea9c 100644 --- a/manila_tempest_tests/tests/api/admin/test_share_groups.py +++ b/manila_tempest_tests/tests/api/admin/test_share_groups.py @@ -48,7 +48,7 @@ class ShareGroupsTest(base.BaseSharesAdminTest): cleanup_in_class=True, version=constants.MIN_SHARE_GROUP_MICROVERSION) - @tc.attr(base.TAG_POSITIVE, base.TAG_API) + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) def test_create_share_group_with_single_share_type_min(self): share_group = self.create_share_group( share_group_type_id=self.sg_type['id'], @@ -81,7 +81,7 @@ class ShareGroupsTest(base.BaseSharesAdminTest): 'Expected %s, got %s' % ( share_group['id'], expected_share_types, actual_share_types)) - @tc.attr(base.TAG_POSITIVE, base.TAG_API) + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) def test_create_share_group_with_multiple_share_types_min(self): share_group = self.create_share_group( share_group_type_id=self.sg_type['id'], @@ -114,7 +114,7 @@ class ShareGroupsTest(base.BaseSharesAdminTest): 'Expected %s, got %s' % ( share_group['id'], expected_share_types, actual_share_types)) - @tc.attr(base.TAG_POSITIVE, base.TAG_API) + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) def test_default_share_group_type_applied(self): default_type = self.shares_v2_client.get_default_share_group_type() default_share_types = default_type['share_types'] @@ -142,7 +142,7 @@ class ShareGroupsTest(base.BaseSharesAdminTest): @testtools.skipUnless( CONF.share.multitenancy_enabled, "Only for multitenancy.") - @tc.attr(base.TAG_POSITIVE, base.TAG_API) + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) def test_create_sg_from_snapshot_verify_share_server_information_min(self): # Create a share group orig_sg = self.create_share_group( @@ -173,3 +173,19 @@ class ShareGroupsTest(base.BaseSharesAdminTest): orig_sg['share_network_id'], new_sg['share_network_id']) self.assertEqual( orig_sg['share_server_id'], new_sg['share_server_id']) + + @tc.attr(base.TAG_POSITIVE, base.TAG_API_WITH_BACKEND) + def test_create_sg_with_sg_type_but_without_any_group_specs(self): + # Create share group type not specifying any group specs + sg_type = self.create_share_group_type( + name=data_utils.rand_name("tempest-manila"), + share_types=[self.share_type['id']], + group_specs={}, + cleanup_in_class=False) + + # Create share group, it should be created always, because we do not + # restrict choice anyhow. + self.create_share_group( + share_type_ids=[self.share_type['id']], + share_group_type_id=sg_type['id'], + cleanup_in_class=False) diff --git a/manila_tempest_tests/tests/api/admin/test_share_groups_negative.py b/manila_tempest_tests/tests/api/admin/test_share_groups_negative.py new file mode 100644 index 0000000000..979e6a43a3 --- /dev/null +++ b/manila_tempest_tests/tests/api/admin/test_share_groups_negative.py @@ -0,0 +1,56 @@ +# Copyright 2017 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 import config +from tempest.lib.common.utils import data_utils +import testtools +from testtools import testcase as tc + +from manila_tempest_tests.common import constants +from manila_tempest_tests import share_exceptions +from manila_tempest_tests.tests.api import base + +CONF = config.CONF + + +@testtools.skipUnless( + CONF.share.run_share_group_tests, 'Share Group tests disabled.') +@base.skip_if_microversion_lt(constants.MIN_SHARE_GROUP_MICROVERSION) +class ShareGroupsNegativeTest(base.BaseSharesAdminTest): + + @tc.attr(base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND) + def test_create_share_group_with_wrong_consistent_snapshot_spec(self): + # Create valid share type for share group type + name = data_utils.rand_name("tempest-manila") + extra_specs = self.add_extra_specs_to_dict() + st = self.create_share_type(name, extra_specs=extra_specs) + share_type = st['share_type'] if 'share_type' in st else st + + # Create share group type with wrong value for + # 'consistent_snapshot_support' capability, we always expect + # NoValidHostFound using this SG type. + sg_type = self.create_share_group_type( + name=name, + share_types=[share_type['id']], + group_specs={"consistent_snapshot_support": "fake"}, + cleanup_in_class=False) + + # Try create share group + self.assertRaises( + share_exceptions.ShareGroupBuildErrorException, + self.create_share_group, + share_type_ids=[share_type['id']], + share_group_type_id=sg_type['id'], + cleanup_in_class=False) diff --git a/manila_tempest_tests/tests/api/base.py b/manila_tempest_tests/tests/api/base.py index de298da7b6..236351dd61 100644 --- a/manila_tempest_tests/tests/api/base.py +++ b/manila_tempest_tests/tests/api/base.py @@ -595,6 +595,11 @@ class BaseSharesTest(test.BaseTestCase): group_specs=None, client=None, cleanup_in_class=True, **kwargs): client = client or cls.shares_v2_client + if group_specs is None: + group_specs = { + 'consistent_snapshot_support': ( + CONF.share.capability_sg_consistent_snapshot_support), + } share_group_type = client.create_share_group_type( name=name, share_types=share_types, diff --git a/setup.cfg b/setup.cfg index 606ad9ce4b..89050f9537 100644 --- a/setup.cfg +++ b/setup.cfg @@ -45,10 +45,14 @@ manila.scheduler.filters = JsonFilter = manila.scheduler.filters.json:JsonFilter RetryFilter = manila.scheduler.filters.retry:RetryFilter ShareReplicationFilter = manila.scheduler.filters.share_replication:ShareReplicationFilter + # Share Group filters + ConsistentSnapshotFilter = manila.scheduler.filters.share_group_filters.consistent_snapshot:ConsistentSnapshotFilter + manila.scheduler.weighers = CapacityWeigher = manila.scheduler.weighers.capacity:CapacityWeigher GoodnessWeigher = manila.scheduler.weighers.goodness:GoodnessWeigher PoolWeigher = manila.scheduler.weighers.pool:PoolWeigher + # These are for backwards compat with Havana notification_driver configuration values oslo_messaging.notify.drivers = manila.openstack.common.notifier.log_notifier = oslo_messaging.notify._impl_log:LogDriver