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