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:
parent
fb2791bb85
commit
3508d01a7e
@ -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}
|
||||
|
@ -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)
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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"
|
||||
|
@ -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"
|
||||
|
@ -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
|
||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user