Merge "Unity Driver: Backup volume via snapshot"
This commit is contained in:
commit
02389a1d2a
@ -98,7 +98,7 @@ class MockClient(object):
|
||||
|
||||
@staticmethod
|
||||
def get_snap(name=None):
|
||||
snap = test_client.MockResource(name=name)
|
||||
snap = test_client.MockResource(name=name, _id=name)
|
||||
if name is not None:
|
||||
ret = snap
|
||||
else:
|
||||
@ -192,6 +192,18 @@ def get_lun_pl(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):
|
||||
@functools.wraps(func)
|
||||
@mock.patch('cinder.volume.drivers.dell_emc.unity.utils.'
|
||||
@ -206,6 +218,28 @@ def patch_for_unity_adapter(func):
|
||||
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
|
||||
@ -233,8 +267,8 @@ class CommonAdapterTest(unittest.TestCase):
|
||||
snap = mock.Mock(volume=volume)
|
||||
snap.name = 'abc-def_snap'
|
||||
result = self.adapter.create_snapshot(snap)
|
||||
self.assertEqual('abc-def_snap', result.name)
|
||||
self.assertEqual('lun_43', result.get_id())
|
||||
self.assertEqual(get_snap_pl('lun_43'), result['provider_location'])
|
||||
self.assertEqual('lun_43', result['provider_id'])
|
||||
|
||||
def test_delete_snap(self):
|
||||
def f():
|
||||
@ -311,22 +345,7 @@ class CommonAdapterTest(unittest.TestCase):
|
||||
self.assertEqual(self.adapter.array_ca_cert_path,
|
||||
self.adapter.verify_cert)
|
||||
|
||||
def test_initialize_connection_common(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 test_terminate_connection_volume(self):
|
||||
def f():
|
||||
volume = mock.Mock(provider_location='id^lun_43', id='id_43')
|
||||
connector = {'host': 'host1'}
|
||||
@ -334,11 +353,12 @@ class CommonAdapterTest(unittest.TestCase):
|
||||
|
||||
self.assertRaises(ex.DetachIsCalled, f)
|
||||
|
||||
def test_terminate_connection_snap(self):
|
||||
def test_terminate_connection_snapshot(self):
|
||||
def f():
|
||||
connector = {'host': 'host1'}
|
||||
snap = test_client.MockResource(_id='snap_0')
|
||||
self.adapter._terminate_connection(snap, connector)
|
||||
snap = mock.Mock(id='snap_0', name='snap_0')
|
||||
snap.name = 'snap_0'
|
||||
self.adapter.terminate_connection_snapshot(snap, connector)
|
||||
|
||||
self.assertRaises(ex.DetachIsCalled, f)
|
||||
|
||||
@ -475,6 +495,25 @@ class FCAdapterTest(unittest.TestCase):
|
||||
wwns = ['8899AABBCCDDEEFF', '8899AABBCCDDFFEE']
|
||||
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):
|
||||
connector = {'host': 'host1', 'wwpns': 'abcdefg'}
|
||||
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.assertTrue(info['target_portal'] in target_portals)
|
||||
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
|
||||
def create_snapshot(snapshot):
|
||||
snapshot.exists = True
|
||||
return snapshot
|
||||
|
||||
@staticmethod
|
||||
def delete_snapshot(snapshot):
|
||||
@ -88,6 +89,14 @@ class MockAdapter(object):
|
||||
def get_pool_name(volume):
|
||||
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):
|
||||
ret = self.driver.unmanage(None)
|
||||
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(
|
||||
lun_type='lun',
|
||||
lun_id=lun.get_id())
|
||||
model_update = {'provider_location': location}
|
||||
return model_update
|
||||
return {'provider_location': location,
|
||||
'provider_id': lun.get_id()}
|
||||
|
||||
def delete_volume(self, volume):
|
||||
lun_id = self.get_lun_id(volume)
|
||||
@ -141,6 +141,7 @@ class CommonAdapter(object):
|
||||
else:
|
||||
self.client.delete_lun(lun_id)
|
||||
|
||||
@cinder_utils.trace
|
||||
def _initialize_connection(self, lun_or_snap, connector, vol_id):
|
||||
host = self.client.create_host(connector['host'],
|
||||
self.get_connector_uids(connector))
|
||||
@ -156,17 +157,20 @@ class CommonAdapter(object):
|
||||
LOG.debug('Initialized connection info: %s', conn_info)
|
||||
return conn_info
|
||||
|
||||
@cinder_utils.trace
|
||||
def initialize_connection(self, volume, connector):
|
||||
lun = self.client.get_lun(lun_id=self.get_lun_id(volume))
|
||||
return self._initialize_connection(lun, connector, volume.id)
|
||||
|
||||
@cinder_utils.trace
|
||||
def _terminate_connection(self, lun_or_snap, connector):
|
||||
host = self.client.get_host(connector['host'])
|
||||
self.client.detach(host, lun_or_snap)
|
||||
|
||||
@cinder_utils.trace
|
||||
def terminate_connection(self, volume, connector):
|
||||
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):
|
||||
return None
|
||||
@ -247,7 +251,11 @@ class CommonAdapter(object):
|
||||
:param snapshot: snapshot information.
|
||||
"""
|
||||
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):
|
||||
"""Deletes a snapshot.
|
||||
@ -300,7 +308,8 @@ class CommonAdapter(object):
|
||||
lun.modify(name=volume.name)
|
||||
return {'provider_location':
|
||||
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):
|
||||
"""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`.
|
||||
"""
|
||||
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()
|
||||
dest_lun = self.client.get_lun(lun_id=self.get_lun_id(volume))
|
||||
try:
|
||||
@ -430,6 +440,16 @@ class CommonAdapter(object):
|
||||
def get_pool_name(self, volume):
|
||||
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):
|
||||
protocol = PROTOCOL_ISCSI
|
||||
@ -496,8 +516,11 @@ class FCAdapter(CommonAdapter):
|
||||
data['target_lun'] = hlu
|
||||
return data
|
||||
|
||||
def terminate_connection(self, volume, connector):
|
||||
super(FCAdapter, self).terminate_connection(volume, connector)
|
||||
@cinder_utils.trace
|
||||
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
|
||||
if self.auto_zone_enabled:
|
||||
@ -510,7 +533,6 @@ class FCAdapter(CommonAdapter):
|
||||
targets = self.client.get_fc_target_info(logged_in_only=True)
|
||||
ret['data'] = self._get_fc_zone_info(connector['wwpns'],
|
||||
targets)
|
||||
|
||||
return ret
|
||||
|
||||
def _get_fc_zone_info(self, initiator_wwns, target_wwns):
|
||||
|
@ -152,27 +152,12 @@ class UnityDriver(driver.ManageableVD,
|
||||
}
|
||||
}
|
||||
"""
|
||||
LOG.debug("Entering initialize_connection"
|
||||
" - 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
|
||||
return self.adapter.initialize_connection(volume, connector)
|
||||
|
||||
@zm_utils.remove_fc_zone
|
||||
def terminate_connection(self, volume, connector, **kwargs):
|
||||
"""Disallow connection from connector."""
|
||||
LOG.debug("Entering terminate_connection"
|
||||
" - 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
|
||||
return self.adapter.terminate_connection(volume, connector)
|
||||
|
||||
def get_volume_stats(self, refresh=False):
|
||||
"""Get volume stats.
|
||||
@ -211,3 +196,20 @@ class UnityDriver(driver.ManageableVD,
|
||||
def unmanage(self, volume):
|
||||
"""Unmanages a volume."""
|
||||
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