diff --git a/cinder/tests/unit/volume/drivers/test_infinidat.py b/cinder/tests/unit/volume/drivers/test_infinidat.py index 265e3826f6f..3142ed8eb58 100644 --- a/cinder/tests/unit/volume/drivers/test_infinidat.py +++ b/cinder/tests/unit/volume/drivers/test_infinidat.py @@ -52,11 +52,16 @@ TEST_TARGET_PORTAL4 = '{}:{}'.format(TEST_IP_ADDRESS4, TEST_ISCSI_TCP_PORT2) TEST_FC_PROTOCOL = 'fc' TEST_ISCSI_PROTOCOL = 'iscsi' TEST_VOLUME_SOURCE_NAME = 'test-volume' +TEST_VOLUME_TYPE = 'MASTER' TEST_VOLUME_SOURCE_ID = 12345 TEST_VOLUME_METADATA = {'cinder_id': fake.VOLUME_ID} TEST_SNAPSHOT_SOURCE_NAME = 'test-snapshot' TEST_SNAPSHOT_SOURCE_ID = 67890 TEST_SNAPSHOT_METADATA = {'cinder_id': fake.SNAPSHOT_ID} +TEST_POOL_NAME = 'pool' +TEST_POOL_NAME2 = 'pool2' +TEST_SYSTEM_SERIAL = 123 +TEST_SYSTEM_SERIAL2 = 456 test_volume = mock.Mock(id=fake.VOLUME_ID, name_id=fake.VOLUME_ID, size=1, volume_type_id=fake.VOLUME_TYPE_ID, group_id=None, @@ -113,7 +118,7 @@ class InfiniboxDriverTestCaseBase(test.TestCase): configuration.SHARED_CONF_GROUP) self.override_config('san_password', 'password', configuration.SHARED_CONF_GROUP) - self.override_config('infinidat_pool_name', 'pool') + self.override_config('infinidat_pool_name', TEST_POOL_NAME) self.driver = infinidat.InfiniboxVolumeDriver( configuration=self.configuration) self._system = self._infinibox_mock() @@ -139,9 +144,8 @@ class InfiniboxDriverTestCaseBase(test.TestCase): self._mock_new_volume = mock.Mock() self._mock_volume.get_id.return_value = TEST_VOLUME_SOURCE_ID self._mock_volume.get_name.return_value = TEST_VOLUME_SOURCE_NAME - self._mock_volume.get_type.return_value = 'MASTER' - self._mock_volume.get_pool_name.return_value = ( - self.configuration.infinidat_pool_name) + self._mock_volume.get_type.return_value = TEST_VOLUME_TYPE + self._mock_volume.get_pool_name.return_value = TEST_POOL_NAME self._mock_volume.get_size.return_value = 1 * units.Gi self._mock_volume.has_children.return_value = False self._mock_volume.get_qos_policy.return_value = None @@ -180,6 +184,7 @@ class InfiniboxDriverTestCaseBase(test.TestCase): result.components.nodes.get_all.return_value = [] result.qos_policies.create.return_value = self._mock_qos_policy result.qos_policies.safe_get.return_value = None + result.get_serial.return_value = TEST_SYSTEM_SERIAL return result def _raise_infinisdk(self, *args, **kwargs): @@ -797,7 +802,7 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase): @mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs') def test_manage_existing_invalid_pool(self, *mocks): existing_ref = {'source-name': TEST_VOLUME_SOURCE_NAME} - self._mock_volume.get_pool_name.return_value = 'invalid' + self._mock_volume.get_pool_name.return_value = TEST_POOL_NAME2 self.assertRaises(exception.InvalidConfigurationValue, self.driver.manage_existing, test_volume, existing_ref) @@ -913,7 +918,7 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase): @mock.patch('cinder.volume.volume_types.get_volume_type_qos_specs') def test_manage_existing_snapshot_invalid_pool(self, *mocks): existing_ref = {'source-name': TEST_SNAPSHOT_SOURCE_NAME} - self._mock_volume.get_pool_name.return_value = 'invalid' + self._mock_volume.get_pool_name.return_value = TEST_POOL_NAME2 self.assertRaises(exception.InvalidConfigurationValue, self.driver.manage_existing_snapshot, test_snapshot, existing_ref) @@ -1049,6 +1054,72 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase): test_volume) self.assertEqual(0, self._log.error.call_count) + @ddt.data(None, {}) + def test_migrate_volume_no_host(self, host): + expected = False, None + update = self.driver.migrate_volume(None, test_volume, host) + self.assertEqual(expected, update) + + @ddt.data(None, {}) + def test_migrate_volume_no_capabilities(self, capabilities): + expected = False, None + host = {'capabilities': capabilities} + update = self.driver.migrate_volume(None, test_volume, host) + self.assertEqual(expected, update) + + @ddt.data(None, 123, 'location') + def test_migrate_volume_invalid_location_info(self, location_info): + expected = False, None + capabilities = {'location_info': location_info} + host = {'capabilities': capabilities} + update = self.driver.migrate_volume(None, test_volume, host) + self.assertEqual(expected, update) + + def test_migrate_volume_invalid_driver(self): + expected = False, None + location_info = 'vendor:0:/path' + capabilities = {'location_info': location_info} + host = {'capabilities': capabilities} + update = self.driver.migrate_volume(None, test_volume, host) + self.assertEqual(expected, update) + + def test_migrate_volume_invalid_serial(self): + expected = False, None + location_info = '%s:%s:%s' % (self.driver.__class__.__name__, + TEST_SYSTEM_SERIAL2, TEST_POOL_NAME2) + capabilities = {'location_info': location_info} + host = {'capabilities': capabilities} + update = self.driver.migrate_volume(None, test_volume, host) + self.assertEqual(expected, update) + + def test_migrate_volume_same_pool(self): + expected = True, None + location_info = '%s:%s:%s' % (self.driver.__class__.__name__, + TEST_SYSTEM_SERIAL, TEST_POOL_NAME) + capabilities = {'location_info': location_info} + host = {'capabilities': capabilities} + update = self.driver.migrate_volume(None, test_volume, host) + self.assertEqual(expected, update) + + def test_migrate_volume_no_pool(self): + expected = False, None + self._system.pools.safe_get.return_value = None + location_info = '%s:%s:%s' % (self.driver.__class__.__name__, + TEST_SYSTEM_SERIAL, TEST_POOL_NAME2) + capabilities = {'location_info': location_info} + host = {'capabilities': capabilities} + update = self.driver.migrate_volume(None, test_volume, host) + self.assertEqual(expected, update) + + def test_migrate_volume(self): + expected = True, None + location_info = '%s:%s:%s' % (self.driver.__class__.__name__, + TEST_SYSTEM_SERIAL, TEST_POOL_NAME2) + capabilities = {'location_info': location_info} + host = {'capabilities': capabilities} + update = self.driver.migrate_volume(None, test_volume, host) + self.assertEqual(expected, update) + @ddt.ddt class InfiniboxDriverTestCaseFC(InfiniboxDriverTestCaseBase): diff --git a/cinder/volume/drivers/infinidat.py b/cinder/volume/drivers/infinidat.py index 0c1e84e780c..741c48019f0 100644 --- a/cinder/volume/drivers/infinidat.py +++ b/cinder/volume/drivers/infinidat.py @@ -128,10 +128,11 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver): 1.11 - fixed generic volume migration 1.12 - fixed volume multi-attach 1.13 - fixed consistency groups feature + 1.14 - added storage assisted volume migration """ - VERSION = '1.13' + VERSION = '1.14' # ThirdPartySystems wiki page CI_WIKI_NAME = "INFINIDAT_CI" @@ -573,6 +574,10 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver): def get_volume_stats(self, refresh=False): if self._volume_stats is None or refresh: pool = self._get_infinidat_pool() + location_info = '%(driver)s:%(serial)s:%(pool)s' % { + 'driver': self.__class__.__name__, + 'serial': self._system.get_serial(), + 'pool': self.configuration.infinidat_pool_name} free_capacity_bytes = (pool.get_free_physical_capacity() / capacity.byte) physical_capacity_bytes = (pool.get_physical_capacity() / @@ -587,6 +592,7 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver): vendor_name=VENDOR_NAME, driver_version=self.VERSION, storage_protocol=self._protocol, + location_info=location_info, consistencygroup_support=False, total_capacity_gb=total_capacity_gb, free_capacity_gb=free_capacity_gb, @@ -1325,3 +1331,44 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver): new_volume_name, volume_name, error) return model_update return {'_name_id': None, 'provider_location': None} + + @infinisdk_to_cinder_exceptions + def migrate_volume(self, ctxt, volume, host): + """Migrate a volume within the same InfiniBox system.""" + LOG.debug('Starting volume migration for volume %s to host %s', + volume.name, host) + if not (host and 'capabilities' in host): + LOG.error('No capabilities found for host %s', host) + return False, None + capabilities = host['capabilities'] + if not (capabilities and 'location_info' in capabilities): + LOG.error('No location info found for host %s', host) + return False, None + location = capabilities['location_info'] + try: + driver, serial, pool = location.split(':') + serial = int(serial) + except (AttributeError, ValueError) as error: + LOG.error('Invalid location info %s found for host %s: %s', + location, host, error) + return False, None + if driver != self.__class__.__name__: + LOG.debug('Unsupported storage driver %s found for host %s', + driver, host) + return False, None + if serial != self._system.get_serial(): + LOG.error('Unable to migrate volume %s to remote host %s', + volume.name, host) + return False, None + infinidat_volume = self._get_infinidat_volume(volume) + if pool == infinidat_volume.get_pool_name(): + LOG.debug('Volume %s already migrated to pool %s', + volume.name, pool) + return True, None + infinidat_pool = self._system.pools.safe_get(name=pool) + if infinidat_pool is None: + LOG.error('Destination pool %s not found on host %s', pool, host) + return False, None + infinidat_volume.move_pool(infinidat_pool) + LOG.info('Migrated volume %s to pool %s', volume.name, pool) + return True, None diff --git a/doc/source/configuration/block-storage/drivers/infinidat-volume-driver.rst b/doc/source/configuration/block-storage/drivers/infinidat-volume-driver.rst index 3b730ee2580..ee093a96155 100644 --- a/doc/source/configuration/block-storage/drivers/infinidat-volume-driver.rst +++ b/doc/source/configuration/block-storage/drivers/infinidat-volume-driver.rst @@ -26,6 +26,7 @@ Supported operations * Manage and unmanage volumes and snapshots. * List manageable volumes and snapshots. * Attach a volume to multiple instances at once (multi-attach). +* Host and storage assisted volume migration. External package installation ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/releasenotes/notes/infinidat-add-storage-assisted-migration-4e12f24ee297ef65.yaml b/releasenotes/notes/infinidat-add-storage-assisted-migration-4e12f24ee297ef65.yaml new file mode 100644 index 00000000000..034da994e2c --- /dev/null +++ b/releasenotes/notes/infinidat-add-storage-assisted-migration-4e12f24ee297ef65.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Infinidat: Added support for storage assisted volume migration + within a same InfiniBox host (iSCSI and FC).