[Unity] Storage-assisted migration support
Adds storage-assisted migration support for Unity driver. Change-Id: Ibaf2e6a09f93b167b6b1477d5653922d807fccce Implements: blueprint unity-storage-assisted-volume-migration-support
This commit is contained in:
parent
7b5d4d03fc
commit
17bef5e591
@ -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):
|
||||
|
@ -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'))
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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 <volume_id> <host>`` or
|
||||
``cinder migrate <volume_id> <host>``, 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
|
||||
~~~~~~~~~~~
|
||||
|
||||
|
@ -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
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Dell EMC Unity Driver: Added storage-assisted migration support.
|
Loading…
Reference in New Issue
Block a user