From 8ddecd772b85a70b1edc7a69048a3ea43c6c8c30 Mon Sep 17 00:00:00 2001 From: Xing Yang Date: Fri, 5 Dec 2014 17:55:28 -0500 Subject: [PATCH] Fix a clone volume problem in VMAX driver If the source volume contains meta members of different sizes, create cloned volume will fail with error "ERROR_FAMILY_NOT_SUPPORTED". This patched fixed this problem. Change-Id: I92e7e8b56e3a21644aa2bff3288c1bdc80d45cc6 Closes-Bug: #1391205 --- cinder/tests/test_emc_vmax.py | 441 +++++++----------- cinder/volume/drivers/emc/emc_vmax_common.py | 337 +++++++++---- cinder/volume/drivers/emc/emc_vmax_fast.py | 34 +- .../volume/drivers/emc/emc_vmax_provision.py | 21 +- cinder/volume/drivers/emc/emc_vmax_utils.py | 53 ++- 5 files changed, 505 insertions(+), 381 deletions(-) diff --git a/cinder/tests/test_emc_vmax.py b/cinder/tests/test_emc_vmax.py index 5bbd40e24..5646ff1e9 100644 --- a/cinder/tests/test_emc_vmax.py +++ b/cinder/tests/test_emc_vmax.py @@ -275,7 +275,7 @@ class FakeEcomConnection(): def InvokeMethod(self, MethodName, Service, ElementName=None, InPool=None, ElementType=None, Size=None, - SyncType=None, SourceElement=None, + SyncType=None, SourceElement=None, TargetElement=None, Operation=None, Synchronization=None, TheElements=None, TheElement=None, LUNames=None, InitiatorPortIDs=None, DeviceAccesses=None, @@ -664,6 +664,13 @@ class FakeEcomConnection(): fakeinstance = instance.fake_getpolicyinstance() return fakeinstance + def _getinstance_syncsvsv(self, objectpath): + svInstance = {} + svInstance['SyncedElement'] = 'SyncedElement' + svInstance['SystemElement'] = 'SystemElement' + svInstance['PercentSynced'] = 100 + return svInstance + def _default_getinstance(self, objectpath): return objectpath @@ -1300,14 +1307,29 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): FakeDB, 'volume_get', return_value=EMCVMAXCommonData.test_source_volume) + @mock.patch.object( + EMCVMAXUtils, + 'get_volume_meta_head', + return_value=[EMCVMAXCommonData.test_volume]) + @mock.patch.object( + EMCVMAXUtils, + 'get_meta_members_capacity_in_bit', + return_value=[1234567, 7654321]) @mock.patch.object( EMCVMAXCommon, - '_find_storage_sync_sv_sv', - return_value=(None, None)) - def test_create_snapshot_no_fast_success( - self, mock_volume_type, - mock_volume, mock_sync_sv): + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_create_snapshot_different_sizes_meta_no_fast_success( + self, mock_volume_type, mock_volume, + mock_meta, mock_size, mock_pool): self.data.test_volume['volume_name'] = "vmax-1234567" + common = self.driver.common + volumeDict = {'classname': u'Symm_StorageVolume', + 'keybindings': EMCVMAXCommonData.keybindings} + common.provision.create_volume_from_pool = ( + mock.Mock(return_value=(volumeDict, 0L))) + common.provision.get_volume_dict_from_job = ( + mock.Mock(return_value=volumeDict)) self.driver.create_snapshot(self.data.test_volume) def test_create_snapshot_no_fast_failed(self): @@ -1320,20 +1342,23 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): volume_types, 'get_volume_type_extra_specs', return_value={'volume_backend_name': 'ISCSINoFAST'}) - @mock.patch.object( - FakeDB, - 'volume_get', - return_value=EMCVMAXCommonData.test_source_volume) @mock.patch.object( EMCVMAXCommon, '_find_storage_sync_sv_sv', return_value=(None, None)) - def test_create_volume_from_snapshot_no_fast_success( - self, mock_volume_type, - mock_volume, mock_sync_sv): + @mock.patch.object( + EMCVMAXUtils, + 'get_volume_meta_head', + return_value=[EMCVMAXCommonData.test_volume]) + @mock.patch.object( + EMCVMAXUtils, + 'get_meta_members_capacity_in_bit', + return_value=[1234567]) + def test_create_volume_from_same_size_meta_snapshot( + self, mock_volume_type, mock_sync_sv, mock_meta, mock_size): self.data.test_volume['volume_name'] = "vmax-1234567" self.driver.create_volume_from_snapshot( - self.data.test_volume, EMCVMAXCommonData.test_source_volume) + self.data.test_volume, self.data.test_volume) def test_create_volume_from_snapshot_no_fast_failed(self): self.data.test_volume['volume_name'] = "vmax-1234567" @@ -1354,8 +1379,13 @@ class EMCVMAXISCSIDriverNoFastTestCase(test.TestCase): EMCVMAXCommon, '_find_storage_sync_sv_sv', return_value=(None, None)) - def test_create_clone_no_fast_success(self, mock_volume_type, - mock_volume, mock_sync_sv): + @mock.patch.object( + EMCVMAXUtils, + 'get_volume_meta_head', + return_value=None) + def test_create_clone_simple_volume_no_fast_success( + self, mock_volume_type, mock_volume, mock_sync_sv, + mock_simple_volume): self.data.test_volume['volume_name'] = "vmax-1234567" self.driver.create_cloned_volume(self.data.test_volume, EMCVMAXCommonData.test_source_volume) @@ -1737,37 +1767,41 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): @mock.patch.object( volume_types, 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'ISCSIFAST', - 'FASTPOLICY': 'FC_GOLD1'}) - @mock.patch.object( - EMCVMAXUtils, - 'find_storage_masking_group', - return_value=EMCVMAXCommonData.storagegroupname) + return_value={'volume_backend_name': 'ISCSIFAST'}) @mock.patch.object( FakeDB, 'volume_get', return_value=EMCVMAXCommonData.test_source_volume) @mock.patch.object( - EMCVMAXCommon, - '_find_storage_sync_sv_sv', - return_value=(None, None)) + EMCVMAXFast, + 'get_pool_associated_to_policy', + return_value=1) @mock.patch.object( EMCVMAXUtils, - 'find_storage_configuration_service', - return_value=1) + 'get_volume_meta_head', + return_value=[EMCVMAXCommonData.test_volume]) @mock.patch.object( EMCVMAXUtils, - 'find_controller_configuration_service', - return_value=1) + 'get_meta_members_capacity_in_bit', + return_value=[1234567, 7654321]) @mock.patch.object( EMCVMAXCommon, - '_get_or_create_default_storage_group', - return_value=1) - def test_create_snapshot_fast_success( - self, mock_volume_type, mock_storage_group, mock_volume, - mock_sync_sv, mock_storage_config_service, mock_controller_service, - mock_default_sg): + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_create_snapshot_different_sizes_meta_fast_success( + self, mock_volume_type, mock_volume, mock_meta, + mock_size, mock_pool, mock_policy): self.data.test_volume['volume_name'] = "vmax-1234567" + common = self.driver.common + + volumeDict = {'classname': u'Symm_StorageVolume', + 'keybindings': EMCVMAXCommonData.keybindings} + common.provision.create_volume_from_pool = ( + mock.Mock(return_value=(volumeDict, 0L))) + common.provision.get_volume_dict_from_job = ( + mock.Mock(return_value=volumeDict)) + common.fast.is_volume_in_default_SG = ( + mock.Mock(return_value=True)) self.driver.create_snapshot(self.data.test_volume) def test_create_snapshot_fast_failed(self): @@ -1779,39 +1813,30 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): @mock.patch.object( volume_types, 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'ISCSIFAST', - 'FASTPOLICY': 'FC_GOLD1'}) - @mock.patch.object( - EMCVMAXUtils, - 'find_storage_masking_group', - return_value=EMCVMAXCommonData.storagegroupname) - @mock.patch.object( - FakeDB, - 'volume_get', - return_value=EMCVMAXCommonData.test_source_volume) + return_value={'volume_backend_name': 'ISCSIFAST'}) @mock.patch.object( EMCVMAXCommon, '_find_storage_sync_sv_sv', return_value=(None, None)) @mock.patch.object( EMCVMAXUtils, - 'find_storage_configuration_service', - return_value=1) + 'get_volume_meta_head', + return_value=[EMCVMAXCommonData.test_volume]) @mock.patch.object( EMCVMAXUtils, - 'find_controller_configuration_service', - return_value=1) - @mock.patch.object( - EMCVMAXCommon, - '_get_or_create_default_storage_group', - return_value=1) - def test_create_volume_from_snapshot_fast_success( - self, mock_volume_type, mock_storage_group, mock_volume, - mock_sync_sv, mock_storage_config_service, mock_controller_service, - mock_default_sg): + 'get_meta_members_capacity_in_bit', + return_value=[1234567]) + def test_create_volume_from_same_size_meta_snapshot( + self, mock_volume_type, mock_sync_sv, mock_meta, mock_size): self.data.test_volume['volume_name'] = "vmax-1234567" + self.driver.common.utils.find_storage_configuration_service = ( + mock.Mock(return_value=EMCVMAXCommonData.storage_system)) + self.driver.common._get_or_create_default_storage_group = ( + mock.Mock(return_value=EMCVMAXCommonData.default_storage_group)) + self.driver.common.fast.is_volume_in_default_SG = ( + mock.Mock(return_value=True)) self.driver.create_volume_from_snapshot( - self.data.test_volume, EMCVMAXCommonData.test_source_volume) + self.data.test_volume, self.data.test_volume) @mock.patch.object( volume_types, @@ -1826,9 +1851,13 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): EMCVMAXCommon, '_find_storage_sync_sv_sv', return_value=(None, None)) + @mock.patch.object( + EMCVMAXUtils, + 'get_volume_meta_head', + return_value=None) def test_create_volume_from_snapshot_fast_failed( - self, mock_volume_type, - mock_rep_service, mock_sync_sv): + self, mock_type, mock_rep_service, mock_sync_sv, mock_meta): + self.data.test_volume['volume_name'] = "vmax-1234567" self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_volume_from_snapshot, @@ -1838,12 +1867,7 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): @mock.patch.object( volume_types, 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'ISCSIFAST', - 'FASTPOLICY': 'FC_GOLD1'}) - @mock.patch.object( - EMCVMAXUtils, - 'find_storage_masking_group', - return_value=EMCVMAXCommonData.storagegroupname) + return_value={'volume_backend_name': 'ISCSIFAST'}) @mock.patch.object( FakeDB, 'volume_get', @@ -1854,38 +1878,51 @@ class EMCVMAXISCSIDriverFastTestCase(test.TestCase): return_value=(None, None)) @mock.patch.object( EMCVMAXUtils, - 'find_storage_configuration_service', - return_value=1) - @mock.patch.object( - EMCVMAXUtils, - 'find_controller_configuration_service', - return_value=1) - @mock.patch.object( - EMCVMAXCommon, - '_get_or_create_default_storage_group', - return_value=1) - def test_create_clone_fast_success(self, mock_volume_type, - mock_storage_group, mock_volume, - mock_sync_sv, - mock_storage_config_service, - mock_controller_service, - mock_default_sg): + 'get_volume_meta_head', + return_value=None) + def test_create_clone_simple_volume_fast_success( + self, mock_volume_type, mock_volume, mock_sync_sv, + mock_simple_volume): self.data.test_volume['volume_name'] = "vmax-1234567" + self.driver.common.utils.find_storage_configuration_service = ( + mock.Mock(return_value=EMCVMAXCommonData.storage_system)) + self.driver.common._get_or_create_default_storage_group = ( + mock.Mock(return_value=EMCVMAXCommonData.default_storage_group)) + self.driver.common.fast.is_volume_in_default_SG = ( + mock.Mock(return_value=True)) self.driver.create_cloned_volume(self.data.test_volume, EMCVMAXCommonData.test_source_volume) @mock.patch.object( volume_types, 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'ISCSIFAST', - 'FASTPOLICY': 'FC_GOLD1'}) + return_value={'volume_backend_name': 'ISCSIFAST'}) + @mock.patch.object( + FakeDB, + 'volume_get', + return_value=EMCVMAXCommonData.test_source_volume) + @mock.patch.object( + EMCVMAXFast, + 'get_pool_associated_to_policy', + return_value=1) + @mock.patch.object( + EMCVMAXUtils, + 'get_volume_meta_head', + return_value=[EMCVMAXCommonData.test_volume]) + @mock.patch.object( + EMCVMAXUtils, + 'get_meta_members_capacity_in_bit', + return_value=[1234567, 7654321]) @mock.patch.object( EMCVMAXCommon, - '_find_storage_sync_sv_sv', - return_value=(None, None)) - def test_create_clone_fast_failed(self, mock_volume_type, - mock_sync_sv): + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_create_clone_fast_failed( + self, mock_volume_type, mock_vol, mock_policy, mock_meta, + mock_size, mock_pool): self.data.test_volume['volume_name'] = "vmax-1234567" + self.driver.common._modify_and_get_composite_volume_instance = ( + mock.Mock(return_value=(1L, None))) self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_cloned_volume, self.data.test_volume, @@ -2229,81 +2266,6 @@ class EMCVMAXFCDriverNoFastTestCase(test.TestCase): self.data.test_volume, newSize) - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'FCNoFAST'}) - @mock.patch.object( - FakeDB, - 'volume_get', - return_value=EMCVMAXCommonData.test_source_volume) - @mock.patch.object( - EMCVMAXCommon, - '_find_storage_sync_sv_sv', - return_value=(None, None)) - def test_create_snapshot_no_fast_success( - self, mock_volume_type, - mock_volume, mock_sync_sv): - self.data.test_volume['volume_name'] = "vmax-1234567" - self.driver.create_snapshot(self.data.test_volume) - - def test_create_snapshot_no_fast_failed(self): - self.data.test_volume['volume_name'] = "vmax-1234567" - self.assertRaises(exception.VolumeBackendAPIException, - self.driver.create_snapshot, - self.data.test_volume) - - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'FCNoFAST'}) - @mock.patch.object( - FakeDB, - 'volume_get', - return_value=EMCVMAXCommonData.test_source_volume) - @mock.patch.object( - EMCVMAXCommon, - '_find_storage_sync_sv_sv', - return_value=(None, None)) - def test_create_volume_from_snapshot_no_fast_success( - self, mock_volume_type, - mock_volume, mock_sync_sv): - self.data.test_volume['volume_name'] = "vmax-1234567" - self.driver.create_volume_from_snapshot( - self.data.test_volume, EMCVMAXCommonData.test_source_volume) - - def test_create_volume_from_snapshot_no_fast_failed(self): - self.data.test_volume['volume_name'] = "vmax-1234567" - self.assertRaises(exception.VolumeBackendAPIException, - self.driver.create_volume_from_snapshot, - self.data.test_volume, - EMCVMAXCommonData.test_source_volume) - - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'FCNoFAST'}) - @mock.patch.object( - FakeDB, - 'volume_get', - return_value=EMCVMAXCommonData.test_source_volume) - @mock.patch.object( - EMCVMAXCommon, - '_find_storage_sync_sv_sv', - return_value=(None, None)) - def test_create_clone_no_fast_success(self, mock_volume_type, - mock_volume, mock_sync_sv): - self.data.test_volume['volume_name'] = "vmax-1234567" - self.driver.create_cloned_volume(self.data.test_volume, - EMCVMAXCommonData.test_source_volume) - - def test_create_clone_no_fast_failed(self): - self.data.test_volume['volume_name'] = "vmax-1234567" - self.assertRaises(exception.VolumeBackendAPIException, - self.driver.create_cloned_volume, - self.data.test_volume, - EMCVMAXCommonData.test_source_volume) - @mock.patch.object( volume_types, 'get_volume_type_extra_specs', @@ -2652,39 +2614,41 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): @mock.patch.object( volume_types, 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'FCFAST', - 'FASTPOLICY': 'FC_GOLD1'}) - @mock.patch.object( - EMCVMAXUtils, - 'find_storage_masking_group', - return_value=EMCVMAXCommonData.storagegroupname) + return_value={'volume_backend_name': 'FCFAST'}) @mock.patch.object( FakeDB, 'volume_get', return_value=EMCVMAXCommonData.test_source_volume) @mock.patch.object( - EMCVMAXCommon, - '_find_storage_sync_sv_sv', - return_value=(None, None)) + EMCVMAXFast, + 'get_pool_associated_to_policy', + return_value=1) @mock.patch.object( EMCVMAXUtils, - 'find_storage_configuration_service', - return_value=1) + 'get_volume_meta_head', + return_value=[EMCVMAXCommonData.test_volume]) @mock.patch.object( EMCVMAXUtils, - 'find_controller_configuration_service', - return_value=1) + 'get_meta_members_capacity_in_bit', + return_value=[1234567, 7654321]) @mock.patch.object( EMCVMAXCommon, - '_get_or_create_default_storage_group', - return_value=1) - def test_create_snapshot_fast_success(self, mock_volume_type, - mock_storage_group, mock_volume, - mock_sync_sv, - mock_storage_config_service, - mock_controller_config_service, - mock_default_sg): + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_create_snapshot_different_sizes_meta_fast_success( + self, mock_volume_type, mock_volume, mock_meta, + mock_size, mock_pool, mock_policy): self.data.test_volume['volume_name'] = "vmax-1234567" + common = self.driver.common + + volumeDict = {'classname': u'Symm_StorageVolume', + 'keybindings': EMCVMAXCommonData.keybindings} + common.provision.create_volume_from_pool = ( + mock.Mock(return_value=(volumeDict, 0L))) + common.provision.get_volume_dict_from_job = ( + mock.Mock(return_value=volumeDict)) + common.fast.is_volume_in_default_SG = ( + mock.Mock(return_value=True)) self.driver.create_snapshot(self.data.test_volume) def test_create_snapshot_fast_failed(self): @@ -2696,12 +2660,7 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): @mock.patch.object( volume_types, 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'FCFAST', - 'FASTPOLICY': 'FC_GOLD1'}) - @mock.patch.object( - EMCVMAXUtils, - 'find_storage_masking_group', - return_value=EMCVMAXCommonData.storagegroupname) + return_value={'volume_backend_name': 'FCFAST'}) @mock.patch.object( FakeDB, 'volume_get', @@ -2712,100 +2671,52 @@ class EMCVMAXFCDriverFastTestCase(test.TestCase): return_value=(None, None)) @mock.patch.object( EMCVMAXUtils, - 'find_storage_configuration_service', - return_value=1) - @mock.patch.object( - EMCVMAXUtils, - 'find_controller_configuration_service', - return_value=1) - @mock.patch.object( - EMCVMAXCommon, - '_get_or_create_default_storage_group', - return_value=1) - def test_create_volume_from_snapshot_fast_success( - self, mock_volume_type, mock_storage_group, mock_volume, - mock_sync_sv, mock_storage_config_service, - mock_controller_config_service, mock_default_sg): - self.data.test_volume['volume_name'] = "vmax-1234567" - self.driver.create_volume_from_snapshot( - self.data.test_volume, EMCVMAXCommonData.test_source_volume) - - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'storagetype: pool': 'gold', - 'volume_backend_name': 'FCFAST'}) - @mock.patch.object( - EMCVMAXUtils, - 'find_replication_service', + 'get_volume_meta_head', return_value=None) - @mock.patch.object( - EMCVMAXCommon, - '_find_storage_sync_sv_sv', - return_value=(None, None)) - def test_create_volume_from_snapshot_fast_failed(self, mock_volume_type, - mock_rep_service, - mock_sync_sv): + def test_create_clone_simple_volume_fast_success( + self, mock_volume_type, + mock_volume, mock_sync_sv, mock_meta): self.data.test_volume['volume_name'] = "vmax-1234567" - self.assertRaises(exception.VolumeBackendAPIException, - self.driver.create_volume_from_snapshot, - self.data.test_volume, - EMCVMAXCommonData.test_source_volume) + self.driver.common.utils.find_storage_configuration_service = ( + mock.Mock(return_value=EMCVMAXCommonData.storage_system)) + self.driver.common._get_or_create_default_storage_group = ( + mock.Mock(return_value=EMCVMAXCommonData.default_storage_group)) + self.driver.common.fast.is_volume_in_default_SG = ( + mock.Mock(return_value=True)) + self.driver.create_cloned_volume( + self.data.test_volume, + EMCVMAXCommonData.test_source_volume) @mock.patch.object( volume_types, 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'FCFAST', - 'FASTPOLICY': 'FC_GOLD1'}) - @mock.patch.object( - EMCVMAXUtils, - 'find_storage_masking_group', - return_value=EMCVMAXCommonData.storagegroupname) + return_value={'volume_backend_name': 'FCFAST'}) @mock.patch.object( FakeDB, 'volume_get', return_value=EMCVMAXCommonData.test_source_volume) @mock.patch.object( - EMCVMAXCommon, - '_find_storage_sync_sv_sv', - return_value=(None, None)) - @mock.patch.object( - EMCVMAXUtils, - 'find_storage_configuration_service', + EMCVMAXFast, + 'get_pool_associated_to_policy', return_value=1) @mock.patch.object( EMCVMAXUtils, - 'find_controller_configuration_service', - return_value=1) - @mock.patch.object( - EMCVMAXCommon, - '_get_or_create_default_storage_group', - return_value=1) - def test_create_clone_fast_success(self, mock_volume_type, - mock_storage_group, mock_volume, - mock_sync_sv, - mock_storage_config_service, - mock_controller_config_service, - mock_default_sg): - self.data.test_volume['volume_name'] = "vmax-1234567" - self.driver.create_cloned_volume(self.data.test_volume, - EMCVMAXCommonData.test_source_volume) - - @mock.patch.object( - volume_types, - 'get_volume_type_extra_specs', - return_value={'volume_backend_name': 'FCFAST'}) + 'get_volume_meta_head', + return_value=[EMCVMAXCommonData.test_volume]) @mock.patch.object( EMCVMAXUtils, - 'find_replication_service', - return_value=None) + 'get_meta_members_capacity_in_bit', + return_value=[1234567, 7654321]) @mock.patch.object( EMCVMAXCommon, - '_find_storage_sync_sv_sv', - return_value=(None, None)) - def test_create_clone_fast_failed(self, mock_volume_type, - mock_rep_service, mock_sync_sv): + '_get_pool_and_storage_system', + return_value=(None, EMCVMAXCommonData.storage_system)) + def test_create_clone_fast_failed( + self, mock_volume_type, mock_vol, + mock_policy, mock_meta, mock_size, mock_pool): self.data.test_volume['volume_name'] = "vmax-1234567" + self.driver.common._modify_and_get_composite_volume_instance = ( + mock.Mock(return_value=(1L, None))) self.assertRaises(exception.VolumeBackendAPIException, self.driver.create_cloned_volume, self.data.test_volume, diff --git a/cinder/volume/drivers/emc/emc_vmax_common.py b/cinder/volume/drivers/emc/emc_vmax_common.py index 2d88a4e2a..101894ee5 100644 --- a/cinder/volume/drivers/emc/emc_vmax_common.py +++ b/cinder/volume/drivers/emc/emc_vmax_common.py @@ -116,87 +116,10 @@ class EMCVMAXCommon(object): volumeName = volume['name'] extraSpecs = self._initial_setup(volume) - memberCount, errorDesc = self.utils.determine_member_count( - volume['size'], extraSpecs[MEMBERCOUNT], extraSpecs[COMPOSITETYPE]) - if errorDesc is not None: - exceptionMessage = (_("The striped meta count of %(memberCount)s " - "is too small for volume: %(volumeName)s. " - "with size %(volumeSize)s ") - % {'memberCount': memberCount, - 'volumeName': volumeName, - 'volumeSize': volume['size']}) - LOG.error(exceptionMessage) - raise exception.VolumeBackendAPIException(data=exceptionMessage) - self.conn = self._get_ecom_connection() - poolInstanceName, storageSystemName = ( - self._get_pool_and_storage_system(extraSpecs)) - - LOG.debug("Create Volume: %(volume)s Pool: %(pool)s " - "Storage System: %(storageSystem)s " - "Size: %(size)lu " - % {'volume': volumeName, - 'pool': poolInstanceName, - 'storageSystem': storageSystemName, - 'size': volumeSize}) - - elementCompositionService = ( - self.utils.find_element_composition_service(self.conn, - storageSystemName)) - - storageConfigService = self.utils.find_storage_configuration_service( - self.conn, storageSystemName) - - # If FAST is intended to be used we must first check that the pool - # is associated with the correct storage tier - if extraSpecs[FASTPOLICY] is not None: - foundPoolInstanceName = self.fast.get_pool_associated_to_policy( - self.conn, extraSpecs[FASTPOLICY], extraSpecs[ARRAY], - storageConfigService, poolInstanceName) - if foundPoolInstanceName is None: - exceptionMessage = (_("Pool: %(poolName)s. " - "is not associated to storage tier for " - "fast policy %(fastPolicy)s.") - % {'poolName': extraSpecs[POOL], - 'fastPolicy': extraSpecs[FASTPOLICY]}) - LOG.error(exceptionMessage) - raise exception.VolumeBackendAPIException( - data=exceptionMessage) - - compositeType = self.utils.get_composite_type( - extraSpecs[COMPOSITETYPE]) - - volumeDict, rc = self.provision.create_composite_volume( - self.conn, elementCompositionService, volumeSize, volumeName, - poolInstanceName, compositeType, memberCount) - - # Now that we have already checked that the pool is associated with - # the correct storage tier and the volume was successfully created - # add the volume to the default storage group created for - # volumes in pools associated with this fast policy - if extraSpecs[FASTPOLICY]: - LOG.info(_LI("Adding volume: %(volumeName)s to " - "default storage group " - "for FAST policy: %(fastPolicyName)s "), - {'volumeName': volumeName, - 'fastPolicyName': extraSpecs[FASTPOLICY]}) - defaultStorageGroupInstanceName = ( - self._get_or_create_default_storage_group( - self.conn, storageSystemName, volumeDict, - volumeName, extraSpecs[FASTPOLICY])) - if not defaultStorageGroupInstanceName: - exceptionMessage = (_( - "Unable to create or get default storage group for " - "FAST policy: %(fastPolicyName)s. ") - % {'fastPolicyName': extraSpecs[FASTPOLICY]}) - LOG.error(exceptionMessage) - raise exception.VolumeBackendAPIException( - data=exceptionMessage) - - self._add_volume_to_default_storage_group_on_create( - volumeDict, volumeName, storageConfigService, - storageSystemName, extraSpecs[FASTPOLICY]) + rc, volumeDict, storageSystemName = self._create_composite_volume( + volume, extraSpecs, volumeName, volumeSize) LOG.info(_LI("Leaving create_volume: %(volumeName)s " "Return code: %(rc)lu " @@ -359,7 +282,7 @@ class EMCVMAXCommon(object): # Device is already mapped so we will leave the state as is deviceNumber = deviceInfoDict['hostlunid'] LOG.info(_LI("Volume %(volume)s is already mapped. " - "The device number is %(deviceNumber)s ") + "The device number is %(deviceNumber)s ") % {'volume': volumeName, 'deviceNumber': deviceNumber}) else: @@ -1072,7 +995,7 @@ class EMCVMAXCommon(object): conn, controllerConfigurationService, volumeInstance, volumeName, targetFastPolicyName)) if assocDefaultStorageGroupName is None: - errorMsg = (_( + errorMsg = (_LE( "Failed to add %(volumeName)s " "to default storage group for fast policy " "%(fastPolicyName)s ") @@ -1113,7 +1036,7 @@ class EMCVMAXCommon(object): "and fast policy")) if targetArraySerialNumber not in sourceArraySerialNumber: - errorMessage = (_( + errorMessage = (_LE( "The source array : %(sourceArraySerialNumber)s does not " "match the target array: %(targetArraySerialNumber)s" "skipping storage-assisted migration") @@ -1129,8 +1052,8 @@ class EMCVMAXCommon(object): assocPoolInstance = self.conn.GetInstance( assocPoolInstanceName) if assocPoolInstance['ElementName'] == targetPoolName: - errorMessage = (_("No action required. Volume : %(volumeName)s is " - "already part of pool : %(pool)s") + errorMessage = (_LE("No action required. Volume : %(volumeName)s " + "is already part of pool : %(pool)s") % {'volumeName': volumeName, 'pool': targetPoolName}) LOG.error(errorMessage) @@ -1139,7 +1062,7 @@ class EMCVMAXCommon(object): LOG.info(_LI("Volume status is: %s"), volumeStatus) if (host['capabilities']['storage_protocol'] != self.protocol and (volumeStatus != 'available' and volumeStatus != 'retyping')): - errorMessage = (_( + errorMessage = (_LE( "Only available volumes can be migrated between " "different protocols")) LOG.error(errorMessage) @@ -1884,7 +1807,7 @@ class EMCVMAXCommon(object): cloneName = cloneVolume['name'] LOG.info(_LI("Create a Clone from Volume: Clone " - "Volume: %(cloneName)s " + "Volume: %(cloneName)s " "Source Volume: %(sourceName)s") % {'cloneName': cloneName, 'sourceName': sourceName}) @@ -1893,15 +1816,6 @@ class EMCVMAXCommon(object): sourceInstance = self._find_lun(sourceVolume) storageSystem = sourceInstance['SystemName'] - - LOG.debug("Create Cloned Volume: Volume: %(cloneName)s " - "Source Volume: %(sourceName)s Source Instance: " - "%(sourceInstance)s Storage System: %(storageSystem)s." - % {'cloneName': cloneName, - 'sourceName': sourceName, - 'sourceInstance': sourceInstance.path, - 'storageSystem': storageSystem}) - repServiceInstanceName = self.utils.find_replication_service( self.conn, storageSystem) @@ -1917,10 +1831,133 @@ class EMCVMAXCommon(object): 'elementname': cloneName, 'sourceelement': sourceInstance.path}) + return self._examine_source_and_create_clone( + repServiceInstanceName, cloneVolume, sourceVolume, + sourceInstance, extraSpecs) + + def _examine_source_and_create_clone(self, repServiceInstanceName, + cloneVolume, sourceVolume, + sourceInstance, extraSpecs): + """Create a clone (v2). + + :param repServiceInstanceName: the replication service + :param cloneVolume: the clone volume object + :param sourceVolume: the source volume object + :param sourceInstance: the device ID of the volume + :param fastPolicyName: the FAST policy name(if it exists) + :returns: rc + """ + # check if the source volume contains any meta devices + metaHeadInstanceName = self.utils.get_volume_meta_head( + self.conn, sourceInstance.path) + + if metaHeadInstanceName is None: # simple volume + return self._create_replica_and_delete_clone_relationship( + repServiceInstanceName, cloneVolume, sourceVolume, + sourceInstance, None, extraSpecs) + else: # composite volume with meta device members + # check if the meta members' capacity + metaMemberInstanceNames = ( + self.utils.get_meta_members_of_composite_volume( + self.conn, metaHeadInstanceName)) + volumeCapacities = self.utils.get_meta_members_capacity_in_bit( + self.conn, metaMemberInstanceNames) + LOG.debug("Volume capacities: %(metasizes)s " + % {'metasizes': volumeCapacities}) + if len(set(volumeCapacities)) == 1: + LOG.debug("Meta volume all of the same size") + return self._create_replica_and_delete_clone_relationship( + repServiceInstanceName, cloneVolume, sourceVolume, + sourceInstance, None, extraSpecs) + + LOG.debug("Meta volumes are of different sizes: " + "%d different sizes." % len(set(volumeCapacities))) + + baseTargetVolumeInstance = None + for volumeSizeInbits in volumeCapacities: + if baseTargetVolumeInstance is None: # Create base volume + baseVolumeName = "TargetBaseVol" + volume = {'size': int(self.utils.convert_bits_to_gbs( + volumeSizeInbits))} + rc, baseVolumeDict, storageSystemName = ( + self._create_composite_volume( + volume, extraSpecs, + baseVolumeName, volumeSizeInbits)) + baseTargetVolumeInstance = self.utils.find_volume_instance( + self.conn, baseVolumeDict, baseVolumeName) + LOG.info(_LI("Base target volume %(targetVol)s created. " + "Capacity in bits: %(capInBits)lu ") + % {'capInBits': volumeSizeInbits, + 'targetVol': baseTargetVolumeInstance.path}) + else: # create append volume + targetVolumeName = "MetaVol" + volume = {'size': int(self.utils.convert_bits_to_gbs( + volumeSizeInbits))} + storageConfigService = ( + self.utils.find_storage_configuration_service( + self.conn, storageSystemName)) + unboundVolumeInstance = ( + self._create_and_get_unbound_volume( + self.conn, storageConfigService, + baseTargetVolumeInstance.path, volumeSizeInbits)) + if unboundVolumeInstance is None: + exceptionMessage = (_( + "Error Creating unbound volume.")) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + # append the new unbound volume to the + # base target composite volume + baseTargetVolumeInstance = self.utils.find_volume_instance( + self.conn, baseVolumeDict, baseVolumeName) + elementCompositionService = ( + self.utils.find_element_composition_service( + self.conn, storageSystemName)) + compositeType = self.utils.get_composite_type( + extraSpecs[COMPOSITETYPE]) + rc, modifiedVolumeDict = ( + self._modify_and_get_composite_volume_instance( + self.conn, elementCompositionService, + baseTargetVolumeInstance, + unboundVolumeInstance.path, + targetVolumeName, compositeType)) + if modifiedVolumeDict is None: + exceptionMessage = (_( + "Error appending volume %(volumename)s to " + "target base volume") + % {'volumename': targetVolumeName}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + LOG.debug("Create replica for meta members of different sizes.") + return self._create_replica_and_delete_clone_relationship( + repServiceInstanceName, cloneVolume, sourceVolume, + sourceInstance, baseTargetVolumeInstance, extraSpecs) + + def _create_replica_and_delete_clone_relationship( + self, repServiceInstanceName, cloneVolume, sourceVolume, + sourceInstance, targetInstance, extraSpecs): + """Helper function to create a clone and delete the relationship. + + This function creates clone of the source volume and then + delete the clone replatinship once the copy completes. + + :param repServiceInstanceName: replication service instance name + :param cloneVolume: target volume of the clone operation + :param sourceVolume: source volume of the clone operation + :param sourceInstance: instance of ECOM StorageVolume object + :param targetInstance: instance of ECOM StorageVolume object + :param extraSpecs: extraSpecs info + :returns: rc the return code, cloneDict the cloned volume dictionary + """ + sourceName = sourceVolume['name'] + cloneName = cloneVolume['name'] # Create a Clone from source volume rc, job = self.provision.create_element_replica( self.conn, repServiceInstanceName, cloneName, sourceName, - sourceInstance) + sourceInstance, targetInstance) cloneDict = self.provision.get_volume_dict_from_job( self.conn, job['Job']) @@ -1960,9 +1997,15 @@ class EMCVMAXCommon(object): raise exception.VolumeBackendAPIException( data=exceptionMessage) - self._add_volume_to_default_storage_group_on_create( - cloneDict, cloneName, storageConfigService, storageSystemName, - extraSpecs[FASTPOLICY]) + # check if the clone/snapshot volume already part of the default sg + cloneInstance = self.utils.find_volume_instance( + self.conn, cloneDict, cloneName) + inDefaultSG = self.fast.is_volume_in_default_SG( + self.conn, cloneInstance.path) + if inDefaultSG is False: + self._add_volume_to_default_storage_group_on_create( + cloneDict, cloneName, storageConfigService, + storageSystemName, extraSpecs[FASTPOLICY]) LOG.debug("Leaving _create_cloned_volume: Volume: " "%(cloneName)s Source Volume: %(sourceName)s " @@ -2051,7 +2094,7 @@ class EMCVMAXCommon(object): self.conn, controllerConfigurationService, volumeInstance, volumeName, fastPolicyName)) if assocDefaultStorageGroupName is None: - errorMsg = (_( + errorMsg = (_LE( "Failed to Roll back to re-add volume %(volumeName)s " "to default storage group for fast policy " "%(fastPolicyName)s: Please contact your sysadmin to " @@ -2218,7 +2261,7 @@ class EMCVMAXCommon(object): self.conn, maskingViewInstanceName) def get_masking_view_by_volume(self, volume): - """Given volume, retrieve the masking view instance name + """Given volume, retrieve the masking view instance name. :param volume: the volume :param mvInstanceName: masking view instance name @@ -2231,7 +2274,7 @@ class EMCVMAXCommon(object): self.conn, volumeInstance) def get_masking_views_by_port_group(self, portGroupInstanceName): - """Given port group, retrieve the masking view instance name + """Given port group, retrieve the masking view instance name. :param : the volume :param mvInstanceName: masking view instance name @@ -2241,3 +2284,93 @@ class EMCVMAXCommon(object): % {'pg': portGroupInstanceName}) return self.masking.get_masking_views_by_port_group( self.conn, portGroupInstanceName) + + def _create_composite_volume( + self, volume, extraSpecs, volumeName, volumeSize): + """Create a composite volume. + + :param volume: the volume object + :param extraSpecs: + :param volumeName: + :param volumeSize: + :returns: + """ + memberCount, errorDesc = self.utils.determine_member_count( + volume['size'], extraSpecs[MEMBERCOUNT], extraSpecs[COMPOSITETYPE]) + if errorDesc is not None: + exceptionMessage = (_("The striped meta count of %(memberCount)s " + "is too small for volume: %(volumeName)s. " + "with size %(volumeSize)s ") + % {'memberCount': memberCount, + 'volumeName': volumeName, + 'volumeSize': volume['size']}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException(data=exceptionMessage) + + poolInstanceName, storageSystemName = ( + self._get_pool_and_storage_system(extraSpecs)) + + LOG.debug("Create Volume: %(volume)s Pool: %(pool)s " + "Storage System: %(storageSystem)s " + "Size: %(size)lu " + % {'volume': volumeName, + 'pool': poolInstanceName, + 'storageSystem': storageSystemName, + 'size': volumeSize}) + + elementCompositionService = ( + self.utils.find_element_composition_service(self.conn, + storageSystemName)) + + storageConfigService = self.utils.find_storage_configuration_service( + self.conn, storageSystemName) + + # If FAST is intended to be used we must first check that the pool + # is associated with the correct storage tier + if extraSpecs[FASTPOLICY] is not None: + foundPoolInstanceName = self.fast.get_pool_associated_to_policy( + self.conn, extraSpecs[FASTPOLICY], extraSpecs[ARRAY], + storageConfigService, poolInstanceName) + if foundPoolInstanceName is None: + exceptionMessage = (_("Pool: %(poolName)s. " + "is not associated to storage tier for " + "fast policy %(fastPolicy)s.") + % {'poolName': extraSpecs[POOL], + 'fastPolicy': extraSpecs[FASTPOLICY]}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + compositeType = self.utils.get_composite_type( + extraSpecs[COMPOSITETYPE]) + + volumeDict, rc = self.provision.create_composite_volume( + self.conn, elementCompositionService, volumeSize, volumeName, + poolInstanceName, compositeType, memberCount) + + # Now that we have already checked that the pool is associated with + # the correct storage tier and the volume was successfully created + # add the volume to the default storage group created for + # volumes in pools associated with this fast policy + if extraSpecs[FASTPOLICY]: + LOG.info(_LI("Adding volume: %(volumeName)s to default storage " + "group for FAST policy: %(fastPolicyName)s ") + % {'volumeName': volumeName, + 'fastPolicyName': extraSpecs[FASTPOLICY]}) + defaultStorageGroupInstanceName = ( + self._get_or_create_default_storage_group( + self.conn, storageSystemName, volumeDict, + volumeName, extraSpecs[FASTPOLICY])) + if not defaultStorageGroupInstanceName: + exceptionMessage = (_( + "Unable to create or get default storage group for " + "FAST policy: %(fastPolicyName)s. ") + % {'fastPolicyName': extraSpecs[FASTPOLICY]}) + LOG.error(exceptionMessage) + raise exception.VolumeBackendAPIException( + data=exceptionMessage) + + self._add_volume_to_default_storage_group_on_create( + volumeDict, volumeName, storageConfigService, + storageSystemName, extraSpecs[FASTPOLICY]) + return rc, volumeDict, storageSystemName diff --git a/cinder/volume/drivers/emc/emc_vmax_fast.py b/cinder/volume/drivers/emc/emc_vmax_fast.py index 677a2c7a2..d3512f7f0 100644 --- a/cinder/volume/drivers/emc/emc_vmax_fast.py +++ b/cinder/volume/drivers/emc/emc_vmax_fast.py @@ -140,12 +140,12 @@ class EMCVMAXFast(object): foundDefaultStorageGroupInstanceName = ( assocStorageGroupInstanceName) else: - exceptionMessage = (_( + errorMessage = (_LW( "Volume: %(volumeName)s Does not belong " "to storage storage group %(defaultSgGroupName)s. ") % {'volumeName': volumeName, 'defaultSgGroupName': defaultSgGroupName}) - LOG.warn(exceptionMessage) + LOG.warn(errorMessage) return foundDefaultStorageGroupInstanceName def add_volume_to_default_storage_group_for_fast_policy( @@ -402,7 +402,7 @@ class EMCVMAXFast(object): if len(storageTierInstanceNames) == 0: storageTierInstanceNames = None LOG.warn(_LW("Unable to get storage tiers " - "from tier policy rule ")) + "from tier policy rule.")) return storageTierInstanceNames @@ -490,7 +490,7 @@ class EMCVMAXFast(object): tierPolicyRuleInstanceName = self._get_service_level_tier_policy( conn, tierPolicyServiceInstanceName, fastPolicyName) if tierPolicyRuleInstanceName is None: - errorMessage = (_( + errorMessage = (_LE( "Cannot find the fast policy %(fastPolicyName)s") % {'fastPolicyName': fastPolicyName}) @@ -511,7 +511,7 @@ class EMCVMAXFast(object): storageGroupName, fastPolicyName) except Exception as ex: LOG.error(_LE("Exception: %s") % six.text_type(ex)) - errorMessage = (_( + errorMessage = (_LE( "Failed to add storage group %(storageGroupInstanceName)s " " to tier policy rule %(tierPolicyRuleInstanceName)s") % {'storageGroupInstanceName': storageGroupInstanceName, @@ -578,7 +578,7 @@ class EMCVMAXFast(object): rc, errordesc = self.utils.wait_for_job_complete(conn, job) if rc != 0L: LOG.error(_LE("Error disassociating storage group from " - "policy: %s") % errordesc) + "policy: %s") % errordesc) else: LOG.debug("Disassociated storage group from policy %s") else: @@ -766,3 +766,25 @@ class EMCVMAXFast(object): fastPolicyName = tierPolicyInstanceName['PolicyRuleName'] return fastPolicyName + + def is_volume_in_default_SG(self, conn, volumeInstanceName): + """Check if the volume is already part of the default storage group. + + :param volumeInstanceName: the volume instance + :returns: True if the volume is already in default storage group + False otherwise + """ + sgInstanceNames = conn.AssociatorNames( + volumeInstanceName, + ResultClass='CIM_DeviceMaskingGroup') + if len(sgInstanceNames) == 0: + LOG.debug("volume %(vol)s is not in default sg." + % {'vol': volumeInstanceName}) + return False + else: + for sgInstance in sgInstanceNames: + if DEFAULT_SG_PREFIX in sgInstance['InstanceID']: + LOG.debug("volume %(vol)s already in default sg." + % {'vol': volumeInstanceName}) + return True + return False diff --git a/cinder/volume/drivers/emc/emc_vmax_provision.py b/cinder/volume/drivers/emc/emc_vmax_provision.py index 4a58a1967..f305669f8 100644 --- a/cinder/volume/drivers/emc/emc_vmax_provision.py +++ b/cinder/volume/drivers/emc/emc_vmax_provision.py @@ -543,7 +543,7 @@ class EMCVMAXProvision(object): def create_element_replica( self, conn, repServiceInstanceName, cloneName, - sourceName, sourceInstance): + sourceName, sourceInstance, targetInstance): """Make SMI-S call to create replica for source element. :param conn: the connection to the ecom server @@ -551,14 +551,23 @@ class EMCVMAXProvision(object): :param cloneName: replica name :param sourceName: source volume name :param sourceInstance: source volume instance + :param targetInstance: target volume instance :returns: rc - return code :returns: job - job object of the replica creation operation """ - rc, job = conn.InvokeMethod( - 'CreateElementReplica', repServiceInstanceName, - ElementName=cloneName, - SyncType=self.utils.get_num(8, '16'), - SourceElement=sourceInstance.path) + if targetInstance is None: + rc, job = conn.InvokeMethod( + 'CreateElementReplica', repServiceInstanceName, + ElementName=cloneName, + SyncType=self.utils.get_num(8, '16'), + SourceElement=sourceInstance.path) + else: + rc, job = conn.InvokeMethod( + 'CreateElementReplica', repServiceInstanceName, + ElementName=cloneName, + SyncType=self.utils.get_num(8, '16'), + SourceElement=sourceInstance.path, + TargetElement=targetInstance.path) if rc != 0L: rc, errordesc = self.utils.wait_for_job_complete(conn, job) diff --git a/cinder/volume/drivers/emc/emc_vmax_utils.py b/cinder/volume/drivers/emc/emc_vmax_utils.py index 59ba6db4e..a8d9022c3 100644 --- a/cinder/volume/drivers/emc/emc_vmax_utils.py +++ b/cinder/volume/drivers/emc/emc_vmax_utils.py @@ -346,11 +346,11 @@ class EMCVMAXUtils(object): """ def _wait_for_sync(): - """Called at an interval until the synchronization is finished""" + """Called at an interval until the synchronization is finished.""" if self._is_sync_complete(conn, syncName): raise loopingcall.LoopingCallDone() if self.retries > JOB_RETRIES: - LOG.error(_LE("_wait_for_sync failed after %(retries)d tries") + LOG.error(_LE("_wait_for_sync failed after %(retries)d tries.") % {'retries': self.retries}) raise loopingcall.LoopingCallDone() try: @@ -1170,3 +1170,52 @@ class EMCVMAXUtils(object): break return foundIpAddress + + def get_volume_meta_head(self, conn, volumeInstanceName): + """Get the head of a meta volume. + + :param volumeInstanceName: the composite volume instance name + :returns: the instance name of the meta volume head + """ + metaHeadInstanceName = None + metaHeads = conn.AssociatorNames( + volumeInstanceName, + ResultClass='EMC_Meta') + + if len(metaHeads) > 0: + metaHeadInstanceName = metaHeads[0] + if metaHeadInstanceName is None: + LOG.info(_LI("Volume %(volume)s does not have meta device " + "members."), + {'volume': volumeInstanceName}) + + return metaHeadInstanceName + + def get_meta_members_of_composite_volume( + self, conn, metaHeadInstanceName): + """Get the member volumes of a composite volume. + + :param metaHeadInstanceName: head of the composite volume + :returns: an array containing instance names of member volumes + """ + metaMembers = conn.AssociatorNames( + metaHeadInstanceName, + AssocClass='CIM_BasedOn', + ResultClass='EMC_PartialAllocOfConcreteExtent') + LOG.debug("metaMembers: %(members)s " % {'members': metaMembers}) + return metaMembers + + def get_meta_members_capacity_in_bit(self, conn, volumeInstanceNames): + """Get the capacity in bits of all meta device member volumes. + + :param volumeInstanceNames: array contains meta device member volumes + :returns: array contains capacities of each member device in bits + """ + capacitiesInBit = [] + for volumeInstanceName in volumeInstanceNames: + volumeInstance = conn.GetInstance(volumeInstanceName) + numOfBlocks = volumeInstance['ConsumableBlocks'] + blockSize = volumeInstance['BlockSize'] + volumeSizeInbits = numOfBlocks * blockSize + capacitiesInBit.append(volumeSizeInbits) + return capacitiesInBit -- 2.45.2