From 6624c3197bfce7092a1b16ae74f1c3c9532d0a04 Mon Sep 17 00:00:00 2001 From: Helen Walsh Date: Tue, 15 Nov 2016 17:52:54 +0000 Subject: [PATCH] VMAX driver - Storage assisted volume migration. Facilitates retyping from one volume type to another where volume types belong to the same backend. Both All Flash and Hybrid arrays are supported. VMAX2 is not supported. Change-Id: Ie4f52afd82867f60556d8fc95d2af7c0f87cee51 Implements: blueprint vmax-volume-migration --- .../unit/volume/drivers/emc/test_emc_vmax.py | 1411 ++++++++--------- cinder/volume/drivers/emc/emc_vmax_common.py | 329 +++- cinder/volume/drivers/emc/emc_vmax_fc.py | 2 + cinder/volume/drivers/emc/emc_vmax_iscsi.py | 2 + .../drivers/emc/emc_vmax_provision_v3.py | 5 +- cinder/volume/drivers/emc/emc_vmax_utils.py | 133 +- ...max-volume-migration-992c8c68e2207bbc.yaml | 5 + 7 files changed, 970 insertions(+), 917 deletions(-) create mode 100644 releasenotes/notes/vmax-volume-migration-992c8c68e2207bbc.yaml 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.