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:
parent
12eccd10f4
commit
efd07037ea
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
||||
|
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- Enable backup snapshot optimal path by implementing attach
|
||||
and detach snapshot in the VMAX driver.
|
Loading…
Reference in New Issue
Block a user