Merge "VMAX driver - Attach and detach snapshot"

This commit is contained in:
Jenkins 2016-12-19 12:34:38 +00:00 committed by Gerrit Code Review
commit 6521bd5392
6 changed files with 366 additions and 155 deletions

View File

@ -251,6 +251,10 @@ class EMCVMAXCommonData(object):
fabric_name_prefix = "fakeFabric" fabric_name_prefix = "fakeFabric"
end_point_map = {connector['wwpns'][0]: [target_wwns[0]], end_point_map = {connector['wwpns'][0]: [target_wwns[0]],
connector['wwpns'][1]: [target_wwns[1]]} 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 = {} device_map = {}
for wwn in connector['wwpns']: for wwn in connector['wwpns']:
fabric_name = ''.join([fabric_name_prefix, fabric_name = ''.join([fabric_name_prefix,
@ -532,6 +536,18 @@ class EMCVMAXCommonData(object):
'ElementName': storagegroupname}, 'ElementName': storagegroupname},
{'CreationClassName': storagegroup_creationclass, {'CreationClassName': storagegroup_creationclass,
'ElementName': 'OS-SRP_1-Bronze-DSS-SG'}] '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 = {} test_ctxt = {}
new_type = {} new_type = {}
diff = {} diff = {}
@ -1731,13 +1747,17 @@ class FakeEcomConnection(object):
sourceVolume['CreationClassName'] = 'Symm_StorageVolume' sourceVolume['CreationClassName'] = 'Symm_StorageVolume'
sourceVolume['DeviceID'] = self.data.test_volume['device_id'] sourceVolume['DeviceID'] = self.data.test_volume['device_id']
sourceInstanceName = conn.GetInstance(sourceVolume) sourceInstanceName = conn.GetInstance(sourceVolume)
targetVolume = {}
targetVolume['CreationClassName'] = 'Symm_StorageVolume'
targetVolume['DeviceID'] = self.data.test_volume['device_id']
targetInstanceName = conn.GetInstance(sourceVolume)
svInstances = [] svInstances = []
svInstance = {} svInstance = {}
svInstance['SyncedElement'] = 'SyncedElement' svInstance['SyncedElement'] = targetInstanceName
svInstance['SystemElement'] = sourceInstanceName svInstance['SystemElement'] = sourceInstanceName
svInstance['CreationClassName'] = 'SE_StorageSynchronized_SV_SV' svInstance['CreationClassName'] = 'SE_StorageSynchronized_SV_SV'
svInstance['PercentSynced'] = 100 svInstance['PercentSynced'] = 100
svInstance['CopyState'] = self.data.UNSYNCHRONIZED svInstance['CopyState'] = 7
svInstances.append(svInstance) svInstances.append(svInstance)
return svInstances return svInstances
@ -2161,14 +2181,6 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
self.assertRaises(exception.VolumeBackendAPIException, self.assertRaises(exception.VolumeBackendAPIException,
self.driver.common.utils._get_random_portgroup, dom) 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): def test_get_correct_port_group(self):
self.driver.common.conn = self.fake_ecom_connection() self.driver.common.conn = self.fake_ecom_connection()
maskingViewInstanceName = {'CreationClassName': 'Symm_LunMaskingView', maskingViewInstanceName = {'CreationClassName': 'Symm_LunMaskingView',
@ -3479,7 +3491,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
return_value=[EMCVMAXCommonData.test_volume]) return_value=[EMCVMAXCommonData.test_volume])
@mock.patch.object( @mock.patch.object(
emc_vmax_utils.EMCVMAXUtils, emc_vmax_utils.EMCVMAXUtils,
'find_sync_sv_by_target', 'find_sync_sv_by_volume',
return_value=(None, None)) return_value=(None, None))
@mock.patch.object( @mock.patch.object(
volume_types, volume_types,
@ -3552,7 +3564,7 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase):
'create_element_replica') 'create_element_replica')
@mock.patch.object( @mock.patch.object(
emc_vmax_utils.EMCVMAXUtils, emc_vmax_utils.EMCVMAXUtils,
'find_sync_sv_by_target', 'find_sync_sv_by_volume',
return_value=(None, None)) return_value=(None, None))
def test_create_clone_assert_clean_up_target_volume( def test_create_clone_assert_clean_up_target_volume(
self, mock_sync, mock_create_replica, mock_volume_type, self, mock_sync, mock_create_replica, mock_volume_type,
@ -4416,7 +4428,7 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase):
return_value=[EMCVMAXCommonData.test_volume]) return_value=[EMCVMAXCommonData.test_volume])
@mock.patch.object( @mock.patch.object(
emc_vmax_utils.EMCVMAXUtils, emc_vmax_utils.EMCVMAXUtils,
'find_sync_sv_by_target', 'find_sync_sv_by_volume',
return_value=(None, None)) return_value=(None, None))
@mock.patch.object( @mock.patch.object(
volume_types, volume_types,
@ -4433,7 +4445,11 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase):
@mock.patch.object( @mock.patch.object(
emc_vmax_utils.EMCVMAXUtils, 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)) return_value=(None, None))
@mock.patch.object( @mock.patch.object(
emc_vmax_utils.EMCVMAXUtils, emc_vmax_utils.EMCVMAXUtils,
@ -4446,7 +4462,7 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase):
'FASTPOLICY': 'FC_GOLD1'}) 'FASTPOLICY': 'FC_GOLD1'})
def test_create_volume_from_snapshot_fast_failed( def test_create_volume_from_snapshot_fast_failed(
self, mock_volume_type, 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.data.test_volume['volume_name'] = "vmax-1234567"
self.assertRaises(exception.VolumeBackendAPIException, self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume_from_snapshot, self.driver.create_volume_from_snapshot,
@ -5629,7 +5645,7 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase):
return_value=[EMCVMAXCommonData.test_volume]) return_value=[EMCVMAXCommonData.test_volume])
@mock.patch.object( @mock.patch.object(
emc_vmax_utils.EMCVMAXUtils, emc_vmax_utils.EMCVMAXUtils,
'find_sync_sv_by_target', 'find_sync_sv_by_volume',
return_value=(None, None)) return_value=(None, None))
@mock.patch.object( @mock.patch.object(
volume_types, volume_types,
@ -5646,7 +5662,11 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase):
@mock.patch.object( @mock.patch.object(
emc_vmax_utils.EMCVMAXUtils, 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)) return_value=(None, None))
@mock.patch.object( @mock.patch.object(
emc_vmax_utils.EMCVMAXUtils, emc_vmax_utils.EMCVMAXUtils,
@ -5658,7 +5678,8 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase):
return_value={'volume_backend_name': 'FCFAST', return_value={'volume_backend_name': 'FCFAST',
'FASTPOLICY': 'FC_GOLD1'}) 'FASTPOLICY': 'FC_GOLD1'})
def test_create_volume_from_snapshot_fast_failed( 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.data.test_volume['volume_name'] = "vmax-1234567"
self.assertRaises(exception.VolumeBackendAPIException, self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume_from_snapshot, self.driver.create_volume_from_snapshot,
@ -6623,7 +6644,7 @@ class EMCV3DriverTestCase(test.TestCase):
'create_element_replica') 'create_element_replica')
@mock.patch.object( @mock.patch.object(
emc_vmax_utils.EMCVMAXUtils, emc_vmax_utils.EMCVMAXUtils,
'find_sync_sv_by_target', 'find_sync_sv_by_volume',
return_value=(None, None)) return_value=(None, None))
def test_create_clone_v3_assert_clean_up_target_volume( def test_create_clone_v3_assert_clean_up_target_volume(
self, mock_sync, mock_create_replica, mock_volume_db, self, mock_sync, mock_create_replica, mock_volume_db,
@ -6778,6 +6799,8 @@ class EMCV2MultiPoolDriverTestCase(test.TestCase):
instancename.fake_getinstancename) instancename.fake_getinstancename)
self.mock_object(emc_vmax_utils.EMCVMAXUtils, 'isArrayV3', self.mock_object(emc_vmax_utils.EMCVMAXUtils, 'isArrayV3',
self.fake_is_v3) self.fake_is_v3)
emc_vmax_utils.EMCVMAXUtils._is_sync_complete = mock.Mock(
return_value=True)
driver = emc_vmax_iscsi.EMCVMAXISCSIDriver(configuration=configuration) driver = emc_vmax_iscsi.EMCVMAXISCSIDriver(configuration=configuration)
driver.db = FakeDB() driver.db = FakeDB()
self.driver = driver self.driver = driver
@ -8195,6 +8218,8 @@ class EMCVMAXFCTest(test.TestCase):
configuration.safe_get.return_value = 'FCTests' configuration.safe_get.return_value = 'FCTests'
configuration.config_group = 'FCTests' configuration.config_group = 'FCTests'
emc_vmax_common.EMCVMAXCommon._gather_info = mock.Mock() 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 = emc_vmax_fc.EMCVMAXFCDriver(configuration=configuration)
driver.db = FakeDB() driver.db = FakeDB()
self.driver = driver self.driver = driver
@ -8336,6 +8361,41 @@ class EMCVMAXFCTest(test.TestCase):
portGroupInstanceName, initiatorGroupInstanceName) portGroupInstanceName, initiatorGroupInstanceName)
self.assertEqual(0, len(mvInstances)) 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 @ddt.ddt
class EMCVMAXUtilsTest(test.TestCase): class EMCVMAXUtilsTest(test.TestCase):
@ -8631,6 +8691,18 @@ class EMCVMAXUtilsTest(test.TestCase):
conn, initiatorgroup) conn, initiatorgroup)
self.assertIsNone(foundIg) 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): class EMCVMAXCommonTest(test.TestCase):
def setUp(self): def setUp(self):
@ -8741,7 +8813,7 @@ class EMCVMAXCommonTest(test.TestCase):
repServiceInstanceName = ( repServiceInstanceName = (
self.driver.utils.find_replication_service( self.driver.utils.find_replication_service(
common.conn, self.data.storage_system)) 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)) return_value=(None, None))
self.driver.common._cleanup_target( self.driver.common._cleanup_target(
@ -8912,6 +8984,9 @@ class EMCVMAXISCSITest(test.TestCase):
configuration.safe_get.return_value = 'iSCSITests' configuration.safe_get.return_value = 'iSCSITests'
configuration.config_group = 'iSCSITests' configuration.config_group = 'iSCSITests'
emc_vmax_common.EMCVMAXCommon._gather_info = mock.Mock() 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 = emc_vmax_iscsi.EMCVMAXISCSIDriver(configuration=configuration)
driver.db = FakeDB() driver.db = FakeDB()
self.driver = driver self.driver = driver
@ -8933,3 +9008,29 @@ class EMCVMAXISCSITest(test.TestCase):
properties['target_iqns']) properties['target_iqns'])
self.assertEqual(['10.10.0.50:3260', '10.10.0.51:3260'], self.assertEqual(['10.10.0.50:3260', '10.10.0.51:3260'],
properties['target_portals']) 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) extraSpecs = self._initial_setup(snapshot)
self.conn = self._get_ecom_connection() self.conn = self._get_ecom_connection()
snapshotInstance = self._find_lun(snapshot) snapshotInstance = self._find_lun(snapshot)
storageSystem = snapshotInstance['SystemName']
syncName = self.utils.find_sync_sv_by_target( self._sync_check(snapshotInstance, snapshot['name'], extraSpecs)
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)
snapshot['host'] = volume['host'] snapshot['host'] = volume['host']
return self._create_cloned_volume(volume, snapshot, extraSpecs, False) return self._create_cloned_volume(volume, snapshot, extraSpecs, False)
@ -2300,6 +2286,8 @@ class EMCVMAXCommon(object):
{'name': volumeName}) {'name': volumeName})
return errorRet return errorRet
self._sync_check(volumeInstance, volumeName, extraSpecs)
storageConfigService = self.utils.find_storage_configuration_service( storageConfigService = self.utils.find_storage_configuration_service(
self.conn, volumeInstance['SystemName']) self.conn, volumeInstance['SystemName'])
@ -2469,58 +2457,16 @@ class EMCVMAXCommon(object):
:param snapshot: snapshot object to be deleted :param snapshot: snapshot object to be deleted
:raises: VolumeBackendAPIException :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() 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. # 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): def create_consistencygroup(self, context, group):
"""Creates a consistency group. """Creates a consistency group.
@ -3773,8 +3719,9 @@ class EMCVMAXCommon(object):
if targetInstance is not None: if targetInstance is not None:
# Check if the copy session exists. # Check if the copy session exists.
storageSystem = targetInstance['SystemName'] storageSystem = targetInstance['SystemName']
syncInstanceName = self.utils.find_sync_sv_by_target( syncInstanceName = self.utils.find_sync_sv_by_volume(
self.conn, storageSystem, targetInstance, False) self.conn, storageSystem, targetInstance, extraSpecs,
False)
if syncInstanceName is not None: if syncInstanceName is not None:
# Remove the Clone relationship. # Remove the Clone relationship.
rc, job = self.provision.delete_clone_relationship( rc, job = self.provision.delete_clone_relationship(
@ -3930,7 +3877,7 @@ class EMCVMAXCommon(object):
sourceInstance, cloneName, extraSpecs) sourceInstance, cloneName, extraSpecs)
try: try:
_rc, job = ( rc, job = (
self.provisionv3.create_element_replica( self.provisionv3.create_element_replica(
self.conn, repServiceInstanceName, cloneName, syncType, self.conn, repServiceInstanceName, cloneName, syncType,
sourceInstance, extraSpecs, targetInstance, rsdInstance)) sourceInstance, extraSpecs, targetInstance, rsdInstance))
@ -3939,7 +3886,6 @@ class EMCVMAXCommon(object):
"Clone failed on V3. Cleaning up the target volume. " "Clone failed on V3. Cleaning up the target volume. "
"Clone name: %(cloneName)s "), "Clone name: %(cloneName)s "),
{'cloneName': cloneName}) {'cloneName': cloneName})
# Check if the copy session exists.
if targetInstance: if targetInstance:
self._cleanup_target( self._cleanup_target(
repServiceInstanceName, targetInstance, extraSpecs) repServiceInstanceName, targetInstance, extraSpecs)
@ -3953,15 +3899,16 @@ class EMCVMAXCommon(object):
LOG.info(_LI("The target instance device id is: %(deviceid)s."), LOG.info(_LI("The target instance device id is: %(deviceid)s."),
{'deviceid': targetVolumeInstance['DeviceID']}) {'deviceid': targetVolumeInstance['DeviceID']})
cloneVolume['provider_location'] = six.text_type(cloneDict) if not isSnapshot:
cloneVolume['provider_location'] = six.text_type(cloneDict)
syncInstanceName, _storageSystem = ( syncInstanceName, _storageSystem = (
self._find_storage_sync_sv_sv(cloneVolume, sourceVolume, self._find_storage_sync_sv_sv(cloneVolume, sourceVolume,
extraSpecs, True)) extraSpecs, True))
rc, job = self.provisionv3.break_replication_relationship( rc, job = self.provisionv3.break_replication_relationship(
self.conn, repServiceInstanceName, syncInstanceName, self.conn, repServiceInstanceName, syncInstanceName,
operation, extraSpecs) operation, extraSpecs)
return rc, cloneDict return rc, cloneDict
def _cleanup_target( def _cleanup_target(
@ -3973,7 +3920,7 @@ class EMCVMAXCommon(object):
:param extraSpecs: extra specifications :param extraSpecs: extra specifications
""" """
storageSystem = targetInstance['SystemName'] storageSystem = targetInstance['SystemName']
syncInstanceName = self.utils.find_sync_sv_by_target( syncInstanceName = self.utils.find_sync_sv_by_volume(
self.conn, storageSystem, targetInstance, False) self.conn, storageSystem, targetInstance, False)
if syncInstanceName is not None: if syncInstanceName is not None:
# Break the clone relationship. # Break the clone relationship.
@ -4668,3 +4615,35 @@ class EMCVMAXCommon(object):
cgName += str(group[update_variable]) cgName += str(group[update_variable])
return cgName 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) - SnapVX licensing checks for VMAX3 (bug #1587017)
- VMAX oversubscription Support (blueprint vmax-oversubscription) - VMAX oversubscription Support (blueprint vmax-oversubscription)
- QoS support (blueprint vmax-qos) - 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 # ThirdPartySystems wiki
CI_WIKI_NAME = "EMC_VMAX_CI" CI_WIKI_NAME = "EMC_VMAX_CI"
@ -185,6 +187,19 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver):
""" """
device_info = self.common.initialize_connection( device_info = self.common.initialize_connection(
volume, connector) 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'] device_number = device_info['hostlunid']
storage_system = device_info['storagesystem'] storage_system = device_info['storagesystem']
target_wwns, init_targ_map = self._build_initiator_target_map( 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 = {'driver_volume_type': 'fibre_channel',
'data': {}} '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'] loc = volume['provider_location']
name = ast.literal_eval(loc) name = ast.literal_eval(loc)
storage_system = name['keybindings']['SystemName'] storage_system = name['keybindings']['SystemName']
@ -225,7 +260,7 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver):
mvInstanceName = self.common.get_masking_view_by_volume( mvInstanceName = self.common.get_masking_view_by_volume(
volume, connector) volume, connector)
if mvInstanceName is not None: if mvInstanceName:
portGroupInstanceName = ( portGroupInstanceName = (
self.common.get_port_group_from_masking_view( self.common.get_port_group_from_masking_view(
mvInstanceName)) mvInstanceName))
@ -240,46 +275,60 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver):
# Map must be populated before the terminate_connection # Map must be populated before the terminate_connection
target_wwns, init_targ_map = self._build_initiator_target_map( target_wwns, init_targ_map = self._build_initiator_target_map(
storage_system, volume, connector) 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 " :param zoning_mappings: zoning mapping dict
"Port Group %s.", portGroupInstanceName) :returns: data - dict
# check if the initiator group has been deleted """
LOG.debug("Looking for masking views still associated with "
"Port Group %s.", zoning_mappings['port_group'])
if zoning_mappings['initiator_group']:
checkIgInstanceName = ( 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 it has not been deleted, check for remaining masking views
if checkIgInstanceName is not None: if checkIgInstanceName:
mvInstances = self._get_common_masking_views( mvInstances = self._get_common_masking_views(
portGroupInstanceName, initiatorGroupInstanceName) zoning_mappings['port_group'],
zoning_mappings['initiator_group'])
if len(mvInstances) > 0: if len(mvInstances) > 0:
LOG.debug("Found %(numViews)lu MaskingViews.", LOG.debug("Found %(numViews)lu MaskingViews.",
{'numViews': len(mvInstances)}) {'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.")
data = {'driver_volume_type': 'fibre_channel', data = {'driver_volume_type': 'fibre_channel',
'data': {'target_wwn': target_wwns, 'data': {}}
'initiator_target_map': init_targ_map}} 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.", LOG.debug("Return FC data for zone removal: %(data)s.",
{'data': data}) {'data': data})
else: else: # The initiator group has been deleted
LOG.warning(_LW("Volume %(volume)s is not in any masking view."), LOG.debug("Initiator Group has been deleted. Deleting zone.")
{'volume': volume['name']}) 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 return data
def _get_common_masking_views( def _get_common_masking_views(
@ -436,3 +485,38 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver):
return self.common.create_consistencygroup_from_src( return self.common.create_consistencygroup_from_src(
context, group, volumes, cgsnapshot, snapshots, source_cg, context, group, volumes, cgsnapshot, snapshots, source_cg,
source_vols) 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) - QoS support (blueprint vmax-qos)
- VMAX2/VMAX3 iscsi multipath support (iscsi only) - VMAX2/VMAX3 iscsi multipath support (iscsi only)
https://blueprints.launchpad.net/cinder/+spec/vmax-iscsi-multipath 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 # ThirdPartySystems wiki
CI_WIKI_NAME = "EMC_VMAX_CI" CI_WIKI_NAME = "EMC_VMAX_CI"
@ -190,6 +191,17 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver):
""" """
device_info = self.common.initialize_connection( device_info = self.common.initialize_connection(
volume, connector) 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: try:
ip_and_iqn = device_info['ip_and_iqn'] ip_and_iqn = device_info['ip_and_iqn']
is_multipath = device_info['is_multipath'] is_multipath = device_info['is_multipath']
@ -202,27 +214,12 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver):
iscsi_properties = self.smis_get_iscsi_properties( iscsi_properties = self.smis_get_iscsi_properties(
volume, connector, ip_and_iqn, is_multipath) 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 { return {
'driver_volume_type': 'iscsi', 'driver_volume_type': 'iscsi',
'data': iscsi_properties '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, def smis_get_iscsi_properties(self, volume, connector, ip_and_iqn,
is_multipath): is_multipath):
"""Gets iscsi configuration. """Gets iscsi configuration.
@ -409,3 +406,41 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver):
return self.common.create_consistencygroup_from_src( return self.common.create_consistencygroup_from_src(
context, group, volumes, cgsnapshot, snapshots, source_cg, context, group, volumes, cgsnapshot, snapshots, source_cg,
source_vols) 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 pickle
import random import random
import re import re
import time
from xml.dom import minidom from xml.dom import minidom
from oslo_log import log as logging from oslo_log import log as logging
@ -1246,14 +1247,14 @@ class EMCVMAXUtils(object):
delta = endTime - startTime delta = endTime - startTime
return six.text_type(datetime.timedelta(seconds=int(delta))) return six.text_type(datetime.timedelta(seconds=int(delta)))
def find_sync_sv_by_target( def find_sync_sv_by_volume(
self, conn, storageSystem, target, extraSpecs, self, conn, storageSystem, volumeInstance, extraSpecs,
waitforsync=True): 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 conn: connection to the ecom server
:param storageSystem: the storage system name :param storageSystem: the storage system name
:param target: target volume object :param volumeInstance: volume instance
:param extraSpecs: the extraSpecs dict :param extraSpecs: the extraSpecs dict
:param waitforsync: wait for the synchronization to complete if True :param waitforsync: wait for the synchronization to complete if True
:returns: foundSyncInstanceName :returns: foundSyncInstanceName
@ -1263,9 +1264,11 @@ class EMCVMAXUtils(object):
'SE_StorageSynchronized_SV_SV') 'SE_StorageSynchronized_SV_SV')
for syncInstanceName in syncInstanceNames: for syncInstanceName in syncInstanceNames:
syncSvTarget = syncInstanceName['SyncedElement'] syncSvTarget = syncInstanceName['SyncedElement']
syncSvSource = syncInstanceName['SystemElement']
if storageSystem != syncSvTarget['SystemName']: if storageSystem != syncSvTarget['SystemName']:
continue continue
if syncSvTarget['DeviceID'] == target['DeviceID']: if syncSvTarget['DeviceID'] == volumeInstance['DeviceID'] or (
syncSvSource['DeviceID'] == volumeInstance['DeviceID']):
# Check that it hasn't recently been deleted. # Check that it hasn't recently been deleted.
try: try:
conn.GetInstance(syncInstanceName) conn.GetInstance(syncInstanceName)
@ -1277,15 +1280,20 @@ class EMCVMAXUtils(object):
foundSyncInstanceName = None foundSyncInstanceName = None
break break
if foundSyncInstanceName is None: if foundSyncInstanceName:
LOG.warning(_LW(
"Storage sync name not found for target %(target)s "
"on %(storageSystem)s."),
{'target': target['DeviceID'], 'storageSystem': storageSystem})
else:
# Wait for SE_StorageSynchronized_SV_SV to be fully synced. # Wait for SE_StorageSynchronized_SV_SV to be fully synced.
if waitforsync: 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) 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 return foundSyncInstanceName

View File

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