diff --git a/cinder/tests/unit/volume/drivers/dell_emc/unity/test_adapter.py b/cinder/tests/unit/volume/drivers/dell_emc/unity/test_adapter.py index 65adef08e1a..c83f829ef56 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/unity/test_adapter.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/unity/test_adapter.py @@ -219,6 +219,18 @@ class MockClient(object): def restore_snapshot(self, snap_name): return test_client.MockResource(name="back_snap") + def get_pool_id_by_name(self, name): + pools = {'PoolA': 'pool_1', + 'PoolB': 'pool_2', + 'PoolC': 'pool_3'} + return pools.get(name, None) + + def migrate_lun(self, lun_id, dest_pool_id): + if dest_pool_id == 'pool_2': + return True + if dest_pool_id == 'pool_3': + return False + class MockLookupService(object): @staticmethod @@ -797,6 +809,38 @@ class CommonAdapterTest(test.TestCase): snapshot = MockOSResource(id='2', name='snap_1') self.adapter.restore_snapshot(volume, snapshot) + def test_get_pool_id_by_name(self): + pool_name = 'PoolA' + pool_id = self.adapter.get_pool_id_by_name(pool_name) + self.assertEqual('pool_1', pool_id) + + def test_migrate_volume(self): + provider_location = 'id^1|system^FNM001|type^lun|version^05.00' + volume = MockOSResource(id='1', name='vol_1', + host='HostA@BackendB#PoolA', + provider_location=provider_location) + host = {'host': 'HostA@BackendB#PoolB'} + ret = self.adapter.migrate_volume(volume, host) + self.assertEqual((True, {}), ret) + + def test_migrate_volume_failed(self): + provider_location = 'id^1|system^FNM001|type^lun|version^05.00' + volume = MockOSResource(id='1', name='vol_1', + host='HostA@BackendB#PoolA', + provider_location=provider_location) + host = {'host': 'HostA@BackendB#PoolC'} + ret = self.adapter.migrate_volume(volume, host) + self.assertEqual((False, None), ret) + + def test_migrate_volume_cross_backends(self): + provider_location = 'id^1|system^FNM001|type^lun|version^05.00' + volume = MockOSResource(id='1', name='vol_1', + host='HostA@BackendA#PoolA', + provider_location=provider_location) + host = {'host': 'HostA@BackendB#PoolB'} + ret = self.adapter.migrate_volume(volume, host) + self.assertEqual((False, None), ret) + class FCAdapterTest(test.TestCase): def setUp(self): diff --git a/cinder/tests/unit/volume/drivers/dell_emc/unity/test_client.py b/cinder/tests/unit/volume/drivers/dell_emc/unity/test_client.py index 8fc03b52807..8a3a5c6d7ce 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/unity/test_client.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/unity/test_client.py @@ -197,6 +197,11 @@ class MockResource(object): def restore(self, delete_backup): return MockResource(_id='snap_1', name="internal_snap") + def migrate(self, dest_pool): + if dest_pool.id == 'pool_2': + return False + return True + class MockResourceList(object): def __init__(self, names=None, ids=None): @@ -253,7 +258,11 @@ class MockSystem(object): return MockResource(name, _id) @staticmethod - def get_pool(): + def get_pool(_id=None, name=None): + if name == 'Pool 3': + return MockResource(name, 'pool_3') + if name or _id: + return MockResource(name, _id) return MockResourceList(['Pool 1', 'Pool 2']) @staticmethod @@ -553,6 +562,17 @@ class ClientTest(unittest.TestCase): lun = self.client.extend_lun('ev_4', 5) self.assertEqual(5, lun.total_size_gb) + def test_migrate_lun_success(self): + ret = self.client.migrate_lun('lun_0', 'pool_1') + self.assertTrue(ret) + + def test_migrate_lun_failed(self): + ret = self.client.migrate_lun('lun_0', 'pool_2') + self.assertFalse(ret) + + def test_get_pool_id_by_name(self): + self.assertEqual('pool_3', self.client.get_pool_id_by_name('Pool 3')) + def test_get_pool_name(self): self.assertEqual('Pool0', self.client.get_pool_name('lun_0')) diff --git a/cinder/tests/unit/volume/drivers/dell_emc/unity/test_driver.py b/cinder/tests/unit/volume/drivers/dell_emc/unity/test_driver.py index 737ea7aa2b0..eb20db77cd7 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/unity/test_driver.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/unity/test_driver.py @@ -100,6 +100,11 @@ class MockAdapter(object): def restore_snapshot(volume, snapshot): return True + @staticmethod + def migrate_volume(volume, host): + return True, {} + + ######################## # # Start of Tests @@ -185,6 +190,13 @@ class UnityDriverTest(unittest.TestCase): self.driver.delete_volume(volume) self.assertFalse(volume.exists) + def test_migrate_volume(self): + volume = self.get_volume() + ret = self.driver.migrate_volume(self.get_context(), + volume, + 'HostA@BackendB#PoolC') + self.assertEqual((True, {}), ret) + def test_create_snapshot(self): snapshot = self.get_snapshot() self.driver.create_snapshot(snapshot) diff --git a/cinder/tests/unit/volume/drivers/dell_emc/unity/test_utils.py b/cinder/tests/unit/volume/drivers/dell_emc/unity/test_utils.py index 752cc63a1fc..c822c90c5f0 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/unity/test_utils.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/unity/test_utils.py @@ -193,6 +193,21 @@ class UnityUtilsTest(unittest.TestCase): volume = test_adapter.MockOSResource(host='host@backend#pool_name') self.assertEqual('pool_name', utils.get_pool_name(volume)) + def test_get_pool_name_from_host(self): + host = {'host': 'host@backend#pool_name'} + ret = utils.get_pool_name_from_host(host) + self.assertEqual('pool_name', ret) + + def get_backend_name_from_volume(self): + volume = test_adapter.MockOSResource(host='host@backend#pool_name') + ret = utils.get_backend_name_from_volume(volume) + self.assertEqual('host@backend', ret) + + def get_backend_name_from_host(self): + host = {'host': 'host@backend#pool_name'} + ret = utils.get_backend_name_from_volume(host) + self.assertEqual('host@backend', ret) + def test_ignore_exception(self): class IgnoredException(Exception): pass diff --git a/cinder/volume/drivers/dell_emc/unity/adapter.py b/cinder/volume/drivers/dell_emc/unity/adapter.py index ec5348f1f48..36d8284898f 100644 --- a/cinder/volume/drivers/dell_emc/unity/adapter.py +++ b/cinder/volume/drivers/dell_emc/unity/adapter.py @@ -763,6 +763,9 @@ class CommonAdapter(object): def get_pool_name(self, volume): return self.client.get_pool_name(volume.name) + def get_pool_id_by_name(self, name): + return self.client.get_pool_id_by_name(name=name) + @cinder_utils.trace def initialize_connection_snapshot(self, snapshot, connector): snap = self.client.get_snap(snapshot.name) @@ -777,6 +780,40 @@ class CommonAdapter(object): def restore_snapshot(self, volume, snapshot): return self.client.restore_snapshot(snapshot.name) + def migrate_volume(self, volume, host): + """Leverage the Unity move session functionality. + + This method is invoked at the source backend. + """ + log_params = { + 'name': volume.name, + 'src_host': volume.host, + 'dest_host': host['host'] + } + LOG.info('Migrate Volume: %(name)s, host: %(src_host)s, destination: ' + '%(dest_host)s', log_params) + + src_backend = utils.get_backend_name_from_volume(volume) + dest_backend = utils.get_backend_name_from_host(host) + + if src_backend != dest_backend: + LOG.debug('Cross-backends migration not supported by Unity ' + 'driver. Falling back to host-assisted migration.') + return False, None + + lun_id = self.get_lun_id(volume) + dest_pool_name = utils.get_pool_name_from_host(host) + dest_pool_id = self.get_pool_id_by_name(dest_pool_name) + + if self.client.migrate_lun(lun_id, dest_pool_id): + LOG.debug('Volume migrated successfully.') + model_update = {} + return True, model_update + + LOG.debug('Volume migrated failed. Falling back to ' + 'host-assisted migration.') + return False, None + class ISCSIAdapter(CommonAdapter): protocol = PROTOCOL_ISCSI diff --git a/cinder/volume/drivers/dell_emc/unity/client.py b/cinder/volume/drivers/dell_emc/unity/client.py index 8ecba335de1..31ffcb72e14 100644 --- a/cinder/volume/drivers/dell_emc/unity/client.py +++ b/cinder/volume/drivers/dell_emc/unity/client.py @@ -137,6 +137,11 @@ class UnityClient(object): lun_id) return lun + def migrate_lun(self, lun_id, dest_pool_id): + lun = self.system.get_lun(lun_id) + dest_pool = self.system.get_pool(dest_pool_id) + return lun.migrate(dest_pool) + def get_pools(self): """Gets all storage pools on the Unity system. @@ -333,6 +338,10 @@ class UnityClient(object): qos_specs.get(utils.QOS_MAX_BWS)) return limit_policy + def get_pool_id_by_name(self, name): + pool = self.system.get_pool(name=name) + return pool.get_id() + def get_pool_name(self, lun_name): lun = self.system.get_lun(name=lun_name) return lun.pool_name diff --git a/cinder/volume/drivers/dell_emc/unity/driver.py b/cinder/volume/drivers/dell_emc/unity/driver.py index 99a1b9b84b6..aed0d161b2d 100644 --- a/cinder/volume/drivers/dell_emc/unity/driver.py +++ b/cinder/volume/drivers/dell_emc/unity/driver.py @@ -59,9 +59,10 @@ class UnityDriver(driver.ManageableVD, 3.1.0 - Support revert to snapshot API 4.0.0 - Support remove empty host 4.2.0 - Support compressed volume + 5.0.0 - Support storage assisted volume migration """ - VERSION = '04.02.00' + VERSION = '05.00.00' VENDOR = 'Dell EMC' # ThirdPartySystems wiki page CI_WIKI_NAME = "EMC_UNITY_CI" @@ -104,6 +105,10 @@ class UnityDriver(driver.ManageableVD, """Deletes a volume.""" self.adapter.delete_volume(volume) + def migrate_volume(self, context, volume, host): + """Migrates a volume.""" + return self.adapter.migrate_volume(volume, host) + def create_snapshot(self, snapshot): """Creates a snapshot.""" self.adapter.create_snapshot(snapshot) diff --git a/cinder/volume/drivers/dell_emc/unity/utils.py b/cinder/volume/drivers/dell_emc/unity/utils.py index c2d77fa5267..e289a0b0ee2 100644 --- a/cinder/volume/drivers/dell_emc/unity/utils.py +++ b/cinder/volume/drivers/dell_emc/unity/utils.py @@ -180,6 +180,18 @@ def get_pool_name(volume): return vol_utils.extract_host(volume.host, 'pool') +def get_pool_name_from_host(host): + return vol_utils.extract_host(host['host'], 'pool') + + +def get_backend_name_from_volume(volume): + return vol_utils.extract_host(volume.host, 'backend') + + +def get_backend_name_from_host(host): + return vol_utils.extract_host(host['host'], 'backend') + + def get_extra_spec(volume, spec_key): spec_value = None type_id = volume.volume_type_id diff --git a/doc/source/configuration/block-storage/drivers/dell-emc-unity-driver.rst b/doc/source/configuration/block-storage/drivers/dell-emc-unity-driver.rst index b05711e3a48..be1f51d5e59 100644 --- a/doc/source/configuration/block-storage/drivers/dell-emc-unity-driver.rst +++ b/doc/source/configuration/block-storage/drivers/dell-emc-unity-driver.rst @@ -251,7 +251,7 @@ following commands to create a thick volume. Compressed volume support -~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ +~~~~~~~~~~~~~~~~~~~~~~~~~ Unity driver supports ``compressed volume`` creation, modification and deletion. In order to create a compressed volume, a volume type which @@ -265,6 +265,23 @@ enables compression support needs to be created first: Then create volume and specify the new created volume type. +Storage-assisted volume migration support +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +Unity driver supports storage-assisted volume migration, when the user starts +migrating with ``cinder migrate --force-host-copy False `` or +``cinder migrate ``, cinder will try to leverage the Unity's +native volume migration functionality. If Unity fails to migrate the volume, +host-assisted migration will be triggered. + +In the following scenarios, Unity storage-assisted volume migration will not be +triggered. Instead, host-assisted volume migration will be triggered: + +- Volume is to be migrated across backends. +- Migration of cloned volume. For example, if vol_2 was cloned from vol_1, + the storage-assisted volume migration of vol_2 will not be triggered. + + QoS support ~~~~~~~~~~~ diff --git a/doc/source/reference/support-matrix.ini b/doc/source/reference/support-matrix.ini index 4cfc4a9c094..192fd5816ab 100644 --- a/doc/source/reference/support-matrix.ini +++ b/doc/source/reference/support-matrix.ini @@ -673,7 +673,7 @@ driver.datera=missing driver.dell_emc_powermax=complete driver.dell_emc_ps=missing driver.dell_emc_sc=missing -driver.dell_emc_unity=missing +driver.dell_emc_unity=complete driver.dell_emc_vmax_af=complete driver.dell_emc_vmax_3=complete driver.dell_emc_vmax_v2=missing diff --git a/releasenotes/notes/unity-storage-assisted-migration-support-145fce87f36f1ecc.yaml b/releasenotes/notes/unity-storage-assisted-migration-support-145fce87f36f1ecc.yaml new file mode 100644 index 00000000000..b76293e85b8 --- /dev/null +++ b/releasenotes/notes/unity-storage-assisted-migration-support-145fce87f36f1ecc.yaml @@ -0,0 +1,4 @@ +--- +features: + - | + Dell EMC Unity Driver: Added storage-assisted migration support.