Merge "Unity Driver: Backup volume via snapshot"
This commit is contained in:
commit
02389a1d2a
@ -98,7 +98,7 @@ class MockClient(object):
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def get_snap(name=None):
|
def get_snap(name=None):
|
||||||
snap = test_client.MockResource(name=name)
|
snap = test_client.MockResource(name=name, _id=name)
|
||||||
if name is not None:
|
if name is not None:
|
||||||
ret = snap
|
ret = snap
|
||||||
else:
|
else:
|
||||||
@ -192,6 +192,18 @@ def get_lun_pl(name):
|
|||||||
return 'id^%s|system^CLIENT_SERIAL|type^lun|version^None' % name
|
return 'id^%s|system^CLIENT_SERIAL|type^lun|version^None' % name
|
||||||
|
|
||||||
|
|
||||||
|
def get_snap_pl(name):
|
||||||
|
return 'id^%s|system^CLIENT_SERIAL|type^snapshot|version^None' % name
|
||||||
|
|
||||||
|
|
||||||
|
def get_connector_uids(adapter, connector):
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
def get_connection_info(adapter, hlu, host, connector):
|
||||||
|
return {}
|
||||||
|
|
||||||
|
|
||||||
def patch_for_unity_adapter(func):
|
def patch_for_unity_adapter(func):
|
||||||
@functools.wraps(func)
|
@functools.wraps(func)
|
||||||
@mock.patch('cinder.volume.drivers.dell_emc.unity.utils.'
|
@mock.patch('cinder.volume.drivers.dell_emc.unity.utils.'
|
||||||
@ -206,6 +218,28 @@ def patch_for_unity_adapter(func):
|
|||||||
return func_wrapper
|
return func_wrapper
|
||||||
|
|
||||||
|
|
||||||
|
def patch_for_concrete_adapter(clz_str):
|
||||||
|
def inner_decorator(func):
|
||||||
|
@functools.wraps(func)
|
||||||
|
@mock.patch('%s.get_connector_uids' % clz_str,
|
||||||
|
new=get_connector_uids)
|
||||||
|
@mock.patch('%s.get_connection_info' % clz_str,
|
||||||
|
new=get_connection_info)
|
||||||
|
def func_wrapper(*args, **kwargs):
|
||||||
|
return func(*args, **kwargs)
|
||||||
|
return func_wrapper
|
||||||
|
|
||||||
|
return inner_decorator
|
||||||
|
|
||||||
|
|
||||||
|
patch_for_iscsi_adapter = patch_for_concrete_adapter(
|
||||||
|
'cinder.volume.drivers.dell_emc.unity.adapter.ISCSIAdapter')
|
||||||
|
|
||||||
|
|
||||||
|
patch_for_fc_adapter = patch_for_concrete_adapter(
|
||||||
|
'cinder.volume.drivers.dell_emc.unity.adapter.FCAdapter')
|
||||||
|
|
||||||
|
|
||||||
########################
|
########################
|
||||||
#
|
#
|
||||||
# Start of Tests
|
# Start of Tests
|
||||||
@ -233,8 +267,8 @@ class CommonAdapterTest(unittest.TestCase):
|
|||||||
snap = mock.Mock(volume=volume)
|
snap = mock.Mock(volume=volume)
|
||||||
snap.name = 'abc-def_snap'
|
snap.name = 'abc-def_snap'
|
||||||
result = self.adapter.create_snapshot(snap)
|
result = self.adapter.create_snapshot(snap)
|
||||||
self.assertEqual('abc-def_snap', result.name)
|
self.assertEqual(get_snap_pl('lun_43'), result['provider_location'])
|
||||||
self.assertEqual('lun_43', result.get_id())
|
self.assertEqual('lun_43', result['provider_id'])
|
||||||
|
|
||||||
def test_delete_snap(self):
|
def test_delete_snap(self):
|
||||||
def f():
|
def f():
|
||||||
@ -311,22 +345,7 @@ class CommonAdapterTest(unittest.TestCase):
|
|||||||
self.assertEqual(self.adapter.array_ca_cert_path,
|
self.assertEqual(self.adapter.array_ca_cert_path,
|
||||||
self.adapter.verify_cert)
|
self.adapter.verify_cert)
|
||||||
|
|
||||||
def test_initialize_connection_common(self):
|
def test_terminate_connection_volume(self):
|
||||||
volume = mock.Mock(provider_location='id^lun_43', id='id_43')
|
|
||||||
connector = {'host': 'host1'}
|
|
||||||
data = self.adapter.initialize_connection(volume, connector)['data']
|
|
||||||
self.assertTrue(data['target_discovered'])
|
|
||||||
self.assertEqual('id_43', data['volume_id'])
|
|
||||||
|
|
||||||
def test_initialize_connection_for_resource(self):
|
|
||||||
snap = test_client.MockResource(_id='snap_1')
|
|
||||||
connector = {'host': 'host1'}
|
|
||||||
data = self.adapter._initialize_connection(
|
|
||||||
snap, connector, 'snap_1')['data']
|
|
||||||
self.assertTrue(data['target_discovered'])
|
|
||||||
self.assertEqual('snap_1', data['volume_id'])
|
|
||||||
|
|
||||||
def test_terminate_connection_common(self):
|
|
||||||
def f():
|
def f():
|
||||||
volume = mock.Mock(provider_location='id^lun_43', id='id_43')
|
volume = mock.Mock(provider_location='id^lun_43', id='id_43')
|
||||||
connector = {'host': 'host1'}
|
connector = {'host': 'host1'}
|
||||||
@ -334,11 +353,12 @@ class CommonAdapterTest(unittest.TestCase):
|
|||||||
|
|
||||||
self.assertRaises(ex.DetachIsCalled, f)
|
self.assertRaises(ex.DetachIsCalled, f)
|
||||||
|
|
||||||
def test_terminate_connection_snap(self):
|
def test_terminate_connection_snapshot(self):
|
||||||
def f():
|
def f():
|
||||||
connector = {'host': 'host1'}
|
connector = {'host': 'host1'}
|
||||||
snap = test_client.MockResource(_id='snap_0')
|
snap = mock.Mock(id='snap_0', name='snap_0')
|
||||||
self.adapter._terminate_connection(snap, connector)
|
snap.name = 'snap_0'
|
||||||
|
self.adapter.terminate_connection_snapshot(snap, connector)
|
||||||
|
|
||||||
self.assertRaises(ex.DetachIsCalled, f)
|
self.assertRaises(ex.DetachIsCalled, f)
|
||||||
|
|
||||||
@ -475,6 +495,25 @@ class FCAdapterTest(unittest.TestCase):
|
|||||||
wwns = ['8899AABBCCDDEEFF', '8899AABBCCDDFFEE']
|
wwns = ['8899AABBCCDDEEFF', '8899AABBCCDDFFEE']
|
||||||
self.assertListEqual(wwns, ret['target_wwn'])
|
self.assertListEqual(wwns, ret['target_wwn'])
|
||||||
|
|
||||||
|
@patch_for_fc_adapter
|
||||||
|
def test_initialize_connection_volume(self):
|
||||||
|
volume = mock.Mock(provider_location='id^lun_43', id='id_43')
|
||||||
|
connector = {'host': 'host1'}
|
||||||
|
conn_info = self.adapter.initialize_connection(volume, connector)
|
||||||
|
self.assertEqual('fibre_channel', conn_info['driver_volume_type'])
|
||||||
|
self.assertTrue(conn_info['data']['target_discovered'])
|
||||||
|
self.assertEqual('id_43', conn_info['data']['volume_id'])
|
||||||
|
|
||||||
|
@patch_for_fc_adapter
|
||||||
|
def test_initialize_connection_snapshot(self):
|
||||||
|
snap = mock.Mock(id='snap_1', name='snap_1')
|
||||||
|
connector = {'host': 'host1'}
|
||||||
|
conn_info = self.adapter.initialize_connection_snapshot(
|
||||||
|
snap, connector)
|
||||||
|
self.assertEqual('fibre_channel', conn_info['driver_volume_type'])
|
||||||
|
self.assertTrue(conn_info['data']['target_discovered'])
|
||||||
|
self.assertEqual('snap_1', conn_info['data']['volume_id'])
|
||||||
|
|
||||||
def test_terminate_connection_auto_zone_enabled(self):
|
def test_terminate_connection_auto_zone_enabled(self):
|
||||||
connector = {'host': 'host1', 'wwpns': 'abcdefg'}
|
connector = {'host': 'host1', 'wwpns': 'abcdefg'}
|
||||||
volume = mock.Mock(provider_location='id^lun_41', id='id_41')
|
volume = mock.Mock(provider_location='id^lun_41', id='id_41')
|
||||||
@ -514,3 +553,22 @@ class ISCSIAdapterTest(unittest.TestCase):
|
|||||||
self.assertEqual(hlu, info['target_lun'])
|
self.assertEqual(hlu, info['target_lun'])
|
||||||
self.assertTrue(info['target_portal'] in target_portals)
|
self.assertTrue(info['target_portal'] in target_portals)
|
||||||
self.assertTrue(info['target_iqn'] in target_iqns)
|
self.assertTrue(info['target_iqn'] in target_iqns)
|
||||||
|
|
||||||
|
@patch_for_iscsi_adapter
|
||||||
|
def test_initialize_connection_volume(self):
|
||||||
|
volume = mock.Mock(provider_location='id^lun_43', id='id_43')
|
||||||
|
connector = {'host': 'host1'}
|
||||||
|
conn_info = self.adapter.initialize_connection(volume, connector)
|
||||||
|
self.assertEqual('iscsi', conn_info['driver_volume_type'])
|
||||||
|
self.assertTrue(conn_info['data']['target_discovered'])
|
||||||
|
self.assertEqual('id_43', conn_info['data']['volume_id'])
|
||||||
|
|
||||||
|
@patch_for_iscsi_adapter
|
||||||
|
def test_initialize_connection_snapshot(self):
|
||||||
|
snap = mock.Mock(id='snap_1', name='snap_1')
|
||||||
|
connector = {'host': 'host1'}
|
||||||
|
conn_info = self.adapter.initialize_connection_snapshot(
|
||||||
|
snap, connector)
|
||||||
|
self.assertEqual('iscsi', conn_info['driver_volume_type'])
|
||||||
|
self.assertTrue(conn_info['data']['target_discovered'])
|
||||||
|
self.assertEqual('snap_1', conn_info['data']['volume_id'])
|
||||||
|
@ -56,6 +56,7 @@ class MockAdapter(object):
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def create_snapshot(snapshot):
|
def create_snapshot(snapshot):
|
||||||
snapshot.exists = True
|
snapshot.exists = True
|
||||||
|
return snapshot
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def delete_snapshot(snapshot):
|
def delete_snapshot(snapshot):
|
||||||
@ -88,6 +89,14 @@ class MockAdapter(object):
|
|||||||
def get_pool_name(volume):
|
def get_pool_name(volume):
|
||||||
return 'pool_0'
|
return 'pool_0'
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def initialize_connection_snapshot(snapshot, connector):
|
||||||
|
return {'snapshot': snapshot, 'connector': connector}
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def terminate_connection_snapshot(snapshot, connector):
|
||||||
|
return {'snapshot': snapshot, 'connector': connector}
|
||||||
|
|
||||||
|
|
||||||
########################
|
########################
|
||||||
#
|
#
|
||||||
@ -232,3 +241,29 @@ class UnityDriverTest(unittest.TestCase):
|
|||||||
def test_unmanage(self):
|
def test_unmanage(self):
|
||||||
ret = self.driver.unmanage(None)
|
ret = self.driver.unmanage(None)
|
||||||
self.assertIsNone(ret)
|
self.assertIsNone(ret)
|
||||||
|
|
||||||
|
def test_backup_use_temp_snapshot(self):
|
||||||
|
self.assertTrue(self.driver.backup_use_temp_snapshot())
|
||||||
|
|
||||||
|
def test_create_export_snapshot(self):
|
||||||
|
snapshot = self.driver.create_export_snapshot(self.get_context(),
|
||||||
|
self.get_snapshot(),
|
||||||
|
self.get_connector())
|
||||||
|
self.assertTrue(snapshot.exists)
|
||||||
|
|
||||||
|
def test_remove_export_snapshot(self):
|
||||||
|
snapshot = self.get_snapshot()
|
||||||
|
self.driver.remove_export_snapshot(self.get_context(), snapshot)
|
||||||
|
self.assertFalse(snapshot.exists)
|
||||||
|
|
||||||
|
def test_initialize_connection_snapshot(self):
|
||||||
|
snapshot = self.get_snapshot()
|
||||||
|
conn_info = self.driver.initialize_connection_snapshot(
|
||||||
|
snapshot, self.get_connector())
|
||||||
|
self.assertEqual(snapshot, conn_info['snapshot'])
|
||||||
|
|
||||||
|
def test_terminate_connection_snapshot(self):
|
||||||
|
snapshot = self.get_snapshot()
|
||||||
|
conn_info = self.driver.terminate_connection_snapshot(
|
||||||
|
snapshot, self.get_connector())
|
||||||
|
self.assertEqual(snapshot, conn_info['snapshot'])
|
||||||
|
@ -129,8 +129,8 @@ class CommonAdapter(object):
|
|||||||
location = self._build_provider_location(
|
location = self._build_provider_location(
|
||||||
lun_type='lun',
|
lun_type='lun',
|
||||||
lun_id=lun.get_id())
|
lun_id=lun.get_id())
|
||||||
model_update = {'provider_location': location}
|
return {'provider_location': location,
|
||||||
return model_update
|
'provider_id': lun.get_id()}
|
||||||
|
|
||||||
def delete_volume(self, volume):
|
def delete_volume(self, volume):
|
||||||
lun_id = self.get_lun_id(volume)
|
lun_id = self.get_lun_id(volume)
|
||||||
@ -141,6 +141,7 @@ class CommonAdapter(object):
|
|||||||
else:
|
else:
|
||||||
self.client.delete_lun(lun_id)
|
self.client.delete_lun(lun_id)
|
||||||
|
|
||||||
|
@cinder_utils.trace
|
||||||
def _initialize_connection(self, lun_or_snap, connector, vol_id):
|
def _initialize_connection(self, lun_or_snap, connector, vol_id):
|
||||||
host = self.client.create_host(connector['host'],
|
host = self.client.create_host(connector['host'],
|
||||||
self.get_connector_uids(connector))
|
self.get_connector_uids(connector))
|
||||||
@ -156,17 +157,20 @@ class CommonAdapter(object):
|
|||||||
LOG.debug('Initialized connection info: %s', conn_info)
|
LOG.debug('Initialized connection info: %s', conn_info)
|
||||||
return conn_info
|
return conn_info
|
||||||
|
|
||||||
|
@cinder_utils.trace
|
||||||
def initialize_connection(self, volume, connector):
|
def initialize_connection(self, volume, connector):
|
||||||
lun = self.client.get_lun(lun_id=self.get_lun_id(volume))
|
lun = self.client.get_lun(lun_id=self.get_lun_id(volume))
|
||||||
return self._initialize_connection(lun, connector, volume.id)
|
return self._initialize_connection(lun, connector, volume.id)
|
||||||
|
|
||||||
|
@cinder_utils.trace
|
||||||
def _terminate_connection(self, lun_or_snap, connector):
|
def _terminate_connection(self, lun_or_snap, connector):
|
||||||
host = self.client.get_host(connector['host'])
|
host = self.client.get_host(connector['host'])
|
||||||
self.client.detach(host, lun_or_snap)
|
self.client.detach(host, lun_or_snap)
|
||||||
|
|
||||||
|
@cinder_utils.trace
|
||||||
def terminate_connection(self, volume, connector):
|
def terminate_connection(self, volume, connector):
|
||||||
lun = self.client.get_lun(lun_id=self.get_lun_id(volume))
|
lun = self.client.get_lun(lun_id=self.get_lun_id(volume))
|
||||||
self._terminate_connection(lun, connector)
|
return self._terminate_connection(lun, connector)
|
||||||
|
|
||||||
def get_connector_uids(self, connector):
|
def get_connector_uids(self, connector):
|
||||||
return None
|
return None
|
||||||
@ -247,7 +251,11 @@ class CommonAdapter(object):
|
|||||||
:param snapshot: snapshot information.
|
:param snapshot: snapshot information.
|
||||||
"""
|
"""
|
||||||
src_lun_id = self.get_lun_id(snapshot.volume)
|
src_lun_id = self.get_lun_id(snapshot.volume)
|
||||||
return self.client.create_snap(src_lun_id, snapshot.name)
|
snap = self.client.create_snap(src_lun_id, snapshot.name)
|
||||||
|
location = self._build_provider_location(lun_type='snapshot',
|
||||||
|
lun_id=snap.get_id())
|
||||||
|
return {'provider_location': location,
|
||||||
|
'provider_id': snap.get_id()}
|
||||||
|
|
||||||
def delete_snapshot(self, snapshot):
|
def delete_snapshot(self, snapshot):
|
||||||
"""Deletes a snapshot.
|
"""Deletes a snapshot.
|
||||||
@ -300,7 +308,8 @@ class CommonAdapter(object):
|
|||||||
lun.modify(name=volume.name)
|
lun.modify(name=volume.name)
|
||||||
return {'provider_location':
|
return {'provider_location':
|
||||||
self._build_provider_location(lun_id=lun.get_id(),
|
self._build_provider_location(lun_id=lun.get_id(),
|
||||||
lun_type='lun')}
|
lun_type='lun'),
|
||||||
|
'provider_id': lun.get_id()}
|
||||||
|
|
||||||
def manage_existing_get_size(self, volume, existing_ref):
|
def manage_existing_get_size(self, volume, existing_ref):
|
||||||
"""Returns size of volume to be managed by `manage_existing`.
|
"""Returns size of volume to be managed by `manage_existing`.
|
||||||
@ -364,7 +373,8 @@ class CommonAdapter(object):
|
|||||||
data from the Unity snapshot to the `volume`.
|
data from the Unity snapshot to the `volume`.
|
||||||
"""
|
"""
|
||||||
model_update = self.create_volume(volume)
|
model_update = self.create_volume(volume)
|
||||||
volume.provider_location = model_update['provider_location']
|
# Update `provider_location` and `provider_id` of `volume` explicitly.
|
||||||
|
volume.update(model_update)
|
||||||
src_id = snap.get_id()
|
src_id = snap.get_id()
|
||||||
dest_lun = self.client.get_lun(lun_id=self.get_lun_id(volume))
|
dest_lun = self.client.get_lun(lun_id=self.get_lun_id(volume))
|
||||||
try:
|
try:
|
||||||
@ -430,6 +440,16 @@ class CommonAdapter(object):
|
|||||||
def get_pool_name(self, volume):
|
def get_pool_name(self, volume):
|
||||||
return self.client.get_pool_name(volume.name)
|
return self.client.get_pool_name(volume.name)
|
||||||
|
|
||||||
|
@cinder_utils.trace
|
||||||
|
def initialize_connection_snapshot(self, snapshot, connector):
|
||||||
|
snap = self.client.get_snap(snapshot.name)
|
||||||
|
return self._initialize_connection(snap, connector, snapshot.id)
|
||||||
|
|
||||||
|
@cinder_utils.trace
|
||||||
|
def terminate_connection_snapshot(self, snapshot, connector):
|
||||||
|
snap = self.client.get_snap(snapshot.name)
|
||||||
|
return self._terminate_connection(snap, connector)
|
||||||
|
|
||||||
|
|
||||||
class ISCSIAdapter(CommonAdapter):
|
class ISCSIAdapter(CommonAdapter):
|
||||||
protocol = PROTOCOL_ISCSI
|
protocol = PROTOCOL_ISCSI
|
||||||
@ -496,8 +516,11 @@ class FCAdapter(CommonAdapter):
|
|||||||
data['target_lun'] = hlu
|
data['target_lun'] = hlu
|
||||||
return data
|
return data
|
||||||
|
|
||||||
def terminate_connection(self, volume, connector):
|
@cinder_utils.trace
|
||||||
super(FCAdapter, self).terminate_connection(volume, connector)
|
def _terminate_connection(self, lun_or_snap, connector):
|
||||||
|
# For FC, terminate_connection needs to return data to zone manager
|
||||||
|
# which would clean the zone based on the data.
|
||||||
|
super(FCAdapter, self)._terminate_connection(lun_or_snap, connector)
|
||||||
|
|
||||||
ret = None
|
ret = None
|
||||||
if self.auto_zone_enabled:
|
if self.auto_zone_enabled:
|
||||||
@ -510,7 +533,6 @@ class FCAdapter(CommonAdapter):
|
|||||||
targets = self.client.get_fc_target_info(logged_in_only=True)
|
targets = self.client.get_fc_target_info(logged_in_only=True)
|
||||||
ret['data'] = self._get_fc_zone_info(connector['wwpns'],
|
ret['data'] = self._get_fc_zone_info(connector['wwpns'],
|
||||||
targets)
|
targets)
|
||||||
|
|
||||||
return ret
|
return ret
|
||||||
|
|
||||||
def _get_fc_zone_info(self, initiator_wwns, target_wwns):
|
def _get_fc_zone_info(self, initiator_wwns, target_wwns):
|
||||||
|
@ -152,27 +152,12 @@ class UnityDriver(driver.ManageableVD,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
"""
|
"""
|
||||||
LOG.debug("Entering initialize_connection"
|
return self.adapter.initialize_connection(volume, connector)
|
||||||
" - connector: %(connector)s.",
|
|
||||||
{'connector': connector})
|
|
||||||
conn_info = self.adapter.initialize_connection(volume,
|
|
||||||
connector)
|
|
||||||
LOG.debug("Exit initialize_connection"
|
|
||||||
" - Returning connection info: %(conn_info)s.",
|
|
||||||
{'conn_info': conn_info})
|
|
||||||
return conn_info
|
|
||||||
|
|
||||||
@zm_utils.remove_fc_zone
|
@zm_utils.remove_fc_zone
|
||||||
def terminate_connection(self, volume, connector, **kwargs):
|
def terminate_connection(self, volume, connector, **kwargs):
|
||||||
"""Disallow connection from connector."""
|
"""Disallow connection from connector."""
|
||||||
LOG.debug("Entering terminate_connection"
|
return self.adapter.terminate_connection(volume, connector)
|
||||||
" - connector: %(connector)s.",
|
|
||||||
{'connector': connector})
|
|
||||||
conn_info = self.adapter.terminate_connection(volume, connector)
|
|
||||||
LOG.debug("Exit terminate_connection"
|
|
||||||
" - Returning connection info: %(conn_info)s.",
|
|
||||||
{'conn_info': conn_info})
|
|
||||||
return conn_info
|
|
||||||
|
|
||||||
def get_volume_stats(self, refresh=False):
|
def get_volume_stats(self, refresh=False):
|
||||||
"""Get volume stats.
|
"""Get volume stats.
|
||||||
@ -211,3 +196,20 @@ class UnityDriver(driver.ManageableVD,
|
|||||||
def unmanage(self, volume):
|
def unmanage(self, volume):
|
||||||
"""Unmanages a volume."""
|
"""Unmanages a volume."""
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
def backup_use_temp_snapshot(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def create_export_snapshot(self, context, snapshot, connector):
|
||||||
|
"""Creates the snapshot for backup."""
|
||||||
|
return self.adapter.create_snapshot(snapshot)
|
||||||
|
|
||||||
|
def remove_export_snapshot(self, context, snapshot):
|
||||||
|
"""Deletes the snapshot for backup."""
|
||||||
|
self.adapter.delete_snapshot(snapshot)
|
||||||
|
|
||||||
|
def initialize_connection_snapshot(self, snapshot, connector, **kwargs):
|
||||||
|
return self.adapter.initialize_connection_snapshot(snapshot, connector)
|
||||||
|
|
||||||
|
def terminate_connection_snapshot(self, snapshot, connector, **kwargs):
|
||||||
|
return self.adapter.terminate_connection_snapshot(snapshot, connector)
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Add support to backup volume using snapshot in the Unity driver.
|
Loading…
Reference in New Issue
Block a user