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 26ce08ef905..2544b498a7d 100644 --- a/cinder/tests/unit/volume/drivers/emc/test_emc_vmax.py +++ b/cinder/tests/unit/volume/drivers/emc/test_emc_vmax.py @@ -13,6 +13,7 @@ # License for the specific language governing permissions and limitations # under the License. +import ast import os import shutil import sys @@ -31,6 +32,7 @@ from cinder.objects import consistencygroup from cinder.objects import fields from cinder import test from cinder.tests.unit import utils +from cinder import utils as cinder_utils from cinder.volume import configuration as conf from cinder.volume.drivers.emc import emc_vmax_common @@ -323,6 +325,8 @@ class EMCVMAXCommonData(object): fake_host = 'HostX@Backend#gold+1234567891011' fake_host_v3 = 'HostX@Backend#Bronze+SRP_1+1234567891011' fake_host_2_v3 = 'HostY@Backend#SRP_1+1234567891011' + fake_host_3_v3 = 'HostX@Backend#Bronze+DSS+SRP_1+1234567891011' + fake_host_4_v3 = 'HostX@Backend#Silver+None+SRP_1+1234567891011' unit_creationclass = 'CIM_ProtocolControllerForUnit' storage_type = 'gold' @@ -414,6 +418,24 @@ class EMCVMAXCommonData(object): 'BlockSize': block_size } + test_volume_v4 = {'name': 'vol1', + 'size': 1, + 'volume_name': 'vol1', + 'id': '1', + 'device_id': '1', + 'provider_auth': None, + 'project_id': 'project', + 'display_name': 'vol1', + 'display_description': 'test volume', + 'volume_type_id': 'abc', + 'provider_location': six.text_type(provider_location), + 'status': 'available', + 'host': fake_host_3_v3, + 'NumberOfBlocks': 100, + 'BlockSize': block_size, + 'pool_name': 'Bronze+DSS+SRP_1+1234567891011' + } + test_volume_CG = {'name': 'volInCG', 'consistencygroup_id': 'abc', 'size': 1, @@ -497,6 +519,22 @@ class EMCVMAXCommonData(object): six.text_type(provider_location3), 'display_description': 'snapshot source volume'} + test_source_volume_1_v3 = {'size': 1, + 'volume_type_id': 'sourceid', + 'display_name': 'sourceVolume', + 'name': 'sourceVolume', + 'id': 'sourceVolume', + 'device_id': '10', + 'volume_name': 'vmax-154326', + 'provider_auth': None, + 'project_id': 'project', + 'host': fake_host_4_v3, + 'NumberOfBlocks': 100, + 'BlockSize': block_size, + 'provider_location': + six.text_type(provider_location), + 'display_description': 'snapshot source volume'} + test_CG = consistencygroup.ConsistencyGroup( context=None, name='myCG1', id='12345abcde', volume_type_id='abc', status=fields.ConsistencyGroupStatus.AVAILABLE) @@ -518,6 +556,13 @@ class EMCVMAXCommonData(object): 'volume': test_source_volume_v3, 'provider_location': six.text_type(provider_location) } + test_snapshot_1_v3 = {'name': 'mySnap', + 'id': '1', + 'status': 'available', + 'host': fake_host_4_v3, + 'volume': test_source_volume_1_v3, + 'provider_location': six.text_type(provider_location) + } test_CG_snapshot = {'name': 'testSnap', 'id': '12345abcde', 'consistencygroup_id': '123456789', @@ -533,6 +578,8 @@ class EMCVMAXCommonData(object): 'host': 'fake_host'} test_host_v3 = {'capabilities': location_info_v3, 'host': fake_host_2_v3} + test_host_1_v3 = {'capabilities': location_info_v3, + 'host': fake_host_4_v3} initiatorNames = ["123456789012345", "123456789054321"] storagegroups = [{'CreationClassName': storagegroup_creationclass, 'ElementName': storagegroupname}, @@ -558,6 +605,7 @@ class EMCVMAXCommonData(object): 'storagetype:workload': u'DSS', 'storagetype:slo': u'Bronze', 'storagetype:array': u'1234567891011', + 'MultiPoolSupport': False, 'isV3': True, 'portgroupname': u'OS-portgroup-PG'} extra_specs_no_slo = {'storagetype:pool': 'SRP_1', @@ -567,9 +615,20 @@ class EMCVMAXCommonData(object): 'storagetype:array': '1234567891011', 'isV3': True, 'portgroupname': 'OS-portgroup-PG'} + + multi_pool_extra_specs = {'storagetype:pool': u'SRP_1', + 'volume_backend_name': 'MULTI_POOL_BE', + 'storagetype:workload': u'DSS', + 'storagetype:slo': u'Bronze', + 'storagetype:array': u'1234567891011', + 'isV3': True, + 'portgroupname': u'OS-portgroup-PG', + 'pool_name': u'Bronze+DSS+SRP_1+1234567891011'} + remainingSLOCapacity = '123456789' SYNCHRONIZED = 4 UNSYNCHRONIZED = 3 + multiPoolSupportEnabled = True class FakeLookupService(object): @@ -899,6 +958,8 @@ class FakeEcomConnection(object): result = None if ResultClass == 'CIM_ProtocolControllerForUnit': result = self._ref_unitnames2() + elif ResultClass == 'SE_StorageSynchronized_SV_SV': + result = self._enum_storageSyncSvSv() else: result = self._default_ref(objectpath) return result @@ -1831,6 +1892,10 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): instancename.fake_getinstancename) self.mock_object(emc_vmax_utils.EMCVMAXUtils, 'isArrayV3', self.fake_is_v3) + self.mock_object(emc_vmax_utils.EMCVMAXUtils, '_is_sync_complete', + return_value=True) + self.mock_object(cinder_utils, 'get_bool_param', + return_value=False) driver = emc_vmax_iscsi.EMCVMAXISCSIDriver(configuration=configuration) driver.db = FakeDB() self.driver = driver @@ -3071,6 +3136,10 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): if bExists: os.remove(file_name) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'override_ratio', + return_value=2.0) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, 'isArrayV3', @@ -3091,8 +3160,18 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): mock_storage_system, mock_is_fast_enabled, mock_capacity, - mock_is_v3): + mock_is_v3, + mock_or): + self.driver.common.pool_info['arrays_info'] = ( + [{'EcomServerIp': '1.1.1.1', + 'EcomServerPort': '5989', + 'EcomUserName': 'name', + 'EcomPassword': 'password', + 'SerialNumber': '1234567890', + 'PoolName': 'v2_pool', + 'FastPolicy': 'gold'}]) self.driver.get_volume_stats(True) + self.driver.common.pool_info['arrays_info'] = [] @mock.patch.object( emc_vmax_common.EMCVMAXCommon, @@ -3400,7 +3479,11 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): mock.Mock(return_value=volumeDict)) self.driver.create_snapshot(self.data.test_snapshot) - def test_create_snapshot_no_fast_failed(self): + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_validate_pool', + return_value=('Bogus_Pool')) + def test_create_snapshot_no_fast_failed(self, mock_pool): self.data.test_volume['volume_name'] = "vmax-1234567" self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_snapshot, @@ -3531,20 +3614,6 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): self.driver.migrate_volume(self.data.test_ctxt, self.data.test_volume, self.data.test_host) - @mock.patch.object( - emc_vmax_utils.EMCVMAXUtils, - 'parse_pool_instance_id', - return_value=('silver', 'SYMMETRIX+000195900551')) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'ISCSINoFAST'}) - def test_retype_volume_no_fast_success( - self, _mock_volume_type, mock_values): - self.driver.retype( - self.data.test_ctxt, self.data.test_volume, self.data.new_type, - self.data.diff, self.data.test_host) - @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', @@ -3942,7 +4011,18 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): emc_vmax_common.EMCVMAXCommon, '_update_pool_stats', return_value={1, 2, 3, 4, 5}) - def test_ssl_support(self, pool_stats): + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'override_ratio', + return_value=1.0) + def test_ssl_support(self, mock_ratio, pool_stats): + self.driver.common.pool_info['arrays_info'] = ( + [{'EcomServerIp': '1.1.1.1', + 'EcomServerPort': '5989', + 'EcomUserName': 'name', + 'EcomPassword': 'password', + 'SerialNumber': '1234567890', + 'PoolName': 'v2_pool'}]) self.driver.common.update_volume_stats() self.assertTrue(self.driver.common.ecomUseSSL) @@ -3977,6 +4057,8 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): instancename.fake_getinstancename) self.mock_object(emc_vmax_utils.EMCVMAXUtils, 'isArrayV3', self.fake_is_v3) + self.mock_object(cinder_utils, 'get_bool_param', + return_value=False) driver = emc_vmax_iscsi.EMCVMAXISCSIDriver(configuration=configuration) driver.db = FakeDB() self.driver = driver @@ -4054,6 +4136,10 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): def fake_is_v3(self, conn, serialNumber): return False + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'override_ratio', + return_value=2.0) @mock.patch.object( emc_vmax_fast.EMCVMAXFast, 'get_capacities_associated_to_policy', @@ -4079,8 +4165,18 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): mock_is_fast_enabled, mock_get_policy, mock_pool_capacities, - mock_capacities_associated_to_policy): + mock_capacities_associated_to_policy, + mock_or): + self.driver.common.pool_info['arrays_info'] = ( + [{'EcomServerIp': '1.1.1.1', + 'EcomServerPort': '5989', + 'EcomUserName': 'name', + 'EcomPassword': 'password', + 'SerialNumber': '1234567890', + 'PoolName': 'v2_pool', + 'FastPolicy': 'gold'}]) self.driver.get_volume_stats(True) + self.driver.common.pool_info['arrays_info'] = [] @mock.patch.object( emc_vmax_fast.EMCVMAXFast, @@ -4333,7 +4429,11 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): mock.Mock(return_value=True)) self.driver.create_snapshot(self.data.test_snapshot) - def test_create_snapshot_fast_failed(self): + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_validate_pool', + return_value=('Bogus_Pool')) + def test_create_snapshot_fast_failed(self, mock_pool): self.data.test_volume['volume_name'] = "vmax-1234567" self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_snapshot, @@ -4618,7 +4718,10 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): instancename.fake_getinstancename) self.mock_object(emc_vmax_utils.EMCVMAXUtils, 'isArrayV3', self.fake_is_v3) - + self.mock_object(emc_vmax_utils.EMCVMAXUtils, '_is_sync_complete', + return_value=True) + self.mock_object(cinder_utils, 'get_bool_param', + return_value=False) driver = emc_vmax_fc.EMCVMAXFCDriver(configuration=configuration) driver.db = FakeDB() driver.common.conn = FakeEcomConnection() @@ -4690,6 +4793,10 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): def fake_is_v3(self, conn, serialNumber): return False + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'override_ratio', + return_value=2.0) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, 'get_pool_capacities', @@ -4705,7 +4812,8 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): def test_get_volume_stats_no_fast(self, mock_storage_system, mock_is_fast_enabled, - mock_capacity): + mock_capacity, + mock_or): self.driver.get_volume_stats(True) @mock.patch.object( @@ -4918,20 +5026,6 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): self.driver.migrate_volume(self.data.test_ctxt, self.data.test_volume, self.data.test_host) - @mock.patch.object( - emc_vmax_utils.EMCVMAXUtils, - 'parse_pool_instance_id', - return_value=('silver', 'SYMMETRIX+000195900551')) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'FCNoFAST'}) - def test_retype_volume_no_fast_success( - self, _mock_volume_type, mock_values): - self.driver.retype( - self.data.test_ctxt, self.data.test_volume, self.data.new_type, - self.data.diff, self.data.test_host) - @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', @@ -5178,6 +5272,10 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): instancename.fake_getinstancename) self.mock_object(emc_vmax_utils.EMCVMAXUtils, 'isArrayV3', self.fake_is_v3) + self.mock_object(emc_vmax_utils.EMCVMAXUtils, '_is_sync_complete', + return_value=True) + self.mock_object(cinder_utils, 'get_bool_param', + return_value=False) driver = emc_vmax_fc.EMCVMAXFCDriver(configuration=configuration) driver.db = FakeDB() driver.common.conn = FakeEcomConnection() @@ -5255,6 +5353,10 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): def fake_is_v3(self, conn, serialNumber): return False + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'override_ratio', + return_value=2.0) @mock.patch.object( emc_vmax_fast.EMCVMAXFast, 'get_capacities_associated_to_policy', @@ -5280,7 +5382,8 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): mock_is_fast_enabled, mock_get_policy, mock_pool_capacities, - mock_capacities_associated_to_policy): + mock_capacities_associated_to_policy, + mock_or): self.driver.get_volume_stats(True) @mock.patch.object( @@ -5683,24 +5786,6 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): self.driver.migrate_volume(self.data.test_ctxt, self.data.test_volume, self.data.test_host) - @mock.patch.object( - emc_vmax_masking.EMCVMAXMasking, - '_wrap_get_storage_group_from_volume', - return_value=None) - @mock.patch.object( - emc_vmax_utils.EMCVMAXUtils, - 'parse_pool_instance_id', - return_value=('silver', 'SYMMETRIX+000195900551')) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'FCFAST'}) - def test_retype_volume_fast_success( - self, _mock_volume_type, mock_values, mock_wrap): - self.driver.retype( - self.data.test_ctxt, self.data.test_volume, self.data.new_type, - self.data.diff, self.data.test_host) - @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', @@ -5785,13 +5870,15 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): self.data.test_ctxt, self.data.test_CG_snapshot, []) # Bug 1385450 - def test_create_clone_without_license(self): - mockRepServCap = {} - mockRepServCap['InstanceID'] = 'SYMMETRIX+1385450' - self.driver.utils.find_replication_service_capabilities = ( - mock.Mock(return_value=mockRepServCap)) - self.driver.utils.is_clone_licensed = ( - mock.Mock(return_value=False)) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'is_clone_licensed', + return_value=False) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'find_replication_service_capabilities', + return_value={'InstanceID': 'SYMMETRIX+1385450'}) + def test_create_clone_without_license(self, mock_service, mock_license): self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_cloned_volume, self.data.test_volume, @@ -5831,23 +5918,24 @@ class EMCV3DriverTestCase(test.TestCase): self.data = EMCVMAXCommonData() self.data.storage_system = 'SYMMETRIX-+-000197200056' - self.flags(rpc_backend='oslo_messaging._drivers.impl_fake') self.tempdir = tempfile.mkdtemp() super(EMCV3DriverTestCase, self).setUp() self.config_file_path = None self.create_fake_config_file_v3() self.addCleanup(self._cleanup) + self.flags(rpc_backend='oslo_messaging._drivers.impl_fake') self.set_configuration() def set_configuration(self): configuration = mock.Mock() configuration.cinder_emc_config_file = self.config_file_path - configuration.safe_get.return_value = 3 configuration.config_group = 'V3' self.mock_object(emc_vmax_common.EMCVMAXCommon, '_get_ecom_connection', self.fake_ecom_connection) + self.mock_object(cinder_utils, 'get_bool_param', + return_value=False) instancename = FakeCIMInstanceName() self.mock_object(emc_vmax_utils.EMCVMAXUtils, 'get_instance_name', instancename.fake_getinstancename) @@ -5936,6 +6024,9 @@ class EMCV3DriverTestCase(test.TestCase): def fake_is_v3(self, conn, serialNumber): return True + def fake_gather_info(self): + return + def default_extraspec(self): return {'storagetype:pool': 'SRP_1', 'volume_backend_name': 'V3_BE', @@ -6024,13 +6115,19 @@ class EMCV3DriverTestCase(test.TestCase): storagegroup, storagegroup['ElementName'], vol, vol['name'], extraSpecs) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'override_ratio', + return_value=2.0) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, 'find_storageSystem', return_value={'Name': EMCVMAXCommonData.storage_system_v3}) def test_get_volume_stats_v3( - self, mock_storage_system): + self, mock_storage_system, mock_or): + self.driver.common.pool_info['reserved_percentage'] = 5 self.driver.get_volume_stats(True) + self.driver.common.pool_info['reserved_percentage'] = 0 @mock.patch.object( emc_vmax_common.EMCVMAXCommon, @@ -6669,497 +6766,398 @@ class EMCV3DriverTestCase(test.TestCase): shutil.rmtree(self.tempdir) -class EMCV2MultiPoolDriverTestCase(test.TestCase): - +class EMCV3MultiPoolDriverTestCase(test.TestCase): def setUp(self): self.data = EMCVMAXCommonData() - self.vol_v2 = self.data.test_volume_v2 - self.vol_v2['provider_location'] = ( - six.text_type(self.data.provider_location_multi_pool)) - self.tempdir = tempfile.mkdtemp() - super(EMCV2MultiPoolDriverTestCase, self).setUp() - self.config_file_path = None - self.create_fake_config_file_multi_pool() - self.addCleanup(self._cleanup) - - configuration = mock.Mock() - configuration.safe_get.return_value = 'MULTI_POOL' - configuration.cinder_emc_config_file = self.config_file_path - configuration.config_group = 'MULTI_POOL' - - self.mock_object(emc_vmax_common.EMCVMAXCommon, '_get_ecom_connection', - self.fake_ecom_connection) - instancename = FakeCIMInstanceName() - self.mock_object(emc_vmax_utils.EMCVMAXUtils, 'get_instance_name', - 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 - self.driver.utils = emc_vmax_utils.EMCVMAXUtils(object) - - def create_fake_config_file_multi_pool(self): - doc = minidom.Document() - emc = doc.createElement("EMC") - doc.appendChild(emc) - - eComServers = doc.createElement("EcomServers") - emc.appendChild(eComServers) - - eComServer = doc.createElement("EcomServer") - eComServers.appendChild(eComServer) - - ecomserverip = doc.createElement("EcomServerIp") - eComServer.appendChild(ecomserverip) - ecomserveriptext = doc.createTextNode("1.1.1.1") - ecomserverip.appendChild(ecomserveriptext) - - ecomserverport = doc.createElement("EcomServerPort") - eComServer.appendChild(ecomserverport) - ecomserverporttext = doc.createTextNode("10") - ecomserverport.appendChild(ecomserverporttext) - - ecomusername = doc.createElement("EcomUserName") - eComServer.appendChild(ecomusername) - ecomusernametext = doc.createTextNode("user") - ecomusername.appendChild(ecomusernametext) - - ecompassword = doc.createElement("EcomPassword") - eComServer.appendChild(ecompassword) - ecompasswordtext = doc.createTextNode("pass") - ecompassword.appendChild(ecompasswordtext) - - arrays = doc.createElement("Arrays") - eComServer.appendChild(arrays) - - array = doc.createElement("Array") - arrays.appendChild(array) - - serialNo = doc.createElement("SerialNumber") - array.appendChild(serialNo) - serialNoText = doc.createTextNode("1234567891011") - serialNo.appendChild(serialNoText) - - portgroups = doc.createElement("PortGroups") - array.appendChild(portgroups) - - portgroup = doc.createElement("PortGroup") - portgroups.appendChild(portgroup) - portgrouptext = doc.createTextNode(self.data.port_group) - portgroup.appendChild(portgrouptext) - - pools = doc.createElement("Pools") - array.appendChild(pools) - - pool = doc.createElement("Pool") - pools.appendChild(pool) - poolName = doc.createElement("PoolName") - pool.appendChild(poolName) - poolNameText = doc.createTextNode("gold") - poolName.appendChild(poolNameText) - - pool2 = doc.createElement("Pool") - pools.appendChild(pool2) - pool2Name = doc.createElement("PoolName") - pool2.appendChild(pool2Name) - pool2NameText = doc.createTextNode("SATA_BRONZE1") - pool2Name.appendChild(pool2NameText) - pool2FastPolicy = doc.createElement("FastPolicy") - pool2.appendChild(pool2FastPolicy) - pool2FastPolicyText = doc.createTextNode("BRONZE1") - pool2FastPolicy.appendChild(pool2FastPolicyText) - - filename = 'cinder_emc_config_V2_MULTI_POOL.xml' - self.config_file_path = self.tempdir + '/' + filename - - f = open(self.config_file_path, 'w') - doc.writexml(f) - f.close() - - def fake_ecom_connection(self): - self.conn = FakeEcomConnection() - return self.conn - - def fake_is_v3(self, conn, serialNumber): - return False - - def default_extraspec(self): - return {'storagetype:pool': u'gold', - 'volume_backend_name': 'MULTI_POOL_BE', - 'storagetype:fastpolicy': None, - 'storagetype:compositetype': u'concatenated', - 'storagetype:membercount': 1, - 'storagetype:array': u'1234567891011', - 'isV3': False, - 'portgroupname': u'OS-portgroup-PG'} - - def test_validate_pool(self): - v2_valid_pool = self.data.test_volume_v2.copy() - # Pool aware scheduler enabled - v2_valid_pool['host'] = self.data.fake_host - pool = self.driver.common._validate_pool(v2_valid_pool) - self.assertEqual('gold+1234567891011', pool) - - # Cannot get the pool from the host - v2_valid_pool['host'] = 'HostX@Backend' - self.assertRaises(exception.VolumeBackendAPIException, - self.driver.common._validate_pool, - v2_valid_pool) - - # Legacy test. Provider Location does not have the version - v2_valid_pool['host'] = self.data.fake_host - v2_valid_pool['provider_location'] = self.data.provider_location - pool = self.driver.common._validate_pool(v2_valid_pool) - self.assertIsNone(pool) - - def test_array_info_multi_pool(self): - - arrayInfo = self.driver.utils.parse_file_to_get_array_map( - self.config_file_path) - self.assertEqual(2, len(arrayInfo)) - for arrayInfoRec in arrayInfo: - self.assertEqual( - '1234567891011', arrayInfoRec['SerialNumber']) - self.assertIn(self.data.port_group, arrayInfoRec['PortGroup']) - self.assertTrue( - self.data.poolname in arrayInfoRec['PoolName'] or - 'SATA_BRONZE1' in arrayInfoRec['PoolName']) - - @mock.patch.object( - emc_vmax_common.EMCVMAXCommon, - '_get_pool_and_storage_system', - return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'MULTI_POOL_BE'}) - def test_create_volume_multi_pool_success( - self, _mock_volume_type, mock_storage_system): - self.vol_v2['provider_location'] = None - self.driver.common._initial_setup = mock.Mock( - return_value=self.default_extraspec()) - self.driver.create_volume(self.vol_v2) - - @mock.patch.object( - emc_vmax_common.EMCVMAXCommon, - '_get_pool_and_storage_system', - return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'MULTI_POOL_BE'}) - def test_delete_volume_multi_pool_success( - self, _mock_volume_type, mock_storage_system): - self.driver.common._initial_setup = mock.Mock( - return_value=self.default_extraspec()) - self.driver.delete_volume(self.vol_v2) - - @mock.patch.object( - emc_vmax_common.EMCVMAXCommon, - '_get_pool_and_storage_system', - return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'MULTI_POOL_BE'}) - def test_create_volume_in_CG_multi_pool_success( - self, _mock_volume_type, mock_storage_system): - self.data.test_volume_CG['provider_location'] = None - self.driver.common._initial_setup = mock.Mock( - return_value=self.default_extraspec()) - self.driver.create_volume(self.data.test_volume_CG) - - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'MULTI_POOL_BE'}) - def test_retype_volume_multi_pool_success( - self, _mock_volume_type): - self.driver.common._initial_setup = mock.Mock( - return_value=self.default_extraspec()) - self.driver.retype( - self.data.test_ctxt, self.vol_v2, self.data.new_type, - self.data.diff, self.data.test_host) - - @mock.patch.object( - emc_vmax_common.EMCVMAXCommon, - '_get_pool_and_storage_system', - return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'MULTI_POOL_BE'}) - # There is only one unique array in the conf file - def test_create_CG_multi_pool_success( - self, _mock_volume_type, _mock_storage_system): - self.driver.create_consistencygroup( - self.data.test_ctxt, self.data.test_CG) - - @mock.patch.object( - FakeDB, - 'volume_get_all_by_group', - return_value=None) - @mock.patch.object( - emc_vmax_common.EMCVMAXCommon, - '_get_pool_and_storage_system', - return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'MULTI_POOL_BE'}) - def test_delete_CG_no_volumes_multi_pool_success( - self, _mock_volume_type, _mock_storage_system, - _mock_db_volumes): - self.driver.delete_consistencygroup( - self.data.test_ctxt, self.data.test_CG, []) - - @mock.patch.object( - emc_vmax_common.EMCVMAXCommon, - '_get_pool_and_storage_system', - return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'MULTI_POOL_BE'}) - def test_delete_CG_with_volumes_multi_pool_success( - self, _mock_volume_type, _mock_storage_system): - self.driver.delete_consistencygroup( - self.data.test_ctxt, self.data.test_CG, []) - - def _cleanup(self): - bExists = os.path.exists(self.config_file_path) - if bExists: - os.remove(self.config_file_path) - shutil.rmtree(self.tempdir) - - -class EMCV3MultiSloDriverTestCase(test.TestCase): - - def setUp(self): - self.data = EMCVMAXCommonData() - self.vol_v3 = self.data.test_volume_v3 + self.vol_v3 = self.data.test_volume_v4 self.vol_v3['provider_location'] = ( six.text_type(self.data.provider_location_multi_pool)) - self.tempdir = tempfile.mkdtemp() - super(EMCV3MultiSloDriverTestCase, self).setUp() - self.config_file_path = None - self.create_fake_config_file_multi_slo_v3() - self.addCleanup(self._cleanup) + super(EMCV3MultiPoolDriverTestCase, self).setUp() self.set_configuration() def set_configuration(self): configuration = mock.Mock() - configuration.safe_get.return_value = 'MULTI_SLO_V3' - configuration.cinder_emc_config_file = self.config_file_path - configuration.config_group = 'MULTI_SLO_V3' - + configuration.safe_get.return_value = 'MULTI_POOL_V3' + configuration.config_group = 'MULTI_POOL_V3' self.mock_object(emc_vmax_common.EMCVMAXCommon, '_get_ecom_connection', self.fake_ecom_connection) + self.mock_object(emc_vmax_common.EMCVMAXCommon, '_gather_info', + self.fake_gather_info) instancename = FakeCIMInstanceName() self.mock_object(emc_vmax_utils.EMCVMAXUtils, 'get_instance_name', instancename.fake_getinstancename) self.mock_object(emc_vmax_utils.EMCVMAXUtils, 'isArrayV3', - self.fake_is_v3) - + return_value=True) + self.mock_object(emc_vmax_utils.EMCVMAXUtils, '_is_sync_complete', + return_value=True) + self.mock_object(emc_vmax_common.EMCVMAXCommon, + '_get_multi_pool_support_enabled_flag', + return_value=True) + volume_types.get_volume_type_extra_specs = mock.Mock( + return_value={'volume_backend_name': 'MULTI_POOL_BE', + 'pool_name': 'Bronze+DSS+SRP_1+1234567891011'}) driver = emc_vmax_fc.EMCVMAXFCDriver(configuration=configuration) driver.db = FakeDB() self.driver = driver self.driver.utils = emc_vmax_utils.EMCVMAXUtils(object) - def create_fake_config_file_multi_slo_v3(self): + def create_fake_config_file_multi_pool_v3(self, tempdir): doc = minidom.Document() emc = doc.createElement("EMC") doc.appendChild(emc) - eComServers = doc.createElement("EcomServers") - emc.appendChild(eComServers) - - eComServer = doc.createElement("EcomServer") - eComServers.appendChild(eComServer) - ecomserverip = doc.createElement("EcomServerIp") - eComServer.appendChild(ecomserverip) ecomserveriptext = doc.createTextNode("1.1.1.1") + emc.appendChild(ecomserverip) ecomserverip.appendChild(ecomserveriptext) ecomserverport = doc.createElement("EcomServerPort") - eComServer.appendChild(ecomserverport) ecomserverporttext = doc.createTextNode("10") + emc.appendChild(ecomserverport) ecomserverport.appendChild(ecomserverporttext) ecomusername = doc.createElement("EcomUserName") - eComServer.appendChild(ecomusername) ecomusernametext = doc.createTextNode("user") + emc.appendChild(ecomusername) ecomusername.appendChild(ecomusernametext) ecompassword = doc.createElement("EcomPassword") - eComServer.appendChild(ecompassword) ecompasswordtext = doc.createTextNode("pass") + emc.appendChild(ecompassword) ecompassword.appendChild(ecompasswordtext) - arrays = doc.createElement("Arrays") - eComServer.appendChild(arrays) - - array = doc.createElement("Array") - arrays.appendChild(array) - - serialNo = doc.createElement("SerialNumber") - array.appendChild(serialNo) - serialNoText = doc.createTextNode("1234567891011") - serialNo.appendChild(serialNoText) - - portgroups = doc.createElement("PortGroups") - array.appendChild(portgroups) - portgroup = doc.createElement("PortGroup") - portgroups.appendChild(portgroup) portgrouptext = doc.createTextNode(self.data.port_group) portgroup.appendChild(portgrouptext) - vpools = doc.createElement("Pools") - array.appendChild(vpools) - vpool = doc.createElement("Pool") - vpools.appendChild(vpool) - poolName = doc.createElement("PoolName") - vpool.appendChild(poolName) - poolNameText = doc.createTextNode("SRP_1") - poolName.appendChild(poolNameText) - poolslo = doc.createElement("ServiceLevel") - vpool.appendChild(poolslo) - poolsloText = doc.createTextNode("Bronze") - poolslo.appendChild(poolsloText) - poolworkload = doc.createElement("Workload") - vpool.appendChild(poolworkload) - poolworkloadText = doc.createTextNode("DSS") - poolworkload.appendChild(poolworkloadText) + pool = doc.createElement("Pool") + pooltext = doc.createTextNode("SRP_1") + emc.appendChild(pool) + pool.appendChild(pooltext) - vpool2 = doc.createElement("Pool") - vpools.appendChild(vpool2) - pool2Name = doc.createElement("PoolName") - vpool2.appendChild(pool2Name) - pool2NameText = doc.createTextNode("SRP_1") - pool2Name.appendChild(pool2NameText) - pool2slo = doc.createElement("ServiceLevel") - vpool2.appendChild(pool2slo) - pool2sloText = doc.createTextNode("Silver") - pool2slo.appendChild(pool2sloText) - pool2workload = doc.createElement("Workload") - vpool.appendChild(pool2workload) - pool2workloadText = doc.createTextNode("OLTP") - pool2workload.appendChild(pool2workloadText) + array = doc.createElement("Array") + arraytext = doc.createTextNode("1234567891011") + emc.appendChild(array) + array.appendChild(arraytext) - filename = 'cinder_emc_config_MULTI_SLO_V3.xml' - self.config_file_path = self.tempdir + '/' + filename + portgroups = doc.createElement("PortGroups") + portgroups.appendChild(portgroup) + emc.appendChild(portgroups) - f = open(self.config_file_path, 'w') + timeout = doc.createElement("Timeout") + timeouttext = doc.createTextNode("0") + emc.appendChild(timeout) + timeout.appendChild(timeouttext) + + filename = 'cinder_emc_config_V3.xml' + + config_file_path = tempdir + '/' + filename + + f = open(config_file_path, 'w') doc.writexml(f) f.close() + return config_file_path + + def create_fake_config_file_legacy_v3(self, tempdir): + + doc = minidom.Document() + emc = doc.createElement("EMC") + doc.appendChild(emc) + + ecomserverip = doc.createElement("EcomServerIp") + ecomserveriptext = doc.createTextNode("1.1.1.1") + emc.appendChild(ecomserverip) + ecomserverip.appendChild(ecomserveriptext) + + ecomserverport = doc.createElement("EcomServerPort") + ecomserverporttext = doc.createTextNode("10") + emc.appendChild(ecomserverport) + ecomserverport.appendChild(ecomserverporttext) + + ecomusername = doc.createElement("EcomUserName") + ecomusernametext = doc.createTextNode("user") + emc.appendChild(ecomusername) + ecomusername.appendChild(ecomusernametext) + + ecompassword = doc.createElement("EcomPassword") + ecompasswordtext = doc.createTextNode("pass") + emc.appendChild(ecompassword) + ecompassword.appendChild(ecompasswordtext) + + portgroup = doc.createElement("PortGroup") + portgrouptext = doc.createTextNode(self.data.port_group) + portgroup.appendChild(portgrouptext) + + pool = doc.createElement("Pool") + pooltext = doc.createTextNode("SRP_1") + emc.appendChild(pool) + pool.appendChild(pooltext) + + array = doc.createElement("Array") + arraytext = doc.createTextNode("1234567891011") + emc.appendChild(array) + array.appendChild(arraytext) + + slo = doc.createElement("ServiceLevel") + slotext = doc.createTextNode("Silver") + emc.appendChild(slo) + slo.appendChild(slotext) + + workload = doc.createElement("Workload") + workloadtext = doc.createTextNode("OLTP") + emc.appendChild(workload) + workload.appendChild(workloadtext) + + portgroups = doc.createElement("PortGroups") + portgroups.appendChild(portgroup) + emc.appendChild(portgroups) + + timeout = doc.createElement("Timeout") + timeouttext = doc.createTextNode("0") + emc.appendChild(timeout) + timeout.appendChild(timeouttext) + + filename = 'cinder_emc_config_V3.xml' + + config_file_path = tempdir + '/' + filename + + f = open(config_file_path, 'w') + doc.writexml(f) + f.close() + return config_file_path def fake_ecom_connection(self): self.conn = FakeEcomConnection() return self.conn - def fake_is_v3(self, conn, serialNumber): - return True + def fake_gather_info(self): + return - def default_extraspec(self): - return {'storagetype:pool': u'SRP_1', - 'volume_backend_name': 'MULTI_SLO_BE', - 'storagetype:workload': u'DSS', - 'storagetype:slo': u'Bronze', - 'storagetype:array': u'1234567891011', - 'isV3': True, - 'portgroupname': u'OS-portgroup-PG'} + def default_array_info_list(self): + return [{'EcomServerIp': u'1.1.1.1', + 'EcomServerPort': 10, + 'EcomUserName': u'user', + 'EcomPassword': u'pass', + 'PoolName': u'SRP_1', + 'PortGroup': u'OS-portgroup-PG', + 'SerialNumber': 1234567891011, + 'SLO': u'Bronze', + 'Workload': u'DSS'}] + + def multiple_array_info_list(self): + return [{'EcomServerIp': u'1.1.1.1', + 'EcomServerPort': 10, + 'EcomUserName': u'user', + 'EcomPassword': u'pass', + 'PoolName': u'SRP_1', + 'PortGroup': u'OS-portgroup-PG', + 'SerialNumber': 1234567891011, + 'SLO': u'Bronze', + 'Workload': u'DSS'}, + {'EcomServerIp': u'1.1.1.1', + 'EcomServerPort': 10, + 'EcomUserName': u'user', + 'EcomPassword': u'pass', + 'PoolName': u'SRP_1', + 'PortGroup': u'OS-portgroup-PG', + 'SerialNumber': 1234567891011, + 'SLO': u'Silver', + 'Workload': u'OLTP'}] + + def test_initial_setup(self): + tempdir = tempfile.mkdtemp() + config_file_path = self.create_fake_config_file_multi_pool_v3(tempdir) + with mock.patch.object( + self.driver.common, '_register_config_file_from_config_group', + return_value=config_file_path): + extraSpecs = self.driver.common._initial_setup(self.vol_v3) + self.assertEqual('SRP_1', extraSpecs['storagetype:pool']) + self.assertEqual('DSS', extraSpecs['storagetype:workload']) + self.assertEqual('Bronze', extraSpecs['storagetype:slo']) + self.assertEqual('1234567891011', extraSpecs['storagetype:array']) + self.assertEqual('OS-portgroup-PG', extraSpecs['portgroupname']) + self.assertTrue(extraSpecs['isV3']) + self.assertTrue(extraSpecs['MultiPoolSupport']) + self.assertEqual('Bronze+DSS+SRP_1+1234567891011', + extraSpecs['pool_name']) + self._cleanup(tempdir, config_file_path) + + def test_initial_setup_with_legacy_file(self): + # Test with legacy config file and verify + # if the values for SLO and workload are used from + # the pool_name and not the config file + tempdir = tempfile.mkdtemp() + config_file_path = self.create_fake_config_file_legacy_v3(tempdir) + with mock.patch.object( + self.driver.common, '_register_config_file_from_config_group', + return_value=config_file_path): + extraSpecs = self.driver.common._initial_setup(self.vol_v3) + self.assertEqual('DSS', extraSpecs['storagetype:workload']) + self.assertEqual('Bronze', extraSpecs['storagetype:slo']) + self._cleanup(tempdir, config_file_path) + + def test_initial_setup_invalid_volume(self): + # Test with volume which don't have pool_name + tempdir = tempfile.mkdtemp() + config_file_path = self.create_fake_config_file_multi_pool_v3(tempdir) + with mock.patch.object( + self.driver.common, '_register_config_file_from_config_group', + return_value=config_file_path): + invalid_vol_v3 = self.data.test_volume_v4.copy() + invalid_vol_v3.pop('host', None) + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.common._initial_setup, + invalid_vol_v3) + self._cleanup(tempdir, config_file_path) def test_validate_pool(self): - v3_valid_pool = self.data.test_volume_v3.copy() + v3_valid_pool = self.data.test_volume_v4.copy() # Pool aware scheduler enabled - v3_valid_pool['host'] = self.data.fake_host_v3 - pool = self.driver.common._validate_pool(v3_valid_pool) - self.assertEqual('Bronze+SRP_1+1234567891011', pool) + v3_valid_pool['host'] = self.data.fake_host_3_v3 + # Validate pool uses extraSpecs as a new argument + # Use default extraSpecs as the argument + pool = self.driver.common._validate_pool( + v3_valid_pool, self.data.multi_pool_extra_specs) + self.assertEqual('Bronze+DSS+SRP_1+1234567891011', pool) + def test_validate_pool_invalid_pool_name(self): + # Validate using older volume dictionary + # and check if a exception is raised if multi_pool_support + # is enabled and pool_name is not specified + extraSpecs = self.data.multi_pool_extra_specs + invalid_pool_name = extraSpecs.copy() + invalid_pool_name['pool_name'] = 'not_valid' + self.assertRaises(exception.VolumeBackendAPIException, + self.driver.common._validate_pool, + self.data.test_volume_v4, invalid_pool_name) + + def test_validate_pool_invalid_host(self): # Cannot get the pool from the host + v3_valid_pool = self.data.test_volume_v4.copy() v3_valid_pool['host'] = 'HostX@Backend' self.assertRaises(exception.VolumeBackendAPIException, self.driver.common._validate_pool, v3_valid_pool) + + def test_validate_pool_legacy(self): # Legacy test. Provider Location does not have the version - v3_valid_pool['host'] = self.data.fake_host_v3 + v3_valid_pool = self.data.test_volume_v4.copy() + v3_valid_pool['host'] = self.data.fake_host_3_v3 v3_valid_pool['provider_location'] = self.data.provider_location pool = self.driver.common._validate_pool(v3_valid_pool) self.assertIsNone(pool) - def test_array_info_multi_slo(self): - - arrayInfo = self.driver.utils.parse_file_to_get_array_map( - self.config_file_path) - self.assertEqual(2, len(arrayInfo)) - for arrayInfoRec in arrayInfo: - self.assertEqual( - '1234567891011', arrayInfoRec['SerialNumber']) - self.assertIn(self.data.port_group, arrayInfoRec['PortGroup']) - self.assertIn('SRP_1', arrayInfoRec['PoolName']) - self.assertTrue( - 'Bronze' in arrayInfoRec['SLO'] or - 'Silver' in arrayInfoRec['SLO']) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'override_ratio', + return_value=2.0) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'find_storageSystem', + return_value={'Name': EMCVMAXCommonData.storage_system_v3}) + def test_get_volume_stats_v3( + self, mock_storage_system, mock_or): + self.driver.common.pool_info['reserved_percentage'] = 5 + self.driver.get_volume_stats(True) + self.driver.common.pool_info['reserved_percentage'] = 0 + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_initial_setup', + return_value=EMCVMAXCommonData.multi_pool_extra_specs) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_or_create_storage_group_v3', + return_value=EMCVMAXCommonData.default_sg_instance_name) @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'MULTI_SLO_BE'}) def test_create_volume_multi_slo_success( - self, _mock_volume_type, mock_storage_system): - self.vol_v3['host'] = self.data.fake_host_v3 + self, mock_storage_system, mock_sg, mock_is): + self.vol_v3['host'] = self.data.fake_host_3_v3 self.vol_v3['provider_location'] = None - self.driver.common._initial_setup = mock.Mock( - return_value=self.default_extraspec()) - self.driver.common._get_or_create_storage_group_v3 = mock.Mock( - return_value = self.data.default_sg_instance_name) - self.driver.create_volume(self.vol_v3) + model_update = self.driver.create_volume(self.vol_v3) + # Verify if the device id is provided in the output + provider_location = model_update['provider_location'] + provider_location = ast.literal_eval(provider_location) + keybindings = provider_location['keybindings'] + device_id = keybindings['DeviceID'] + self.assertEqual('1', device_id) + @mock.patch.object( + emc_vmax_masking.EMCVMAXMasking, + 'get_associated_masking_groups_from_device', + return_value=EMCVMAXCommonData.storagegroups) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_initial_setup', + return_value=EMCVMAXCommonData.multi_pool_extra_specs) @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'MULTI_SLO_BE'}) def test_delete_volume_multi_slo_success( - self, _mock_volume_type, mock_storage_system): - self.driver.common._initial_setup = mock.Mock( - return_value=self.default_extraspec()) + self, mock_storage_system, mock_is, mock_mv): + provider_location = ( + {'classname': 'Symm_StorageVolume', + 'keybindings': + {'CreationClassName': 'Symm_StorageVolume', + 'SystemName': 'SYMMETRIX+000195900551', + 'DeviceID': '1', + 'SystemCreationClassName': 'Symm_StorageSystem' + } + }) + volumeInstanceName = ( + {'NumberOfBlocks': 100, + 'ElementName': '1', + 'Name': 'vol1', + 'BlockSize': 512, + 'provider_location': six.text_type(provider_location), + 'SystemName': 'SYMMETRIX+000195900551', + 'DeviceID': '1', + 'CreationClassName': 'Symm_StorageVolume', + 'Id': '1', + 'SystemCreationClassName': 'Symm_StorageSystem'}) self.driver.delete_volume(self.vol_v3) + masking = self.driver.common.masking + get_groups_from_device = ( + masking.get_associated_masking_groups_from_device) + get_groups_from_device.assert_called_once_with( + self.conn, volumeInstanceName) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_or_create_storage_group_v3', + return_value=EMCVMAXCommonData.default_sg_instance_name) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_initial_setup', + return_value=EMCVMAXCommonData.multi_pool_extra_specs) @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'MULTI_SLO_BE'}) def test_create_volume_in_CG_multi_slo_success( - self, _mock_volume_type, mock_storage_system): + self, mock_storage_system, mock_is, mock_sg): self.data.test_volume_CG_v3['provider_location'] = None - self.driver.common._initial_setup = mock.Mock( - return_value=self.default_extraspec()) - self.driver.common._get_or_create_storage_group_v3 = mock.Mock( - return_value = self.data.default_sg_instance_name) - self.driver.create_volume(self.data.test_volume_CG_v3) + model_update = self.driver.create_volume(self.data.test_volume_CG_v3) + # Verify if the device id is provided in the output + provider_location = model_update['provider_location'] + provider_location = ast.literal_eval(provider_location) + keybindings = provider_location['keybindings'] + device_id = keybindings['DeviceID'] + self.assertEqual('1', device_id) @mock.patch.object( emc_vmax_utils.EMCVMAXUtils, 'get_volume_element_name', return_value='1') + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_initial_setup', + return_value=EMCVMAXCommonData.multi_pool_extra_specs) @mock.patch.object( emc_vmax_provision_v3.EMCVMAXProvisionV3, '_find_new_storage_group', @@ -7172,315 +7170,31 @@ class EMCV3MultiSloDriverTestCase(test.TestCase): emc_vmax_utils.EMCVMAXUtils, '_get_fast_settings_from_storage_group', return_value='Gold+DSS_REP') - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'MULTI_SLO_BE'}) def test_retype_volume_multi_slo_success( - self, _mock_volume_type, mock_fast_settings, - mock_storage_group, mock_found_SG, mock_element_name): - self.driver.common._initial_setup = mock.Mock( - return_value=self.default_extraspec()) + self, mock_fast_settings, + mock_storage_group, mock_found_SG, mock_is, mock_element_name): self.assertTrue(self.driver.retype( - self.data.test_ctxt, self.data.test_volume_v3, self.data.new_type, - self.data.diff, self.data.test_host_v3)) + self.data.test_ctxt, self.data.test_volume_v4, self.data.new_type, + self.data.diff, self.data.test_host_1_v3)) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_initial_setup', + return_value=EMCVMAXCommonData.multi_pool_extra_specs) @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'MULTI_SLO_BE'}) # There is only one unique array in the conf file def test_create_CG_multi_slo_success( - self, _mock_volume_type, _mock_storage_system): - self.driver.common._initial_setup = mock.Mock( - return_value=self.default_extraspec()) + self, _mock_storage_system, mock_is): self.driver.create_consistencygroup( self.data.test_ctxt, self.data.test_CG) - @mock.patch.object( - FakeDB, - 'volume_get_all_by_group', - return_value=None) @mock.patch.object( emc_vmax_common.EMCVMAXCommon, - '_get_pool_and_storage_system', - return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'MULTI_SLO_BE'}) - def test_delete_CG_no_volumes_multi_slo_success( - self, _mock_volume_type, _mock_storage_system, - _mock_db_volumes): - self.driver.delete_consistencygroup( - self.data.test_ctxt, self.data.test_CG, []) - - @mock.patch.object( - emc_vmax_common.EMCVMAXCommon, - '_get_pool_and_storage_system', - return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'MULTI_SLO_BE'}) - def test_delete_CG_with_volumes_multi_slo_success( - self, _mock_volume_type, _mock_storage_system): - self.driver.delete_consistencygroup( - self.data.test_ctxt, self.data.test_CG, []) - - def _cleanup(self): - bExists = os.path.exists(self.config_file_path) - if bExists: - os.remove(self.config_file_path) - shutil.rmtree(self.tempdir) - - -class EMCV2MultiPoolDriverMultipleEcomsTestCase(test.TestCase): - - def setUp(self): - - self.data = EMCVMAXCommonData() - self.vol_v2 = self.data.test_volume_v2 - self.vol_v2['provider_location'] = ( - six.text_type(self.data.provider_location_multi_pool)) - - self.tempdir = tempfile.mkdtemp() - super(EMCV2MultiPoolDriverMultipleEcomsTestCase, self).setUp() - self.config_file_path = None - self.create_fake_config_file_multi_ecom() - self.addCleanup(self._cleanup) - - configuration = mock.Mock() - configuration.cinder_emc_config_file = self.config_file_path - configuration.safe_get.return_value = 'MULTI_ECOM' - configuration.config_group = 'MULTI_ECOM' - - self.mock_object(emc_vmax_common.EMCVMAXCommon, '_get_ecom_connection', - self.fake_ecom_connection) - instancename = FakeCIMInstanceName() - self.mock_object(emc_vmax_utils.EMCVMAXUtils, 'get_instance_name', - instancename.fake_getinstancename) - self.mock_object(emc_vmax_utils.EMCVMAXUtils, 'isArrayV3', - self.fake_is_v3) - driver = emc_vmax_fc.EMCVMAXFCDriver(configuration=configuration) - driver.db = FakeDB() - driver.common.conn = FakeEcomConnection() - driver.zonemanager_lookup_service = FakeLookupService() - self.driver = driver - self.driver.utils = emc_vmax_utils.EMCVMAXUtils(object) - - def create_fake_config_file_multi_ecom(self): - doc = minidom.Document() - emc = doc.createElement("EMC") - doc.appendChild(emc) - - eComServers = doc.createElement("EcomServers") - emc.appendChild(eComServers) - - eComServer = doc.createElement("EcomServer") - eComServers.appendChild(eComServer) - - ecomserverip = doc.createElement("EcomServerIp") - eComServer.appendChild(ecomserverip) - ecomserveriptext = doc.createTextNode("1.1.1.1") - ecomserverip.appendChild(ecomserveriptext) - - ecomserverport = doc.createElement("EcomServerPort") - eComServer.appendChild(ecomserverport) - ecomserverporttext = doc.createTextNode("10") - ecomserverport.appendChild(ecomserverporttext) - - ecomusername = doc.createElement("EcomUserName") - eComServer.appendChild(ecomusername) - ecomusernametext = doc.createTextNode("user") - ecomusername.appendChild(ecomusernametext) - - ecompassword = doc.createElement("EcomPassword") - eComServer.appendChild(ecompassword) - ecompasswordtext = doc.createTextNode("pass") - ecompassword.appendChild(ecompasswordtext) - - arrays = doc.createElement("Arrays") - eComServer.appendChild(arrays) - - array = doc.createElement("Array") - arrays.appendChild(array) - - serialNo = doc.createElement("SerialNumber") - array.appendChild(serialNo) - serialNoText = doc.createTextNode("1110987654321") - serialNo.appendChild(serialNoText) - - portgroups = doc.createElement("PortGroups") - array.appendChild(portgroups) - - portgroup = doc.createElement("PortGroup") - portgroups.appendChild(portgroup) - portgrouptext = doc.createTextNode(self.data.port_group) - portgroup.appendChild(portgrouptext) - - pools = doc.createElement("Pools") - array.appendChild(pools) - - pool = doc.createElement("Pool") - pools.appendChild(pool) - poolName = doc.createElement("PoolName") - pool.appendChild(poolName) - poolNameText = doc.createTextNode("gold") - poolName.appendChild(poolNameText) - - pool2 = doc.createElement("Pool") - pools.appendChild(pool2) - pool2Name = doc.createElement("PoolName") - pool2.appendChild(pool2Name) - pool2NameText = doc.createTextNode("SATA_BRONZE1") - pool2Name.appendChild(pool2NameText) - pool2FastPolicy = doc.createElement("FastPolicy") - pool2.appendChild(pool2FastPolicy) - pool2FastPolicyText = doc.createTextNode("BRONZE1") - pool2FastPolicy.appendChild(pool2FastPolicyText) - - eComServer = doc.createElement("EcomServer") - eComServers.appendChild(eComServer) - - ecomserverip = doc.createElement("EcomServerIp") - eComServer.appendChild(ecomserverip) - ecomserveriptext = doc.createTextNode("1.1.1.1") - ecomserverip.appendChild(ecomserveriptext) - - ecomserverport = doc.createElement("EcomServerPort") - eComServer.appendChild(ecomserverport) - ecomserverporttext = doc.createTextNode("10") - ecomserverport.appendChild(ecomserverporttext) - - ecomusername = doc.createElement("EcomUserName") - eComServer.appendChild(ecomusername) - ecomusernametext = doc.createTextNode("user") - ecomusername.appendChild(ecomusernametext) - - ecompassword = doc.createElement("EcomPassword") - eComServer.appendChild(ecompassword) - ecompasswordtext = doc.createTextNode("pass") - ecompassword.appendChild(ecompasswordtext) - - arrays = doc.createElement("Arrays") - eComServer.appendChild(arrays) - - array = doc.createElement("Array") - arrays.appendChild(array) - - serialNo = doc.createElement("SerialNumber") - array.appendChild(serialNo) - serialNoText = doc.createTextNode("1234567891011") - serialNo.appendChild(serialNoText) - - portgroups = doc.createElement("PortGroups") - array.appendChild(portgroups) - - portgroup = doc.createElement("PortGroup") - portgroups.appendChild(portgroup) - portgrouptext = doc.createTextNode(self.data.port_group) - portgroup.appendChild(portgrouptext) - - pools = doc.createElement("Pools") - array.appendChild(pools) - - pool = doc.createElement("Pool") - pools.appendChild(pool) - poolName = doc.createElement("PoolName") - pool.appendChild(poolName) - poolNameText = doc.createTextNode("gold") - poolName.appendChild(poolNameText) - - pool2 = doc.createElement("Pool") - pools.appendChild(pool2) - pool2Name = doc.createElement("PoolName") - pool2.appendChild(pool2Name) - pool2NameText = doc.createTextNode("SATA_BRONZE1") - pool2Name.appendChild(pool2NameText) - pool2FastPolicy = doc.createElement("FastPolicy") - pool2.appendChild(pool2FastPolicy) - pool2FastPolicyText = doc.createTextNode("BRONZE1") - pool2FastPolicy.appendChild(pool2FastPolicyText) - - filename = 'cinder_emc_config_V2_MULTI_ECOM.xml' - self.config_file_path = self.tempdir + '/' + filename - - f = open(self.config_file_path, 'w') - doc.writexml(f) - f.close() - - def fake_ecom_connection(self): - self.conn = FakeEcomConnection() - return self.conn - - def fake_is_v3(self, conn, serialNumber): - return False - - def test_array_info_multi_ecom_no_fast(self): - pool = 'gold+1234567891011' - arrayInfo = self.driver.utils.parse_file_to_get_array_map( - self.config_file_path) - self.assertEqual(4, len(arrayInfo)) - poolRec = self.driver.utils.extract_record(arrayInfo, pool) - - self.assertEqual('1234567891011', poolRec['SerialNumber']) - self.assertEqual(self.data.port_group, poolRec['PortGroup']) - self.assertEqual(self.data.poolname, poolRec['PoolName']) - self.assertEqual('user', poolRec['EcomUserName']) - self.assertEqual('pass', poolRec['EcomPassword']) - self.assertIsNone(poolRec['FastPolicy']) - self.assertFalse(poolRec['EcomUseSSL']) - - def test_array_info_multi_ecom_fast(self): - pool = 'SATA_BRONZE1+1234567891011' - - arrayInfo = self.driver.utils.parse_file_to_get_array_map( - self.config_file_path) - self.assertEqual(4, len(arrayInfo)) - poolRec = self.driver.utils.extract_record(arrayInfo, pool) - - self.assertEqual('1234567891011', poolRec['SerialNumber']) - self.assertEqual(self.data.port_group, poolRec['PortGroup']) - self.assertEqual('SATA_BRONZE1', poolRec['PoolName']) - self.assertEqual('user', poolRec['EcomUserName']) - self.assertEqual('pass', poolRec['EcomPassword']) - self.assertEqual('BRONZE1', poolRec['FastPolicy']) - self.assertFalse(poolRec['EcomUseSSL']) - - @mock.patch.object( - emc_vmax_common.EMCVMAXCommon, - '_get_pool_and_storage_system', - return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'MULTI_ECOM_BE'}) - def test_create_volume_multi_ecom_success( - self, _mock_volume_type, mock_storage_system): - self.vol_v2['provider_location'] = None - self.driver.create_volume(self.vol_v2) - - @mock.patch.object( - emc_vmax_common.EMCVMAXCommon, - '_get_pool_and_storage_system', - return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'MULTI_ECOM_BE'}) - # If there are more than one unique arrays in conf file - def test_create_CG_multi_array_failure( - self, _mock_volume_type, _mock_storage_system): - self.assertRaises(exception.VolumeBackendAPIException, - self.driver.create_consistencygroup, - self.data.test_ctxt, - self.data.test_CG) - + '_initial_setup', + return_value=EMCVMAXCommonData.multi_pool_extra_specs) @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_get_members_of_replication_group', @@ -7493,38 +7207,175 @@ class EMCV2MultiPoolDriverMultipleEcomsTestCase(test.TestCase): emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'MULTI_ECOM_BE'}) - # There is more than one unique arrays in the conf file - def test_delete_CG_no_volumes_multi_array_failure( - self, _mock_volume_type, _mock_storage_system, - _mock_db_volumes, _mock_members): - self.assertRaises(exception.VolumeBackendAPIException, - self.driver.delete_consistencygroup, - self.data.test_ctxt, - self.data.test_CG, - []) + def test_delete_CG_no_volumes_multi_slo_success( + self, _mock_storage_system, + _mock_db_volumes, _mock_members, mock_is): + # This is a CG delete with no volumes + # there won't be a deleted status + model_update = {} + ret_model_update, ret_volumes_model_update = ( + self.driver.delete_consistencygroup(self.data.test_ctxt, + self.data.test_CG, [])) + self.assertEqual(model_update, ret_model_update) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_initial_setup', + return_value=EMCVMAXCommonData.multi_pool_extra_specs) @mock.patch.object( emc_vmax_common.EMCVMAXCommon, '_get_pool_and_storage_system', return_value=(None, EMCVMAXCommonData.storage_system)) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'MULTI_ECOM_BE'}) - def test_create_volume_in_CG_multi_ecom_success( - self, _mock_volume_type, mock_storage_system): - self.data.test_volume_CG['provider_location'] = None - self.driver.create_volume(self.data.test_volume_CG) + def test_delete_CG_with_volumes_multi_slo_success( + self, _mock_storage_system, mock_is): + # Check for the status deleted after a successful delete CG + model_update = {'status': 'deleted'} + ret_model_update, ret_volumes_model_update = ( + self.driver.delete_consistencygroup(self.data.test_ctxt, + self.data.test_CG, [])) + self.assertEqual(model_update, ret_model_update) - def _cleanup(self): - bExists = os.path.exists(self.config_file_path) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_initial_setup', + return_value=EMCVMAXCommonData.multi_pool_extra_specs) + def test_migrate_volume_v3_success(self, mock_is): + retVal, retList = self.driver.migrate_volume( + self.data.test_ctxt, self.data.test_volume_v4, + self.data.test_host_1_v3) + self.assertTrue(retVal) + + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'get_volume_element_name', + return_value='1') + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'get_v3_default_sg_instance_name', + return_value=(None, None, EMCVMAXCommonData.default_sg_instance_name)) + @mock.patch.object( + emc_vmax_utils.EMCVMAXUtils, + 'is_clone_licensed', + return_value=True) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_initial_setup', + return_value=EMCVMAXCommonData.multi_pool_extra_specs) + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_create_snapshot_v3_success( + self, mock_pool, mock_is, mock_license, mock_sg, mock_element): + self.data.test_volume_v4['volume_name'] = "vmax-1234567" + self.driver.create_snapshot(self.data.test_snapshot_1_v3) + utils = self.driver.common.provisionv3.utils + utils.get_v3_default_sg_instance_name.assert_called_once_with( + self.conn, u'SRP_1', u'Bronze', u'DSS', u'SYMMETRIX+000195900551') + + @mock.patch.object( + emc_vmax_common.EMCVMAXCommon, + '_initial_setup', + return_value=EMCVMAXCommonData.multi_pool_extra_specs) + def test_delete_snapshot_v3_success(self, mock_is): + masking = self.driver.common.masking + with mock.patch.object( + masking, 'get_associated_masking_groups_from_device', + return_value=self.data.storagegroups): + self.driver.delete_snapshot(self.data.test_snapshot_1_v3) + + @mock.patch.object( + emc_vmax_provision_v3.EMCVMAXProvisionV3, + 'get_srp_pool_stats', + return_value=(100, 10, 1, 20, False)) + def test_update_volume_stats_single_array_info(self, mock_stats): + self.driver.common.pool_info['reserved_percentage'] = 5 + self.driver.common.pool_info['arrays_info'] = ( + self.default_array_info_list()) + self.driver.common.multiPoolSupportEnabled = True + data = self.driver.common.update_volume_stats() + pools = data['pools'] + self.assertEqual("Bronze+DSS+SRP_1+1234567891011", + pools[0]['pool_name']) + self.assertEqual("1234567891011#SRP_1#Bronze#DSS", + pools[0]['location_info']) + self._cleanup_pool_info() + + @mock.patch.object( + emc_vmax_provision_v3.EMCVMAXProvisionV3, + 'get_srp_pool_stats', + return_value=(100, 10, 1, 20, False)) + def test_update_volume_stats_multiple_array_info_wlp_disabled( + self, mock_stats): + self.driver.common.pool_info['reserved_percentage'] = 5 + self.driver.common.pool_info['arrays_info'] = ( + self.multiple_array_info_list()) + self.driver.common.multiPoolSupportEnabled = True + data = self.driver.common.update_volume_stats() + pools = data['pools'] + self.assertEqual("Bronze+DSS+SRP_1+1234567891011", + pools[0]['pool_name']) + self.assertEqual("1234567891011#SRP_1#Bronze#DSS", + pools[0]['location_info']) + self.assertEqual("Silver+OLTP+SRP_1+1234567891011", + pools[1]['pool_name']) + self.assertEqual("1234567891011#SRP_1#Silver#OLTP", + pools[1]['location_info']) + self._cleanup_pool_info() + + @mock.patch.object( + emc_vmax_provision_v3.EMCVMAXProvisionV3, + 'get_srp_pool_stats', + return_value=(100, 10, 1, 20, False)) + def test_update_volume_stats_multiple_array_info_wlp_enabled( + self, mock_stats): + self.driver.common.pool_info['reserved_percentage'] = 5 + self.driver.common.pool_info['arrays_info'] = ( + self.multiple_array_info_list()) + self.driver.common.multiPoolSupportEnabled = True + data = self.driver.common.update_volume_stats() + pools = data['pools'] + self.assertEqual("Bronze+DSS+SRP_1+1234567891011", + pools[0]['pool_name']) + self.assertEqual("1234567891011#SRP_1#Bronze#DSS", + pools[0]['location_info']) + self.assertEqual("Silver+OLTP+SRP_1+1234567891011", + pools[1]['pool_name']) + self.assertEqual("1234567891011#SRP_1#Silver#OLTP", + pools[1]['location_info']) + self._cleanup_pool_info() + + @mock.patch.object( + emc_vmax_provision_v3.EMCVMAXProvisionV3, + 'get_srp_pool_stats', + return_value=(100, 10, 1, 20, False)) + def test_update_volume_stats_without_multi_pool(self, mock_stats): + self.driver.common.pool_info['reserved_percentage'] = 5 + self.driver.common.pool_info['arrays_info'] = ( + self.multiple_array_info_list()) + data = self.driver.common.update_volume_stats() + pools = data['pools'] + # Match with the older pool_name format + self.assertEqual("Bronze+SRP_1+1234567891011", + pools[0]['pool_name']) + self.assertEqual("1234567891011#SRP_1#Bronze#DSS", + pools[0]['location_info']) + self.assertEqual("Silver+SRP_1+1234567891011", + pools[1]['pool_name']) + self.assertEqual("1234567891011#SRP_1#Silver#OLTP", + pools[1]['location_info']) + self._cleanup_pool_info() + + def _cleanup(self, tempdir, config_file_path): + bExists = os.path.exists(config_file_path) if bExists: - os.remove(self.config_file_path) - shutil.rmtree(self.tempdir) + os.remove(config_file_path) + shutil.rmtree(tempdir) + + def _cleanup_pool_info(self): + self.driver.common.pool_info['reserved_percentage'] = 0 + self.driver.common.pool_info['arrays_info'] = [] + self.driver.common.multiPoolSupportEnabled = False class EMCVMAXProvisionV3Test(test.TestCase): diff --git a/cinder/volume/drivers/emc/emc_vmax_common.py b/cinder/volume/drivers/emc/emc_vmax_common.py index e8f91002bc8..1134e06dca4 100644 --- a/cinder/volume/drivers/emc/emc_vmax_common.py +++ b/cinder/volume/drivers/emc/emc_vmax_common.py @@ -20,9 +20,11 @@ import os.path from oslo_config import cfg from oslo_log import log as logging from oslo_utils import units +import re import six from cinder import exception +from cinder import utils as cinder_utils from cinder.i18n import _, _LE, _LI, _LW from cinder.objects import fields from cinder.volume.drivers.emc import emc_vmax_fast @@ -56,6 +58,7 @@ ARRAY = 'storagetype:array' FASTPOLICY = 'storagetype:fastpolicy' BACKENDNAME = 'volume_backend_name' COMPOSITETYPE = 'storagetype:compositetype' +MULTI_POOL_SUPPORT = 'MultiPoolSupport' STRIPECOUNT = 'storagetype:stripecount' MEMBERCOUNT = 'storagetype:membercount' STRIPED = 'striped' @@ -79,7 +82,11 @@ emc_opts = [ cfg.StrOpt('cinder_emc_config_file', default=CINDER_EMC_CONFIG_FILE, help='use this file for cinder emc plugin ' - 'config data'), ] + 'config data'), + cfg.StrOpt('multi_pool_support', + default=False, + help='use this value to specify' + 'multi-pool support for VMAX3')] CONF.register_opts(emc_opts) @@ -128,6 +135,7 @@ class EMCVMAXCommon(object): self.provision = emc_vmax_provision.EMCVMAXProvision(prtcl) self.provisionv3 = emc_vmax_provision_v3.EMCVMAXProvisionV3(prtcl) self.version = version + self.multiPoolSupportEnabled = False self._gather_info() def _gather_info(self): @@ -138,7 +146,11 @@ class EMCVMAXCommon(object): else: self.pool_info['config_file'] = ( self.configuration.safe_get('cinder_emc_config_file')) - + if hasattr(self.configuration, 'multi_pool_support'): + tempMultiPoolSupported = cinder_utils.get_bool_param( + 'multi_pool_support', self.configuration) + if tempMultiPoolSupported: + self.multiPoolSupportEnabled = True self.pool_info['backend_name'] = ( self.configuration.safe_get('volume_backend_name')) self.pool_info['max_over_subscription_ratio'] = ( @@ -151,9 +163,75 @@ class EMCVMAXCommon(object): {'emcConfigFileName': self.pool_info['config_file'], 'backendName': self.pool_info['backend_name']}) - self.pool_info['arrays_info'] = ( - self.utils.parse_file_to_get_array_map( - self.pool_info['config_file'])) + arrayInfoList = self.utils.parse_file_to_get_array_map( + self.pool_info['config_file']) + # Assuming that there is a single array info object always + # Check if Multi pool support is enabled + if self.multiPoolSupportEnabled is False: + self.pool_info['arrays_info'] = arrayInfoList + else: + finalArrayInfoList = self._get_slo_workload_combinations( + arrayInfoList) + self.pool_info['arrays_info'] = finalArrayInfoList + + def _get_slo_workload_combinations(self, arrayInfoList): + """Method to query the array for SLO and Workloads. + + Takes the arrayInfoList object and generates a set which has + all available SLO & Workload combinations + + :param arrayInfoList: + :return: finalArrayInfoList + :raises: Exception + """ + try: + sloWorkloadSet = set() + # Pattern for extracting the SLO & Workload String + pattern = re.compile("^-S[A-Z]+") + for arrayInfo in arrayInfoList: + self._set_ecom_credentials(arrayInfo) + isV3 = self.utils.isArrayV3(self.conn, + arrayInfo['SerialNumber']) + # Only if the array is VMAX3 + if isV3: + poolInstanceName, storageSystemStr = ( + self._find_pool_in_array(arrayInfo['SerialNumber'], + arrayInfo['PoolName'], isV3)) + # Get the pool capability + storagePoolCapability = ( + self.provisionv3.get_storage_pool_capability( + self.conn, poolInstanceName)) + # Get the pool settings + storagePoolSettings = self.conn.AssociatorNames( + storagePoolCapability, + ResultClass='CIM_storageSetting') + for storagePoolSetting in storagePoolSettings: + settingInstanceID = storagePoolSetting['InstanceID'] + settingInstanceDetails = settingInstanceID.split('+') + sloWorkloadString = settingInstanceDetails[2] + if pattern.match(sloWorkloadString): + length = len(sloWorkloadString) + tempSloWorkloadString = ( + sloWorkloadString[2:length - 1]) + sloWorkloadSet.add(tempSloWorkloadString) + # Assuming that there is always a single arrayInfo object + finalArrayInfoList = [] + for sloWorkload in sloWorkloadSet: + # Doing a shallow copy will work as we are modifying + # only strings + temparrayInfo = arrayInfoList[0].copy() + slo, workload = sloWorkload.split(':') + if temparrayInfo['SLO'] is None: + temparrayInfo['SLO'] = slo + temparrayInfo['Workload'] = workload + finalArrayInfoList.append(temparrayInfo) + except Exception: + exceptionMessage = (_( + "Unable to get the SLO/Workload combinations from the array")) + LOG.exception(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + return finalArrayInfoList def create_volume(self, volume): """Creates a EMC(VMAX) volume from a pre-existing storage pool. @@ -230,14 +308,12 @@ class EMCVMAXCommon(object): :raises: VolumeBackendAPIException """ LOG.debug("Entering create_volume_from_snapshot.") - snapshot['host'] = volume['host'] - extraSpecs = self._initial_setup(snapshot) + extraSpecs = self._initial_setup(snapshot, host=volume['host']) self.conn = self._get_ecom_connection() snapshotInstance = self._find_lun(snapshot) self._sync_check(snapshotInstance, snapshot['name'], extraSpecs) - snapshot['host'] = volume['host'] return self._create_cloned_volume(volume, snapshot, extraSpecs, False) def create_cloned_volume(self, cloneVolume, sourceVolume): @@ -285,8 +361,7 @@ class EMCVMAXCommon(object): """ LOG.info(_LI("Delete Snapshot: %(snapshotName)s."), {'snapshotName': snapshot['name']}) - snapshot['host'] = volume['host'] - self._delete_snapshot(snapshot) + self._delete_snapshot(snapshot, volume['host']) def _remove_members(self, controllerConfigService, volumeInstance, connector, extraSpecs): @@ -662,6 +737,10 @@ class EMCVMAXCommon(object): def update_volume_stats(self): """Retrieve stats info.""" pools = [] + # Dictionary to hold the VMAX3 arrays for which the SRP details + # have already been queried + # This only applies to the arrays for which WLP is not enabled + arrays = {} backendName = self.pool_info['backend_name'] max_oversubscription_ratio = ( self.pool_info['max_over_subscription_ratio']) @@ -669,17 +748,43 @@ class EMCVMAXCommon(object): array_max_over_subscription = None array_reserve_percent = None for arrayInfo in self.pool_info['arrays_info']: + alreadyQueried = False self._set_ecom_credentials(arrayInfo) # Check what type of array it is - isV3 = self.utils.isArrayV3(self.conn, arrayInfo['SerialNumber']) + isV3 = self.utils.isArrayV3(self.conn, + arrayInfo['SerialNumber']) if isV3: - (location_info, total_capacity_gb, free_capacity_gb, - provisioned_capacity_gb, - array_reserve_percent) = self._update_srp_stats(arrayInfo) - poolName = ("%(slo)s+%(poolName)s+%(array)s" - % {'slo': arrayInfo['SLO'], - 'poolName': arrayInfo['PoolName'], - 'array': arrayInfo['SerialNumber']}) + # Report only the SLO name in the pool name for + # backward compatibility + if self.multiPoolSupportEnabled is False: + (location_info, total_capacity_gb, free_capacity_gb, + provisioned_capacity_gb, + array_reserve_percent, + wlpEnabled) = self._update_srp_stats(arrayInfo) + poolName = ("%(slo)s+%(poolName)s+%(array)s" + % {'slo': arrayInfo['SLO'], + 'poolName': arrayInfo['PoolName'], + 'array': arrayInfo['SerialNumber']}) + else: + # Add both SLO & Workload name in the pool name + # Query the SRP only once if WLP is not enabled + # Only insert the array details in the dict once + if arrayInfo['SerialNumber'] not in arrays: + (location_info, total_capacity_gb, free_capacity_gb, + provisioned_capacity_gb, + array_reserve_percent, + wlpEnabled) = self._update_srp_stats(arrayInfo) + else: + alreadyQueried = True + poolName = ("%(slo)s+%(workload)s+%(poolName)s+%(array)s" + % {'slo': arrayInfo['SLO'], + 'workload': arrayInfo['Workload'], + 'poolName': arrayInfo['PoolName'], + 'array': arrayInfo['SerialNumber']}) + if wlpEnabled is False: + arrays[arrayInfo['SerialNumber']] = ( + [total_capacity_gb, free_capacity_gb, + provisioned_capacity_gb, array_reserve_percent]) else: # This is V2 (location_info, total_capacity_gb, free_capacity_gb, @@ -689,28 +794,64 @@ class EMCVMAXCommon(object): % {'poolName': arrayInfo['PoolName'], 'array': arrayInfo['SerialNumber']}) - pool = {'pool_name': poolName, - 'total_capacity_gb': total_capacity_gb, - 'free_capacity_gb': free_capacity_gb, - 'provisioned_capacity_gb': provisioned_capacity_gb, - 'QoS_support': False, - 'location_info': location_info, - 'consistencygroup_support': True, - 'thin_provisioning_support': True, - 'thick_provisioning_support': False, - 'max_over_subscription_ratio': max_oversubscription_ratio - } + if (alreadyQueried + is True and self.multiPoolSupportEnabled is True): + # The dictionary will only have one key per VMAX3 + # Construct the location info + temp_location_info = ( + ("%(arrayName)s#%(poolName)s#%(slo)s#%(workload)s" + % {'arrayName': arrayInfo['SerialNumber'], + 'poolName': arrayInfo['PoolName'], + 'slo': arrayInfo['SLO'], + 'workload': arrayInfo['Workload']})) + pool = {'pool_name': poolName, + 'total_capacity_gb': + arrays[arrayInfo['SerialNumber']][0], + 'free_capacity_gb': + arrays[arrayInfo['SerialNumber']][1], + 'provisioned_capacity_gb': + arrays[arrayInfo['SerialNumber']][2], + 'QoS_support': True, + 'location_info': temp_location_info, + 'consistencygroup_support': True, + 'thin_provisioning_support': True, + 'thick_provisioning_support': False, + 'max_over_subscription_ratio': + max_oversubscription_ratio + } + if ( + arrays[arrayInfo['SerialNumber']][3] and + (arrays[arrayInfo['SerialNumber']][3] > + reservedPercentage)): + pool['reserved_percentage'] = ( + arrays[arrayInfo['SerialNumber']][3]) + else: + pool['reserved_percentage'] = reservedPercentage + else: + pool = {'pool_name': poolName, + 'total_capacity_gb': total_capacity_gb, + 'free_capacity_gb': free_capacity_gb, + 'provisioned_capacity_gb': provisioned_capacity_gb, + 'QoS_support': False, + 'location_info': location_info, + 'consistencygroup_support': True, + 'thin_provisioning_support': True, + 'thick_provisioning_support': False, + 'max_over_subscription_ratio': + max_oversubscription_ratio + } + if ( + array_reserve_percent and + (array_reserve_percent > reservedPercentage)): + pool['reserved_percentage'] = array_reserve_percent + else: + pool['reserved_percentage'] = reservedPercentage + if array_max_over_subscription: pool['max_over_subscription_ratio'] = ( self.utils.override_ratio( max_oversubscription_ratio, array_max_over_subscription)) - - if array_reserve_percent and ( - array_reserve_percent > reservedPercentage): - pool['reserved_percentage'] = array_reserve_percent - else: - pool['reserved_percentage'] = reservedPercentage pools.append(pool) data = {'vendor_name': "EMC", @@ -736,10 +877,11 @@ class EMCVMAXCommon(object): :returns: remainingManagedSpaceGbs :returns: provisionedManagedSpaceGbs :returns: array_reserve_percent + :returns: wlpEnabled """ (totalManagedSpaceGbs, remainingManagedSpaceGbs, - provisionedManagedSpaceGbs, array_reserve_percent) = ( + provisionedManagedSpaceGbs, array_reserve_percent, wlpEnabled) = ( self.provisionv3.get_srp_pool_stats(self.conn, arrayInfo)) LOG.info(_LI( @@ -761,7 +903,7 @@ class EMCVMAXCommon(object): return (location_info, totalManagedSpaceGbs, remainingManagedSpaceGbs, provisionedManagedSpaceGbs, - array_reserve_percent) + array_reserve_percent, wlpEnabled) def retype(self, ctxt, volume, new_type, diff, host): """Migrate volume to another host using retype. @@ -1338,14 +1480,31 @@ class EMCVMAXCommon(object): extraSpecs = self.utils.get_volumetype_extraspecs(volume, volumeTypeId) qosSpecs = self.utils.get_volumetype_qosspecs(volume, volumeTypeId) configGroup = None - # If there are no extra specs then the default case is assumed. if extraSpecs: configGroup = self.configuration.config_group configurationFile = self._register_config_file_from_config_group( configGroup) + self.multiPoolSupportEnabled = ( + self._get_multi_pool_support_enabled_flag()) + extraSpecs[MULTI_POOL_SUPPORT] = self.multiPoolSupportEnabled return extraSpecs, configurationFile, qosSpecs + def _get_multi_pool_support_enabled_flag(self): + """Reads the configuration fpr multi pool support flag. + + :returns: MultiPoolSupportEnabled flag + """ + + confString = ( + self.configuration.safe_get('multi_pool_support')) + retVal = False + stringTrue = "True" + if confString: + if confString.lower() == stringTrue.lower(): + retVal = True + return retVal + def _get_ecom_connection(self): """Get the ecom connection. @@ -1774,7 +1933,7 @@ class EMCVMAXCommon(object): % {'ip_port': ip_port}) self.conn = self._get_ecom_connection() - def _initial_setup(self, volume, volumeTypeId=None): + def _initial_setup(self, volume, volumeTypeId=None, host=None): """Necessary setup to accumulate the relevant information. The volume object has a host in which we can parse the @@ -1794,13 +1953,17 @@ class EMCVMAXCommon(object): extraSpecs, configurationFile, qosSpecs = ( self._set_config_file_and_get_extra_specs( volume, volumeTypeId)) - - pool = self._validate_pool(volume) + pool = self._validate_pool(volume, extraSpecs=extraSpecs, + host=host) LOG.debug("Pool returned is %(pool)s.", {'pool': pool}) arrayInfo = self.utils.parse_file_to_get_array_map( configurationFile) - poolRecord = self.utils.extract_record(arrayInfo, pool) + if arrayInfo is not None: + if extraSpecs['MultiPoolSupport'] is True: + poolRecord = arrayInfo[0] + else: + poolRecord = self.utils.extract_record(arrayInfo, pool) if not poolRecord: exceptionMessage = (_( @@ -2148,7 +2311,6 @@ class EMCVMAXCommon(object): 'sourceName': sourceName}) self.conn = self._get_ecom_connection() - sourceInstance = self._find_lun(sourceVolume) storageSystem = sourceInstance['SystemName'] repServCapabilityInstanceName = ( @@ -2267,7 +2429,7 @@ class EMCVMAXCommon(object): cloneDict, cloneName, storageConfigService, storageSystemName, fastPolicyName, extraSpecs) - def _delete_volume(self, volume): + def _delete_volume(self, volume, host=None): """Helper function to delete the specified volume. :param volume: volume object to be deleted @@ -2278,7 +2440,7 @@ class EMCVMAXCommon(object): rc = -1 errorRet = (rc, volumeName) - extraSpecs = self._initial_setup(volume) + extraSpecs = self._initial_setup(volume, host=host) self.conn = self._get_ecom_connection() volumeInstance = self._find_lun(volume) @@ -2454,7 +2616,7 @@ class EMCVMAXCommon(object): return numVolumesMapped - def _delete_snapshot(self, snapshot): + def _delete_snapshot(self, snapshot, host=None): """Helper function to delete the specified snapshot. :param snapshot: snapshot object to be deleted @@ -2465,7 +2627,7 @@ class EMCVMAXCommon(object): self.conn = self._get_ecom_connection() # Delete the target device. - rc, snapshotname = self._delete_volume(snapshot) + rc, snapshotname = self._delete_volume(snapshot, host) LOG.info(_LI("Leaving delete_snapshot: %(ssname)s Return code: " "%(rc)lu."), {'ssname': snapshotname, @@ -2486,7 +2648,7 @@ class EMCVMAXCommon(object): cgName = self._update_consistency_group_name(group) volumeTypeId = group['volume_type_id'].replace(",", "") - extraSpecs = self._initial_setup(None, volumeTypeId) + extraSpecs = self._initial_setup(None, volumeTypeId=volumeTypeId) _poolInstanceName, storageSystem = ( self._get_pool_and_storage_system(extraSpecs)) @@ -2522,8 +2684,8 @@ class EMCVMAXCommon(object): {'group': group['id']}) cgName = self._update_consistency_group_name(group) - modelUpdate = {} + volumes_model_update = {} if not self.conn: self.conn = self._get_ecom_connection() @@ -2547,7 +2709,6 @@ class EMCVMAXCommon(object): memberInstanceNames = self._get_members_of_replication_group( cgInstanceName) - self.provision.delete_consistency_group(self.conn, replicationService, cgInstanceName, cgName, @@ -3177,23 +3338,9 @@ class EMCVMAXCommon(object): "belonging to any storage group."), {'volumeName': volumeName}) else: - self.provision.remove_device_from_storage_group( - self.conn, - controllerConfigService, - foundStorageGroupInstanceName, - volumeInstance.path, - volumeName, extraSpecs) - # Check that it has been removed. - sgFromVolRemovedInstanceName = ( - self.utils.wrap_get_storage_group_from_volume( - self.conn, volumeInstance.path, defaultSgName)) - if sgFromVolRemovedInstanceName is not None: - LOG.error(_LE( - "Volume : %(volumeName)s has not been " - "removed from source storage group %(storageGroup)s."), - {'volumeName': volumeName, - 'storageGroup': sgFromVolRemovedInstanceName}) - return False + self.masking.remove_and_reset_members( + self.conn, controllerConfigService, volumeInstance, + volumeName, extraSpecs, None, False) storageGroupName = self.utils.get_v3_storage_group_name( poolName, targetSlo, targetWorkload) @@ -3385,8 +3532,33 @@ class EMCVMAXCommon(object): :param poolRecord: pool record :returns: dict -- the extra specifications dictionary """ - extraSpecs[SLO] = poolRecord['SLO'] - extraSpecs[WORKLOAD] = poolRecord['Workload'] + if extraSpecs['MultiPoolSupport'] is True: + sloFromExtraSpec = None + workloadFromExtraSpec = None + if 'pool_name' in extraSpecs: + try: + poolDetails = extraSpecs['pool_name'].split('+') + sloFromExtraSpec = poolDetails[0] + workloadFromExtraSpec = poolDetails[1] + except KeyError: + LOG.error(_LE("Error parsing SLO, workload from " + "the provided extra_specs.")) + else: + # Throw an exception as it is compulsory to have + # pool_name in the extra specs + exceptionMessage = (_( + "Pool_name is not present in the extraSpecs " + "and MultiPoolSupport is enabled")) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + # If MultiPoolSupport is enabled, we completely + # ignore any entry for SLO & Workload in the poolRecord + extraSpecs[SLO] = sloFromExtraSpec + extraSpecs[WORKLOAD] = workloadFromExtraSpec + else: + extraSpecs[SLO] = poolRecord['SLO'] + extraSpecs[WORKLOAD] = poolRecord['Workload'] + extraSpecs[ISV3] = True extraSpecs = self._set_common_extraSpecs(extraSpecs, poolRecord) LOG.debug("Pool is: %(pool)s " @@ -4021,7 +4193,7 @@ class EMCVMAXCommon(object): extraSpecs) return rc - def _validate_pool(self, volume): + def _validate_pool(self, volume, extraSpecs=None, host=None): """Get the pool from volume['host']. There may be backward compatibiliy concerns, so putting in a @@ -4030,6 +4202,7 @@ class EMCVMAXCommon(object): assume it was created pre 'Pool Aware Scheduler' feature. :param volume: the volume Object + :param extraSpecs: extraSpecs provided in the volume type :returns: string -- pool :raises: VolumeBackendAPIException """ @@ -4038,6 +4211,9 @@ class EMCVMAXCommon(object): if volume is None: return pool + if host is None: + host = volume['host'] + # This check is for all operations except a create. # On a create provider_location is None try: @@ -4049,10 +4225,21 @@ class EMCVMAXCommon(object): except KeyError: return pool try: - pool = volume_utils.extract_host(volume['host'], 'pool') + pool = volume_utils.extract_host(host, 'pool') if pool: LOG.debug("Pool from volume['host'] is %(pool)s.", {'pool': pool}) + # Check if it matches with the poolname if it is provided + # in the extra specs + if extraSpecs is not None: + if 'pool_name' in extraSpecs: + if extraSpecs['pool_name'] != pool: + exceptionMessage = (_( + "Pool from volume['host'] %(host)s doesn't" + " match with pool_name in extraSpecs.") + % {'host': volume['host']}) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) else: exceptionMessage = (_( "Pool from volume['host'] %(host)s not found.") diff --git a/cinder/volume/drivers/emc/emc_vmax_fc.py b/cinder/volume/drivers/emc/emc_vmax_fc.py index b6633657720..698bdadd68d 100644 --- a/cinder/volume/drivers/emc/emc_vmax_fc.py +++ b/cinder/volume/drivers/emc/emc_vmax_fc.py @@ -72,6 +72,8 @@ class EMCVMAXFCDriver(driver.FibreChannelDriver): - QoS support (blueprint vmax-qos) 2.5.0 - Attach and detach snapshot (blueprint vmax-attach-snapshot) - MVs and SGs not reflecting correct protocol (bug #1640222) + - Storage assisted volume migration via retype + (bp vmax-volume-migration) """ diff --git a/cinder/volume/drivers/emc/emc_vmax_iscsi.py b/cinder/volume/drivers/emc/emc_vmax_iscsi.py index e6854f83395..952a56818fb 100644 --- a/cinder/volume/drivers/emc/emc_vmax_iscsi.py +++ b/cinder/volume/drivers/emc/emc_vmax_iscsi.py @@ -78,6 +78,8 @@ class EMCVMAXISCSIDriver(driver.ISCSIDriver): https://blueprints.launchpad.net/cinder/+spec/vmax-iscsi-multipath 2.5.0 - Attach and detach snapshot (blueprint vmax-attach-snapshot) - MVs and SGs not reflecting correct protocol (bug #1640222) + - Storage assisted volume migration via retype + (bp vmax-volume-migration) """ diff --git a/cinder/volume/drivers/emc/emc_vmax_provision_v3.py b/cinder/volume/drivers/emc/emc_vmax_provision_v3.py index 9d0ec4c717f..c422e05e8de 100644 --- a/cinder/volume/drivers/emc/emc_vmax_provision_v3.py +++ b/cinder/volume/drivers/emc/emc_vmax_provision_v3.py @@ -706,11 +706,13 @@ class EMCVMAXProvisionV3(object): :returns: remainingCapacityGb :returns: subscribedCapacityGb :returns: array_reserve_percent + :returns: wlpEnabled """ totalCapacityGb = -1 remainingCapacityGb = -1 subscribedCapacityGb = -1 array_reserve_percent = -1 + wlpEnabled = False storageSystemInstanceName = self.utils.find_storageSystem( conn, arrayInfo['SerialNumber']) @@ -756,6 +758,7 @@ class EMCVMAXProvisionV3(object): storageSystemInstanceName['Name'])) if remainingSLOCapacityGb != -1: remainingCapacityGb = remainingSLOCapacityGb + wlpEnabled = True else: LOG.warning(_LW( "Remaining capacity %(remainingCapacityGb)s " @@ -765,7 +768,7 @@ class EMCVMAXProvisionV3(object): {'remainingCapacityGb': remainingCapacityGb}) return (totalCapacityGb, remainingCapacityGb, subscribedCapacityGb, - array_reserve_percent) + array_reserve_percent, wlpEnabled) def _get_remaining_slo_capacity_wlp(self, conn, srpPoolInstanceName, arrayInfo, systemName): diff --git a/cinder/volume/drivers/emc/emc_vmax_utils.py b/cinder/volume/drivers/emc/emc_vmax_utils.py index fc2cdf5649e..ba574de0450 100644 --- a/cinder/volume/drivers/emc/emc_vmax_utils.py +++ b/cinder/volume/drivers/emc/emc_vmax_utils.py @@ -1260,13 +1260,12 @@ class EMCVMAXUtils(object): :returns: foundSyncInstanceName """ foundSyncInstanceName = None - syncInstanceNames = conn.EnumerateInstanceNames( - 'SE_StorageSynchronized_SV_SV') + syncInstanceNames = conn.ReferenceNames( + volumeInstance.path, + ResultClass='SE_StorageSynchronized_SV_SV') for syncInstanceName in syncInstanceNames: syncSvTarget = syncInstanceName['SyncedElement'] syncSvSource = syncInstanceName['SystemElement'] - if storageSystem != syncSvTarget['SystemName']: - continue if syncSvTarget['DeviceID'] == volumeInstance['DeviceID'] or ( syncSvSource['DeviceID'] == volumeInstance['DeviceID']): # Check that it hasn't recently been deleted. @@ -1902,65 +1901,10 @@ class EMCVMAXUtils(object): return kwargs - def _multi_pool_support(self, fileName): - """Multi pool support. - - - - - 10.108.246.202 - ... - - - 000198700439 - ... - - - FC_SLVR1 - ... - - - - - - - - - :param fileName: the configuration file - :returns: list - """ - myList = [] - connargs = {} - myFile = open(fileName, 'r') - data = myFile.read() - myFile.close() - dom = minidom.parseString(data) - interval = self._process_tag(dom, 'Interval') - retries = self._process_tag(dom, 'Retries') - try: - ecomElements = dom.getElementsByTagName('EcomServer') - if ecomElements and len(ecomElements) > 0: - for ecomElement in ecomElements: - connargs = self._get_connection_info(ecomElement) - arrayElements = ecomElement.getElementsByTagName('Array') - if arrayElements and len(arrayElements) > 0: - for arrayElement in arrayElements: - myList = self._get_pool_info(arrayElement, - fileName, connargs, - interval, retries, - myList) - else: - LOG.error(_LE( - "Please check your xml for format or syntax " - "errors. Please see documentation for more " - "details.")) - except IndexError: - pass - return myList - def _single_pool_support(self, fileName): """Single pool support. + VMAX2 10.108.246.202 5988 @@ -1972,6 +1916,7 @@ class EMCVMAXUtils(object): 000198700439 FC_SLVR1 + VMAX3 :param fileName: the configuration file :returns: list @@ -2021,12 +1966,70 @@ class EMCVMAXUtils(object): :param fileName: the path and name of the file :returns: list - """ - # Multi-pool support. - myList = self._multi_pool_support(fileName) - if len(myList) == 0: - myList = self._single_pool_support(fileName) + Sample VMAX2 XML file + + 10.108.246.202 + 5988 + admin + #1Password + + OS-PORTGROUP1-PG + + 000198700439 + FC_SLVR1 + + Sample VMAX3 XML file + + 10.108.246.202 + 5988 + admin + #1Password + + OS-PORTGROUP1-PG + + 000198700439 + FC_SLVR1 + Diamond <--This is optional + OLTP <--This is optional + + :param fileName: the configuration file + :returns: list + """ + myList = [] + kwargs = {} + connargs = {} + with open(fileName, 'r') as my_file: + data = my_file.read() + my_file.close() + dom = minidom.parseString(data) + try: + connargs = self._get_connection_info(dom) + interval = self._process_tag(dom, 'Interval') + retries = self._process_tag(dom, 'Retries') + portGroup = self._get_random_portgroup(dom) + serialNumber = self._process_tag(dom, 'Array') + if serialNumber is None: + LOG.error(_LE( + "Array Serial Number must be in the file " + "%(fileName)s."), + {'fileName': fileName}) + poolName = self._process_tag(dom, 'Pool') + if poolName is None: + LOG.error(_LE( + "PoolName must be in the file " + "%(fileName)s."), + {'fileName': fileName}) + kwargs = self._fill_record( + connargs, serialNumber, poolName, portGroup, dom) + if interval: + kwargs['Interval'] = interval + if retries: + kwargs['Retries'] = retries + + myList.append(kwargs) + except IndexError: + pass return myList def extract_record(self, arrayInfo, pool): diff --git a/releasenotes/notes/vmax-volume-migration-992c8c68e2207bbc.yaml b/releasenotes/notes/vmax-volume-migration-992c8c68e2207bbc.yaml new file mode 100644 index 00000000000..4c20b59e727 --- /dev/null +++ b/releasenotes/notes/vmax-volume-migration-992c8c68e2207bbc.yaml @@ -0,0 +1,5 @@ +--- +features: + - Storage assisted volume migration from one Pool/SLO/Workload combination + to another, on the same array, via retype, for the VMAX driver. Both + All Flash and Hybrid VMAX3 arrays are supported. VMAX2 is not supported.