Merge "PowerMax Driver - Volume & Snapshot Metadata"

This commit is contained in:
Zuul 2019-09-18 21:10:52 +00:00 committed by Gerrit Code Review
commit d7e1a68c8c
9 changed files with 452 additions and 47 deletions

View File

@ -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}

View File

@ -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)
@ -2821,3 +2877,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)

View File

@ -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(

View File

@ -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)

View File

@ -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

View File

@ -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"

View File

@ -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"

View File

@ -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

View File

@ -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.