diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py index 200493bbdd0..81978438443 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/powermax_data.py @@ -35,6 +35,7 @@ class PowerMaxData(object): array = '000197800123' uni_array = u'000197800123' array_herc = '000197900123' + array_model = 'PowerMax_8000' srp = 'SRP_1' srp2 = 'SRP_2' slo = 'Diamond' @@ -78,6 +79,7 @@ class PowerMaxData(object): no_slo_sg_name = 'OS-HostX-No_SLO-OS-fibre-PG' temp_snapvx = 'temp-00001-snapshot_for_clone' next_gen_ucode = 5978 + gvg_group_id = 'test-gvg' # connector info wwpn1 = '123456789012345' @@ -1124,3 +1126,56 @@ class PowerMaxData(object): {'generation': 1, 'expired': False, 'copy_mode': False, 'snap_name': 'temp-000AA-snapshot_for_clone', 'state': 'Copied', 'source_vol_id': device_id, 'target_vol_id': device_id4}] + + device_label = 'OS-00001' + priv_vol_response_rep = { + 'volumeHeader': { + 'private': False, 'capGB': 1.0, 'capMB': 1026.0, + 'serviceState': 'Normal', 'emulationType': 'FBA', + 'volumeId': '00001', 'status': 'Ready', 'mapped': False, + 'numStorageGroups': 0, 'reservationInfo': {'reserved': False}, + 'encapsulated': False, 'formattedName': '00001', + 'system_resource': False, 'numSymDevMaskingViews': 0, + 'nameModifier': "", 'configuration': 'TDEV', + 'userDefinedIdentifier': 'OS-00001'}, + 'maskingInfo': {'masked': False}, + 'rdfInfo': { + 'dynamicRDF': False, 'RDF': True, + 'concurrentRDF': False, + 'getDynamicRDFCapability': 'RDF1_Capable', 'RDFA': False, + 'RDFSession': [ + {'SRDFStatus': 'Ready', + 'SRDFReplicationMode': 'Synchronized', + 'remoteDeviceID': device_id2, + 'remoteSymmetrixID': remote_array, + 'SRDFGroupNumber': 1, + 'SRDFRemoteGroupNumber': 1}]}} + + priv_vol_response_no_rep = { + 'volumeHeader': { + 'private': False, 'capGB': 1.0, 'capMB': 1026.0, + 'serviceState': 'Normal', 'emulationType': 'FBA', + 'volumeId': '00001', 'status': 'Ready', 'mapped': False, + 'numStorageGroups': 0, 'reservationInfo': {'reserved': False}, + 'encapsulated': False, 'formattedName': '00001', + 'system_resource': False, 'numSymDevMaskingViews': 0, + 'nameModifier': "", 'configuration': 'TDEV', + 'userDefinedIdentifier': 'OS-00001'}, + 'maskingInfo': {'masked': False}, + 'rdfInfo': {'RDF': False}} + + snap_device_label = ('%(dev)s:%(label)s' % {'dev': device_id, + 'label': managed_snap_id}) + priv_snap_response = { + 'deviceName': snap_device_label, 'snapshotLnks': [], + 'snapshotSrcs': [ + {'generation': 0, + 'linkedDevices': [ + {'targetDevice': device_id2, 'percentageCopied': 100, + 'state': 'Copied', 'copy': True, 'defined': True, + 'linked': True}], + 'snapshotName': test_snapshot_snap_name, + 'state': 'Established'}]} + + volume_metadata = { + 'DeviceID': device_id, 'ArrayID': array, 'ArrayModel': array_model} diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_common.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_common.py index a7fc2c2028e..241af93427f 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_common.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_common.py @@ -136,15 +136,29 @@ class PowerMaxCommonTest(test.TestCase): exception.VolumeBackendAPIException, self.common._get_slo_workload_combinations, array_info) - def test_create_volume(self): + @mock.patch.object( + common.PowerMaxCommon, 'get_volume_metadata', + return_value={'device-meta-key-1': 'device-meta-value-1', + 'device-meta-key-2': 'device-meta-value-2'}) + def test_create_volume(self, mck_meta): ref_model_update = ( - {'provider_location': six.text_type(self.data.provider_location)}) - model_update = self.common.create_volume(self.data.test_volume) + {'provider_location': six.text_type(self.data.provider_location), + 'metadata': {'device-meta-key-1': 'device-meta-value-1', + 'device-meta-key-2': 'device-meta-value-2', + 'user-meta-key-1': 'user-meta-value-1', + 'user-meta-key-2': 'user-meta-value-2'}}) + volume = deepcopy(self.data.test_volume) + volume.metadata = {'user-meta-key-1': 'user-meta-value-1', + 'user-meta-key-2': 'user-meta-value-2'} + model_update = self.common.create_volume(volume) self.assertEqual(ref_model_update, model_update) - def test_create_volume_qos(self): + @mock.patch.object(common.PowerMaxCommon, 'get_volume_metadata', + return_value='') + def test_create_volume_qos(self, mck_meta): ref_model_update = ( - {'provider_location': six.text_type(self.data.provider_location)}) + {'provider_location': six.text_type(self.data.provider_location), + 'metadata': ''}) extra_specs = deepcopy(self.data.extra_specs_intervals_set) extra_specs['qos'] = { 'total_iops_sec': '4000', 'DistributionType': 'Always'} @@ -154,7 +168,9 @@ class PowerMaxCommonTest(test.TestCase): self.assertEqual(ref_model_update, model_update) @mock.patch.object(common.PowerMaxCommon, '_clone_check') - def test_create_volume_from_snapshot(self, mck_clone_chk): + @mock.patch.object(common.PowerMaxCommon, 'get_volume_metadata', + return_value='') + def test_create_volume_from_snapshot(self, mck_meta, mck_clone_chk): ref_model_update = ({'provider_location': six.text_type( deepcopy(self.data.provider_location_snapshot))}) model_update = self.common.create_volume_from_snapshot( @@ -174,7 +190,9 @@ class PowerMaxCommonTest(test.TestCase): ast.literal_eval(model_update['provider_location'])) @mock.patch.object(common.PowerMaxCommon, '_clone_check') - def test_cloned_volume(self, mck_clone_chk): + @mock.patch.object(common.PowerMaxCommon, 'get_volume_metadata', + return_value='') + def test_cloned_volume(self, mck_meta, mck_clone_chk): ref_model_update = ({'provider_location': six.text_type( self.data.provider_location_clone)}) model_update = self.common.create_cloned_volume( @@ -189,11 +207,22 @@ class PowerMaxCommonTest(test.TestCase): mock_delete.assert_called_once_with(self.data.test_volume) @mock.patch.object(common.PowerMaxCommon, '_clone_check') - def test_create_snapshot(self, mck_clone_chk): - ref_model_update = ({'provider_location': six.text_type( - self.data.snap_location)}) + @mock.patch.object( + common.PowerMaxCommon, 'get_snapshot_metadata', + return_value={'snap-meta-key-1': 'snap-meta-value-1', + 'snap-meta-key-2': 'snap-meta-value-2'}) + def test_create_snapshot(self, mck_meta, mck_clone_chk): + ref_model_update = ( + {'provider_location': six.text_type(self.data.snap_location), + 'metadata': {'snap-meta-key-1': 'snap-meta-value-1', + 'snap-meta-key-2': 'snap-meta-value-2', + 'user-meta-key-1': 'user-meta-value-1', + 'user-meta-key-2': 'user-meta-value-2'}}) + snapshot = deepcopy(self.data.test_snapshot_manage) + snapshot.metadata = {'user-meta-key-1': 'user-meta-value-1', + 'user-meta-key-2': 'user-meta-value-2'} model_update = self.common.create_snapshot( - self.data.test_snapshot, self.data.test_volume) + snapshot, self.data.test_volume) self.assertEqual(ref_model_update, model_update) def test_delete_snapshot(self): @@ -1261,15 +1290,25 @@ class PowerMaxCommonTest(test.TestCase): array, target_device_id, clone_name, extra_specs) - def test_manage_existing_success(self): + @mock.patch.object( + common.PowerMaxCommon, 'get_volume_metadata', + return_value={'device-meta-key-1': 'device-meta-value-1', + 'device-meta-key-2': 'device-meta-value-2'}) + def test_manage_existing_success(self, mck_meta): external_ref = {u'source-name': u'00002'} provider_location = {'device_id': u'00002', 'array': u'000197800123'} - ref_update = {'provider_location': six.text_type(provider_location)} + ref_update = {'provider_location': six.text_type(provider_location), + 'metadata': {'device-meta-key-1': 'device-meta-value-1', + 'device-meta-key-2': 'device-meta-value-2', + 'user-meta-key-1': 'user-meta-value-1', + 'user-meta-key-2': 'user-meta-value-2'}} + volume = deepcopy(self.data.test_volume) + volume.metadata = {'user-meta-key-1': 'user-meta-value-1', + 'user-meta-key-2': 'user-meta-value-2'} with mock.patch.object( self.common, '_check_lun_valid_for_cinder_management', return_value=('vol1', 'test_sg')): - model_update = self.common.manage_existing( - self.data.test_volume, external_ref) + model_update = self.common.manage_existing(volume, external_ref) self.assertEqual(ref_update, model_update) @mock.patch.object( @@ -1604,7 +1643,9 @@ class PowerMaxCommonTest(test.TestCase): self.data.workload, volume_name, new_type, extra_specs) @mock.patch.object(masking.PowerMaxMasking, 'remove_and_reset_members') - def test_migrate_volume_success(self, mock_remove): + @mock.patch.object(common.PowerMaxCommon, 'get_volume_metadata', + return_value='') + def test_migrate_volume_success(self, mck_meta, mock_remove): with mock.patch.object(self.rest, 'is_volume_in_storagegroup', return_value=True): device_id = self.data.device_id @@ -1645,8 +1686,10 @@ class PowerMaxCommonTest(test.TestCase): return_value=('Status', 'Data', 'Info')) @mock.patch.object(common.PowerMaxCommon, '_retype_remote_volume', return_value=True) - def test_migrate_in_use_volume(self, mck_remote_retype, mck_setup, - mck_retype, mck_cleanup): + @mock.patch.object(common.PowerMaxCommon, 'get_volume_metadata', + return_value='') + def test_migrate_in_use_volume(self, mck_meta, mck_remote_retype, + mck_setup, mck_retype, mck_cleanup): # Array/Volume info array = self.data.array srp = self.data.srp @@ -1746,9 +1789,11 @@ class PowerMaxCommonTest(test.TestCase): return_value=('Status', 'Data', 'Info')) @mock.patch.object(common.PowerMaxCommon, '_retype_remote_volume', return_value=True) - def test_migrate_volume_attachment_path(self, mck_remote_retype, mck_setup, - mck_inuse_retype, mck_cleanup, - mck_retype): + @mock.patch.object(common.PowerMaxCommon, 'get_volume_metadata', + return_value='') + def test_migrate_volume_attachment_path( + self, mck_meta, mck_remote_retype, mck_setup, mck_inuse_retype, + mck_cleanup, mck_retype): # Array/Volume info array = self.data.array srp = self.data.srp @@ -2109,7 +2154,9 @@ class PowerMaxCommonTest(test.TestCase): return_value=True) @mock.patch.object(volume_utils, 'is_group_a_type', return_value=False) - def test_create_group_from_src_success(self, mock_type, + @mock.patch.object(common.PowerMaxCommon, 'get_volume_metadata', + return_value='') + def test_create_group_from_src_success(self, mck_meta, mock_type, mock_cg_type, mock_info): ref_model_update = {'status': fields.GroupStatus.AVAILABLE} model_update, volumes_model_update = ( @@ -2233,17 +2280,26 @@ class PowerMaxCommonTest(test.TestCase): @mock.patch.object(rest.PowerMaxRest, 'get_volume_snap', return_value={'snap_name': 'snap_name'}) - def test_manage_snapshot_success(self, mock_snap): - snapshot = self.data.test_snapshot_manage + @mock.patch.object( + common.PowerMaxCommon, 'get_snapshot_metadata', + return_value={'snap-meta-key-1': 'snap-meta-value-1', + 'snap-meta-key-2': 'snap-meta-value-2'}) + def test_manage_snapshot_success(self, mck_meta, mock_snap): + snapshot = deepcopy(self.data.test_snapshot_manage) + snapshot.metadata = {'user-meta-key-1': 'user-meta-value-1', + 'user-meta-key-2': 'user-meta-value-2'} existing_ref = {u'source-name': u'test_snap'} updates_response = self.common.manage_existing_snapshot( snapshot, existing_ref) prov_loc = {'source_id': self.data.device_id, 'snap_name': 'OS-%s' % existing_ref['source-name']} - updates = {'display_name': 'my_snap', - 'provider_location': six.text_type(prov_loc)} + 'provider_location': six.text_type(prov_loc), + 'metadata': {'snap-meta-key-1': 'snap-meta-value-1', + 'snap-meta-key-2': 'snap-meta-value-2', + 'user-meta-key-1': 'user-meta-value-1', + 'user-meta-key-2': 'user-meta-value-2'}} self.assertEqual(updates_response, updates) @@ -2814,3 +2870,113 @@ class PowerMaxCommonTest(test.TestCase): exception.VolumeBackendAPIException, self.common._unlink_targets_and_delete_temp_snapvx, array, session, extra_specs) + + @mock.patch.object(rest.PowerMaxRest, '_get_private_volume', + return_value=tpd.PowerMaxData.priv_vol_response_rep) + @mock.patch.object(rest.PowerMaxRest, 'get_array_model_info', + return_value=(tpd.PowerMaxData.array_model, None)) + @mock.patch.object(rest.PowerMaxRest, 'get_rdf_group', + return_value=(tpd.PowerMaxData.rdf_group_details)) + def test_get_volume_metadata_rep(self, mck_rdf, mck_model, mck_priv): + ref_metadata = { + 'DeviceID': self.data.device_id, + 'DeviceLabel': self.data.device_label, 'ArrayID': self.data.array, + 'ArrayModel': self.data.array_model, 'ServiceLevel': 'None', + 'Workload': 'None', 'Emulation': 'FBA', 'Configuration': 'TDEV', + 'CompressionEnabled': 'False', 'ReplicationEnabled': 'True', + 'R2-DeviceID': self.data.device_id2, + 'R2-ArrayID': self.data.remote_array, + 'R2-ArrayModel': self.data.array_model, + 'ReplicationMode': 'Synchronized', + 'RDFG-Label': self.data.rdf_group_name, + 'R1-RDFG': 1, 'R2-RDFG': 1} + array = self.data.array + device_id = self.data.device_id + act_metadata = self.common.get_volume_metadata(array, device_id) + self.assertEqual(ref_metadata, act_metadata) + + @mock.patch.object(rest.PowerMaxRest, '_get_private_volume', + return_value=tpd.PowerMaxData.priv_vol_response_no_rep) + @mock.patch.object(rest.PowerMaxRest, 'get_array_model_info', + return_value=(tpd.PowerMaxData.array_model, None)) + def test_get_volume_metadata_no_rep(self, mck_model, mck_priv): + ref_metadata = { + 'DeviceID': self.data.device_id, + 'DeviceLabel': self.data.device_label, 'ArrayID': self.data.array, + 'ArrayModel': self.data.array_model, 'ServiceLevel': 'None', + 'Workload': 'None', 'Emulation': 'FBA', 'Configuration': 'TDEV', + 'CompressionEnabled': 'False', 'ReplicationEnabled': 'False'} + array = self.data.array + device_id = self.data.device_id + act_metadata = self.common.get_volume_metadata(array, device_id) + self.assertEqual(ref_metadata, act_metadata) + + @mock.patch.object(rest.PowerMaxRest, 'get_volume_snap_info', + return_value=tpd.PowerMaxData.priv_snap_response) + def test_get_snapshot_metadata(self, mck_snap): + array = self.data.array + device_id = self.data.device_id + device_label = self.data.managed_snap_id + snap_name = self.data.test_snapshot_snap_name + ref_metadata = {'SnapshotLabel': snap_name, + 'SourceDeviceID': device_id, + 'SourceDeviceLabel': device_label} + + act_metadata = self.common.get_snapshot_metadata( + array, device_id, snap_name) + self.assertEqual(ref_metadata, act_metadata) + + def test_update_metadata(self): + model_update = {'provider_location': six.text_type( + self.data.provider_location)} + ref_model_update = ( + {'provider_location': six.text_type(self.data.provider_location), + 'metadata': {'device-meta-key-1': 'device-meta-value-1', + 'device-meta-key-2': 'device-meta-value-2', + 'user-meta-key-1': 'user-meta-value-1', + 'user-meta-key-2': 'user-meta-value-2'}}) + + existing_metadata = {'user-meta-key-1': 'user-meta-value-1', + 'user-meta-key-2': 'user-meta-value-2'} + + object_metadata = {'device-meta-key-1': 'device-meta-value-1', + 'device-meta-key-2': 'device-meta-value-2'} + + model_update = self.common.update_metadata( + model_update, existing_metadata, object_metadata) + self.assertEqual(ref_model_update, model_update) + + def test_update_metadata_no_model(self): + model_update = None + ref_model_update = ( + {'metadata': {'device-meta-key-1': 'device-meta-value-1', + 'device-meta-key-2': 'device-meta-value-2', + 'user-meta-key-1': 'user-meta-value-1', + 'user-meta-key-2': 'user-meta-value-2'}}) + + existing_metadata = {'user-meta-key-1': 'user-meta-value-1', + 'user-meta-key-2': 'user-meta-value-2'} + + object_metadata = {'device-meta-key-1': 'device-meta-value-1', + 'device-meta-key-2': 'device-meta-value-2'} + + model_update = self.common.update_metadata( + model_update, existing_metadata, object_metadata) + self.assertEqual(ref_model_update, model_update) + + def test_update_metadata_no_existing_metadata(self): + model_update = {'provider_location': six.text_type( + self.data.provider_location)} + ref_model_update = ( + {'provider_location': six.text_type(self.data.provider_location), + 'metadata': {'device-meta-key-1': 'device-meta-value-1', + 'device-meta-key-2': 'device-meta-value-2'}}) + + existing_metadata = None + + object_metadata = {'device-meta-key-1': 'device-meta-value-1', + 'device-meta-key-2': 'device-meta-value-2'} + + model_update = self.common.update_metadata( + model_update, existing_metadata, object_metadata) + self.assertEqual(ref_model_update, model_update) diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py index 0e2caa5cd0c..c1051a9dfaf 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_replication.py @@ -125,8 +125,11 @@ class PowerMaxReplicationTest(test.TestCase): return_value=({ 'replication_driver_data': tpd.PowerMaxData.test_volume.replication_driver_data}, {})) - def test_create_replicated_volume(self, mock_rep, mock_add, mock_match, - mock_check, mock_get, mock_cg): + @mock.patch.object(common.PowerMaxCommon, 'get_volume_metadata', + return_value='') + def test_create_replicated_volume( + self, mck_meta, mock_rep, mock_add, mock_match, mock_check, + mock_get, mock_cg): extra_specs = deepcopy(self.extra_specs) extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f vol_identifier = self.utils.get_volume_element_name( @@ -149,8 +152,10 @@ class PowerMaxReplicationTest(test.TestCase): return_value=True) @mock.patch.object(rest.PowerMaxRest, 'get_rdf_group_number', side_effect=['4', None]) + @mock.patch.object(common.PowerMaxCommon, 'get_volume_metadata', + return_value='') def test_create_replicated_vol_side_effect( - self, mock_rdf_no, mock_rep_enabled, mock_rep_vol): + self, mck_meta, mock_rdf_no, mock_rep_enabled, mock_rep_vol): self.common.rep_config = self.utils.get_replication_config( [self.replication_device]) ref_rep_data = {'array': six.text_type(self.data.remote_array), @@ -158,7 +163,8 @@ class PowerMaxReplicationTest(test.TestCase): ref_model_update = { 'provider_location': six.text_type( self.data.test_volume.provider_location), - 'replication_driver_data': six.text_type(ref_rep_data)} + 'replication_driver_data': six.text_type(ref_rep_data), + 'metadata': ''} model_update = self.common.create_volume(self.data.test_volume) self.assertEqual(ref_model_update, model_update) self.assertRaises(exception.VolumeBackendAPIException, @@ -166,7 +172,9 @@ class PowerMaxReplicationTest(test.TestCase): self.data.test_volume) @mock.patch.object(common.PowerMaxCommon, '_clone_check') - def test_create_cloned_replicated_volume(self, mck_clone): + @mock.patch.object(common.PowerMaxCommon, 'get_volume_metadata', + return_value='') + def test_create_cloned_replicated_volume(self, mck_meta, mck_clone): extra_specs = deepcopy(self.extra_specs) extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f with mock.patch.object(self.common, '_replicate_volume', @@ -179,7 +187,9 @@ class PowerMaxReplicationTest(test.TestCase): self.data.test_clone_volume.name, volume_dict, extra_specs) @mock.patch.object(common.PowerMaxCommon, '_clone_check') - def test_create_replicated_volume_from_snap(self, mck_clone): + @mock.patch.object(common.PowerMaxCommon, 'get_volume_metadata', + return_value='') + def test_create_replicated_volume_from_snap(self, mck_meta, mck_clone): extra_specs = deepcopy(self.extra_specs) extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f with mock.patch.object(self.common, '_replicate_volume', @@ -343,7 +353,10 @@ class PowerMaxReplicationTest(test.TestCase): return_value=({}, {})) @mock.patch.object(rest.PowerMaxRest, 'get_array_model_info', return_value=('VMAX250F', False)) - def test_manage_existing_is_replicated(self, mock_model, mock_rep): + @mock.patch.object(common.PowerMaxCommon, 'get_volume_metadata', + return_value='') + def test_manage_existing_is_replicated(self, mck_meta, mock_model, + mock_rep): extra_specs = deepcopy(self.extra_specs) extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f external_ref = {u'source-name': u'00002'} @@ -708,7 +721,9 @@ class PowerMaxReplicationTest(test.TestCase): rep_config, array_info) self.assertEqual(ref_info, secondary_info) - def test_replicate_group(self): + @mock.patch.object(common.PowerMaxCommon, 'get_volume_metadata', + return_value='') + def test_replicate_group(self, mck_meta): volume_model_update = { 'id': self.data.test_volume.id, 'provider_location': self.data.test_volume.provider_location} @@ -721,7 +736,8 @@ class PowerMaxReplicationTest(test.TestCase): 'id': self.data.test_volume.id, 'provider_location': self.data.test_volume.provider_location, 'replication_driver_data': ref_rep_data, - 'replication_status': fields.ReplicationStatus.ENABLED} + 'replication_status': fields.ReplicationStatus.ENABLED, + 'metadata': ''} # Decode string representations of dicts into dicts, because # the string representations are randomly ordered and therefore @@ -934,9 +950,11 @@ class PowerMaxReplicationTest(test.TestCase): '_remove_vol_and_cleanup_replication') @mock.patch.object(utils.PowerMaxUtils, 'is_replication_enabled', side_effect=[False, True, True, False, True, True]) - def test_migrate_volume_replication(self, mock_re, mock_rm_rep, - mock_setup, mock_retype, - mock_rm, mock_rt): + @mock.patch.object(common.PowerMaxCommon, 'get_volume_metadata', + return_value='') + def test_migrate_volume_replication( + self, mck_meta, mock_re, mock_rm_rep, mock_setup, mock_retype, + mock_rm, mock_rt): new_type = {'extra_specs': {}} for x in range(0, 3): success, model_update = self.common._migrate_volume( diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_utils.py b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_utils.py index 1bfef219557..467d5184273 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_utils.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powermax/test_powermax_utils.py @@ -540,3 +540,23 @@ class PowerMaxUtilsTest(test.TestCase): self.assertRaises(exception.VolumeBackendAPIException, self.utils.compare_cylinders, source_cylinders, target_cylinders) + + def test_get_grp_volume_model_update(self): + volume = self.data.test_volume + volume_dict = self.data.provider_location + group_id = self.data.gvg_group_id + metadata = self.data.volume_metadata + + ref_model_update_meta = { + 'id': volume.id, 'status': 'available', 'metadata': metadata, + 'provider_location': six.text_type(volume_dict)} + act_model_update_meta = self.utils.get_grp_volume_model_update( + volume, volume_dict, group_id, metadata) + self.assertEqual(ref_model_update_meta, act_model_update_meta) + + ref_model_update_no_meta = { + 'id': volume.id, 'status': 'available', + 'provider_location': six.text_type(volume_dict)} + act_model_update_no_meta = self.utils.get_grp_volume_model_update( + volume, volume_dict, group_id) + self.assertEqual(ref_model_update_no_meta, act_model_update_no_meta) diff --git a/cinder/volume/drivers/dell_emc/powermax/common.py b/cinder/volume/drivers/dell_emc/powermax/common.py index 5a57ea8914d..a7b27c13274 100644 --- a/cinder/volume/drivers/dell_emc/powermax/common.py +++ b/cinder/volume/drivers/dell_emc/powermax/common.py @@ -449,8 +449,12 @@ class PowerMaxCommon(object): group_name = self._add_new_volume_to_volume_group( volume, volume_dict['device_id'], volume_name, extra_specs, rep_driver_data) + model_update.update( {'provider_location': six.text_type(volume_dict)}) + model_update = self.update_metadata( + model_update, volume.metadata, self.get_volume_metadata( + volume_dict['array'], volume_dict['device_id'])) self.volume_metadata.capture_create_volume( volume_dict['device_id'], volume, group_name, group_id, @@ -515,7 +519,9 @@ class PowerMaxCommon(object): model_update.update( {'provider_location': six.text_type(clone_dict)}) - + model_update = self.update_metadata( + model_update, volume.metadata, self.get_volume_metadata( + clone_dict['array'], clone_dict['device_id'])) self.volume_metadata.capture_create_volume( clone_dict['device_id'], volume, None, None, extra_specs, rep_info_dict, 'createFromSnapshot', @@ -543,6 +549,9 @@ class PowerMaxCommon(object): model_update.update( {'provider_location': six.text_type(clone_dict)}) + model_update = self.update_metadata( + model_update, clone_volume.metadata, self.get_volume_metadata( + clone_dict['array'], clone_dict['device_id'])) self.volume_metadata.capture_create_volume( clone_dict['device_id'], clone_volume, None, None, extra_specs, rep_info_dict, 'createFromVolume', @@ -599,9 +608,19 @@ class PowerMaxCommon(object): extra_specs = self._initial_setup(volume) snapshot_dict = self._create_cloned_volume( snapshot, volume, extra_specs, is_snapshot=True) + + model_update = { + 'provider_location': six.text_type(snapshot_dict)} + model_update = self.update_metadata( + model_update, snapshot.metadata, self.get_snapshot_metadata( + extra_specs['array'], snapshot_dict['source_id'], + snapshot_dict['snap_name'])) + if snapshot.metadata: + model_update['metadata'].update(snapshot.metadata) + self.volume_metadata.capture_snapshot_info( volume, extra_specs, 'createSnapshot', snapshot_dict['snap_name']) - model_update = {'provider_location': six.text_type(snapshot_dict)} + return model_update def delete_snapshot(self, snapshot, volume): @@ -2463,6 +2482,10 @@ class PowerMaxCommon(object): raise exception.VolumeBackendAPIException( message=exception_message) + model_update = self.update_metadata( + model_update, volume.metadata, self.get_volume_metadata( + array, device_id)) + self.volume_metadata.capture_manage_existing( volume, rep_info_dict, device_id, extra_specs) @@ -2683,9 +2706,12 @@ class PowerMaxCommon(object): message=exception_message) prov_loc = {'source_id': device_id, 'snap_name': snap_backend_name} - - updates = {'display_name': snap_display_name, - 'provider_location': six.text_type(prov_loc)} + model_update = { + 'display_name': snap_display_name, + 'provider_location': six.text_type(prov_loc)} + model_update = self.update_metadata( + model_update, snapshot.metadata, self.get_snapshot_metadata( + array, device_id, snap_backend_name)) LOG.info("Managing SnapVX Snapshot %(snap_name)s of source " "volume %(device_id)s, OpenStack Snapshot display name: " @@ -2693,7 +2719,7 @@ class PowerMaxCommon(object): 'snap_name': snap_name, 'device_id': device_id, 'snap_display_name': snap_display_name}) - return updates + return model_update def manage_existing_snapshot_get_size(self, snapshot): """Return the size of the source volume for manage-existing-snapshot. @@ -3176,6 +3202,10 @@ class PowerMaxCommon(object): model_update = { 'replication_status': rep_status, 'replication_driver_data': six.text_type(rdf_dict)} + model_update = self.update_metadata( + model_update, volume.metadata, + self.get_volume_metadata(array, device_id)) + return True, model_update try: @@ -3198,6 +3228,10 @@ class PowerMaxCommon(object): rep_mode, is_rep_enabled, target_extra_specs) if success: + model_update = self.update_metadata( + model_update, volume.metadata, + self.get_volume_metadata(array, device_id)) + self.volume_metadata.capture_retype_info( volume, device_id, array, srp, target_slo, target_workload, target_sg_name, is_rep_enabled, rep_mode, @@ -4411,11 +4445,18 @@ class PowerMaxCommon(object): for snapshot in snapshots: src_dev_id = self._get_src_device_id_for_group_snap(snapshot) + extra_specs = self._initial_setup(snapshot.volume) + array = extra_specs['array'] + snapshots_model_update.append( {'id': snapshot.id, 'provider_location': six.text_type( {'source_id': src_dev_id, 'snap_name': snap_name}), 'status': fields.SnapshotStatus.AVAILABLE}) + snapshots_model_update = self.update_metadata( + snapshots_model_update, snapshot.metadata, + self.get_snapshot_metadata( + array, src_dev_id, snap_name)) model_update = {'status': fields.GroupStatus.AVAILABLE} return model_update, snapshots_model_update @@ -4851,7 +4892,10 @@ class PowerMaxCommon(object): (device_id, extra_specs, volume)) volumes_model_update.append( self.utils.get_grp_volume_model_update( - volume, volume_dict, group_id)) + volume, volume_dict, group_id, + meta=self.get_volume_metadata(volume_dict['array'], + volume_dict['device_id']))) + return volumes_model_update, rollback_dict, list_volume_pairs def _get_clone_vol_info(self, volume, source_vols, snapshots): @@ -4932,6 +4976,7 @@ class PowerMaxCommon(object): :param extra_specs: the extra specs :return: volumes_model_update """ + ret_volumes_model_update = [] rdf_group_no, remote_array = self.get_rdf_details(array) self.rest.replicate_group( array, group_name, rdf_group_no, remote_array, extra_specs) @@ -4953,7 +4998,11 @@ class PowerMaxCommon(object): volume_model_update.update( {'replication_driver_data': six.text_type(rep_update), 'replication_status': fields.ReplicationStatus.ENABLED}) - return volumes_model_update + volume_model_update = self.update_metadata( + volume_model_update, None, self.get_volume_metadata( + array, src_device_id)) + ret_volumes_model_update.append(volume_model_update) + return ret_volumes_model_update def enable_replication(self, context, group, volumes): """Enable replication for a group. @@ -5279,3 +5328,89 @@ class PowerMaxCommon(object): LOG.error(exception_message) raise exception.VolumeBackendAPIException( message=exception_message) + + def update_metadata( + self, model_update, existing_metadata, object_metadata): + """Update volume metadata in model_update. + + :param model_update: existing model + :param existing_metadata: existing metadata + :param object_metadata: object metadata + :returns: dict -- updated model + """ + if model_update: + if 'metadata' in model_update: + model_update['metadata'].update(object_metadata) + else: + model_update.update({'metadata': object_metadata}) + else: + model_update = {} + model_update.update({'metadata': object_metadata}) + + if existing_metadata: + model_update['metadata'].update(existing_metadata) + + return model_update + + def get_volume_metadata(self, array, device_id): + """Get volume metadata for model_update. + + :param array: the array ID + :param device_id: the device ID + :returns: dict -- volume metadata + """ + vol_info = self.rest._get_private_volume(array, device_id) + vol_header = vol_info['volumeHeader'] + array_model, __ = self.rest.get_array_model_info(array) + sl = (vol_header['serviceLevel'] if + vol_header.get('serviceLevel') else 'None') + wl = vol_header['workload'] if vol_header.get('workload') else 'None' + ce = 'True' if vol_header.get('compressionEnabled') else 'False' + + metadata = {'DeviceID': device_id, + 'DeviceLabel': vol_header['userDefinedIdentifier'], + 'ArrayID': array, 'ArrayModel': array_model, + 'ServiceLevel': sl, 'Workload': wl, + 'Emulation': vol_header['emulationType'], + 'Configuration': vol_header['configuration'], + 'CompressionEnabled': ce} + + is_rep_enabled = vol_info['rdfInfo']['RDF'] + if is_rep_enabled: + rdf_info = vol_info['rdfInfo'] + rdf_session = rdf_info['RDFSession'][0] + rdf_num = rdf_session['SRDFGroupNumber'] + rdfg_info = self.rest.get_rdf_group(array, str(rdf_num)) + r2_array_model, __ = self.rest.get_array_model_info( + rdf_session['remoteSymmetrixID']) + + metadata.update( + {'ReplicationEnabled': 'True', + 'R2-DeviceID': rdf_session['remoteDeviceID'], + 'R2-ArrayID': rdf_session['remoteSymmetrixID'], + 'R2-ArrayModel': r2_array_model, + 'ReplicationMode': rdf_session['SRDFReplicationMode'], + 'RDFG-Label': rdfg_info['label'], + 'R1-RDFG': rdf_session['SRDFGroupNumber'], + 'R2-RDFG': rdf_session['SRDFRemoteGroupNumber']}) + else: + metadata['ReplicationEnabled'] = 'False' + + return metadata + + def get_snapshot_metadata(self, array, device_id, snap_name): + """Get snapshot metadata for model_update. + + :param array: the array ID + :param device_id: the device ID + :param snap_name: the snapshot name + :returns: dict -- volume metadata + """ + snap_info = self.rest.get_volume_snap_info(array, device_id) + device_name = snap_info['deviceName'] + device_label = device_name.split(':')[1] + metadata = {'SnapshotLabel': snap_name, + 'SourceDeviceID': device_id, + 'SourceDeviceLabel': device_label} + + return metadata diff --git a/cinder/volume/drivers/dell_emc/powermax/fc.py b/cinder/volume/drivers/dell_emc/powermax/fc.py index 85496046197..ea057ee31c5 100644 --- a/cinder/volume/drivers/dell_emc/powermax/fc.py +++ b/cinder/volume/drivers/dell_emc/powermax/fc.py @@ -114,6 +114,7 @@ class PowerMaxFCDriver(san.SanDriver, driver.FibreChannelDriver): - Support for Metro ODE (bp/powermax-metro-ode) - Removal of san_rest_port from PowerMax cinder.conf config - SnapVX noCopy mode enabled for all links + - Volume/Snapshot backed metadata inclusion """ VERSION = "4.1.0" diff --git a/cinder/volume/drivers/dell_emc/powermax/iscsi.py b/cinder/volume/drivers/dell_emc/powermax/iscsi.py index 47cdc2c976c..9134981b766 100644 --- a/cinder/volume/drivers/dell_emc/powermax/iscsi.py +++ b/cinder/volume/drivers/dell_emc/powermax/iscsi.py @@ -119,6 +119,7 @@ class PowerMaxISCSIDriver(san.SanISCSIDriver): - Support for Metro ODE (bp/powermax-metro-ode) - Removal of san_rest_port from PowerMax cinder.conf config - SnapVX noCopy mode enabled for all links + - Volume/Snapshot backed metadata inclusion """ VERSION = "4.1.0" diff --git a/cinder/volume/drivers/dell_emc/powermax/utils.py b/cinder/volume/drivers/dell_emc/powermax/utils.py index a77f7e4adf6..5507ca47afe 100644 --- a/cinder/volume/drivers/dell_emc/powermax/utils.py +++ b/cinder/volume/drivers/dell_emc/powermax/utils.py @@ -508,17 +508,20 @@ class PowerMaxUtils(object): return volume_model_updates @staticmethod - def get_grp_volume_model_update(volume, volume_dict, group_id): + def get_grp_volume_model_update(volume, volume_dict, group_id, meta=None): """Create and return the volume model update on creation. :param volume: volume object :param volume_dict: the volume dict :param group_id: consistency group id + :param meta: the volume metadata :returns: model_update """ LOG.info("Updating status for group: %(id)s.", {'id': group_id}) model_update = ({'id': volume.id, 'status': 'available', 'provider_location': six.text_type(volume_dict)}) + if meta: + model_update['metadata'] = meta return model_update @staticmethod diff --git a/releasenotes/notes/powermax-vol-metadata-acd2555818d25b72.yaml b/releasenotes/notes/powermax-vol-metadata-acd2555818d25b72.yaml new file mode 100644 index 00000000000..1304328d6e2 --- /dev/null +++ b/releasenotes/notes/powermax-vol-metadata-acd2555818d25b72.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + All volumes and snapshots created using the PowerMax for Cinder driver now + have additional metadata included pertaining to the details of the asset on + the backend storage array.