VMAX driver - Attach and detach snapshot

"Attach snapshot" and "Detach snapshot" are used internally
by non-disruptive backup and to backup a snapshot
separately from the backend storage.

Change-Id: I96993078a4123396206ce69a81c4d3f7180fec6b
Implements: blueprint vmax-attach-snapshot
This commit is contained in:
Helen Walsh 2016-11-04 16:28:59 +00:00
parent 12eccd10f4
commit efd07037ea
6 changed files with 366 additions and 155 deletions

View File

@ -251,6 +251,10 @@ class EMCVMAXCommonData(object):
fabric_name_prefix = "fakeFabric"
end_point_map = {connector['wwpns'][0]: [target_wwns[0]],
connector['wwpns'][1]: [target_wwns[1]]}
zoning_mappings = {'port_group': None,
'initiator_group': None,
'target_wwns': target_wwns,
'init_targ_map': end_point_map}
device_map = {}
for wwn in connector['wwpns']:
fabric_name = ''.join([fabric_name_prefix,
@ -532,6 +536,18 @@ class EMCVMAXCommonData(object):
'ElementName': storagegroupname},
{'CreationClassName': storagegroup_creationclass,
'ElementName': 'OS-SRP_1-Bronze-DSS-SG'}]
iqn = u'iqn.1992-04.com.emc:600009700bca30c01e3e012e00000001,t,0x0001'
iscsi_device_info = {'maskingview': u'OS-host-SRP_1-Diamond-NONE-MV',
'ip_and_iqn': [{'ip': u'123.456.7.8',
'iqn': iqn}],
'is_multipath': False,
'storagesystem': u'SYMMETRIX-+-012345678901',
'controller': {'host': '10.00.00.00'},
'hostlunid': 3}
fc_device_info = {'maskingview': u'OS-host-SRP_1-Diamond-NONE-MV',
'storagesystem': u'SYMMETRIX-+-012345678901',
'controller': {'host': '10.00.00.00'},
'hostlunid': 3}
test_ctxt = {}
new_type = {}
diff = {}
@ -1731,13 +1747,17 @@ class FakeEcomConnection(object):
sourceVolume['CreationClassName'] = 'Symm_StorageVolume'
sourceVolume['DeviceID'] = self.data.test_volume['device_id']
sourceInstanceName = conn.GetInstance(sourceVolume)
targetVolume = {}
targetVolume['CreationClassName'] = 'Symm_StorageVolume'
targetVolume['DeviceID'] = self.data.test_volume['device_id']
targetInstanceName = conn.GetInstance(sourceVolume)
svInstances = []
svInstance = {}
svInstance['SyncedElement'] = 'SyncedElement'
svInstance['SyncedElement'] = targetInstanceName
svInstance['SystemElement'] = sourceInstanceName
svInstance['CreationClassName'] = 'SE_StorageSynchronized_SV_SV'
svInstance['PercentSynced'] = 100
svInstance['CopyState'] = self.data.UNSYNCHRONIZED
svInstance['CopyState'] = 7
svInstances.append(svInstance)
return svInstances
@ -2161,14 +2181,6 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.common.utils._get_random_portgroup, dom)
def test_is_sync_complete(self):
conn = self.fake_ecom_connection()
syncname = SE_ConcreteJob()
syncname.classname = 'SE_StorageSynchronized_SV_SV'
syncname['CopyState'] = self.data.UNSYNCHRONIZED
issynched = self.driver.common.utils._is_sync_complete(conn, syncname)
self.assertFalse(issynched)
def test_get_correct_port_group(self):
self.driver.common.conn = self.fake_ecom_connection()
maskingViewInstanceName = {'CreationClassName': 'Symm_LunMaskingView',
@ -3479,7 +3491,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
return_value=[EMCVMAXCommonData.test_volume])
@mock.patch.object(
emc_vmax_utils.EMCVMAXUtils,
'find_sync_sv_by_target',
'find_sync_sv_by_volume',
return_value=(None, None))
@mock.patch.object(
volume_types,
@ -3552,7 +3564,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
'create_element_replica')
@mock.patch.object(
emc_vmax_utils.EMCVMAXUtils,
'find_sync_sv_by_target',
'find_sync_sv_by_volume',
return_value=(None, None))
def test_create_clone_assert_clean_up_target_volume(
self, mock_sync, mock_create_replica, mock_volume_type,
@ -4416,7 +4428,7 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase):
return_value=[EMCVMAXCommonData.test_volume])
@mock.patch.object(
emc_vmax_utils.EMCVMAXUtils,
'find_sync_sv_by_target',
'find_sync_sv_by_volume',
return_value=(None, None))
@mock.patch.object(
volume_types,
@ -4433,7 +4445,11 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase):
@mock.patch.object(
emc_vmax_utils.EMCVMAXUtils,
'find_sync_sv_by_target',
'is_clone_licensed',
return_value=False)
@mock.patch.object(
emc_vmax_utils.EMCVMAXUtils,
'find_sync_sv_by_volume',
return_value=(None, None))
@mock.patch.object(
emc_vmax_utils.EMCVMAXUtils,
@ -4446,7 +4462,7 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase):
'FASTPOLICY': 'FC_GOLD1'})
def test_create_volume_from_snapshot_fast_failed(
self, mock_volume_type,
mock_rep_service, mock_sync_sv):
mock_rep_service, mock_sync_sv, mock_license):
self.data.test_volume['volume_name'] = "vmax-1234567"
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume_from_snapshot,
@ -5629,7 +5645,7 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase):
return_value=[EMCVMAXCommonData.test_volume])
@mock.patch.object(
emc_vmax_utils.EMCVMAXUtils,
'find_sync_sv_by_target',
'find_sync_sv_by_volume',
return_value=(None, None))
@mock.patch.object(
volume_types,
@ -5646,7 +5662,11 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase):
@mock.patch.object(
emc_vmax_utils.EMCVMAXUtils,
'find_sync_sv_by_target',
'is_clone_licensed',
return_value=False)
@mock.patch.object(
emc_vmax_utils.EMCVMAXUtils,
'find_sync_sv_by_volume',
return_value=(None, None))
@mock.patch.object(
emc_vmax_utils.EMCVMAXUtils,
@ -5658,7 +5678,8 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase):
return_value={'volume_backend_name': 'FCFAST',
'FASTPOLICY': 'FC_GOLD1'})
def test_create_volume_from_snapshot_fast_failed(
self, mock_volume_type, mock_rep_service, mock_sync_sv):
self, mock_volume_type, mock_rep_service, mock_sync_sv,
mock_license):
self.data.test_volume['volume_name'] = "vmax-1234567"
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume_from_snapshot,
@ -6623,7 +6644,7 @@ class EMCV3DriverTestCase(test.TestCase):
'create_element_replica')
@mock.patch.object(
emc_vmax_utils.EMCVMAXUtils,
'find_sync_sv_by_target',
'find_sync_sv_by_volume',
return_value=(None, None))
def test_create_clone_v3_assert_clean_up_target_volume(
self, mock_sync, mock_create_replica, mock_volume_db,
@ -6778,6 +6799,8 @@ class EMCV2MultiPoolDriverTestCase(test.TestCase):
instancename.fake_getinstancename)
self.mock_object(emc_vmax_utils.EMCVMAXUtils, 'isArrayV3',
self.fake_is_v3)
emc_vmax_utils.EMCVMAXUtils._is_sync_complete = mock.Mock(
return_value=True)
driver = emc_vmax_iscsi.EMCVMAXISCSIDriver(configuration=configuration)
driver.db = FakeDB()
self.driver = driver
@ -8195,6 +8218,8 @@ class EMCVMAXFCTest(test.TestCase):
configuration.safe_get.return_value = 'FCTests'
configuration.config_group = 'FCTests'
emc_vmax_common.EMCVMAXCommon._gather_info = mock.Mock()
emc_vmax_common.EMCVMAXCommon._get_ecom_connection = mock.Mock(
return_value=FakeEcomConnection())
driver = emc_vmax_fc.EMCVMAXFCDriver(configuration=configuration)
driver.db = FakeDB()
self.driver = driver
@ -8336,6 +8361,41 @@ class EMCVMAXFCTest(test.TestCase):
portGroupInstanceName, initiatorGroupInstanceName)
self.assertEqual(0, len(mvInstances))
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'initialize_connection',
return_value=EMCVMAXCommonData.fc_device_info)
@mock.patch.object(
emc_vmax_fc.EMCVMAXFCDriver,
'_build_initiator_target_map',
return_value=(EMCVMAXCommonData.target_wwns,
EMCVMAXCommonData.end_point_map))
def test_initialize_connection_snapshot(self, mock_map, mock_conn):
data = self.driver.initialize_connection_snapshot(
self.data.test_snapshot_v3, self.data.connector)
self.assertEqual('fibre_channel', data['driver_volume_type'])
self.assertEqual(3, data['data']['target_lun'])
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'_unmap_lun')
@mock.patch.object(
emc_vmax_fc.EMCVMAXFCDriver,
'_get_zoning_mappings',
return_value=(EMCVMAXCommonData.zoning_mappings))
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'check_ig_instance_name',
return_value=None)
def test_terminate_connection_snapshot(
self, mock_check_ig, mock_zoning_map, mock_unmap):
common = self.driver.common
common.conn = FakeEcomConnection()
data = self.driver.terminate_connection_snapshot(
self.data.test_snapshot_v3, self.data.connector)
self.assertEqual('fibre_channel', data['driver_volume_type'])
self.assertEqual(2, len(data['data']['target_wwn']))
@ddt.ddt
class EMCVMAXUtilsTest(test.TestCase):
@ -8631,6 +8691,18 @@ class EMCVMAXUtilsTest(test.TestCase):
conn, initiatorgroup)
self.assertIsNone(foundIg)
@mock.patch.object(
emc_vmax_utils.EMCVMAXUtils,
'_is_sync_complete',
return_value=False)
def test_is_sync_complete(self, mock_sync):
conn = FakeEcomConnection()
syncname = SE_ConcreteJob()
syncname.classname = 'SE_StorageSynchronized_SV_SV'
syncname['CopyState'] = self.data.UNSYNCHRONIZED
issynched = self.driver.common.utils._is_sync_complete(conn, syncname)
self.assertFalse(issynched)
class EMCVMAXCommonTest(test.TestCase):
def setUp(self):
@ -8741,7 +8813,7 @@ class EMCVMAXCommonTest(test.TestCase):
repServiceInstanceName = (
self.driver.utils.find_replication_service(
common.conn, self.data.storage_system))
common.utils.find_sync_sv_by_target = mock.Mock(
common.utils.find_sync_sv_by_volume = mock.Mock(
return_value=(None, None))
self.driver.common._cleanup_target(
@ -8912,6 +8984,9 @@ class EMCVMAXISCSITest(test.TestCase):
configuration.safe_get.return_value = 'iSCSITests'
configuration.config_group = 'iSCSITests'
emc_vmax_common.EMCVMAXCommon._gather_info = mock.Mock()
instancename = FakeCIMInstanceName()
self.mock_object(emc_vmax_utils.EMCVMAXUtils, 'get_instance_name',
instancename.fake_getinstancename)
driver = emc_vmax_iscsi.EMCVMAXISCSIDriver(configuration=configuration)
driver.db = FakeDB()
self.driver = driver
@ -8933,3 +9008,29 @@ class EMCVMAXISCSITest(test.TestCase):
properties['target_iqns'])
self.assertEqual(['10.10.0.50:3260', '10.10.0.51:3260'],
properties['target_portals'])
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'find_device_number',
return_value={'hostlunid': 1,
'storagesystem': EMCVMAXCommonData.storage_system})
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'initialize_connection',
return_value=EMCVMAXCommonData.iscsi_device_info)
def test_initialize_connection_snapshot(self, mock_conn, mock_num):
data = self.driver.initialize_connection_snapshot(
self.data.test_snapshot_v3, self.data.connector)
self.assertEqual('iscsi', data['driver_volume_type'])
self.assertEqual(1, data['data']['target_lun'])
@mock.patch.object(
emc_vmax_common.EMCVMAXCommon,
'_unmap_lun')
def test_terminate_connection_snapshot(self, mock_unmap):
common = self.driver.common
common.conn = FakeEcomConnection()
self.driver.terminate_connection_snapshot(
self.data.test_snapshot_v3, self.data.connector)
common._unmap_lun.assert_called_once_with(
self.data.test_snapshot_v3, self.data.connector)

View File

@ -234,22 +234,8 @@ class EMCVMAXCommon(object):
extraSpecs = self._initial_setup(snapshot)
self.conn = self._get_ecom_connection()
snapshotInstance = self._find_lun(snapshot)
storageSystem = snapshotInstance['SystemName']
syncName = self.utils.find_sync_sv_by_target(
self.conn, storageSystem, snapshotInstance, extraSpecs, True)
if syncName is not None:
repservice = self.utils.find_replication_service(self.conn,
storageSystem)
if repservice is None:
exception_message = (_("Cannot find Replication Service to "
"create volume for snapshot %s.")
% snapshotInstance)
raise exception.VolumeBackendAPIException(
data=exception_message)
self.provision.delete_clone_relationship(
self.conn, repservice, syncName, extraSpecs)
self._sync_check(snapshotInstance, snapshot['name'], extraSpecs)
snapshot['host'] = volume['host']
return self._create_cloned_volume(volume, snapshot, extraSpecs, False)
@ -2300,6 +2286,8 @@ class EMCVMAXCommon(object):
{'name': volumeName})
return errorRet
self._sync_check(volumeInstance, volumeName, extraSpecs)
storageConfigService = self.utils.find_storage_configuration_service(
self.conn, volumeInstance['SystemName'])
@ -2469,58 +2457,16 @@ class EMCVMAXCommon(object):
:param snapshot: snapshot object to be deleted
:raises: VolumeBackendAPIException
"""
LOG.debug("Entering delete_snapshot.")
LOG.debug("Entering _delete_snapshot.")
snapshotname = snapshot['name']
LOG.info(_LI("Delete Snapshot: %(snapshot)s."),
{'snapshot': snapshotname})
extraSpecs = self._initial_setup(snapshot)
self.conn = self._get_ecom_connection()
if not extraSpecs[ISV3]:
snapshotInstance = self._find_lun(snapshot)
if snapshotInstance is None:
LOG.error(_LE(
"Snapshot %(snapshotname)s not found on the array. "
"No volume to delete."),
{'snapshotname': snapshotname})
return (-1, snapshotname)
storageSystem = snapshotInstance['SystemName']
# Wait for it to fully sync in case there is an ongoing
# create volume from snapshot request.
syncName = self.utils.find_sync_sv_by_target(
self.conn, storageSystem, snapshotInstance, extraSpecs,
True)
if syncName is None:
LOG.info(_LI(
"Snapshot: %(snapshot)s: not found on the array."),
{'snapshot': snapshotname})
else:
repservice = self.utils.find_replication_service(self.conn,
storageSystem)
if repservice is None:
exception_message = _(
"Cannot find Replication Service to"
" delete snapshot %s.") % snapshotname
raise exception.VolumeBackendAPIException(
data=exception_message)
# Break the replication relationship
LOG.debug("Deleting snap relationship: Target: %(snapshot)s "
"Method: ModifyReplicaSynchronization "
"Replication Service: %(service)s Operation: 8 "
"Synchronization: %(syncName)s.",
{'snapshot': snapshotname,
'service': repservice,
'syncName': syncName})
self.provision.delete_clone_relationship(
self.conn, repservice, syncName, extraSpecs, True)
# Delete the target device.
self._delete_volume(snapshot)
rc, snapshotname = self._delete_volume(snapshot)
LOG.info(_LI("Leaving delete_snapshot: %(ssname)s Return code: "
"%(rc)lu."),
{'ssname': snapshotname,
'rc': rc})
def create_consistencygroup(self, context, group):
"""Creates a consistency group.
@ -3773,8 +3719,9 @@ class EMCVMAXCommon(object):
if targetInstance is not None:
# Check if the copy session exists.
storageSystem = targetInstance['SystemName']
syncInstanceName = self.utils.find_sync_sv_by_target(
self.conn, storageSystem, targetInstance, False)
syncInstanceName = self.utils.find_sync_sv_by_volume(
self.conn, storageSystem, targetInstance, extraSpecs,
False)
if syncInstanceName is not None:
# Remove the Clone relationship.
rc, job = self.provision.delete_clone_relationship(
@ -3930,7 +3877,7 @@ class EMCVMAXCommon(object):
sourceInstance, cloneName, extraSpecs)
try:
_rc, job = (
rc, job = (
self.provisionv3.create_element_replica(
self.conn, repServiceInstanceName, cloneName, syncType,
sourceInstance, extraSpecs, targetInstance, rsdInstance))
@ -3939,7 +3886,6 @@ class EMCVMAXCommon(object):
"Clone failed on V3. Cleaning up the target volume. "
"Clone name: %(cloneName)s "),
{'cloneName': cloneName})
# Check if the copy session exists.
if targetInstance:
self._cleanup_target(
repServiceInstanceName, targetInstance, extraSpecs)
@ -3953,15 +3899,16 @@ class EMCVMAXCommon(object):
LOG.info(_LI("The target instance device id is: %(deviceid)s."),
{'deviceid': targetVolumeInstance['DeviceID']})
cloneVolume['provider_location'] = six.text_type(cloneDict)
if not isSnapshot:
cloneVolume['provider_location'] = six.text_type(cloneDict)
syncInstanceName, _storageSystem = (
self._find_storage_sync_sv_sv(cloneVolume, sourceVolume,
extraSpecs, True))
syncInstanceName, _storageSystem = (
self._find_storage_sync_sv_sv(cloneVolume, sourceVolume,
extraSpecs, True))
rc, job = self.provisionv3.break_replication_relationship(
self.conn, repServiceInstanceName, syncInstanceName,
operation, extraSpecs)
rc, job = self.provisionv3.break_replication_relationship(
self.conn, repServiceInstanceName, syncInstanceName,
operation, extraSpecs)
return rc, cloneDict
def _cleanup_target(
@ -3973,7 +3920,7 @@ class EMCVMAXCommon(object):
:param extraSpecs: extra specifications
"""
storageSystem = targetInstance['SystemName']
syncInstanceName = self.utils.find_sync_sv_by_target(
syncInstanceName = self.utils.find_sync_sv_by_volume(
self.conn, storageSystem, targetInstance, False)
if syncInstanceName is not None:
# Break the clone relationship.
@ -4668,3 +4615,35 @@ class EMCVMAXCommon(object):
cgName += str(group[update_variable])
return cgName
def _sync_check(self, volumeInstance, volumeName, extraSpecs):
"""Check if volume is part of a sync process.
:param volumeInstance: volume instance
:param volumeName: volume name
:param extraSpecs: extra specifications
"""
storageSystem = volumeInstance['SystemName']
# Wait for it to fully sync in case there is an ongoing
# create volume from snapshot request.
syncInstanceName = self.utils.find_sync_sv_by_volume(
self.conn, storageSystem, volumeInstance, extraSpecs,
True)
if syncInstanceName:
repservice = self.utils.find_replication_service(self.conn,
storageSystem)
# Break the replication relationship
LOG.debug("Deleting snap relationship: Source: %(volume)s "
"Synchronization: %(syncName)s.",
{'volume': volumeName,
'syncName': syncInstanceName})
if extraSpecs[ISV3]:
rc, job = self.provisionv3.break_replication_relationship(
self.conn, repservice, syncInstanceName,
DISSOLVE_SNAPVX, extraSpecs)
else:
self.provision.delete_clone_relationship(
self.conn, repservice, syncInstanceName, extraSpecs, True)

View File

@ -70,9 +70,11 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver):
- SnapVX licensing checks for VMAX3 (bug #1587017)
- VMAX oversubscription Support (blueprint vmax-oversubscription)
- QoS support (blueprint vmax-qos)
2.5.0 - Attach and detach snapshot (blueprint vmax-attach-snapshot)
"""
VERSION = "2.4.0"
VERSION = "2.5.0"
# ThirdPartySystems wiki
CI_WIKI_NAME = "EMC_VMAX_CI"
@ -185,6 +187,19 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver):
"""
device_info = self.common.initialize_connection(
volume, connector)
return self.populate_data(device_info, volume, connector)
def populate_data(self, device_info, volume, connector):
"""Populate data dict.
Add relevant data to data dict, target_lun, target_wwn and
initiator_target_map.
:param device_info: device_info
:param volume: the volume object
:param connector: the connector object
:returns: dict -- the target_wwns and initiator_target_map
"""
device_number = device_info['hostlunid']
storage_system = device_info['storagesystem']
target_wwns, init_targ_map = self._build_initiator_target_map(
@ -217,6 +232,26 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver):
"""
data = {'driver_volume_type': 'fibre_channel',
'data': {}}
zoning_mappings = (
self._get_zoning_mappings(volume, connector))
if zoning_mappings:
self.common.terminate_connection(volume, connector)
data = self._cleanup_zones(zoning_mappings)
return data
def _get_zoning_mappings(self, volume, connector):
"""Get zoning mappings by building up initiator/target map.
:param volume: the volume object
:param connector: the connector object
:returns: dict -- the target_wwns and initiator_target_map if the
zone is to be removed, otherwise empty
"""
zoning_mappings = {'port_group': None,
'initiator_group': None,
'target_wwns': None,
'init_targ_map': None}
loc = volume['provider_location']
name = ast.literal_eval(loc)
storage_system = name['keybindings']['SystemName']
@ -225,7 +260,7 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver):
mvInstanceName = self.common.get_masking_view_by_volume(
volume, connector)
if mvInstanceName is not None:
if mvInstanceName:
portGroupInstanceName = (
self.common.get_port_group_from_masking_view(
mvInstanceName))
@ -240,46 +275,60 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver):
# Map must be populated before the terminate_connection
target_wwns, init_targ_map = self._build_initiator_target_map(
storage_system, volume, connector)
zoning_mappings = {'port_group': portGroupInstanceName,
'initiator_group': initiatorGroupInstanceName,
'target_wwns': target_wwns,
'init_targ_map': init_targ_map}
else:
LOG.warning(_LW("Volume %(volume)s is not in any masking view."),
{'volume': volume['name']})
return zoning_mappings
self.common.terminate_connection(volume, connector)
def _cleanup_zones(self, zoning_mappings):
"""Cleanup zones after terminate connection.
LOG.debug("Looking for masking views still associated with "
"Port Group %s.", portGroupInstanceName)
# check if the initiator group has been deleted
:param zoning_mappings: zoning mapping dict
:returns: data - dict
"""
LOG.debug("Looking for masking views still associated with "
"Port Group %s.", zoning_mappings['port_group'])
if zoning_mappings['initiator_group']:
checkIgInstanceName = (
self.common.check_ig_instance_name(initiatorGroupInstanceName))
self.common.check_ig_instance_name(
zoning_mappings['initiator_group']))
else:
checkIgInstanceName = None
# if it has not been deleted, check for remaining masking views
if checkIgInstanceName is not None:
mvInstances = self._get_common_masking_views(
portGroupInstanceName, initiatorGroupInstanceName)
# if it has not been deleted, check for remaining masking views
if checkIgInstanceName:
mvInstances = self._get_common_masking_views(
zoning_mappings['port_group'],
zoning_mappings['initiator_group'])
if len(mvInstances) > 0:
LOG.debug("Found %(numViews)lu MaskingViews.",
{'numViews': len(mvInstances)})
data = {'driver_volume_type': 'fibre_channel',
'data': {}}
else: # no masking views found
LOG.debug("No MaskingViews were found. Deleting zone.")
data = {'driver_volume_type': 'fibre_channel',
'data': {'target_wwn': target_wwns,
'initiator_target_map': init_targ_map}}
LOG.debug("Return FC data for zone removal: %(data)s.",
{'data': data})
else: # The initiator group has been deleted
LOG.debug("Initiator Group has been deleted. Deleting zone.")
if len(mvInstances) > 0:
LOG.debug("Found %(numViews)lu MaskingViews.",
{'numViews': len(mvInstances)})
data = {'driver_volume_type': 'fibre_channel',
'data': {'target_wwn': target_wwns,
'initiator_target_map': init_targ_map}}
'data': {}}
else: # no masking views found
LOG.debug("No MaskingViews were found. Deleting zone.")
data = {'driver_volume_type': 'fibre_channel',
'data': {'target_wwn': zoning_mappings['target_wwns'],
'initiator_target_map':
zoning_mappings['init_targ_map']}}
LOG.debug("Return FC data for zone removal: %(data)s.",
{'data': data})
else:
LOG.warning(_LW("Volume %(volume)s is not in any masking view."),
{'volume': volume['name']})
else: # The initiator group has been deleted
LOG.debug("Initiator Group has been deleted. Deleting zone.")
data = {'driver_volume_type': 'fibre_channel',
'data': {'target_wwn': zoning_mappings['target_wwns'],
'initiator_target_map':
zoning_mappings['init_targ_map']}}
LOG.debug("Return FC data for zone removal: %(data)s.",
{'data': data})
return data
def _get_common_masking_views(
@ -436,3 +485,38 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver):
return self.common.create_consistencygroup_from_src(
context, group, volumes, cgsnapshot, snapshots, source_cg,
source_vols)
def create_export_snapshot(self, context, snapshot, connector):
"""Driver entry point to get the export info for a new snapshot."""
pass
def remove_export_snapshot(self, context, snapshot):
"""Driver entry point to remove an export for a snapshot."""
pass
def initialize_connection_snapshot(self, snapshot, connector, **kwargs):
"""Allows connection to snapshot.
:param snapshot: the snapshot object
:param connector: the connector object
:param kwargs: additional parameters
:returns: data dict
"""
src_volume = snapshot['volume']
snapshot['host'] = src_volume['host']
return self.initialize_connection(snapshot, connector)
def terminate_connection_snapshot(self, snapshot, connector, **kwargs):
"""Disallows connection to snapshot.
:param snapshot: the snapshot object
:param connector: the connector object
:param kwargs: additional parameters
"""
src_volume = snapshot['volume']
snapshot['host'] = src_volume['host']
return self.terminate_connection(snapshot, connector, **kwargs)
def backup_use_temp_snapshot(self):
return True

View File

@ -76,10 +76,11 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver):
- QoS support (blueprint vmax-qos)
- VMAX2/VMAX3 iscsi multipath support (iscsi only)
https://blueprints.launchpad.net/cinder/+spec/vmax-iscsi-multipath
2.5.0 - Attach and detach snapshot (blueprint vmax-attach-snapshot)
"""
VERSION = "2.4.0"
VERSION = "2.5.0"
# ThirdPartySystems wiki
CI_WIKI_NAME = "EMC_VMAX_CI"
@ -190,6 +191,17 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver):
"""
device_info = self.common.initialize_connection(
volume, connector)
return self.get_iscsi_dict(
device_info, volume, connector)
def get_iscsi_dict(self, device_info, volume, connector):
"""Populate iscsi dict to pass to nova.
:param device_info: device info dict
:param volume: volume object
:param connector: connector object
:return: iscsi dict
"""
try:
ip_and_iqn = device_info['ip_and_iqn']
is_multipath = device_info['is_multipath']
@ -202,27 +214,12 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver):
iscsi_properties = self.smis_get_iscsi_properties(
volume, connector, ip_and_iqn, is_multipath)
LOG.info(_LI("Leaving initialize_connection: %s"), iscsi_properties)
LOG.info(_LI("iSCSI properties are: %s"), iscsi_properties)
return {
'driver_volume_type': 'iscsi',
'data': iscsi_properties
}
def _parse_target_list(self, targets):
"""Parse target list into usable format.
:param targets: list of all targets
:return: outTargets
"""
outTargets = []
for target in targets:
results = target.split(" ")
properties = {}
properties['target_portal'] = results[0].split(",")[0]
properties['target_iqn'] = results[1]
outTargets.append(properties)
return outTargets
def smis_get_iscsi_properties(self, volume, connector, ip_and_iqn,
is_multipath):
"""Gets iscsi configuration.
@ -409,3 +406,41 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver):
return self.common.create_consistencygroup_from_src(
context, group, volumes, cgsnapshot, snapshots, source_cg,
source_vols)
def create_export_snapshot(self, context, snapshot, connector):
"""Driver entry point to get the export info for a new snapshot."""
pass
def remove_export_snapshot(self, context, snapshot):
"""Driver entry point to remove an export for a snapshot."""
pass
def initialize_connection_snapshot(self, snapshot, connector, **kwargs):
"""Allows connection to snapshot.
:param snapshot: the snapshot object
:param connector: the connector object
:param kwargs: additional parameters
:returns: iscsi dict
"""
src_volume = snapshot['volume']
snapshot['host'] = src_volume['host']
device_info = self.common.initialize_connection(
snapshot, connector)
return self.get_iscsi_dict(
device_info, snapshot, connector)
def terminate_connection_snapshot(self, snapshot, connector, **kwargs):
"""Disallows connection to snapshot.
:param snapshot: the snapshot object
:param connector: the connector object
:param kwargs: additional parameters
"""
src_volume = snapshot['volume']
snapshot['host'] = src_volume['host']
return self.common.terminate_connection(snapshot,
connector)
def backup_use_temp_snapshot(self):
return True

View File

@ -19,6 +19,7 @@ import os
import pickle
import random
import re
import time
from xml.dom import minidom
from oslo_log import log as logging
@ -1246,14 +1247,14 @@ class EMCVMAXUtils(object):
delta = endTime - startTime
return six.text_type(datetime.timedelta(seconds=int(delta)))
def find_sync_sv_by_target(
self, conn, storageSystem, target, extraSpecs,
def find_sync_sv_by_volume(
self, conn, storageSystem, volumeInstance, extraSpecs,
waitforsync=True):
"""Find the storage synchronized name by target device ID.
"""Find the storage synchronized name by device ID.
:param conn: connection to the ecom server
:param storageSystem: the storage system name
:param target: target volume object
:param volumeInstance: volume instance
:param extraSpecs: the extraSpecs dict
:param waitforsync: wait for the synchronization to complete if True
:returns: foundSyncInstanceName
@ -1263,9 +1264,11 @@ class EMCVMAXUtils(object):
'SE_StorageSynchronized_SV_SV')
for syncInstanceName in syncInstanceNames:
syncSvTarget = syncInstanceName['SyncedElement']
syncSvSource = syncInstanceName['SystemElement']
if storageSystem != syncSvTarget['SystemName']:
continue
if syncSvTarget['DeviceID'] == target['DeviceID']:
if syncSvTarget['DeviceID'] == volumeInstance['DeviceID'] or (
syncSvSource['DeviceID'] == volumeInstance['DeviceID']):
# Check that it hasn't recently been deleted.
try:
conn.GetInstance(syncInstanceName)
@ -1277,15 +1280,20 @@ class EMCVMAXUtils(object):
foundSyncInstanceName = None
break
if foundSyncInstanceName is None:
LOG.warning(_LW(
"Storage sync name not found for target %(target)s "
"on %(storageSystem)s."),
{'target': target['DeviceID'], 'storageSystem': storageSystem})
else:
if foundSyncInstanceName:
# Wait for SE_StorageSynchronized_SV_SV to be fully synced.
if waitforsync:
LOG.warning(_LW(
"Expect a performance hit as volume is not fully "
"synced on %(deviceId)s."),
{'deviceId': volumeInstance['DeviceID']})
startTime = time.time()
self.wait_for_sync(conn, foundSyncInstanceName, extraSpecs)
LOG.warning(_LW(
"Synchronization process took "
"took: %(delta)s H:MM:SS."),
{'delta': self.get_time_delta(startTime,
time.time())})
return foundSyncInstanceName

View File

@ -0,0 +1,4 @@
---
features:
- Enable backup snapshot optimal path by implementing attach
and detach snapshot in the VMAX driver.