Unity Driver: Backup volume via snapshot

When a volume is in-use, the Unity driver could achieve the backup of
that volume through creating a temp snapshot, and then backing-up the
snapshot.

DocImpact
Implements: blueprint unity-backup-using-snapshot

Change-Id: I0535e8096a8e191c93dc02ef0880e4669b2f2495
This commit is contained in:
Jenkins 2016-12-12 22:21:06 +00:00 committed by Ryan Liang
parent 7880246ca1
commit 17171f6b15
5 changed files with 168 additions and 48 deletions

View File

@ -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'])

View File

@ -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'])

View File

@ -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):

View File

@ -155,27 +155,12 @@ class UnityDriver(driver.TransferVD,
}
}
"""
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.RemoveFCZone
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.
@ -214,3 +199,20 @@ class UnityDriver(driver.TransferVD,
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)

View File

@ -0,0 +1,3 @@
---
features:
- Add support to backup volume using snapshot in the Unity driver.