PowerMax Driver - Metro ODE Support
PowerMax for Cinder now supports extending in-use Metro RDF enabled volumes using Online Device Expansion. This submission implements this new feature. Change-Id: I5342cbd64d33c38a68c92e4e56cbfce8aaa621c3 Implements: blueprint powermax-metro-ode
This commit is contained in:
parent
e2f52da0d3
commit
fd8b74b61c
@ -77,6 +77,7 @@ class PowerMaxData(object):
|
||||
volume_id = '2b06255d-f5f0-4520-a953-b029196add6a'
|
||||
no_slo_sg_name = 'OS-HostX-No_SLO-OS-fibre-PG'
|
||||
temp_snapvx = 'temp-00001-snapshot_for_clone'
|
||||
next_gen_ucode = 5978
|
||||
|
||||
# connector info
|
||||
wwpn1 = '123456789012345'
|
||||
@ -294,6 +295,14 @@ class PowerMaxData(object):
|
||||
rep_extra_specs5 = deepcopy(rep_extra_specs2)
|
||||
rep_extra_specs5['target_array_model'] = 'VMAX250F'
|
||||
|
||||
rep_extra_specs_ode = deepcopy(rep_extra_specs2)
|
||||
rep_extra_specs_ode['array'] = array
|
||||
rep_extra_specs_ode.pop('rep_mode')
|
||||
rep_extra_specs_ode['mode'] = 'Metro'
|
||||
|
||||
rep_extra_specs_legacy = deepcopy(rep_extra_specs_ode)
|
||||
rep_extra_specs_legacy['mode'] = 'Synchronous'
|
||||
|
||||
test_volume_type_1 = volume_type.VolumeType(
|
||||
id='2b06255d-f5f0-4520-a953-b029196add6a', name='abc',
|
||||
extra_specs=extra_specs)
|
||||
|
@ -58,29 +58,28 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
self.utils.get_volumetype_extra_specs = (
|
||||
mock.Mock(return_value=self.data.vol_type_extra_specs))
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'set_rest_credentials')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_get_slo_workload_combinations',
|
||||
return_value=[])
|
||||
@mock.patch.object(
|
||||
common.PowerMaxCommon, 'get_attributes_from_cinder_config',
|
||||
return_value=[])
|
||||
def test_gather_info_no_opts(self, mock_parse, mock_combo, mock_rest):
|
||||
configuration = tpfo.FakeConfiguration(
|
||||
None, 'config_group', None, None)
|
||||
fc.PowerMaxFCDriver(configuration=configuration)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_array_ucode_version',
|
||||
return_value=tpd.PowerMaxData.next_gen_ucode)
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_array_model_info',
|
||||
return_value=('PowerMax 2000', True))
|
||||
@mock.patch.object(rest.PowerMaxRest, 'set_rest_credentials')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_get_slo_workload_combinations',
|
||||
return_value=[])
|
||||
@mock.patch.object(
|
||||
common.PowerMaxCommon, 'get_attributes_from_cinder_config',
|
||||
return_value=tpd.PowerMaxData.array_info_wl)
|
||||
def test_gather_info_next_gen(self, mock_parse, mock_combo, mock_rest,
|
||||
mock_nextgen):
|
||||
@mock.patch.object(common.PowerMaxCommon,
|
||||
'get_attributes_from_cinder_config',
|
||||
side_effect=[[], tpd.PowerMaxData.array_info_wl])
|
||||
def test_gather_info_tests(self, mck_parse, mck_combo, mck_rest,
|
||||
mck_nextgen, mck_ucode):
|
||||
|
||||
# Use-Case 1: Gather info no-opts
|
||||
configuration = tpfo.FakeConfiguration(
|
||||
None, 'config_group', None, None)
|
||||
fc.PowerMaxFCDriver(configuration=configuration)
|
||||
|
||||
# Use-Case 2: Gather info next-gen with ucode/version
|
||||
self.common._gather_info()
|
||||
self.assertTrue(self.common.next_gen)
|
||||
self.assertEqual(self.common.ucode_level, self.data.next_gen_ucode)
|
||||
|
||||
def test_get_slo_workload_combinations_powermax(self):
|
||||
array_info = self.common.get_attributes_from_cinder_config()
|
||||
@ -445,28 +444,42 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
mock_unmap.assert_called_once_with(
|
||||
volume, connector)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'is_next_gen_array',
|
||||
return_value=True)
|
||||
@mock.patch.object(common.PowerMaxCommon, '_sync_check')
|
||||
@mock.patch.object(provision.PowerMaxProvision, 'extend_volume')
|
||||
def test_extend_volume_success(self, mock_extend, mock_sync, mock_newgen):
|
||||
@mock.patch.object(common.PowerMaxCommon, '_array_ode_capabilities_check',
|
||||
return_value=[True] * 4)
|
||||
@mock.patch.object(common.PowerMaxCommon, '_extend_vol_validation_checks')
|
||||
def test_extend_vol_no_rep_success(self, mck_val_chk, mck_ode_chk,
|
||||
mck_extend):
|
||||
volume = self.data.test_volume
|
||||
array = self.data.array
|
||||
device_id = self.data.device_id
|
||||
new_size = self.data.test_volume.size
|
||||
ref_extra_specs = deepcopy(self.data.extra_specs_intervals_set)
|
||||
ref_extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f
|
||||
with mock.patch.object(self.rest, 'is_vol_in_rep_session',
|
||||
side_effect=[(False, False, None),
|
||||
(False, True, None)]):
|
||||
self.common.extend_volume(volume, new_size)
|
||||
mck_extend.assert_called_once_with(
|
||||
array, device_id, new_size, ref_extra_specs, None)
|
||||
|
||||
@mock.patch.object(provision.PowerMaxProvision, 'extend_volume')
|
||||
@mock.patch.object(common.PowerMaxCommon, 'get_rdf_details',
|
||||
return_value=(10, None))
|
||||
@mock.patch.object(common.PowerMaxCommon, '_array_ode_capabilities_check',
|
||||
return_value=[True] * 4)
|
||||
@mock.patch.object(common.PowerMaxCommon, '_extend_vol_validation_checks')
|
||||
def test_extend_vol_rep_success(self, mck_val_chk, mck_ode_chk,
|
||||
mck_get_rdf, mck_extend):
|
||||
volume = self.data.test_volume
|
||||
array = self.data.array
|
||||
device_id = self.data.device_id
|
||||
new_size = self.data.test_volume.size
|
||||
ref_extra_specs = deepcopy(self.data.rep_extra_specs_ode)
|
||||
with mock.patch.object(self.common, '_initial_setup',
|
||||
return_value=self.data.rep_extra_specs_ode):
|
||||
self.common.next_gen = True
|
||||
self.common.rep_config = deepcopy(ref_extra_specs)
|
||||
self.common.extend_volume(volume, new_size)
|
||||
mock_extend.assert_called_once_with(
|
||||
array, device_id, new_size, ref_extra_specs)
|
||||
# Success, with snapshot, on new VMAX array
|
||||
mock_extend.reset_mock()
|
||||
self.common.extend_volume(volume, new_size)
|
||||
mock_extend.assert_called_once_with(
|
||||
array, device_id, new_size, ref_extra_specs)
|
||||
mck_extend.assert_called_with(
|
||||
array, device_id, new_size, ref_extra_specs, 10)
|
||||
|
||||
def test_extend_volume_failed_snap_src(self):
|
||||
volume = self.data.test_volume
|
||||
@ -2647,3 +2660,124 @@ class PowerMaxCommonTest(test.TestCase):
|
||||
def test_retest_primary_u4p(self, mock_primary_u4p, mock_request):
|
||||
self.common.retest_primary_u4p()
|
||||
self.assertFalse(self.rest.u4p_in_failover)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'is_vol_in_rep_session',
|
||||
return_value=(None, False, None))
|
||||
@mock.patch.object(common.PowerMaxCommon, '_sync_check')
|
||||
def test_extend_vol_validation_checks_success(self, mck_sync, mck_rep):
|
||||
volume = self.data.test_volume
|
||||
array = self.data.array
|
||||
device_id = self.data.device_id
|
||||
new_size = self.data.test_volume.size + 1
|
||||
extra_specs = deepcopy(self.data.extra_specs)
|
||||
self.common._extend_vol_validation_checks(
|
||||
array, device_id, volume.name, extra_specs, volume.size, new_size)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'is_vol_in_rep_session',
|
||||
return_value=(None, False, None))
|
||||
@mock.patch.object(common.PowerMaxCommon, '_sync_check')
|
||||
def test_extend_vol_val_check_no_device(self, mck_sync, mck_rep):
|
||||
volume = self.data.test_volume
|
||||
array = self.data.array
|
||||
device_id = None
|
||||
new_size = self.data.test_volume.size + 1
|
||||
extra_specs = deepcopy(self.data.extra_specs)
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
self.common._extend_vol_validation_checks,
|
||||
array, device_id, volume.name, extra_specs, volume.size, new_size)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'is_vol_in_rep_session',
|
||||
return_value=(None, True, None))
|
||||
@mock.patch.object(common.PowerMaxCommon, '_sync_check')
|
||||
def test_extend_vol_val_check_snap_src(self, mck_sync, mck_rep):
|
||||
volume = self.data.test_volume
|
||||
array = self.data.array
|
||||
device_id = self.data.device_id
|
||||
new_size = self.data.test_volume.size + 1
|
||||
extra_specs = deepcopy(self.data.extra_specs)
|
||||
self.common.next_gen = False
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
self.common._extend_vol_validation_checks,
|
||||
array, device_id, volume.name, extra_specs, volume.size, new_size)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'is_vol_in_rep_session',
|
||||
return_value=(None, False, None))
|
||||
@mock.patch.object(common.PowerMaxCommon, '_sync_check')
|
||||
def test_extend_vol_val_check_wrong_size(self, mck_sync, mck_rep):
|
||||
volume = self.data.test_volume
|
||||
array = self.data.array
|
||||
device_id = self.data.device_id
|
||||
new_size = volume.size - 1
|
||||
extra_specs = deepcopy(self.data.extra_specs)
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
self.common._extend_vol_validation_checks,
|
||||
array, device_id, volume.name, extra_specs, volume.size, new_size)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'is_next_gen_array',
|
||||
return_value=True)
|
||||
@mock.patch.object(
|
||||
rest.PowerMaxRest, 'get_array_ucode_version',
|
||||
return_value=tpd.PowerMaxData.powermax_model_details['ucode'])
|
||||
@mock.patch.object(common.PowerMaxCommon, 'get_rdf_details',
|
||||
return_value=(10, tpd.PowerMaxData.remote_array))
|
||||
def test_array_ode_capabilities_check(self, mck_rdf, mck_ucode, mck_gen):
|
||||
|
||||
array = self.data.powermax_model_details['symmetrixId']
|
||||
self.common.ucode_level = self.data.powermax_model_details['ucode']
|
||||
self.common.next_gen = True
|
||||
|
||||
r1, r1_ode, r2, r2_ode = self.common._array_ode_capabilities_check(
|
||||
array, True)
|
||||
self.assertTrue(r1)
|
||||
self.assertTrue(r1_ode)
|
||||
self.assertTrue(r2)
|
||||
self.assertTrue(r2_ode)
|
||||
|
||||
@mock.patch.object(common.PowerMaxCommon,
|
||||
'_add_new_volume_to_volume_group')
|
||||
@mock.patch.object(common.PowerMaxCommon, 'setup_volume_replication')
|
||||
@mock.patch.object(provision.PowerMaxProvision, 'extend_volume')
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_size_of_device_on_array',
|
||||
return_value=tpd.PowerMaxData.test_volume.size)
|
||||
@mock.patch.object(provision.PowerMaxProvision, 'break_rdf_relationship')
|
||||
@mock.patch.object(masking.PowerMaxMasking, 'remove_and_reset_members')
|
||||
@mock.patch.object(
|
||||
common.PowerMaxCommon, '_get_replication_extra_specs',
|
||||
return_value=tpd.PowerMaxData.rep_extra_specs)
|
||||
@mock.patch.object(
|
||||
common.PowerMaxCommon, 'get_remote_target_device',
|
||||
return_value=(
|
||||
tpd.PowerMaxData.device_id2, tpd.PowerMaxData.remote_array,
|
||||
tpd.PowerMaxData.rdf_group_vol_details['localRdfGroupNumber'],
|
||||
tpd.PowerMaxData.rdf_group_vol_details['localVolumeState'],
|
||||
tpd.PowerMaxData.rdf_group_vol_details['rdfpairState']))
|
||||
def test_extend_legacy_replicated_vol(self, mck_get_tgt, mck_rdf_specs,
|
||||
mck_reset, mck_break_rdf, mck_size,
|
||||
mck_extend, mck_set_rep, mck_add):
|
||||
|
||||
volume = self.data.test_volume_group_member
|
||||
array = self.data.array
|
||||
device_id = self.data.device_id
|
||||
new_size = volume.size + 1
|
||||
extra_specs = deepcopy(self.data.extra_specs)
|
||||
|
||||
self.common._extend_legacy_replicated_vol(
|
||||
array, volume, device_id, volume.name, new_size, extra_specs)
|
||||
|
||||
@mock.patch.object(
|
||||
common.PowerMaxCommon, 'get_remote_target_device',
|
||||
return_value=(None, None, None, None, None))
|
||||
def test_extend_legacy_replicated_vol_fail(self, mck_get_tgt):
|
||||
|
||||
volume = self.data.test_volume_group_member
|
||||
array = self.data.array
|
||||
device_id = self.data.device_id
|
||||
new_size = volume.size + 1
|
||||
extra_specs = deepcopy(self.data.extra_specs)
|
||||
self.assertRaises(
|
||||
exception.VolumeBackendAPIException,
|
||||
self.common._extend_vol_validation_checks,
|
||||
array, device_id, volume.name, extra_specs, volume.size, new_size)
|
||||
|
@ -260,6 +260,7 @@ class PowerMaxProvisionTest(test.TestCase):
|
||||
device_id = self.data.device_id
|
||||
new_size = '3'
|
||||
extra_specs = self.data.extra_specs
|
||||
rdfg_num = self.data.rdf_group_no
|
||||
with mock.patch.object(self.provision.rest, 'extend_volume'
|
||||
) as mock_ex:
|
||||
self.provision.extend_volume(array, device_id, new_size,
|
||||
@ -269,9 +270,9 @@ class PowerMaxProvisionTest(test.TestCase):
|
||||
mock_ex.reset_mock()
|
||||
# Pass in rdf group
|
||||
self.provision.extend_volume(array, device_id, new_size,
|
||||
extra_specs, self.data.rdf_group_no)
|
||||
extra_specs, rdfg_num)
|
||||
mock_ex.assert_called_once_with(
|
||||
array, device_id, new_size, extra_specs)
|
||||
array, device_id, new_size, extra_specs, rdfg_num)
|
||||
|
||||
def test_get_srp_pool_stats(self):
|
||||
array = self.data.array
|
||||
|
@ -322,22 +322,6 @@ class PowerMaxReplicationTest(test.TestCase):
|
||||
self.data.extra_specs, rep_extra_specs)
|
||||
mock_pre.assert_called_once()
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'is_vol_in_rep_session',
|
||||
return_value=(False, False, None))
|
||||
@mock.patch.object(common.PowerMaxCommon, 'extend_volume_is_replicated')
|
||||
@mock.patch.object(common.PowerMaxCommon, '_sync_check')
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_array_model_info',
|
||||
return_value=('VMAX250F', False))
|
||||
def test_extend_volume_rep_enabled(self, mock_model, mock_sync,
|
||||
mock_ex_re, mock_is_re):
|
||||
extra_specs = deepcopy(self.extra_specs)
|
||||
extra_specs[utils.PORTGROUPNAME] = self.data.port_group_name_f
|
||||
volume_name = self.data.test_volume.name
|
||||
self.common.extend_volume(self.data.test_volume, '5')
|
||||
mock_ex_re.assert_called_once_with(
|
||||
self.data.array, self.data.test_volume,
|
||||
self.data.device_id, volume_name, '5', extra_specs)
|
||||
|
||||
def test_set_config_file_get_extra_specs_rep_enabled(self):
|
||||
extra_specs, _ = self.common._set_config_file_and_get_extra_specs(
|
||||
self.data.test_volume)
|
||||
@ -607,43 +591,6 @@ class PowerMaxReplicationTest(test.TestCase):
|
||||
self.data.device_id))
|
||||
self.assertIsNone(target_device4)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_array_model_info',
|
||||
return_value=('PowerMax 2000', True))
|
||||
@mock.patch.object(common.PowerMaxCommon, 'setup_volume_replication')
|
||||
@mock.patch.object(provision.PowerMaxProvision, 'extend_volume')
|
||||
@mock.patch.object(provision.PowerMaxProvision, 'break_rdf_relationship')
|
||||
@mock.patch.object(masking.PowerMaxMasking, 'remove_and_reset_members')
|
||||
def test_extend_volume_is_replicated(self, mock_remove, mock_break,
|
||||
mock_extend, mock_setup, mock_model):
|
||||
self.common.extend_volume_is_replicated(
|
||||
self.data.array, self.data.test_volume, self.data.device_id,
|
||||
'vol1', '5', self.data.extra_specs_rep_enabled)
|
||||
self.assertEqual(2, mock_remove.call_count)
|
||||
self.assertEqual(2, mock_extend.call_count)
|
||||
mock_remove.reset_mock()
|
||||
mock_extend.reset_mock()
|
||||
with mock.patch.object(self.rest, 'is_next_gen_array',
|
||||
return_value=True):
|
||||
self.common.extend_volume_is_replicated(
|
||||
self.data.array, self.data.test_volume, self.data.device_id,
|
||||
'vol1', '5', self.data.extra_specs_rep_enabled)
|
||||
mock_remove.assert_not_called()
|
||||
self.assertEqual(2, mock_extend.call_count)
|
||||
|
||||
def test_extend_volume_is_replicated_exception(self):
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.common.extend_volume_is_replicated,
|
||||
self.data.failed_resource, self.data.test_volume,
|
||||
self.data.device_id, 'vol1', '1',
|
||||
self.data.extra_specs_rep_enabled)
|
||||
with mock.patch.object(self.utils, 'is_metro_device',
|
||||
return_value=True):
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.common.extend_volume_is_replicated,
|
||||
self.data.array, self.data.test_volume,
|
||||
self.data.device_id, 'vol1', '1',
|
||||
self.data.extra_specs_rep_enabled)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_array_model_info',
|
||||
return_value=('VMAX250F', False))
|
||||
@mock.patch.object(common.PowerMaxCommon,
|
||||
|
@ -544,22 +544,31 @@ class PowerMaxRestTest(test.TestCase):
|
||||
self.rest._modify_volume, self.data.array,
|
||||
device_id, payload)
|
||||
|
||||
def test_extend_volume(self):
|
||||
@mock.patch.object(rest.PowerMaxRest, 'wait_for_job')
|
||||
def test_extend_volume(self, mck_wait):
|
||||
array = self.data.array
|
||||
device_id = self.data.device_id
|
||||
new_size = '3'
|
||||
extra_specs = self.data.extra_specs,
|
||||
rdfg_num = self.data.rdf_group_no
|
||||
|
||||
extend_vol_payload = {'executionOption': 'ASYNCHRONOUS',
|
||||
'editVolumeActionParam': {
|
||||
'expandVolumeParam': {
|
||||
'volumeAttribute': {
|
||||
'volume_size': new_size,
|
||||
'capacityUnit': 'GB'}}}}
|
||||
'capacityUnit': 'GB'},
|
||||
'rdfGroupNumber': rdfg_num}}}
|
||||
|
||||
with mock.patch.object(
|
||||
self.rest, '_modify_volume',
|
||||
return_value=(202, self.data.job_list[0])) as mock_modify:
|
||||
self.rest.extend_volume(self.data.array, device_id, new_size,
|
||||
self.data.extra_specs)
|
||||
mock_modify.assert_called_once_with(
|
||||
self.data.array, device_id, extend_vol_payload)
|
||||
return_value=(202, self.data.job_list[0])) as mck_modify:
|
||||
|
||||
self.rest.extend_volume(array, device_id, new_size, extra_specs,
|
||||
rdfg_num)
|
||||
|
||||
mck_modify.assert_called_once_with(array, device_id,
|
||||
extend_vol_payload)
|
||||
|
||||
def test_legacy_delete_volume(self):
|
||||
device_id = self.data.device_id
|
||||
@ -1731,3 +1740,10 @@ class PowerMaxRestTest(test.TestCase):
|
||||
self.rest.u4p_failover_targets = []
|
||||
self.assertRaises(exception.VolumeBackendAPIException,
|
||||
self.rest._handle_u4p_failover)
|
||||
|
||||
@mock.patch.object(rest.PowerMaxRest, 'get_array_detail',
|
||||
return_value=tpd.PowerMaxData.powermax_model_details)
|
||||
def test_get_array_ucode(self, mck_ucode):
|
||||
array = self.data.array
|
||||
ucode = self.rest.get_array_ucode_version(array)
|
||||
self.assertEqual(self.data.powermax_model_details['ucode'], ucode)
|
||||
|
@ -169,27 +169,31 @@ class PowerMaxCommon(object):
|
||||
def __init__(self, prtcl, version, configuration=None,
|
||||
active_backend_id=None):
|
||||
|
||||
self.protocol = prtcl
|
||||
self.configuration = configuration
|
||||
self.configuration.append_config_values(powermax_opts)
|
||||
self.rest = rest.PowerMaxRest()
|
||||
self.utils = utils.PowerMaxUtils()
|
||||
self.masking = masking.PowerMaxMasking(prtcl, self.rest)
|
||||
self.provision = provision.PowerMaxProvision(self.rest)
|
||||
self.version = version
|
||||
self.volume_metadata = volume_metadata.PowerMaxVolumeMetadata(
|
||||
self.rest, version, LOG.isEnabledFor(logging.DEBUG))
|
||||
# replication
|
||||
|
||||
# Configuration/Attributes
|
||||
self.protocol = prtcl
|
||||
self.configuration = configuration
|
||||
self.configuration.append_config_values(powermax_opts)
|
||||
self.active_backend_id = active_backend_id
|
||||
self.version = version
|
||||
self.version_dict = {}
|
||||
self.ucode_level = None
|
||||
self.next_gen = False
|
||||
self.replication_enabled = False
|
||||
self.extend_replicated_vol = False
|
||||
self.rep_devices = None
|
||||
self.active_backend_id = active_backend_id
|
||||
self.failover = False
|
||||
|
||||
# Gather environment info
|
||||
self._get_replication_info()
|
||||
self._get_u4p_failover_info()
|
||||
self.next_gen = False
|
||||
self._gather_info()
|
||||
self.version_dict = {}
|
||||
|
||||
def _gather_info(self):
|
||||
"""Gather the relevant information for update_volume_stats."""
|
||||
@ -202,7 +206,9 @@ class PowerMaxCommon(object):
|
||||
"longer supported.")
|
||||
self.rest.set_rest_credentials(array_info)
|
||||
if array_info:
|
||||
self.array_model, self.next_gen = self.rest.get_array_model_info(
|
||||
self.array_model, self.next_gen = (
|
||||
self.rest.get_array_model_info(array_info['SerialNumber']))
|
||||
self.ucode_level = self.rest.get_array_ucode_version(
|
||||
array_info['SerialNumber'])
|
||||
finalarrayinfolist = self._get_slo_workload_combinations(
|
||||
array_info)
|
||||
@ -949,60 +955,203 @@ class PowerMaxCommon(object):
|
||||
|
||||
:param volume: the volume Object
|
||||
:param new_size: the new size to increase the volume to
|
||||
:returns: dict -- modifiedVolumeDict - the extended volume Object
|
||||
:raises: VolumeBackendAPIException:
|
||||
"""
|
||||
original_vol_size = volume.size
|
||||
volume_name = volume.name
|
||||
extra_specs = self._initial_setup(volume)
|
||||
array = extra_specs[utils.ARRAY]
|
||||
device_id = self._find_device_on_array(volume, extra_specs)
|
||||
# Set specific attributes for extend operation
|
||||
ex_specs = self._initial_setup(volume)
|
||||
array = ex_specs[utils.ARRAY]
|
||||
device_id = self._find_device_on_array(volume, ex_specs)
|
||||
vol_name = volume.name
|
||||
orig_vol_size = volume.size
|
||||
rep_enabled = self.utils.is_replication_enabled(ex_specs)
|
||||
rdf_grp_no = None
|
||||
legacy_extend = False
|
||||
metro_exception = False
|
||||
|
||||
# Run validation and capabilities checks
|
||||
self._extend_vol_validation_checks(
|
||||
array, device_id, vol_name, ex_specs, orig_vol_size, new_size)
|
||||
r1_ode, r1_ode_metro, r2_ode, r2_ode_metro = (
|
||||
self._array_ode_capabilities_check(array, rep_enabled))
|
||||
|
||||
# Get extend workflow dependent on array gen and replication status
|
||||
if self.next_gen:
|
||||
if rep_enabled:
|
||||
(rdf_grp_no, __) = self.get_rdf_details(array)
|
||||
if self.utils.is_metro_device(self.rep_config, ex_specs):
|
||||
if not r1_ode_metro or not r2_ode_metro:
|
||||
metro_exception = True
|
||||
|
||||
elif not self.next_gen and rep_enabled:
|
||||
if self.utils.is_metro_device(self.rep_config, ex_specs):
|
||||
metro_exception = True
|
||||
else:
|
||||
legacy_extend = True
|
||||
|
||||
# If volume to be extended is SRDF Metro enabled and not FoxTail uCode
|
||||
if metro_exception:
|
||||
metro_exception_message = (_(
|
||||
"Extending a replicated volume with SRDF/Metro enabled is not "
|
||||
"permitted on this backend. Please contact your storage "
|
||||
"administrator. Note that you cannot extend SRDF/Metro "
|
||||
"protected volumes unless running FoxTail PowerMax OS uCode "
|
||||
"level."))
|
||||
LOG.error(metro_exception_message)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
message=metro_exception_message)
|
||||
|
||||
# Handle the extend process using workflow info from previous steps
|
||||
if legacy_extend:
|
||||
LOG.info("Legacy extend volume %(volume)s to %(new_size)d GBs",
|
||||
{'volume': vol_name,
|
||||
'new_size': int(new_size)})
|
||||
self._extend_legacy_replicated_vol(
|
||||
array, volume, device_id, vol_name, new_size, ex_specs)
|
||||
else:
|
||||
LOG.info("ODE extend volume %(volume)s to %(new_size)d GBs",
|
||||
{'volume': vol_name,
|
||||
'new_size': int(new_size)})
|
||||
self.provision.extend_volume(
|
||||
array, device_id, new_size, ex_specs, rdf_grp_no)
|
||||
|
||||
LOG.debug("Leaving extend_volume: %(volume_name)s. ",
|
||||
{'volume_name': vol_name})
|
||||
|
||||
def _extend_vol_validation_checks(self, array, device_id, vol_name,
|
||||
ex_specs, orig_vol_size, new_size):
|
||||
"""Run validation checks on settings for extend volume operation.
|
||||
|
||||
:param array: the array serial number
|
||||
:param device_id: the device id
|
||||
:param vol_name: the volume name
|
||||
:param ex_specs: extra specifications
|
||||
:param orig_vol_size: the original volume size
|
||||
:param new_size: the new size the volume should be
|
||||
:raises: VolumeBackendAPIException:
|
||||
"""
|
||||
# 1 - Check device exists
|
||||
if device_id is None:
|
||||
exception_message = (_("Cannot find Volume: %(volume_name)s. "
|
||||
"Extend operation. Exiting....")
|
||||
% {'volume_name': volume_name})
|
||||
exception_message = (_(
|
||||
"Cannot find Volume: %(volume_name)s. Extend operation. "
|
||||
"Exiting....") % {'volume_name': vol_name})
|
||||
LOG.error(exception_message)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
message=exception_message)
|
||||
# Check if volume is part of an on-going clone operation
|
||||
self._sync_check(array, device_id, extra_specs)
|
||||
|
||||
# 2 - Check if volume is part of an on-going clone operation or if vol
|
||||
# has source snapshots but not next-gen array
|
||||
self._sync_check(array, device_id, ex_specs)
|
||||
__, snapvx_src, __ = self.rest.is_vol_in_rep_session(array, device_id)
|
||||
if snapvx_src:
|
||||
if not self.rest.is_next_gen_array(array):
|
||||
if not self.next_gen:
|
||||
exception_message = (
|
||||
_("The volume: %(volume)s is a snapshot source. "
|
||||
"Extending a volume with snapVx snapshots is only "
|
||||
"supported on PowerMax/VMAX from HyperMaxOS version "
|
||||
"5978 onwards. Exiting...") % {'volume': volume_name})
|
||||
"supported on PowerMax/VMAX from OS version 5978 "
|
||||
"onwards. Exiting...") % {'volume': vol_name})
|
||||
LOG.error(exception_message)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
message=exception_message)
|
||||
|
||||
if int(original_vol_size) > int(new_size):
|
||||
# 3 - Check new size is larger than old size
|
||||
if int(orig_vol_size) >= int(new_size):
|
||||
exception_message = (_(
|
||||
"Your original size: %(original_vol_size)s GB is greater "
|
||||
"than: %(new_size)s GB. Only Extend is supported. Exiting...")
|
||||
% {'original_vol_size': original_vol_size,
|
||||
'new_size': new_size})
|
||||
"Your original size: %(orig_vol_size)s GB is greater "
|
||||
"than or the same as: %(new_size)s GB. Only extend ops are "
|
||||
"supported. Exiting...") % {'orig_vol_size': orig_vol_size,
|
||||
'new_size': new_size})
|
||||
LOG.error(exception_message)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
message=exception_message)
|
||||
LOG.info("Extending volume %(volume)s to %(new_size)d GBs",
|
||||
{'volume': volume_name,
|
||||
'new_size': int(new_size)})
|
||||
if self.utils.is_replication_enabled(extra_specs):
|
||||
# Extra logic required if volume is replicated
|
||||
self.extend_volume_is_replicated(
|
||||
array, volume, device_id, volume_name, new_size, extra_specs)
|
||||
else:
|
||||
self.provision.extend_volume(
|
||||
array, device_id, new_size, extra_specs)
|
||||
|
||||
self.volume_metadata.capture_extend_info(
|
||||
volume, new_size, device_id, extra_specs, array)
|
||||
def _array_ode_capabilities_check(self, array, rep_enabled=False):
|
||||
"""Given an array, check Online Device Expansion (ODE) support.
|
||||
|
||||
LOG.debug("Leaving extend_volume: %(volume_name)s. ",
|
||||
{'volume_name': volume_name})
|
||||
:param array: the array serial number
|
||||
:param rep_enabled: if replication is enabled for backend
|
||||
:returns: r1_ode: (bool) If R1 array supports ODE
|
||||
:returns: r1_ode_metro: (bool) If R1 array supports ODE with Metro vols
|
||||
:returns: r2_ode: (bool) If R1 array supports ODE
|
||||
:returns: r2_ode_metro: (bool) If R1 array supports ODE with Metro vols
|
||||
"""
|
||||
r1_ucode = self.ucode_level.split('.')
|
||||
r1_ode, r1_ode_metro = False, False
|
||||
r2_ode, r2_ode_metro = False, False
|
||||
|
||||
if self.next_gen:
|
||||
r1_ode = True
|
||||
if rep_enabled:
|
||||
__, r2_array = self.get_rdf_details(array)
|
||||
r2_ucode = self.rest.get_array_ucode_version(array)
|
||||
if int(r1_ucode[2]) > utils.UCODE_5978_ELMSR:
|
||||
r1_ode_metro = True
|
||||
r2_ucode = r2_ucode.split('.')
|
||||
if self.rest.is_next_gen_array(r2_array):
|
||||
r2_ode = True
|
||||
if int(r2_ucode[2]) > utils.UCODE_5978_ELMSR:
|
||||
r2_ode_metro = True
|
||||
|
||||
return r1_ode, r1_ode_metro, r2_ode, r2_ode_metro
|
||||
|
||||
def _extend_legacy_replicated_vol(self, array, volume, device_id,
|
||||
volume_name, new_size, extra_specs):
|
||||
"""Extend a legacy OS volume without Online Device Expansion
|
||||
|
||||
:param array: the array serial number
|
||||
:param volume: the volume objcet
|
||||
:param device_id: the volume device id
|
||||
:param volume_name: the volume name
|
||||
:param new_size: the new size the volume should be
|
||||
:param extra_specs: extra specifications
|
||||
"""
|
||||
try:
|
||||
(target_device, remote_array, rdf_group, local_vol_state,
|
||||
pair_state) = self.get_remote_target_device(
|
||||
array, volume, device_id)
|
||||
rep_extra_specs = self._get_replication_extra_specs(
|
||||
extra_specs, self.rep_config)
|
||||
|
||||
# Volume must be removed from replication (storage) group before
|
||||
# the replication relationship can be ended (cannot have a mix of
|
||||
# replicated and non-replicated volumes as the SRDF groups become
|
||||
# unmanageable)
|
||||
self.masking.remove_and_reset_members(
|
||||
array, volume, device_id, volume_name, extra_specs, False)
|
||||
# Repeat on target side
|
||||
self.masking.remove_and_reset_members(
|
||||
remote_array, volume, target_device, volume_name,
|
||||
rep_extra_specs, False)
|
||||
LOG.info("Breaking replication relationship...")
|
||||
self.provision.break_rdf_relationship(array, device_id,
|
||||
target_device, rdf_group,
|
||||
rep_extra_specs, pair_state)
|
||||
# Extend the target volume
|
||||
LOG.info("Extending target volume...")
|
||||
# Check to make sure the R2 device requires extending first...
|
||||
r2_size = self.rest.get_size_of_device_on_array(remote_array,
|
||||
target_device)
|
||||
if int(r2_size) < int(new_size):
|
||||
self.provision.extend_volume(remote_array, target_device,
|
||||
new_size, rep_extra_specs)
|
||||
# Extend the source volume
|
||||
LOG.info("Extending source volume...")
|
||||
self.provision.extend_volume(array, device_id, new_size,
|
||||
extra_specs)
|
||||
# Re-create replication relationship
|
||||
LOG.info("Recreating replication relationship...")
|
||||
self.setup_volume_replication(array, volume, device_id,
|
||||
extra_specs, target_device)
|
||||
# Check if volume needs to be returned to volume group
|
||||
if volume.group_id:
|
||||
self._add_new_volume_to_volume_group(
|
||||
volume, device_id, volume_name, extra_specs)
|
||||
|
||||
except Exception as e:
|
||||
exception_message = (_("Error extending volume. Error received "
|
||||
"was %(e)s") % {'e': e})
|
||||
LOG.error(exception_message)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
message=exception_message)
|
||||
|
||||
def update_volume_stats(self):
|
||||
"""Retrieve stats info."""
|
||||
@ -3496,8 +3645,8 @@ class PowerMaxCommon(object):
|
||||
self.provision.get_or_create_group(
|
||||
remote_array, group_name, extra_specs)
|
||||
self.masking.add_volume_to_storage_group(
|
||||
remote_array, target_device_id,
|
||||
group_name, volume_name, extra_specs)
|
||||
remote_array, target_device_id, group_name, volume_name,
|
||||
extra_specs, force=True)
|
||||
except Exception as e:
|
||||
exception_message = (
|
||||
_('Exception occurred adding volume %(vol)s to its async '
|
||||
@ -3890,120 +4039,6 @@ class PowerMaxCommon(object):
|
||||
return (target_device, remote_array, rdf_group,
|
||||
local_vol_state, pair_state)
|
||||
|
||||
def extend_volume_is_replicated(
|
||||
self, array, volume, device_id, volume_name,
|
||||
new_size, extra_specs):
|
||||
"""Extend a replication-enabled volume.
|
||||
|
||||
Cannot extend volumes in a synchronization pair where the source
|
||||
and/or target arrays are running HyperMax versions < 5978. Must first
|
||||
break the relationship, extend them separately, then recreate the
|
||||
pair. Extending Metro protected volumes is not supported.
|
||||
:param array: the array serial number
|
||||
:param volume: the volume objcet
|
||||
:param device_id: the volume device id
|
||||
:param volume_name: the volume name
|
||||
:param new_size: the new size the volume should be
|
||||
:param extra_specs: extra specifications
|
||||
"""
|
||||
ode_replication, allow_extend = False, self.extend_replicated_vol
|
||||
if (self.rest.is_next_gen_array(array)
|
||||
and not self.utils.is_metro_device(
|
||||
self.rep_config, extra_specs)):
|
||||
# Check if remote array is next gen
|
||||
__, remote_array = self.get_rdf_details(array)
|
||||
if self.rest.is_next_gen_array(remote_array):
|
||||
ode_replication = True
|
||||
if self.utils.is_metro_device(self.rep_config, extra_specs):
|
||||
allow_extend = False
|
||||
if allow_extend is True or ode_replication is True:
|
||||
self._extend_with_or_without_ode_replication(
|
||||
array, volume, device_id, ode_replication, volume_name,
|
||||
new_size, extra_specs)
|
||||
else:
|
||||
exception_message = (_(
|
||||
"Extending a replicated volume is not permitted on this "
|
||||
"backend. Please contact your administrator. Note that "
|
||||
"you cannot extend SRDF/Metro protected volumes."))
|
||||
LOG.error(exception_message)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
message=exception_message)
|
||||
|
||||
def _extend_with_or_without_ode_replication(
|
||||
self, array, volume, device_id, ode_replication, volume_name,
|
||||
new_size, extra_specs):
|
||||
"""Extend a volume with or without Online Device Expansion
|
||||
|
||||
:param array: the array serial number
|
||||
:param volume: the volume objcet
|
||||
:param device_id: the volume device id
|
||||
:param ode_replication: Online device expansion
|
||||
:param volume_name: the volume name
|
||||
:param new_size: the new size the volume should be
|
||||
:param extra_specs: extra specifications
|
||||
"""
|
||||
try:
|
||||
(target_device, remote_array, rdf_group,
|
||||
local_vol_state, pair_state) = (
|
||||
self.get_remote_target_device(
|
||||
array, volume, device_id))
|
||||
rep_extra_specs = self._get_replication_extra_specs(
|
||||
extra_specs, self.rep_config)
|
||||
lock_rdf_group = rdf_group
|
||||
if not ode_replication:
|
||||
# Volume must be removed from replication (storage) group
|
||||
# before the replication relationship can be ended (cannot
|
||||
# have a mix of replicated and non-replicated volumes as
|
||||
# the SRDF groups become unmanageable)
|
||||
lock_rdf_group = None
|
||||
self.masking.remove_and_reset_members(
|
||||
array, volume, device_id, volume_name,
|
||||
extra_specs, False)
|
||||
|
||||
# Repeat on target side
|
||||
self.masking.remove_and_reset_members(
|
||||
remote_array, volume, target_device, volume_name,
|
||||
rep_extra_specs, False)
|
||||
|
||||
LOG.info("Breaking replication relationship...")
|
||||
self.provision.break_rdf_relationship(
|
||||
array, device_id, target_device, rdf_group,
|
||||
rep_extra_specs, pair_state)
|
||||
|
||||
# Extend the target volume
|
||||
LOG.info("Extending target volume...")
|
||||
# Check to make sure the R2 device requires extending first...
|
||||
r2_size = self.rest.get_size_of_device_on_array(
|
||||
remote_array, target_device)
|
||||
if int(r2_size) < int(new_size):
|
||||
self.provision.extend_volume(
|
||||
remote_array, target_device, new_size,
|
||||
rep_extra_specs, lock_rdf_group)
|
||||
|
||||
# Extend the source volume
|
||||
LOG.info("Extending source volume...")
|
||||
self.provision.extend_volume(
|
||||
array, device_id, new_size, extra_specs, lock_rdf_group)
|
||||
|
||||
if not ode_replication:
|
||||
# Re-create replication relationship
|
||||
LOG.info("Recreating replication relationship...")
|
||||
self.setup_volume_replication(
|
||||
array, volume, device_id, extra_specs, target_device)
|
||||
|
||||
# Check if volume needs to be returned to volume group
|
||||
if volume.group_id:
|
||||
self._add_new_volume_to_volume_group(
|
||||
volume, device_id, volume_name, extra_specs)
|
||||
|
||||
except Exception as e:
|
||||
exception_message = (_("Error extending volume. "
|
||||
"Error received was %(e)s") %
|
||||
{'e': e})
|
||||
LOG.error(exception_message)
|
||||
raise exception.VolumeBackendAPIException(
|
||||
message=exception_message)
|
||||
|
||||
def enable_rdf(self, array, volume, device_id, rdf_group_no, rep_config,
|
||||
target_name, remote_array, target_device, extra_specs):
|
||||
"""Create a replication relationship with a target volume.
|
||||
|
@ -111,6 +111,7 @@ class PowerMaxFCDriver(san.SanDriver, driver.FibreChannelDriver):
|
||||
4.1.0 - Changing from 90 to 91 rest endpoints
|
||||
- Support for Rapid TDEV Delete (bp powermax-tdev-deallocation)
|
||||
- PowerMax OS Metro formatted volumes fix (bug #1829876)
|
||||
- Support for Metro ODE (bp/powermax-metro-ode)
|
||||
"""
|
||||
|
||||
VERSION = "4.1.0"
|
||||
|
@ -116,6 +116,7 @@ class PowerMaxISCSIDriver(san.SanISCSIDriver):
|
||||
4.1.0 - Changing from 90 to 91 rest endpoints
|
||||
- Support for Rapid TDEV Delete (bp powermax-tdev-deallocation)
|
||||
- PowerMax OS Metro formatted volumes fix (bug #1829876)
|
||||
- Support for Metro ODE (bp/powermax-metro-ode)
|
||||
"""
|
||||
|
||||
VERSION = "4.1.0"
|
||||
|
@ -401,7 +401,7 @@ class PowerMaxProvision(object):
|
||||
@coordination.synchronized('emc-rg-{rdf_group}')
|
||||
def _extend_replicated_volume(rdf_group):
|
||||
self.rest.extend_volume(array, device_id,
|
||||
new_size, extra_specs)
|
||||
new_size, extra_specs, rdf_group)
|
||||
_extend_replicated_volume(rdf_group)
|
||||
else:
|
||||
self.rest.extend_volume(array, device_id, new_size, extra_specs)
|
||||
|
@ -671,6 +671,18 @@ class PowerMaxRest(object):
|
||||
is_next_gen = True
|
||||
return array_model, is_next_gen
|
||||
|
||||
def get_array_ucode_version(self, array):
|
||||
"""Get the PowerMax/VMAX uCode version.
|
||||
|
||||
:param array: the array serial number
|
||||
:return: the PowerMax/VMAX uCode version
|
||||
"""
|
||||
ucode_version = None
|
||||
system_info = self.get_array_detail(array)
|
||||
if system_info:
|
||||
ucode_version = system_info['ucode']
|
||||
return ucode_version
|
||||
|
||||
def is_compression_capable(self, array):
|
||||
"""Check if array is compression capable.
|
||||
|
||||
@ -1239,20 +1251,26 @@ class PowerMaxRest(object):
|
||||
return self.modify_resource(array, SLOPROVISIONING, 'volume',
|
||||
payload, resource_name=device_id)
|
||||
|
||||
def extend_volume(self, array, device_id, new_size, extra_specs):
|
||||
def extend_volume(self, array, device_id, new_size, extra_specs,
|
||||
rdf_grp_no=None):
|
||||
"""Extend a PowerMax/VMAX volume.
|
||||
|
||||
:param array: the array serial number
|
||||
:param device_id: volume device id
|
||||
:param new_size: the new required size for the device
|
||||
:param extra_specs: the extra specifications
|
||||
:param rdf_grp_no: the RDG group number
|
||||
"""
|
||||
extend_vol_payload = {"executionOption": "ASYNCHRONOUS",
|
||||
"editVolumeActionParam": {
|
||||
"expandVolumeParam": {
|
||||
"volumeAttribute": {
|
||||
"volume_size": new_size,
|
||||
"capacityUnit": "GB"}}}}
|
||||
extend_vol_payload = {'executionOption': 'ASYNCHRONOUS',
|
||||
'editVolumeActionParam': {
|
||||
'expandVolumeParam': {
|
||||
'volumeAttribute': {
|
||||
'volume_size': new_size,
|
||||
'capacityUnit': 'GB'}}}}
|
||||
|
||||
if rdf_grp_no:
|
||||
extend_vol_payload['editVolumeActionParam'][
|
||||
'expandVolumeParam'].update({'rdfGroupNumber': rdf_grp_no})
|
||||
|
||||
status_code, job = self._modify_volume(
|
||||
array, device_id, extend_vol_payload)
|
||||
@ -2320,7 +2338,6 @@ class PowerMaxRest(object):
|
||||
extra_specs[utils.RDF_CONS_EXEMPT] = False
|
||||
payload = self.get_metro_payload_info(
|
||||
array, payload, rdf_group_no, extra_specs)
|
||||
|
||||
resource_type = ("rdf_group/%(rdf_num)s/volume"
|
||||
% {'rdf_num': rdf_group_no})
|
||||
status_code, job = self.create_resource(array, REPLICATION,
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
PowerMax for Cinder driver now supports extending in-use Metro RDF enabled
|
||||
volumes.
|
Loading…
Reference in New Issue
Block a user