diff --git a/contrib/ci/pre_test_hook.sh b/contrib/ci/pre_test_hook.sh index bf748eec..23af2303 100755 --- a/contrib/ci/pre_test_hook.sh +++ b/contrib/ci/pre_test_hook.sh @@ -68,6 +68,7 @@ fi # Set MANILA_ADMIN_NET_RANGE for admin_network and data_service IP echo "MANILA_ADMIN_NET_RANGE=${MANILA_ADMIN_NET_RANGE:=10.2.5.0/24}" >> $localrc_path echo "MANILA_DATA_NODE_IP=${MANILA_DATA_NODE_IP:=$MANILA_ADMIN_NET_RANGE}" >> $localrc_path +echo "MANILA_DATA_COPY_CHECK_HASH=${MANILA_DATA_COPY_CHECK_HASH:=True}" >> $localrc_path # Share Migration CI tests migration_continue period task interval echo "MANILA_SHARE_MIGRATION_PERIOD_TASK_INTERVAL=${MANILA_SHARE_MIGRATION_PERIOD_TASK_INTERVAL:=5}" >> $localrc_path diff --git a/devstack/plugin.sh b/devstack/plugin.sh index c34efaec..a422f873 100755 --- a/devstack/plugin.sh +++ b/devstack/plugin.sh @@ -186,6 +186,10 @@ function configure_manila { iniset $MANILA_CONF DEFAULT migration_driver_continue_update_interval $MANILA_SHARE_MIGRATION_PERIOD_TASK_INTERVAL fi + if ! [[ -z $MANILA_DATA_COPY_CHECK_HASH ]]; then + iniset $MANILA_CONF DEFAULT check_hash $MANILA_DATA_COPY_CHECK_HASH + fi + iniset $MANILA_CONF DEFAULT enabled_share_protocols $MANILA_ENABLED_SHARE_PROTOCOLS iniset $MANILA_CONF oslo_concurrency lock_path $MANILA_LOCK_PATH diff --git a/devstack/settings b/devstack/settings index 62d4efb3..6c66d94b 100644 --- a/devstack/settings +++ b/devstack/settings @@ -176,6 +176,9 @@ MANILA_ADMIN_NET_RANGE=${MANILA_ADMIN_NET_RANGE:=10.2.5.0/24} # Data Service IP configuration MANILA_DATA_NODE_IP=${MANILA_DATA_NODE_IP:=$MANILA_ADMIN_NET_RANGE} +# Data Service copy validation +MANILA_DATA_COPY_CHECK_HASH=${MANILA_DATA_COPY_CHECK_HASH:=True} + # Enable manila services # ---------------------- # We have to add Manila to enabled services for screen_it to work diff --git a/manila_tempest_tests/tests/api/admin/test_migration.py b/manila_tempest_tests/tests/api/admin/test_migration.py index 0f555987..27ceb8b8 100644 --- a/manila_tempest_tests/tests/api/admin/test_migration.py +++ b/manila_tempest_tests/tests/api/admin/test_migration.py @@ -13,7 +13,6 @@ # License for the specific language governing permissions and limitations # under the License. -import six import ddt from tempest import config @@ -39,6 +38,8 @@ class MigrationNFSTest(base.BaseSharesAdminTest): 2) Host-assisted migration: force_host_assisted_migration is True, nondisruptive, writable and preserve-metadata are False. 3) 2-phase migration of both Host-assisted and Driver-assisted. + 4) Cancelling migration past first phase. + 5) Changing driver modes through migration. No need to test with writable, preserve-metadata and non-disruptive as True, values are supplied to the driver which decides what to do. Test @@ -62,17 +63,16 @@ class MigrationNFSTest(base.BaseSharesAdminTest): CONF.share.run_driver_assisted_migration_tests): raise cls.skipException("Share migration tests are disabled.") - extra_specs = { - 'storage_protocol': CONF.share.capability_storage_protocol, - 'driver_handles_share_servers': ( - CONF.share.multitenancy_enabled), - 'snapshot_support': six.text_type( - CONF.share.capability_snapshot_support), - } cls.new_type = cls.create_share_type( name=data_utils.rand_name('new_share_type_for_migration'), cleanup_in_class=True, - extra_specs=extra_specs) + extra_specs=utils.get_configured_extra_specs()) + + cls.new_type_opposite = cls.create_share_type( + name=data_utils.rand_name('new_share_type_for_migration_opposite'), + cleanup_in_class=True, + extra_specs=utils.get_configured_extra_specs( + variation='opposite_driver_modes')) @test.attr(type=[base.TAG_POSITIVE, base.TAG_BACKEND]) @base.skip_if_microversion_lt("2.22") @@ -83,13 +83,6 @@ class MigrationNFSTest(base.BaseSharesAdminTest): share, dest_pool = self._setup_migration() - old_exports = self.shares_v2_client.list_share_export_locations( - share['id']) - self.assertNotEmpty(old_exports) - old_exports = [x['path'] for x in old_exports - if x['is_admin_only'] is False] - self.assertNotEmpty(old_exports) - task_state = (constants.TASK_STATE_DATA_COPYING_COMPLETED if force_host_assisted else constants.TASK_STATE_MIGRATION_DRIVER_PHASE1_DONE) @@ -101,12 +94,83 @@ class MigrationNFSTest(base.BaseSharesAdminTest): self._validate_migration_successful( dest_pool, share, task_state, complete=False) + progress = self.shares_v2_client.migration_get_progress(share['id']) + + self.assertEqual(task_state, progress['task_state']) + self.assertEqual(100, progress['total_progress']) + share = self.migration_cancel(share['id'], dest_pool) + progress = self.shares_v2_client.migration_get_progress(share['id']) + + self.assertEqual( + constants.TASK_STATE_MIGRATION_CANCELLED, progress['task_state']) + self.assertEqual(100, progress['total_progress']) + self._validate_migration_successful( dest_pool, share, constants.TASK_STATE_MIGRATION_CANCELLED, complete=False) + @test.attr(type=[base.TAG_POSITIVE, base.TAG_BACKEND]) + @base.skip_if_microversion_lt("2.22") + @ddt.data(True, False) + def test_migration_opposite_driver_modes(self, force_host_assisted): + + self._check_migration_enabled(force_host_assisted) + + share, dest_pool = self._setup_migration(opposite=True) + + old_share_network_id = share['share_network_id'] + + # If currently configured is DHSS=False, + # then we need it for DHSS=True + if not CONF.share.multitenancy_enabled: + + new_share_network_id = self.provide_share_network( + self.shares_v2_client, self.os_admin.networks_client, + isolated_creds_client=None, ignore_multitenancy_config=True) + + # If currently configured is DHSS=True, + # then we must pass None for DHSS=False + else: + new_share_network_id = None + + old_share_type_id = share['share_type'] + new_share_type_id = self.new_type_opposite['share_type']['id'] + + task_state = (constants.TASK_STATE_DATA_COPYING_COMPLETED + if force_host_assisted + else constants.TASK_STATE_MIGRATION_DRIVER_PHASE1_DONE) + + share = self.migrate_share( + share['id'], dest_pool, + force_host_assisted_migration=force_host_assisted, + wait_for_status=task_state, new_share_type_id=new_share_type_id, + new_share_network_id=new_share_network_id) + + self._validate_migration_successful( + dest_pool, share, task_state, complete=False, + share_network_id=old_share_network_id, + share_type_id=old_share_type_id) + + progress = self.shares_v2_client.migration_get_progress(share['id']) + + self.assertEqual(task_state, progress['task_state']) + self.assertEqual(100, progress['total_progress']) + + share = self.migration_complete(share['id'], dest_pool) + + progress = self.shares_v2_client.migration_get_progress(share['id']) + + self.assertEqual( + constants.TASK_STATE_MIGRATION_SUCCESS, progress['task_state']) + self.assertEqual(100, progress['total_progress']) + + self._validate_migration_successful( + dest_pool, share, constants.TASK_STATE_MIGRATION_SUCCESS, + complete=True, share_network_id=new_share_network_id, + share_type_id=new_share_type_id) + @test.attr(type=[base.TAG_POSITIVE, base.TAG_BACKEND]) @base.skip_if_microversion_lt("2.22") @ddt.data(True, False) @@ -116,20 +180,18 @@ class MigrationNFSTest(base.BaseSharesAdminTest): share, dest_pool = self._setup_migration() - old_exports = self.shares_v2_client.list_share_export_locations( - share['id']) - self.assertNotEmpty(old_exports) - old_exports = [x['path'] for x in old_exports - if x['is_admin_only'] is False] - self.assertNotEmpty(old_exports) - task_state = (constants.TASK_STATE_DATA_COPYING_COMPLETED if force_host_assisted else constants.TASK_STATE_MIGRATION_DRIVER_PHASE1_DONE) old_share_network_id = share['share_network_id'] - new_share_network_id = self._create_secondary_share_network( - old_share_network_id) + + if CONF.share.multitenancy_enabled: + new_share_network_id = self._create_secondary_share_network( + old_share_network_id) + else: + new_share_network_id = None + old_share_type_id = share['share_type'] new_share_type_id = self.new_type['share_type']['id'] @@ -151,12 +213,18 @@ class MigrationNFSTest(base.BaseSharesAdminTest): share = self.migration_complete(share['id'], dest_pool) + progress = self.shares_v2_client.migration_get_progress(share['id']) + + self.assertEqual( + constants.TASK_STATE_MIGRATION_SUCCESS, progress['task_state']) + self.assertEqual(100, progress['total_progress']) + self._validate_migration_successful( dest_pool, share, constants.TASK_STATE_MIGRATION_SUCCESS, complete=True, share_network_id=new_share_network_id, share_type_id=new_share_type_id) - def _setup_migration(self): + def _setup_migration(self, opposite=False): pools = self.shares_v2_client.list_pools(detail=True)['pools'] @@ -167,25 +235,42 @@ class MigrationNFSTest(base.BaseSharesAdminTest): share = self.create_share(self.protocol) share = self.shares_v2_client.get_share(share['id']) + old_exports = self.shares_v2_client.list_share_export_locations( + share['id']) + self.assertNotEmpty(old_exports) + old_exports = [x['path'] for x in old_exports + if x['is_admin_only'] is False] + self.assertNotEmpty(old_exports) + self.shares_v2_client.create_access_rule( share['id'], access_to="50.50.50.50", access_level="rw") self.shares_v2_client.wait_for_share_status( - share['id'], 'active', status_attr='access_rules_status') + share['id'], constants.RULE_STATE_ACTIVE, + status_attr='access_rules_status') self.shares_v2_client.create_access_rule( share['id'], access_to="51.51.51.51", access_level="ro") self.shares_v2_client.wait_for_share_status( - share['id'], 'active', status_attr='access_rules_status') + share['id'], constants.RULE_STATE_ACTIVE, + status_attr='access_rules_status') - default_type = self.shares_v2_client.list_share_types( - default=True)['share_type'] + if opposite: + dest_type = self.new_type_opposite['share_type'] + else: + dest_type = self.new_type['share_type'] - dest_pool = utils.choose_matching_backend(share, pools, default_type) + dest_pool = utils.choose_matching_backend(share, pools, dest_type) - self.assertIsNotNone(dest_pool) - self.assertIsNotNone(dest_pool.get('name')) + if opposite: + if not dest_pool: + raise self.skipException( + "This test requires two pools enabled with different " + "driver modes.") + else: + self.assertIsNotNone(dest_pool) + self.assertIsNotNone(dest_pool.get('name')) dest_pool = dest_pool['name'] @@ -216,9 +301,33 @@ class MigrationNFSTest(base.BaseSharesAdminTest): # Share migrated if complete: self.assertEqual(dest_pool, share['host']) + + rules = self.shares_v2_client.list_access_rules(share['id']) + expected_rules = [{ + 'state': constants.RULE_STATE_ACTIVE, + 'access_to': '50.50.50.50', + 'access_type': 'ip', + 'access_level': 'rw', + }, { + 'state': constants.RULE_STATE_ACTIVE, + 'access_to': '51.51.51.51', + 'access_type': 'ip', + 'access_level': 'ro', + }] + filtered_rules = [{'state': rule['state'], + 'access_to': rule['access_to'], + 'access_level': rule['access_level'], + 'access_type': rule['access_type']} + for rule in rules] + + for r in expected_rules: + self.assertIn(r, filtered_rules) + self.assertEqual(len(expected_rules), len(filtered_rules)) + self.shares_v2_client.delete_share(share['id']) self.shares_v2_client.wait_for_resource_deletion( share_id=share['id']) + # Share not migrated yet else: self.assertNotEqual(dest_pool, share['host']) @@ -235,18 +344,13 @@ class MigrationNFSTest(base.BaseSharesAdminTest): "Driver-assisted migration tests are disabled.") def _create_secondary_share_network(self, old_share_network_id): - if (utils.is_microversion_ge( - CONF.share.max_api_microversion, "2.22") and - CONF.share.multitenancy_enabled): - old_share_network = self.shares_v2_client.get_share_network( - old_share_network_id) + old_share_network = self.shares_v2_client.get_share_network( + old_share_network_id) - new_share_network = self.create_share_network( - cleanup_in_class=True, - neutron_net_id=old_share_network['neutron_net_id'], - neutron_subnet_id=old_share_network['neutron_subnet_id']) + new_share_network = self.create_share_network( + cleanup_in_class=True, + neutron_net_id=old_share_network['neutron_net_id'], + neutron_subnet_id=old_share_network['neutron_subnet_id']) - return new_share_network['id'] - else: - return None + return new_share_network['id'] diff --git a/manila_tempest_tests/tests/api/admin/test_migration_negative.py b/manila_tempest_tests/tests/api/admin/test_migration_negative.py index 5d7a578e..d806ed63 100644 --- a/manila_tempest_tests/tests/api/admin/test_migration_negative.py +++ b/manila_tempest_tests/tests/api/admin/test_migration_negative.py @@ -13,8 +13,8 @@ # License for the specific language governing permissions and limitations # under the License. -import six +import ddt from tempest import config from tempest.lib.common.utils import data_utils from tempest.lib import exceptions as lib_exc @@ -29,6 +29,7 @@ from manila_tempest_tests import utils CONF = config.CONF +@ddt.ddt class MigrationTest(base.BaseSharesAdminTest): """Tests Share Migration. @@ -68,17 +69,11 @@ class MigrationTest(base.BaseSharesAdminTest): cls.dest_pool = dest_pool['name'] - extra_specs = { - 'storage_protocol': CONF.share.capability_storage_protocol, - 'driver_handles_share_servers': CONF.share.multitenancy_enabled, - 'snapshot_support': six.text_type( - not CONF.share.capability_snapshot_support), - } - cls.new_type = cls.create_share_type( + cls.new_type_invalid = cls.create_share_type( name=data_utils.rand_name( 'new_invalid_share_type_for_migration'), cleanup_in_class=True, - extra_specs=extra_specs) + extra_specs=utils.get_configured_extra_specs(variation='invalid')) @test.attr(type=[base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND]) @base.skip_if_microversion_lt("2.22") @@ -165,9 +160,17 @@ class MigrationTest(base.BaseSharesAdminTest): @test.attr(type=[base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND]) @base.skip_if_microversion_lt("2.22") def test_migrate_share_change_type_no_valid_host(self): + if not CONF.share.multitenancy_enabled: + new_share_network_id = self.create_share_network( + neutron_net_id='fake_net_id', + neutron_subnet_id='fake_subnet_id')['id'] + else: + new_share_network_id = None + self.shares_v2_client.migrate_share( self.share['id'], self.dest_pool, - new_share_type_id=self.new_type['share_type']['id']) + new_share_type_id=self.new_type_invalid['share_type']['id'], + new_share_network_id=new_share_network_id) self.shares_v2_client.wait_for_migration_status( self.share['id'], self.dest_pool, constants.TASK_STATE_MIGRATION_ERROR) @@ -207,5 +210,30 @@ class MigrationTest(base.BaseSharesAdminTest): def test_migrate_share_invalid_share_type(self): self.assertRaises( lib_exc.BadRequest, self.shares_v2_client.migrate_share, - self.share['id'], self.dest_pool, True, + self.share['id'], self.dest_pool, new_share_type_id='invalid_type_id') + + @test.attr(type=[base.TAG_NEGATIVE, base.TAG_API_WITH_BACKEND]) + @base.skip_if_microversion_lt("2.22") + def test_migrate_share_opposite_type_share_network_invalid(self): + + extra_specs = utils.get_configured_extra_specs( + variation='opposite_driver_modes') + + new_type_opposite = self.create_share_type( + name=data_utils.rand_name('share_type_migration_negative'), + extra_specs=extra_specs) + + new_share_network_id = None + + if CONF.share.multitenancy_enabled: + + new_share_network_id = self.create_share_network( + neutron_net_id='fake_net_id', + neutron_subnet_id='fake_subnet_id')['id'] + + self.assertRaises( + lib_exc.BadRequest, self.shares_v2_client.migrate_share, + self.share['id'], self.dest_pool, + new_share_type_id=new_type_opposite['share_type']['id'], + new_share_network_id=new_share_network_id) diff --git a/manila_tempest_tests/tests/api/base.py b/manila_tempest_tests/tests/api/base.py index a36f3919..6b483485 100644 --- a/manila_tempest_tests/tests/api/base.py +++ b/manila_tempest_tests/tests/api/base.py @@ -268,7 +268,8 @@ class BaseSharesTest(test.BaseTestCase): @classmethod @network_synchronized def provide_share_network(cls, shares_client, networks_client, - isolated_creds_client=None): + isolated_creds_client=None, + ignore_multitenancy_config=False): """Used for finding/creating share network for multitenant driver. This method creates/gets entity share-network for one tenant. This @@ -279,6 +280,8 @@ class BaseSharesTest(test.BaseTestCase): :param isolated_creds_client: DynamicCredentialProvider instance If provided, then its networking will be used if needed. If not provided, then common network will be used if needed. + :param ignore_multitenancy_config: provide a share network regardless + of 'multitenancy_enabled' configuration value. :returns: str -- share network id for shares_client tenant :returns: None -- if single-tenant driver used """ @@ -287,83 +290,87 @@ class BaseSharesTest(test.BaseTestCase): search_word = "reusable" sn_name = "autogenerated_by_tempest_%s" % search_word - if not CONF.share.multitenancy_enabled: + if (not ignore_multitenancy_config and + not CONF.share.multitenancy_enabled): # Assumed usage of a single-tenant driver share_network_id = None - elif sc.share_network_id: - # Share-network already exists, use it - share_network_id = sc.share_network_id - elif not CONF.share.create_networks_when_multitenancy_enabled: - share_network_id = None - - # Try get suitable share-network - share_networks = sc.list_share_networks_with_detail() - for sn in share_networks: - if (sn["neutron_net_id"] is None and - sn["neutron_subnet_id"] is None and - sn["name"] and search_word in sn["name"]): - share_network_id = sn["id"] - break - - # Create new share-network if one was not found - if share_network_id is None: - sn_desc = "This share-network was created by tempest" - sn = sc.create_share_network(name=sn_name, description=sn_desc) - share_network_id = sn["id"] else: - net_id = subnet_id = share_network_id = None - - if not isolated_creds_client: - # Search for networks, created in previous runs - service_net_name = "share-service" - networks = networks_client.list_networks() - if "networks" in networks.keys(): - networks = networks["networks"] - for network in networks: - if (service_net_name in network["name"] and - sc.tenant_id == network['tenant_id']): - net_id = network["id"] - if len(network["subnets"]) > 0: - subnet_id = network["subnets"][0] - break - - # Create suitable network - if (net_id is None or subnet_id is None): - ic = dynamic_creds.DynamicCredentialProvider( - identity_version=CONF.identity.auth_version, - name=service_net_name, - admin_role=CONF.identity.admin_role, - admin_creds=( - common_creds.get_configured_admin_credentials())) - net_data = ic._create_network_resources(sc.tenant_id) - network, subnet, router = net_data - net_id = network["id"] - subnet_id = subnet["id"] + if sc.share_network_id: + # Share-network already exists, use it + share_network_id = sc.share_network_id + elif not CONF.share.create_networks_when_multitenancy_enabled: + share_network_id = None # Try get suitable share-network share_networks = sc.list_share_networks_with_detail() for sn in share_networks: - if (net_id == sn["neutron_net_id"] and - subnet_id == sn["neutron_subnet_id"] and + if (sn["neutron_net_id"] is None and + sn["neutron_subnet_id"] is None and sn["name"] and search_word in sn["name"]): share_network_id = sn["id"] break - else: - sn_name = "autogenerated_by_tempest_for_isolated_creds" - # Use precreated network and subnet from isolated creds - net_id = isolated_creds_client.get_credentials( - isolated_creds_client.type_of_creds).network['id'] - subnet_id = isolated_creds_client.get_credentials( - isolated_creds_client.type_of_creds).subnet['id'] - # Create suitable share-network - if share_network_id is None: - sn_desc = "This share-network was created by tempest" - sn = sc.create_share_network(name=sn_name, - description=sn_desc, - neutron_net_id=net_id, - neutron_subnet_id=subnet_id) - share_network_id = sn["id"] + # Create new share-network if one was not found + if share_network_id is None: + sn_desc = "This share-network was created by tempest" + sn = sc.create_share_network(name=sn_name, + description=sn_desc) + share_network_id = sn["id"] + else: + net_id = subnet_id = share_network_id = None + + if not isolated_creds_client: + # Search for networks, created in previous runs + service_net_name = "share-service" + networks = networks_client.list_networks() + if "networks" in networks.keys(): + networks = networks["networks"] + for network in networks: + if (service_net_name in network["name"] and + sc.tenant_id == network['tenant_id']): + net_id = network["id"] + if len(network["subnets"]) > 0: + subnet_id = network["subnets"][0] + break + + # Create suitable network + if net_id is None or subnet_id is None: + ic = dynamic_creds.DynamicCredentialProvider( + identity_version=CONF.identity.auth_version, + name=service_net_name, + admin_role=CONF.identity.admin_role, + admin_creds=( + common_creds. + get_configured_admin_credentials())) + net_data = ic._create_network_resources(sc.tenant_id) + network, subnet, router = net_data + net_id = network["id"] + subnet_id = subnet["id"] + + # Try get suitable share-network + share_networks = sc.list_share_networks_with_detail() + for sn in share_networks: + if (net_id == sn["neutron_net_id"] and + subnet_id == sn["neutron_subnet_id"] and + sn["name"] and search_word in sn["name"]): + share_network_id = sn["id"] + break + else: + sn_name = "autogenerated_by_tempest_for_isolated_creds" + # Use precreated network and subnet from isolated creds + net_id = isolated_creds_client.get_credentials( + isolated_creds_client.type_of_creds).network['id'] + subnet_id = isolated_creds_client.get_credentials( + isolated_creds_client.type_of_creds).subnet['id'] + + # Create suitable share-network + if share_network_id is None: + sn_desc = "This share-network was created by tempest" + sn = sc.create_share_network(name=sn_name, + description=sn_desc, + neutron_net_id=net_id, + neutron_subnet_id=subnet_id) + share_network_id = sn["id"] return share_network_id diff --git a/manila_tempest_tests/utils.py b/manila_tempest_tests/utils.py index 277130ee..5c934438 100644 --- a/manila_tempest_tests/utils.py +++ b/manila_tempest_tests/utils.py @@ -115,3 +115,36 @@ def choose_matching_backend(share, pools, share_type): None) return selected_pool + + +def get_configured_extra_specs(variation=None): + """Retrieve essential extra specs according to configuration in tempest. + + :param variation: can assume possible values: None to be as configured in + tempest; 'opposite_driver_modes' for as configured in tempest but + inverse driver mode; 'invalid' for inverse as configured in tempest, + ideal for negative tests. + :return: dict containing essential extra specs. + """ + + extra_specs = {'storage_protocol': CONF.share.capability_storage_protocol} + + if variation == 'invalid': + extra_specs['driver_handles_share_servers'] = ( + not CONF.share.multitenancy_enabled) + extra_specs['snapshot_support'] = ( + not CONF.share.capability_snapshot_support) + + elif variation == 'opposite_driver_modes': + extra_specs['driver_handles_share_servers'] = ( + not CONF.share.multitenancy_enabled) + extra_specs['snapshot_support'] = ( + CONF.share.capability_snapshot_support) + + else: + extra_specs['driver_handles_share_servers'] = ( + CONF.share.multitenancy_enabled) + extra_specs['snapshot_support'] = ( + CONF.share.capability_snapshot_support) + + return extra_specs