VMAX driver - Manage/Unmanage performance fix
Previously managing an existing volume made a call to the ECOM
server which retrieved every volume across all connected arrays.
When this call was made if there were heavily populated arrays
the call would hang and eventually crash. To get around this,
instead of seeking the volume at the start of the manage operation,
the relevant details are filled in to a CIMInstanceName object and
this object is used for a more directed search to the ECOM server,
returning the volume successfully if it exists.
Change-Id: Ib8a628016caf88f67ec54ce10bc735053b70c8c2
Closes-Bug: #1681888
(cherry picked from commit dd7ea30547
)
This commit is contained in:
parent
01bede894b
commit
a051532fdc
|
@ -384,6 +384,22 @@ class VMAXCommonData(object):
|
|||
provider_location_multi_pool = {'classname': 'Symm_StorageVolume',
|
||||
'keybindings': keybindings,
|
||||
'version': '2.2.0'}
|
||||
|
||||
keybindings_manage = {'CreationClassName': 'Symm_StorageVolume',
|
||||
'SystemName': 'SYMMETRIX+000195900551',
|
||||
'DeviceID': '10',
|
||||
'SystemCreationClassName': 'Symm_StorageSystem'}
|
||||
provider_location_manage = {'classname': 'Symm_StorageVolume',
|
||||
'keybindings': keybindings_manage}
|
||||
|
||||
manage_vol = EMC_StorageVolume()
|
||||
manage_vol['CreationClassName'] = 'Symm_StorageVolume'
|
||||
manage_vol['ElementName'] = 'OS-Test_Manage_vol'
|
||||
manage_vol['DeviceID'] = '10'
|
||||
manage_vol['SystemName'] = 'SYMMETRIX+000195900551'
|
||||
manage_vol['SystemCreationClassName'] = 'Symm_StorageSystem'
|
||||
manage_vol.path = manage_vol
|
||||
|
||||
replication_driver_data = re_keybindings
|
||||
block_size = 512
|
||||
majorVersion = 1
|
||||
|
@ -400,6 +416,9 @@ class VMAXCommonData(object):
|
|||
properties = {'ConsumableBlocks': '12345',
|
||||
'BlockSize': '512'}
|
||||
|
||||
array = '000197800123'
|
||||
array_v3 = '1234567891011'
|
||||
|
||||
test_volume = {'name': 'vol1',
|
||||
'size': 1,
|
||||
'volume_name': 'vol1',
|
||||
|
@ -3942,17 +3961,6 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
|
|||
common._create_composite_volume.assert_called_with(
|
||||
volume, "TargetBaseVol", 1234567, extraSpecs, 1)
|
||||
|
||||
def test_find_volume_by_device_id_on_array(self):
|
||||
conn = self.fake_ecom_connection()
|
||||
utils = self.driver.common.utils
|
||||
volumeInstanceName = utils.find_volume_by_device_id_on_array(
|
||||
conn, self.data.storage_system, self.data.test_volume['device_id'])
|
||||
expectVolume = {}
|
||||
expectVolume['CreationClassName'] = 'Symm_StorageVolume'
|
||||
expectVolume['DeviceID'] = self.data.test_volume['device_id']
|
||||
expect = conn.GetInstance(expectVolume)
|
||||
self.assertEqual(expect, volumeInstanceName)
|
||||
|
||||
def test_get_volume_element_name(self):
|
||||
volumeId = 'ea95aa39-080b-4f11-9856-a03acf9112ad'
|
||||
util = self.driver.common.utils
|
||||
|
@ -3974,10 +3982,11 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
|
|||
|
||||
def test_get_array_and_device_id_success(self):
|
||||
deviceId = '0123'
|
||||
arrayId = u'array1234'
|
||||
arrayId = '1234567891011'
|
||||
external_ref = {u'source-name': deviceId}
|
||||
volume = {'volume_metadata': [{'key': 'array', 'value': arrayId}]
|
||||
}
|
||||
volume['host'] = 'HostX@Backend#Bronze+SRP_1+1234567891011'
|
||||
utils = self.driver.common.utils
|
||||
(arrId, devId) = utils.get_array_and_device_id(volume, external_ref)
|
||||
self.assertEqual(arrayId, arrId)
|
||||
|
@ -3985,10 +3994,11 @@ class VMAXISCSIDriverNoFastTestCase(test.TestCase):
|
|||
|
||||
def test_get_array_and_device_id_failed(self):
|
||||
deviceId = '0123'
|
||||
arrayId = u'array1234'
|
||||
arrayId = '1234567891011'
|
||||
external_ref = {u'no-source-name': deviceId}
|
||||
volume = {'volume_metadata': [{'key': 'array', 'value': arrayId}]
|
||||
}
|
||||
volume['host'] = 'HostX@Backend#Bronze+SRP_1+1234567891011'
|
||||
utils = self.driver.common.utils
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
utils.get_array_and_device_id,
|
||||
|
@ -5242,73 +5252,6 @@ class VMAXFCDriverNoFastTestCase(test.TestCase):
|
|||
self.driver.delete_cgsnapshot(
|
||||
self.data.test_ctxt, self.data.test_CG_snapshot, [])
|
||||
|
||||
@ddt.data((2, 2), (1.75, 2))
|
||||
@ddt.unpack
|
||||
def test_manage_existing_get_size(self, gb_size, exp_size):
|
||||
volume = {}
|
||||
metadata = {'key': 'array',
|
||||
'value': '12345'}
|
||||
volume['volume_metadata'] = [metadata]
|
||||
external_ref = {'source-name': '0123'}
|
||||
utils = self.driver.common.utils
|
||||
utils.get_volume_size = mock.Mock(
|
||||
return_value=int(gb_size * units.Gi))
|
||||
volumeInstanceName = {'CreationClassName': "Symm_StorageVolume",
|
||||
'DeviceID': "0123",
|
||||
'SystemName': "12345"}
|
||||
utils.find_volume_by_device_id_on_array = mock.Mock(
|
||||
return_value=volumeInstanceName)
|
||||
size = self.driver.manage_existing_get_size(volume, external_ref)
|
||||
self.assertEqual(exp_size, size)
|
||||
|
||||
def test_manage_existing_no_fast_success(self):
|
||||
volume = {}
|
||||
metadata = {'key': 'array',
|
||||
'value': '12345'}
|
||||
poolInstanceName = {}
|
||||
storageSystem = {}
|
||||
poolInstanceName['InstanceID'] = "SATA_GOLD1"
|
||||
storageSystem['InstanceID'] = "SYMMETRIX+00019870000"
|
||||
volume['volume_metadata'] = [metadata]
|
||||
volume['name'] = "test-volume"
|
||||
volume['id'] = "test-volume"
|
||||
external_ref = {'source-name': '0123'}
|
||||
utils = self.driver.common.utils
|
||||
gbSize = 2
|
||||
utils.get_volume_size = mock.Mock(
|
||||
return_value=gbSize * units.Gi)
|
||||
utils.get_associated_replication_from_source_volume = mock.Mock(
|
||||
return_value=None)
|
||||
utils.get_assoc_pool_from_volume = mock.Mock(
|
||||
return_value=(poolInstanceName))
|
||||
|
||||
vol = EMC_StorageVolume()
|
||||
vol['CreationClassName'] = 'Symm_StorageVolume'
|
||||
vol['ElementName'] = 'OS-' + volume['id']
|
||||
vol['DeviceID'] = external_ref['source-name']
|
||||
vol['SystemName'] = storageSystem['InstanceID']
|
||||
vol['SystemCreationClassName'] = 'Symm_StorageSystem'
|
||||
vol.path = vol
|
||||
utils.rename_volume = mock.Mock(
|
||||
return_value=vol)
|
||||
common = self.driver.common
|
||||
common._initial_setup = mock.Mock(
|
||||
return_value={'volume_backend_name': 'FCNoFAST',
|
||||
'storagetype:fastpolicy': None})
|
||||
common._get_pool_and_storage_system = mock.Mock(
|
||||
return_value=(poolInstanceName, storageSystem))
|
||||
volumeInstanceName = {'CreationClassName': "Symm_StorageVolume",
|
||||
'DeviceID': "0123",
|
||||
'SystemName': "12345"}
|
||||
utils.find_volume_by_device_id_on_array = mock.Mock(
|
||||
return_value=volumeInstanceName)
|
||||
masking = self.driver.common.masking
|
||||
masking.get_masking_view_from_storage_group = mock.Mock(
|
||||
return_value={})
|
||||
self.driver.manage_existing(volume, external_ref)
|
||||
utils.rename_volume.assert_called_once_with(
|
||||
common.conn, volumeInstanceName, volume['name'])
|
||||
|
||||
def test_unmanage_no_fast_success(self):
|
||||
keybindings = {'CreationClassName': u'Symm_StorageVolume',
|
||||
'SystemName': u'SYMMETRIX+000195900000',
|
||||
|
@ -6030,6 +5973,7 @@ class VMAXFCDriverFastTestCase(test.TestCase):
|
|||
storageSystem['InstanceID'] = "SYMMETRIX+00019870000"
|
||||
volume['volume_metadata'] = [metadata]
|
||||
volume['name'] = "test-volume"
|
||||
volume['host'] = 'HostX@Backend#Bronze+SRP_1+1234567891011'
|
||||
external_ref = {'source-name': '0123'}
|
||||
common = self.driver.common
|
||||
common._initial_setup = mock.Mock(
|
||||
|
@ -8781,6 +8725,215 @@ class VMAXUtilsTest(test.TestCase):
|
|||
conn, storageSystem, volumeInstance, extraSpecs)
|
||||
self.assertEqual(syncInstance, foundSyncInstance)
|
||||
|
||||
def test_get_assoc_v2_pool_from_vol(self):
|
||||
conn = FakeEcomConnection()
|
||||
volumeInstanceName = {'CreationClassName': "Symm_StorageVolume",
|
||||
'DeviceID': "0123",
|
||||
'SystemName': "12345"}
|
||||
|
||||
pool = conn.AssociatorNames(
|
||||
volumeInstanceName, ResultClass='EMC_VirtualProvisioningPool')
|
||||
poolName = self.driver.utils.get_assoc_v2_pool_from_volume(
|
||||
conn, volumeInstanceName)
|
||||
|
||||
self.assertEqual(pool[0]['ElementName'], poolName['ElementName'])
|
||||
|
||||
def test_get_assoc_v2_pool_from_vol_fail(self):
|
||||
conn = FakeEcomConnection()
|
||||
volumeInstanceName = {'CreationClassName': "Symm_StorageVolume",
|
||||
'DeviceID': "0123",
|
||||
'SystemName': "12345"}
|
||||
|
||||
conn.AssociatorNames = mock.Mock(return_value={})
|
||||
|
||||
poolName = self.driver.utils.get_assoc_v2_pool_from_volume(
|
||||
conn, volumeInstanceName)
|
||||
|
||||
self.assertIsNone(poolName)
|
||||
|
||||
def test_get_assoc_v3_pool_from_vol(self):
|
||||
conn = FakeEcomConnection()
|
||||
volumeInstanceName = {'CreationClassName': "Symm_StorageVolume",
|
||||
'DeviceID': "0123",
|
||||
'SystemName': "12345"}
|
||||
|
||||
pool = conn.AssociatorNames(
|
||||
volumeInstanceName, ResultClass='Symm_SRPStoragePool')
|
||||
poolName = self.driver.utils.get_assoc_v3_pool_from_volume(
|
||||
conn, volumeInstanceName)
|
||||
|
||||
self.assertEqual(pool[0]['ElementName'], poolName['ElementName'])
|
||||
|
||||
def test_get_assoc_v3_pool_from_vol_fail(self):
|
||||
conn = FakeEcomConnection()
|
||||
volumeInstanceName = {'CreationClassName': "Symm_StorageVolume",
|
||||
'DeviceID': "0123",
|
||||
'SystemName': "12345"}
|
||||
|
||||
conn.AssociatorNames = mock.Mock(return_value={})
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.utils.get_assoc_v3_pool_from_volume,
|
||||
conn, volumeInstanceName)
|
||||
|
||||
def test_check_volume_no_fast_fail(self):
|
||||
utils = self.driver.common.utils
|
||||
initial_setup = {'volume_backend_name': 'FCFAST',
|
||||
'storagetype:fastpolicy': 'GOLD'}
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
utils.check_volume_no_fast,
|
||||
initial_setup)
|
||||
|
||||
def test_check_volume_no_fast_pass(self):
|
||||
utils = self.driver.common.utils
|
||||
initial_setup = {'volume_backend_name': 'FCnoFAST',
|
||||
'storagetype:fastpolicy': None}
|
||||
|
||||
self.assertTrue(utils.check_volume_no_fast(
|
||||
initial_setup))
|
||||
|
||||
def test_check_volume_not_in_masking_view_pass(self):
|
||||
conn = FakeEcomConnection()
|
||||
utils = self.driver.common.utils
|
||||
|
||||
bindings = {'CreationClassName': 'Symm_StorageVolume',
|
||||
'SystemName': self.data.storage_system,
|
||||
'DeviceID': self.data.test_volume['device_id'],
|
||||
'SystemCreationClassName': 'Symm_StorageSystem'}
|
||||
inst = FakeCIMInstanceName()
|
||||
fake_inst = inst.fake_getinstancename('Symm_StorageVolume', bindings)
|
||||
|
||||
sgInstanceNames = conn.AssociatorNames(fake_inst,
|
||||
ResultClass=
|
||||
'CIM_DeviceMaskingGroup')
|
||||
|
||||
conn.AssociatorNames = mock.Mock(return_value={})
|
||||
|
||||
mock.patch.object(self.driver.utils, 'get_storage_groups_from_volume',
|
||||
return_value=sgInstanceNames)
|
||||
|
||||
self.assertTrue(
|
||||
utils.check_volume_not_in_masking_view(
|
||||
conn, fake_inst, self.data.test_volume['device_id']))
|
||||
|
||||
def test_check_volume_not_in_masking_view_fail(self):
|
||||
conn = FakeEcomConnection()
|
||||
utils = self.driver.common.utils
|
||||
|
||||
bindings = {'CreationClassName': 'Symm_StorageVolume',
|
||||
'SystemName': self.data.storage_system,
|
||||
'DeviceID': self.data.test_volume['device_id'],
|
||||
'SystemCreationClassName': 'Symm_StorageSystem'}
|
||||
inst = FakeCIMInstanceName()
|
||||
fake_inst = inst.fake_getinstancename('Symm_StorageVolume', bindings)
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
utils.check_volume_not_in_masking_view,
|
||||
conn, fake_inst, self.data.test_volume['device_id'])
|
||||
|
||||
def test_check_volume_not_replication_source_pass(self):
|
||||
conn = FakeEcomConnection()
|
||||
utils = self.driver.common.utils
|
||||
|
||||
self.assertTrue(
|
||||
utils.check_volume_not_replication_source(
|
||||
conn, self.data.storage_system_v3,
|
||||
self.data.test_volume['device_id']))
|
||||
|
||||
def test_check_volume_not_replication_source_fail(self):
|
||||
conn = FakeEcomConnection()
|
||||
utils = self.driver.common.utils
|
||||
|
||||
replication_source = 'testReplicationSync'
|
||||
|
||||
utils.get_associated_replication_from_source_volume = (
|
||||
mock.Mock(return_value=replication_source))
|
||||
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
utils.check_volume_not_replication_source,
|
||||
conn, self.data.storage_system_v3,
|
||||
self.data.test_volume['device_id'])
|
||||
|
||||
def test_check_is_volume_in_cinder_managed_pool_fail(self):
|
||||
conn = FakeEcomConnection()
|
||||
utils = self.driver.common.utils
|
||||
|
||||
volumeInstanceName = {'CreationClassName': "Symm_StorageVolume",
|
||||
'DeviceID': "0123",
|
||||
'SystemName': "12345"}
|
||||
|
||||
poolInstanceName = {}
|
||||
poolInstanceName['InstanceID'] = "SATA_GOLD1"
|
||||
deviceId = '0123'
|
||||
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
utils.check_is_volume_in_cinder_managed_pool,
|
||||
conn, volumeInstanceName, poolInstanceName, deviceId)
|
||||
|
||||
def test_check_is_volume_in_cinder_managed_pool_pass(self):
|
||||
conn = FakeEcomConnection()
|
||||
utils = self.driver.common.utils
|
||||
|
||||
volumeInstanceName = {}
|
||||
poolInstanceName = {}
|
||||
poolInstanceName['InstanceID'] = "SATA_GOLD2"
|
||||
deviceId = self.data.test_volume['device_id']
|
||||
|
||||
utils.get_assoc_v2_pool_from_volume = (
|
||||
mock.Mock(return_value=poolInstanceName))
|
||||
|
||||
self.assertTrue(
|
||||
utils.check_is_volume_in_cinder_managed_pool(
|
||||
conn, volumeInstanceName, poolInstanceName, deviceId))
|
||||
|
||||
def test_find_volume_by_device_id_on_array(self):
|
||||
conn = FakeEcomConnection()
|
||||
utils = self.driver.common.utils
|
||||
|
||||
bindings = {'CreationClassName': 'Symm_StorageVolume',
|
||||
'SystemName': self.data.storage_system,
|
||||
'DeviceID': self.data.test_volume['device_id'],
|
||||
'SystemCreationClassName': 'Symm_StorageSystem'}
|
||||
|
||||
inst = FakeCIMInstanceName()
|
||||
fake_inst = inst.fake_getinstancename('Symm_StorageVolume', bindings)
|
||||
utils.find_volume_by_device_id_on_array = mock.Mock(
|
||||
return_value=fake_inst)
|
||||
|
||||
volumeInstanceName = utils.find_volume_by_device_id_on_array(
|
||||
self.data.storage_system, self.data.test_volume['device_id'])
|
||||
|
||||
expectVolume = {}
|
||||
expectVolume['CreationClassName'] = 'Symm_StorageVolume'
|
||||
expectVolume['DeviceID'] = self.data.test_volume['device_id']
|
||||
expect = conn.GetInstance(expectVolume)
|
||||
|
||||
provider_location = ast.literal_eval(expect['provider_location'])
|
||||
bindings = provider_location['keybindings']
|
||||
|
||||
self.assertEqual(bindings, volumeInstanceName)
|
||||
|
||||
def test_get_array_and_device_id(self):
|
||||
utils = self.driver.common.utils
|
||||
volume = self.data.test_volume.copy()
|
||||
volume['volume_metadata'] = {'array': self.data.array_v3}
|
||||
external_ref = {u'source-name': u'00002'}
|
||||
array, device_id = utils.get_array_and_device_id(
|
||||
volume, external_ref)
|
||||
self.assertEqual(self.data.array_v3, array)
|
||||
self.assertEqual('00002', device_id)
|
||||
|
||||
def test_get_array_and_device_id_exception(self):
|
||||
utils = self.driver.common.utils
|
||||
volume = self.data.test_volume.copy()
|
||||
volume['volume_metadata'] = {'array': self.data.array}
|
||||
external_ref = {u'source-name': None}
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
utils.get_array_and_device_id, volume, external_ref)
|
||||
|
||||
|
||||
class VMAXCommonTest(test.TestCase):
|
||||
def setUp(self):
|
||||
|
@ -9567,6 +9720,152 @@ class VMAXCommonTest(test.TestCase):
|
|||
exception.VolumeBackendAPIException,
|
||||
common._get_port_group_from_source, deviceInfoDict)
|
||||
|
||||
def test_manage_existing_get_size(self):
|
||||
common = self.driver.common
|
||||
common.conn = FakeEcomConnection()
|
||||
|
||||
gb_size = 2
|
||||
exp_size = 2
|
||||
volume = {}
|
||||
metadata = {'key': 'array',
|
||||
'value': '12345'}
|
||||
volume['volume_metadata'] = [metadata]
|
||||
volume['host'] = 'HostX@Backend#Bronze+SRP_1+1234567891011'
|
||||
external_ref = {'source-name': '0123'}
|
||||
volumeInstanceName = {'CreationClassName': "Symm_StorageVolume",
|
||||
'DeviceID': "0123",
|
||||
'SystemName': "12345"}
|
||||
|
||||
utils = self.driver.common.utils
|
||||
utils.get_volume_size = mock.Mock(
|
||||
return_value=int(gb_size * units.Gi))
|
||||
utils.find_volume_by_device_id_on_array = mock.Mock(
|
||||
return_value=volumeInstanceName)
|
||||
|
||||
size = self.driver.manage_existing_get_size(volume, external_ref)
|
||||
self.assertEqual(exp_size, size)
|
||||
|
||||
def test_manage_existing_get_size_fail(self):
|
||||
common = self.driver.common
|
||||
common.conn = FakeEcomConnection()
|
||||
|
||||
gb_size = 2
|
||||
volume = {}
|
||||
metadata = {'key': 'array',
|
||||
'value': '12345'}
|
||||
volume['volume_metadata'] = [metadata]
|
||||
volume['host'] = 'HostX@Backend#Bronze+SRP_1+1234567891011'
|
||||
external_ref = {'source-name': '0123'}
|
||||
|
||||
utils = self.driver.common.utils
|
||||
utils.get_volume_size = mock.Mock(
|
||||
return_value=int(gb_size * units.Gi))
|
||||
|
||||
utils.find_volume_by_device_id_on_array = mock.Mock(
|
||||
return_value=None)
|
||||
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.driver.common.manage_existing_get_size,
|
||||
volume, external_ref)
|
||||
|
||||
def test_set_volume_replication_if_enabled(self):
|
||||
common = self.driver.common
|
||||
common.conn = FakeEcomConnection()
|
||||
|
||||
volume = {}
|
||||
provider_location = {}
|
||||
|
||||
replication_status = 'replicated'
|
||||
replication_driver_data = 'replication_data'
|
||||
|
||||
model_update = {}
|
||||
model_update.update(
|
||||
{'replication_status': replication_status})
|
||||
model_update.update(
|
||||
{'replication_driver_data': six.text_type(
|
||||
replication_driver_data)})
|
||||
|
||||
extra_specs = self.data.extra_specs_is_re
|
||||
|
||||
common.setup_volume_replication = mock.Mock(
|
||||
return_value=(replication_status, replication_driver_data))
|
||||
|
||||
new_model_update = common.set_volume_replication_if_enabled(
|
||||
common.conn, extra_specs, volume, provider_location)
|
||||
|
||||
self.assertEqual(new_model_update, model_update)
|
||||
|
||||
@mock.patch.object(
|
||||
common.VMAXCommon,
|
||||
'set_volume_replication_if_enabled',
|
||||
return_value={'replication_status': 'replicated',
|
||||
'replication_driver_data': 'driver_data',
|
||||
'display_name': 'vol1',
|
||||
'provider_location':
|
||||
VMAXCommonData.provider_location3})
|
||||
@mock.patch.object(
|
||||
utils.VMAXUtils,
|
||||
'rename_volume',
|
||||
return_value=VMAXCommonData.manage_vol)
|
||||
@mock.patch.object(
|
||||
utils.VMAXUtils,
|
||||
'check_is_volume_in_cinder_managed_pool',
|
||||
return_value=True)
|
||||
@mock.patch.object(
|
||||
utils.VMAXUtils,
|
||||
'check_volume_not_replication_source',
|
||||
return_value=True)
|
||||
@mock.patch.object(
|
||||
common.VMAXCommon,
|
||||
'_get_pool_and_storage_system',
|
||||
return_value=('cinder_pool', 'vmax_storage_system'))
|
||||
@mock.patch.object(
|
||||
utils.VMAXUtils,
|
||||
'check_volume_not_in_masking_view',
|
||||
return_value=True)
|
||||
@mock.patch.object(
|
||||
utils.VMAXUtils,
|
||||
'find_volume_by_device_id_on_array',
|
||||
return_value=VMAXCommonData.test_volume)
|
||||
@mock.patch.object(
|
||||
utils.VMAXUtils,
|
||||
'check_volume_no_fast',
|
||||
return_value=True)
|
||||
@mock.patch.object(
|
||||
utils.VMAXUtils,
|
||||
'get_array_and_device_id',
|
||||
return_value=('12345', '1'))
|
||||
@mock.patch.object(
|
||||
common.VMAXCommon,
|
||||
'_get_ecom_connection',
|
||||
return_value=FakeEcomConnection())
|
||||
@mock.patch.object(
|
||||
common.VMAXCommon,
|
||||
'_initial_setup',
|
||||
return_value=VMAXCommonData.extra_specs_is_re)
|
||||
def test_manage_existing(self, mock_setup, mock_ecom, mock_ids,
|
||||
mock_vol_fast, mock_vol_by_deviceId,
|
||||
mock_vol_in_mv, mock_pool_sg, mock_vol_rep_src,
|
||||
mock_vol_in_mng_pool, mock_rename_vol,
|
||||
mock_set_vol_rep):
|
||||
common = self.driver.common
|
||||
volume = EMC_StorageVolume()
|
||||
volume.name = 'vol1'
|
||||
volume.display_name = 'vol1'
|
||||
external_ref = {}
|
||||
|
||||
model_update = {
|
||||
'replication_status': 'replicated',
|
||||
'replication_driver_data': 'driver_data',
|
||||
'display_name': 'vol1',
|
||||
'provider_location': six.text_type(
|
||||
self.data.provider_location_manage)}
|
||||
|
||||
new_model_update = common.manage_existing(volume,
|
||||
external_ref)
|
||||
|
||||
self.assertEqual(model_update, new_model_update)
|
||||
|
||||
|
||||
class VMAXProvisionTest(test.TestCase):
|
||||
def setUp(self):
|
||||
|
|
|
@ -14,7 +14,6 @@
|
|||
# under the License.
|
||||
|
||||
import ast
|
||||
import math
|
||||
import os.path
|
||||
|
||||
from oslo_config import cfg
|
||||
|
@ -4636,84 +4635,34 @@ class VMAXCommon(object):
|
|||
"""
|
||||
extraSpecs = self._initial_setup(volume)
|
||||
self.conn = self._get_ecom_connection()
|
||||
arrayName, deviceId = self.utils.get_array_and_device_id(volume,
|
||||
external_ref)
|
||||
arrayName, deviceId = self.utils.get_array_and_device_id(
|
||||
volume, external_ref)
|
||||
|
||||
self.utils.check_volume_no_fast(extraSpecs)
|
||||
|
||||
# Manage existing volume is not supported if fast enabled.
|
||||
if extraSpecs[FASTPOLICY]:
|
||||
LOG.warning(_LW(
|
||||
"FAST is enabled. Policy: %(fastPolicyName)s."),
|
||||
{'fastPolicyName': extraSpecs[FASTPOLICY]})
|
||||
exceptionMessage = (_(
|
||||
"Manage volume is not supported if FAST is enable. "
|
||||
"FAST policy: %(fastPolicyName)s.")
|
||||
% {'fastPolicyName': extraSpecs[FASTPOLICY]})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
# Check if the volume is attached by checking if in any masking view.
|
||||
volumeInstanceName = (
|
||||
self.utils.find_volume_by_device_id_on_array(self.conn,
|
||||
arrayName, deviceId))
|
||||
sgInstanceNames = (
|
||||
self.utils.get_storage_groups_from_volume(
|
||||
self.conn, volumeInstanceName))
|
||||
self.utils.find_volume_by_device_id_on_array(
|
||||
arrayName, deviceId))
|
||||
|
||||
for sgInstanceName in sgInstanceNames:
|
||||
mvInstanceNames = (
|
||||
self.masking.get_masking_view_from_storage_group(
|
||||
self.conn, sgInstanceName))
|
||||
for mvInstanceName in mvInstanceNames:
|
||||
exceptionMessage = (_(
|
||||
"Unable to import volume %(deviceId)s to cinder. "
|
||||
"Volume is in masking view %(mv)s.")
|
||||
% {'deviceId': deviceId,
|
||||
'mv': mvInstanceName})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
self.utils.check_volume_not_in_masking_view(
|
||||
self.conn, volumeInstanceName, deviceId)
|
||||
|
||||
# Check if there is any associated snapshots with the volume.
|
||||
cinderPoolInstanceName, storageSystemName = (
|
||||
self._get_pool_and_storage_system(extraSpecs))
|
||||
repSessionInstanceName = (
|
||||
self.utils.get_associated_replication_from_source_volume(
|
||||
self.conn, storageSystemName, deviceId))
|
||||
if repSessionInstanceName:
|
||||
exceptionMessage = (_(
|
||||
"Unable to import volume %(deviceId)s to cinder. "
|
||||
"It is the source volume of replication session %(sync)s.")
|
||||
% {'deviceId': deviceId,
|
||||
'sync': repSessionInstanceName})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
|
||||
# Make sure the existing external volume is in the same storage pool.
|
||||
volumePoolInstanceName = (
|
||||
self.utils.get_assoc_pool_from_volume(self.conn,
|
||||
volumeInstanceName))
|
||||
volumePoolName = volumePoolInstanceName['InstanceID']
|
||||
cinderPoolName = cinderPoolInstanceName['InstanceID']
|
||||
LOG.debug("Storage pool of existing volume: %(volPool)s, "
|
||||
"Storage pool currently managed by cinder: %(cinderPool)s.",
|
||||
{'volPool': volumePoolName,
|
||||
'cinderPool': cinderPoolName})
|
||||
if volumePoolName != cinderPoolName:
|
||||
exceptionMessage = (_(
|
||||
"Unable to import volume %(deviceId)s to cinder. The external "
|
||||
"volume is not in the pool managed by current cinder host.")
|
||||
% {'deviceId': deviceId})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
self.utils.check_volume_not_replication_source(
|
||||
self.conn, storageSystemName, deviceId)
|
||||
|
||||
# Rename the volume
|
||||
volumeId = volume['id']
|
||||
self.utils.check_is_volume_in_cinder_managed_pool(
|
||||
self.conn, volumeInstanceName, cinderPoolInstanceName,
|
||||
deviceId)
|
||||
|
||||
volumeId = volume.name
|
||||
volumeElementName = self.utils.get_volume_element_name(volumeId)
|
||||
LOG.debug("Rename volume %(vol)s to %(elementName)s.",
|
||||
LOG.debug("Rename volume %(vol)s to %(volumeId)s.",
|
||||
{'vol': volumeInstanceName,
|
||||
'elementName': volumeElementName})
|
||||
'volumeId': volumeElementName})
|
||||
|
||||
volumeInstance = self.utils.rename_volume(self.conn,
|
||||
volumeInstanceName,
|
||||
volumeElementName)
|
||||
|
@ -4724,25 +4673,44 @@ class VMAXCommon(object):
|
|||
keys['DeviceID'] = volpath['DeviceID']
|
||||
keys['SystemCreationClassName'] = volpath['SystemCreationClassName']
|
||||
|
||||
model_update = {}
|
||||
provider_location = {}
|
||||
provider_location['classname'] = volpath['CreationClassName']
|
||||
provider_location['keybindings'] = keys
|
||||
|
||||
# set-up volume replication, if enabled
|
||||
model_update = self.set_volume_replication_if_enabled(
|
||||
self.conn, extraSpecs, volume, provider_location)
|
||||
|
||||
volumeDisplayName = volume.display_name
|
||||
model_update.update(
|
||||
{'display_name': volumeDisplayName})
|
||||
model_update.update(
|
||||
{'provider_location': six.text_type(provider_location)})
|
||||
return model_update
|
||||
|
||||
def set_volume_replication_if_enabled(self, conn, extraSpecs,
|
||||
volume, provider_location):
|
||||
"""Set volume replication if enabled
|
||||
|
||||
If volume replication is enabled, set relevant
|
||||
values in associated model_update dict.
|
||||
|
||||
:param conn: connection to the ecom server
|
||||
:param extraSpecs: additional info
|
||||
:param volume: the volume object
|
||||
:param provider_location: volume classname & keybindings
|
||||
:return: updated model_update
|
||||
"""
|
||||
model_update = {}
|
||||
if self.utils.is_replication_enabled(extraSpecs):
|
||||
replication_status, replication_driver_data = (
|
||||
self.setup_volume_replication(
|
||||
self.conn, volume, provider_location, extraSpecs))
|
||||
conn, volume, provider_location, extraSpecs))
|
||||
model_update.update(
|
||||
{'replication_status': replication_status})
|
||||
model_update.update(
|
||||
{'replication_driver_data': six.text_type(
|
||||
replication_driver_data)})
|
||||
|
||||
model_update.update({'display_name': volumeElementName})
|
||||
model_update.update(
|
||||
{'provider_location': six.text_type(provider_location)})
|
||||
return model_update
|
||||
|
||||
def manage_existing_get_size(self, volume, external_ref):
|
||||
|
@ -4758,15 +4726,25 @@ class VMAXCommon(object):
|
|||
arrayName, deviceId = self.utils.get_array_and_device_id(volume,
|
||||
external_ref)
|
||||
volumeInstanceName = (
|
||||
self.utils.find_volume_by_device_id_on_array(self.conn,
|
||||
arrayName, deviceId))
|
||||
volumeInstance = self.conn.GetInstance(volumeInstanceName)
|
||||
byteSize = self.utils.get_volume_size(self.conn, volumeInstance)
|
||||
gbSize = int(math.ceil(float(byteSize) / units.Gi))
|
||||
self.utils.find_volume_by_device_id_on_array(arrayName, deviceId))
|
||||
|
||||
try:
|
||||
volumeInstance = self.conn.GetInstance(volumeInstanceName)
|
||||
byteSize = self.utils.get_volume_size(self.conn, volumeInstance)
|
||||
fByteSize = float(byteSize)
|
||||
gbSize = int(fByteSize / units.Gi)
|
||||
|
||||
except Exception:
|
||||
exceptionMessage = (_("Volume %(deviceID)s not found.")
|
||||
% {'deviceID': deviceId})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(data=exceptionMessage)
|
||||
|
||||
LOG.debug(
|
||||
"Size of volume %(deviceID)s is %(volumeSize)s GB.",
|
||||
"Size of volume %(deviceID)s is %(volumeSize)s GB",
|
||||
{'deviceID': deviceId,
|
||||
'volumeSize': gbSize})
|
||||
|
||||
return gbSize
|
||||
|
||||
def unmanage(self, volume):
|
||||
|
@ -4793,9 +4771,7 @@ class VMAXCommon(object):
|
|||
raise exception.VolumeBackendAPIException(data=exceptionMessage)
|
||||
|
||||
# Rename the volume to volumeId, thus remove the 'OS-' prefix.
|
||||
volumeInstance = self.utils.rename_volume(self.conn,
|
||||
volumeInstance,
|
||||
volumeId)
|
||||
self.utils.rename_volume(self.conn, volumeInstance, volumeId)
|
||||
|
||||
def update_consistencygroup(self, group, add_volumes,
|
||||
remove_volumes):
|
||||
|
|
|
@ -799,6 +799,61 @@ class VMAXUtils(object):
|
|||
foundPoolInstanceName = foundPoolInstanceNames[0]
|
||||
return foundPoolInstanceName
|
||||
|
||||
def get_assoc_v2_pool_from_volume(self, conn, volumeInstanceName):
|
||||
"""Give the volume instance get the associated pool instance.
|
||||
|
||||
:param conn: connection to the ecom server
|
||||
:param volumeInstanceName: the volume instance name
|
||||
:returns: foundPoolInstanceName
|
||||
"""
|
||||
foundPoolInstanceName = None
|
||||
foundPoolInstanceNames = (
|
||||
conn.AssociatorNames(volumeInstanceName,
|
||||
ResultClass='EMC_VirtualProvisioningPool'))
|
||||
|
||||
if not foundPoolInstanceNames:
|
||||
deviceID = volumeInstanceName['DeviceID']
|
||||
LOG.debug("Volume %(deviceId)s not in V2 pool",
|
||||
{'deviceId': deviceID})
|
||||
else:
|
||||
LOG.debug("Retrieved pool: %(foundPoolInstanceNames)s",
|
||||
{'foundPoolInstanceNames': foundPoolInstanceNames})
|
||||
|
||||
if foundPoolInstanceNames and len(foundPoolInstanceNames) > 0:
|
||||
foundPoolInstanceName = foundPoolInstanceNames[0]
|
||||
|
||||
return foundPoolInstanceName
|
||||
|
||||
def get_assoc_v3_pool_from_volume(self, conn, volumeInstanceName):
|
||||
"""Give the volume instance get the associated pool instance.
|
||||
|
||||
:param conn: connection to the ecom server
|
||||
:param volumeInstanceName: the volume instance name
|
||||
:returns: foundPoolInstanceName
|
||||
"""
|
||||
foundPoolInstanceName = None
|
||||
foundPoolInstanceNames = (
|
||||
conn.AssociatorNames(volumeInstanceName,
|
||||
ResultClass='Symm_SRPStoragePool'))
|
||||
|
||||
if not foundPoolInstanceNames:
|
||||
deviceID = volumeInstanceName['DeviceID']
|
||||
LOG.debug("Volume %(deviceId)s not in V3 SRP",
|
||||
{'deviceId': deviceID})
|
||||
exceptionMessage = ("Unable to locate volume %(deviceId)s",
|
||||
{'deviceId': deviceID})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
else:
|
||||
LOG.debug("Retrieved pool: %(foundPoolInstanceNames)s",
|
||||
{'foundPoolInstanceNames': foundPoolInstanceNames})
|
||||
|
||||
if foundPoolInstanceNames and len(foundPoolInstanceNames) > 0:
|
||||
foundPoolInstanceName = foundPoolInstanceNames[0]
|
||||
|
||||
return foundPoolInstanceName
|
||||
|
||||
def check_if_volume_is_extendable(self, conn, volumeInstance):
|
||||
"""Checks if a volume is extendable or not.
|
||||
|
||||
|
@ -2156,32 +2211,153 @@ class VMAXUtils(object):
|
|||
myList.append(kwargs)
|
||||
return myList
|
||||
|
||||
def find_volume_by_device_id_on_array(self, conn, storageSystem, deviceID):
|
||||
def find_volume_by_device_id_on_array(self, storageSystem, deviceID):
|
||||
"""Find the volume by device ID on a specific array.
|
||||
|
||||
:param conn: connection to the ecom server
|
||||
:param storageSystem: the storage system name
|
||||
:param deviceID: string value of the volume device ID
|
||||
:returns: foundVolumeInstanceName
|
||||
"""
|
||||
foundVolumeInstanceName = None
|
||||
volumeInstanceNames = conn.EnumerateInstanceNames(
|
||||
'CIM_StorageVolume')
|
||||
for volumeInstanceName in volumeInstanceNames:
|
||||
if storageSystem not in volumeInstanceName['SystemName']:
|
||||
continue
|
||||
if deviceID == volumeInstanceName['DeviceID']:
|
||||
foundVolumeInstanceName = volumeInstanceName
|
||||
LOG.debug("Found volume: %(vol)s",
|
||||
{'vol': foundVolumeInstanceName})
|
||||
break
|
||||
if foundVolumeInstanceName is None:
|
||||
exceptionMessage = (_("Volume %(deviceID)s not found.")
|
||||
% {'deviceID': deviceID})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(data=exceptionMessage)
|
||||
systemName = 'SYMMETRIX-+-%s' % storageSystem
|
||||
bindings = {'CreationClassName': 'Symm_StorageVolume',
|
||||
'SystemName': systemName,
|
||||
'DeviceID': deviceID,
|
||||
'SystemCreationClassName': 'Symm_StorageSystem'}
|
||||
|
||||
return foundVolumeInstanceName
|
||||
instanceName = pywbem.CIMInstanceName(
|
||||
classname='Symm_StorageVolume',
|
||||
namespace=EMC_ROOT,
|
||||
keybindings=bindings)
|
||||
|
||||
LOG.debug("Retrieved volume from VMAX: %(instanceName)s",
|
||||
{'instanceName': instanceName})
|
||||
|
||||
return instanceName
|
||||
|
||||
def check_volume_no_fast(self, extraSpecs):
|
||||
"""Check if the volume's extraSpecs indicate FAST is enabled.
|
||||
|
||||
:param extraSpecs: dict -- extra spec dict
|
||||
:return: True if not fast
|
||||
:raises: VolumeBackendAPIException
|
||||
"""
|
||||
try:
|
||||
if extraSpecs['storagetype:fastpolicy'] is not None:
|
||||
LOG.warning(_LW(
|
||||
"FAST is enabled. Policy: %(fastPolicyName)s."),
|
||||
{'fastPolicyName': extraSpecs['storagetype:fastpolicy']})
|
||||
exceptionMessage = (_(
|
||||
"Manage volume is not supported if FAST is enabled. "
|
||||
"FAST policy: %(fastPolicyName)s."
|
||||
) % {'fastPolicyName': extraSpecs[
|
||||
'storagetype:fastpolicy']})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
else:
|
||||
return True
|
||||
except KeyError:
|
||||
return True
|
||||
|
||||
def check_volume_not_in_masking_view(self, conn, volumeInstanceName,
|
||||
deviceId):
|
||||
"""Check if volume is in Masking View.
|
||||
|
||||
:param conn: connection to the ecom server
|
||||
:param volumeInstanceName: the volume instance name
|
||||
:param deviceId: string value of the volume device ID
|
||||
:raises: VolumeBackendAPIException
|
||||
:return: True if not in Masking View
|
||||
"""
|
||||
sgInstanceNames = (
|
||||
self.get_storage_groups_from_volume(
|
||||
conn, volumeInstanceName))
|
||||
|
||||
mvInstanceName = None
|
||||
for sgInstanceName in sgInstanceNames:
|
||||
maskingViews = conn.AssociatorNames(
|
||||
sgInstanceName,
|
||||
ResultClass='Symm_LunMaskingView')
|
||||
if len(maskingViews) > 0:
|
||||
mvInstanceName = maskingViews[0]
|
||||
if mvInstanceName:
|
||||
exceptionMessage = (_(
|
||||
"Unable to import volume %(deviceId)s to cinder. "
|
||||
"Volume is in masking view %(mv)s.")
|
||||
% {'deviceId': deviceId, 'mv': mvInstanceName})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
|
||||
if not mvInstanceName:
|
||||
return True
|
||||
|
||||
def check_volume_not_replication_source(self, conn, storageSystemName,
|
||||
deviceId):
|
||||
"""Check volume not replication source.
|
||||
|
||||
Check if the volume is the source of a replicated
|
||||
volume.
|
||||
|
||||
:param conn: connection to the ecom server
|
||||
:param storageSystemName: the storage system name
|
||||
:param deviceId: string value of the volume device ID
|
||||
:raises: VolumeBackendAPIException
|
||||
:returns: True if not replication source
|
||||
"""
|
||||
repSessionInstanceName = (
|
||||
self.get_associated_replication_from_source_volume(
|
||||
conn, storageSystemName, deviceId))
|
||||
|
||||
if repSessionInstanceName:
|
||||
exceptionMessage = (_(
|
||||
"Unable to import volume %(deviceId)s to cinder. "
|
||||
"It is the source volume of replication session %(sync)s.")
|
||||
% {'deviceId': deviceId, 'sync': repSessionInstanceName})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
else:
|
||||
return True
|
||||
|
||||
def check_is_volume_in_cinder_managed_pool(
|
||||
self, conn, volumeInstanceName, cinderPoolInstanceName,
|
||||
deviceId):
|
||||
"""Check if volume is in a Cinder managed pool.
|
||||
|
||||
:param conn: connection to the ecom server
|
||||
:param volumeInstanceName: the volume instance name
|
||||
:param cinderPoolInstanceName: the name of the storage pool
|
||||
:param deviceId: string value of the volume device ID
|
||||
:raises: VolumeBackendAPIException
|
||||
:returns: True if volume in cinder managed pool
|
||||
"""
|
||||
volumePoolInstanceName = (
|
||||
self.get_assoc_v2_pool_from_volume(conn,
|
||||
volumeInstanceName))
|
||||
if not volumePoolInstanceName:
|
||||
volumePoolInstanceName = (
|
||||
self.get_assoc_v3_pool_from_volume(conn,
|
||||
volumeInstanceName))
|
||||
|
||||
volumePoolName = volumePoolInstanceName['InstanceID']
|
||||
cinderPoolName = cinderPoolInstanceName['InstanceID']
|
||||
|
||||
LOG.debug("Storage pool of existing volume: %(volPool)s, "
|
||||
"Storage pool currently managed by cinder: %(cinderPool)s.",
|
||||
{'volPool': volumePoolName,
|
||||
'cinderPool': cinderPoolName})
|
||||
|
||||
if volumePoolName != cinderPoolName:
|
||||
exceptionMessage = (_(
|
||||
"Unable to import volume %(deviceId)s to cinder. The external "
|
||||
"volume is not in the pool managed by current cinder host.")
|
||||
% {'deviceId': deviceId})
|
||||
LOG.error(exceptionMessage)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exceptionMessage)
|
||||
else:
|
||||
return True
|
||||
|
||||
def get_volume_element_name(self, volumeId):
|
||||
"""Get volume element name follows naming convention, i.e. 'OS-UUID'.
|
||||
|
@ -2214,6 +2390,7 @@ class VMAXUtils(object):
|
|||
"""
|
||||
if type(volume) is pywbem.cim_obj.CIMInstance:
|
||||
volumeInstance = volume
|
||||
volumeInstance['ElementName'] = newName
|
||||
else:
|
||||
volumeInstance = conn.GetInstance(volume)
|
||||
volumeInstance['ElementName'] = newName
|
||||
|
@ -2225,30 +2402,32 @@ class VMAXUtils(object):
|
|||
|
||||
return volumeInstance
|
||||
|
||||
def get_array_and_device_id(self, volume, external_ref):
|
||||
@staticmethod
|
||||
def get_array_and_device_id(volume, external_ref):
|
||||
"""Helper function for manage volume to get array name and device ID.
|
||||
|
||||
:param volume: volume object from API
|
||||
:param external_ref: the existing volume object to be manged
|
||||
:returns: string value of the array name and device ID
|
||||
"""
|
||||
deviceId = external_ref.get(u'source-name', None)
|
||||
arrayName = ''
|
||||
for metadata in volume['volume_metadata']:
|
||||
if metadata['key'].lower() == 'array':
|
||||
arrayName = metadata['value']
|
||||
break
|
||||
device_id = external_ref.get(u'source-name', None)
|
||||
LOG.debug("External_ref: %(er)s", {'er': external_ref})
|
||||
if not device_id:
|
||||
device_id = external_ref.get(u'source-id', None)
|
||||
host = volume['host']
|
||||
host_list = host.split('+')
|
||||
array = host_list[(len(host_list) - 1)]
|
||||
|
||||
if deviceId:
|
||||
if device_id:
|
||||
LOG.debug("Get device ID of existing volume - device ID: "
|
||||
"%(deviceId)s, Array: %(arrayName)s.",
|
||||
{'deviceId': deviceId,
|
||||
'arrayName': arrayName})
|
||||
"%(device_id)s, Array: %(array)s.",
|
||||
{'device_id': device_id,
|
||||
'array': array})
|
||||
else:
|
||||
exception_message = (_("Source volume device ID is required."))
|
||||
raise exception.VolumeBackendAPIException(
|
||||
data=exception_message)
|
||||
return (arrayName, deviceId)
|
||||
return array, device_id
|
||||
|
||||
def get_associated_replication_from_source_volume(
|
||||
self, conn, storageSystem, sourceDeviceId):
|
||||
|
|
Loading…
Reference in New Issue