diff --git a/cinder/tests/unit/volume/drivers/emc/test_emc_vmax.py b/cinder/tests/unit/volume/drivers/emc/test_emc_vmax.py index 950eb21a64d..8f28024a6ba 100644 --- a/cinder/tests/unit/volume/drivers/emc/test_emc_vmax.py +++ b/cinder/tests/unit/volume/drivers/emc/test_emc_vmax.py @@ -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) diff --git a/cinder/volume/drivers/emc/emc_vmax_common.py b/cinder/volume/drivers/emc/emc_vmax_common.py index 3ada0805b60..532ed160705 100644 --- a/cinder/volume/drivers/emc/emc_vmax_common.py +++ b/cinder/volume/drivers/emc/emc_vmax_common.py @@ -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) diff --git a/cinder/volume/drivers/emc/emc_vmax_fc.py b/cinder/volume/drivers/emc/emc_vmax_fc.py index 93df4b43cad..1fbe847ccec 100644 --- a/cinder/volume/drivers/emc/emc_vmax_fc.py +++ b/cinder/volume/drivers/emc/emc_vmax_fc.py @@ -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 diff --git a/cinder/volume/drivers/emc/emc_vmax_iscsi.py b/cinder/volume/drivers/emc/emc_vmax_iscsi.py index 6c79642383f..36052eb388b 100644 --- a/cinder/volume/drivers/emc/emc_vmax_iscsi.py +++ b/cinder/volume/drivers/emc/emc_vmax_iscsi.py @@ -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 diff --git a/cinder/volume/drivers/emc/emc_vmax_utils.py b/cinder/volume/drivers/emc/emc_vmax_utils.py index b96a4eb54ea..551ef6b89e7 100644 --- a/cinder/volume/drivers/emc/emc_vmax_utils.py +++ b/cinder/volume/drivers/emc/emc_vmax_utils.py @@ -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 diff --git a/releasenotes/notes/vmax-attach-snapshot-3137e59ab4ff39a4.yaml b/releasenotes/notes/vmax-attach-snapshot-3137e59ab4ff39a4.yaml new file mode 100644 index 00000000000..1b22fb6cd8c --- /dev/null +++ b/releasenotes/notes/vmax-attach-snapshot-3137e59ab4ff39a4.yaml @@ -0,0 +1,4 @@ +--- +features: + - Enable backup snapshot optimal path by implementing attach + and detach snapshot in the VMAX driver.