Merge "PowerMax Driver - Volume & Snapshot Metadata"
This commit is contained in:
commit
d7e1a68c8c
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
|
|
|
@ -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