PowerMax Driver - Volume & Snapshot Metadata

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.

Change-Id: I35b8685e127605d19f2316802d44b7373a673e98
This commit is contained in:
michael-mcaleer 2019-07-26 16:27:29 +00:00 committed by Helen Walsh
parent fb2791bb85
commit 3508d01a7e
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)
@ -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)

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.