HPE 3PAR: Few issues with new WSAPI (of 2023)

In recent HPE 3PAR WSAPI, observed issues with parameters
like snapCPG, localSnapCPG, etc.
Due to this, few operations (eg. create volume, create
remote copy group [RCG]) fail.
In order to work with new wsapi (of 2023), performed minor
code changes in 3par driver code.

Closes-Bug: #2015746
Change-Id: I98e021594c59dbad1723597e38aeb4c0cec6934b
This commit is contained in:
raghavendrat 2023-04-10 10:53:32 +00:00
parent 2e8aff660b
commit b2c8bc3590
3 changed files with 183 additions and 32 deletions

View File

@ -675,6 +675,11 @@ class HPE3PARBaseDriver(test.TestCase):
'minor': 5, 'minor': 5,
'revision': 0} 'revision': 0}
wsapi_version_2023 = {'major': 1,
'build': 100000050,
'minor': 10,
'revision': 0}
# Use this to point to latest version of wsapi # Use this to point to latest version of wsapi
wsapi_version_latest = wsapi_version_for_compression wsapi_version_latest = wsapi_version_for_compression
@ -892,28 +897,41 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
mock_client.assert_has_calls(expected) mock_client.assert_has_calls(expected)
self.assertEqual(self.STATUS_DONE, status) self.assertEqual(self.STATUS_DONE, status)
def test_create_volume(self): # (i) wsapi version is old/default
# (ii) wsapi version is 2023, then snapCPG isn't required
@ddt.data({'wsapi_version': None},
{'wsapi_version': HPE3PARBaseDriver.wsapi_version_2023})
@ddt.unpack
def test_create_volume(self, wsapi_version):
# setup_mock_client drive with default configuration # setup_mock_client drive with default configuration
# and return the mock HTTP 3PAR client # and return the mock HTTP 3PAR client
mock_client = self.setup_driver() mock_client = self.setup_driver(wsapi_version=wsapi_version)
with mock.patch.object(hpecommon.HPE3PARCommon, with mock.patch.object(hpecommon.HPE3PARCommon,
'_create_client') as mock_create_client: '_create_client') as mock_create_client:
mock_create_client.return_value = mock_client mock_create_client.return_value = mock_client
self.driver.create_volume(self.volume) if not wsapi_version:
# (i) old/default
self.driver.create_volume(self.volume)
else:
# (ii) wsapi 2023
common = self.driver._login()
common.create_volume(self.volume)
comment = Comment({ comment = Comment({
"display_name": "Foo Volume", "display_name": "Foo Volume",
"type": "OpenStack", "type": "OpenStack",
"name": "volume-d03338a9-9115-48a3-8dfc-35cdfcdc15a7", "name": "volume-d03338a9-9115-48a3-8dfc-35cdfcdc15a7",
"volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7"}) "volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7"})
optional = {'comment': comment,
'tpvv': True,
'tdvv': False}
if not wsapi_version:
optional['snapCPG'] = HPE3PAR_CPG_SNAP
expected = [ expected = [
mock.call.createVolume( mock.call.createVolume(
self.VOLUME_3PAR_NAME, self.VOLUME_3PAR_NAME,
HPE3PAR_CPG, HPE3PAR_CPG,
2048, { 2048, optional)]
'comment': comment,
'tpvv': True,
'tdvv': False,
'snapCPG': HPE3PAR_CPG_SNAP})]
mock_client.assert_has_calls(expected) mock_client.assert_has_calls(expected)
@ -1254,6 +1272,89 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
mirror_config=True)] mirror_config=True)]
mock_client.assert_has_calls(expected) mock_client.assert_has_calls(expected)
@mock.patch.object(volume_types, 'get_volume_type')
def test_create_volume_replicated_periodic_2023(self, _mock_volume_types):
# setup_mock_client drive with default configuration
# and return the mock HTTP 3PAR client
conf = self.setup_configuration()
self.replication_targets[0]['replication_mode'] = 'periodic'
conf.replication_device = self.replication_targets
mock_client = self.setup_driver(conf, None, self.wsapi_version_2023)
mock_client.getStorageSystemInfo.return_value = (
{'id': self.CLIENT_ID})
mock_client.getRemoteCopyGroup.side_effect = (
hpeexceptions.HTTPNotFound)
mock_client.getCPG.return_value = {'domain': None}
mock_replicated_client = self.setup_driver(conf, None,
self.wsapi_version_2023)
mock_replicated_client.getStorageSystemInfo.return_value = (
{'id': self.REPLICATION_CLIENT_ID})
_mock_volume_types.return_value = {
'name': 'replicated',
'extra_specs': {
'replication_enabled': '<is> True',
'replication:mode': 'periodic',
'replication:sync_period': '900',
'volume_type': self.volume_type_replicated}}
with mock.patch.object(
hpecommon.HPE3PARCommon,
'_create_client') as mock_create_client, \
mock.patch.object(
hpecommon.HPE3PARCommon,
'_create_replication_client') as mock_replication_client:
mock_create_client.return_value = mock_client
mock_replication_client.return_value = mock_replicated_client
common = self.driver._login()
return_model = common.create_volume(self.volume_replicated)
comment = Comment({
"volume_type_name": "replicated",
"display_name": "Foo Volume",
"name": "volume-d03338a9-9115-48a3-8dfc-35cdfcdc15a7",
"volume_type_id": "be9181f1-4040-46f2-8298-e7532f2bf9db",
"volume_id": "d03338a9-9115-48a3-8dfc-35cdfcdc15a7",
"qos": {},
"type": "OpenStack"})
backend_id = self.replication_targets[0]['backend_id']
expected = [
mock.call.createVolume(
self.VOLUME_3PAR_NAME,
HPE3PAR_CPG,
2048, {
'comment': comment,
'tpvv': True,
'tdvv': False}),
mock.call.getRemoteCopyGroup(self.RCG_3PAR_NAME),
mock.call.getCPG(HPE3PAR_CPG),
mock.call.createRemoteCopyGroup(
self.RCG_3PAR_NAME,
[{'userCPG': HPE3PAR_CPG_REMOTE,
'targetName': backend_id,
'mode': PERIODIC_MODE}],
{'localUserCPG': HPE3PAR_CPG}),
mock.call.addVolumeToRemoteCopyGroup(
self.RCG_3PAR_NAME,
self.VOLUME_3PAR_NAME,
[{'secVolumeName': self.VOLUME_3PAR_NAME,
'targetName': backend_id}],
optional={'volumeAutoCreation': True}),
mock.call.modifyRemoteCopyGroup(
self.RCG_3PAR_NAME,
{'targets': [{'syncPeriod': SYNC_PERIOD,
'targetName': backend_id}]}),
mock.call.startRemoteCopy(self.RCG_3PAR_NAME)]
mock_client.assert_has_calls(
self.get_id_login +
self.standard_logout +
self.standard_login +
expected)
self.assertEqual({'replication_status': 'enabled',
'provider_location': self.CLIENT_ID},
return_model)
@mock.patch.object(volume_types, 'get_volume_type') @mock.patch.object(volume_types, 'get_volume_type')
def test_create_volume_replicated_sync(self, _mock_volume_types): def test_create_volume_replicated_sync(self, _mock_volume_types):
# setup_mock_client drive with default configuration # setup_mock_client drive with default configuration
@ -4269,10 +4370,16 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
expected_retype_specs) expected_retype_specs)
self.assertEqual(expected_obj, obj) self.assertEqual(expected_obj, obj)
# (i) wsapi version is old/default
# (ii) wsapi version is 2023, then snapCPG isn't required
@ddt.data({'wsapi_version': None},
{'wsapi_version': HPE3PARBaseDriver.wsapi_version_2023})
@ddt.unpack
@mock.patch.object(volume_types, 'get_volume_type') @mock.patch.object(volume_types, 'get_volume_type')
def test_manage_existing_with_no_snap_cpg(self, _mock_volume_types): def test_manage_existing_with_no_snap_cpg(self, _mock_volume_types,
wsapi_version):
_mock_volume_types.return_value = self.volume_type _mock_volume_types.return_value = self.volume_type
mock_client = self.setup_driver() mock_client = self.setup_driver(wsapi_version=wsapi_version)
new_comment = Comment({ new_comment = Comment({
"display_name": "Foo Volume", "display_name": "Foo Volume",
@ -4304,15 +4411,20 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
obj = self.driver.manage_existing(volume, existing_ref) obj = self.driver.manage_existing(volume, existing_ref)
optional = {'newName': osv_matcher,
'comment': new_comment}
if not wsapi_version:
# (i) old/default
# manage_existing() should be setting
# blank snapCPG to the userCPG
optional['snapCPG'] = 'testUserCpg0'
expected_manage = [ expected_manage = [
mock.call.getVolume(existing_ref['source-name']), mock.call.getVolume(existing_ref['source-name']),
mock.call.modifyVolume( mock.call.modifyVolume(
existing_ref['source-name'], existing_ref['source-name'],
{'newName': osv_matcher, optional)
'comment': new_comment,
# manage_existing() should be setting
# blank snapCPG to the userCPG
'snapCPG': 'testUserCpg0'})
] ]
mock_client.assert_has_calls(self.standard_login + expected_manage) mock_client.assert_has_calls(self.standard_login + expected_manage)
@ -6076,16 +6188,21 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
mock_client.assert_has_calls(expected) mock_client.assert_has_calls(expected)
# (i) wsapi version is old/default
# (ii) wsapi version is 2023, then snapCPG isn't required
@ddt.data({'wsapi_version': None},
{'wsapi_version': HPE3PARBaseDriver.wsapi_version_2023})
@ddt.unpack
@mock.patch('cinder.volume.drivers.hpe.hpe_3par_common.HPE3PARCommon.' @mock.patch('cinder.volume.drivers.hpe.hpe_3par_common.HPE3PARCommon.'
'get_volume_settings_from_type') 'get_volume_settings_from_type')
@mock.patch('cinder.volume.drivers.hpe.hpe_3par_common.HPE3PARCommon.' @mock.patch('cinder.volume.drivers.hpe.hpe_3par_common.HPE3PARCommon.'
'is_volume_group_snap_type') 'is_volume_group_snap_type')
@mock.patch('cinder.volume.volume_utils.is_group_a_cg_snapshot_type') @mock.patch('cinder.volume.volume_utils.is_group_a_cg_snapshot_type')
def test_create_group_from_src_group(self, cg_ss_enable, vol_ss_enable, def test_create_group_from_src_group(self, cg_ss_enable, vol_ss_enable,
typ_info): typ_info, wsapi_version):
cg_ss_enable.return_value = True cg_ss_enable.return_value = True
vol_ss_enable.return_value = True vol_ss_enable.return_value = True
mock_client = self.setup_driver() mock_client = self.setup_driver(wsapi_version=wsapi_version)
task_id = 1 task_id = 1
mock_client.copyVolume.return_value = {'taskid': task_id} mock_client.copyVolume.return_value = {'taskid': task_id}
mock_client.getStorageSystemInfo.return_value = {'id': self.CLIENT_ID} mock_client.getStorageSystemInfo.return_value = {'id': self.CLIENT_ID}
@ -6116,6 +6233,10 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
source_grp = self.fake_group_object( source_grp = self.fake_group_object(
grp_id=self.SRC_CONSIS_GROUP_ID) grp_id=self.SRC_CONSIS_GROUP_ID)
optional = {'online': True,
'tpvv': mock.ANY, 'tdvv': mock.ANY}
if not wsapi_version:
optional['snapCPG'] = HPE3PAR_CPG
expected = [ expected = [
mock.call.getCPG(HPE3PAR_CPG), mock.call.getCPG(HPE3PAR_CPG),
mock.call.createVolumeSet( mock.call.createVolumeSet(
@ -6131,17 +6252,25 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
mock.ANY, mock.ANY,
self.VOLUME_NAME_3PAR, self.VOLUME_NAME_3PAR,
HPE3PAR_CPG, HPE3PAR_CPG,
{'snapCPG': HPE3PAR_CPG, 'online': True, optional),
'tpvv': mock.ANY, 'tdvv': mock.ANY}),
mock.call.addVolumeToVolumeSet( mock.call.addVolumeToVolumeSet(
self.CONSIS_GROUP_NAME, self.CONSIS_GROUP_NAME,
self.VOLUME_NAME_3PAR)] self.VOLUME_NAME_3PAR)]
# Create a consistency group from a source consistency group. # Create a consistency group from a source consistency group.
self.driver.create_group_from_src( if not wsapi_version:
context.get_admin_context(), group, # (i) old/default
[volume], source_group=source_grp, self.driver.create_group_from_src(
source_vols=[source_volume]) context.get_admin_context(), group,
[volume], source_group=source_grp,
source_vols=[source_volume])
else:
# (ii) wsapi 2023
common = self.driver._login()
common.create_group_from_src(
context.get_admin_context(), group,
[volume], source_group=source_grp,
source_vols=[source_volume])
mock_client.assert_has_calls(expected) mock_client.assert_has_calls(expected)

View File

@ -81,6 +81,7 @@ FLASH_CACHE_API_VERSION = 30201200
COMPRESSION_API_VERSION = 30301215 COMPRESSION_API_VERSION = 30301215
SRSTATLD_API_VERSION = 30201200 SRSTATLD_API_VERSION = 30201200
REMOTE_COPY_API_VERSION = 30202290 REMOTE_COPY_API_VERSION = 30202290
API_VERSION_2023 = 100000000
hpe3par_opts = [ hpe3par_opts = [
cfg.StrOpt('hpe3par_api_url', cfg.StrOpt('hpe3par_api_url',
@ -302,11 +303,12 @@ class HPE3PARCommon(object):
Bug #1819903 Bug #1819903
4.0.18 - During conversion of volume to base volume, 4.0.18 - During conversion of volume to base volume,
error out if it has child snapshot(s). Bug #1994521 error out if it has child snapshot(s). Bug #1994521
4.0.19 - Update code to work with new WSAPI (of 2023). Bug #2015746
""" """
VERSION = "4.0.18" VERSION = "4.0.19"
stats = {} stats = {}
@ -706,9 +708,12 @@ class HPE3PARCommon(object):
compression = self.get_compression_policy( compression = self.get_compression_policy(
type_info['hpe3par_keys']) type_info['hpe3par_keys'])
optional = {'online': True, 'snapCPG': snapcpg, optional = {'online': True,
'tpvv': tpvv, 'tdvv': tdvv} 'tpvv': tpvv, 'tdvv': tdvv}
if self.API_VERSION < API_VERSION_2023:
optional['snapCPG'] = snapcpg
if compression is not None: if compression is not None:
optional['compression'] = compression optional['compression'] = compression
@ -1006,7 +1011,7 @@ class HPE3PARCommon(object):
'comment': json.dumps(new_comment)} 'comment': json.dumps(new_comment)}
# Ensure that snapCPG is set # Ensure that snapCPG is set
if 'snapCPG' not in vol: if 'snapCPG' not in vol and self.API_VERSION < API_VERSION_2023:
new_vals['snapCPG'] = vol['userCPG'] new_vals['snapCPG'] = vol['userCPG']
LOG.info("Virtual volume %(disp)s '%(new)s' snapCPG " LOG.info("Virtual volume %(disp)s '%(new)s' snapCPG "
"is empty so it will be set to: %(cpg)s", "is empty so it will be set to: %(cpg)s",
@ -2395,9 +2400,14 @@ class HPE3PARCommon(object):
comments['qos'] = qos comments['qos'] = qos
extras = {'comment': json.dumps(comments), extras = {'comment': json.dumps(comments),
'snapCPG': snap_cpg,
'tpvv': tpvv} 'tpvv': tpvv}
LOG.debug("self.API_VERSION: %(version)s",
{'version': self.API_VERSION})
if self.API_VERSION < API_VERSION_2023:
extras['snapCPG'] = snap_cpg
# Only set the dedup option if the backend supports it. # Only set the dedup option if the backend supports it.
if self.API_VERSION >= DEDUP_API_VERSION: if self.API_VERSION >= DEDUP_API_VERSION:
extras['tdvv'] = tdvv extras['tdvv'] = tdvv
@ -2468,7 +2478,7 @@ class HPE3PARCommon(object):
{'src': src_name, 'dest': dest_name}) {'src': src_name, 'dest': dest_name})
optional = {'tpvv': tpvv, 'online': True} optional = {'tpvv': tpvv, 'online': True}
if snap_cpg is not None: if snap_cpg is not None and self.API_VERSION < API_VERSION_2023:
optional['snapCPG'] = snap_cpg optional['snapCPG'] = snap_cpg
if self.API_VERSION >= DEDUP_API_VERSION: if self.API_VERSION >= DEDUP_API_VERSION:
@ -4377,15 +4387,17 @@ class HPE3PARCommon(object):
local_cpg) local_cpg)
rcg_target = {'targetName': target['backend_id'], rcg_target = {'targetName': target['backend_id'],
'mode': replication_mode_num, 'mode': replication_mode_num,
'snapCPG': cpg,
'userCPG': cpg} 'userCPG': cpg}
if self.API_VERSION < API_VERSION_2023:
rcg_target['snapCPG'] = cpg
rcg_targets.append(rcg_target) rcg_targets.append(rcg_target)
sync_target = {'targetName': target['backend_id'], sync_target = {'targetName': target['backend_id'],
'syncPeriod': replication_sync_period} 'syncPeriod': replication_sync_period}
sync_targets.append(sync_target) sync_targets.append(sync_target)
optional = {'localSnapCPG': vol_settings['snap_cpg'], optional = {'localUserCPG': local_cpg}
'localUserCPG': local_cpg} if self.API_VERSION < API_VERSION_2023:
optional['localSnapCPG'] = vol_settings['snap_cpg']
pool = volume_utils.extract_host(volume['host'], level='pool') pool = volume_utils.extract_host(volume['host'], level='pool')
domain = self.get_domain(pool) domain = self.get_domain(pool)
if domain: if domain:
@ -4400,6 +4412,8 @@ class HPE3PARCommon(object):
LOG.error(msg) LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg) raise exception.VolumeBackendAPIException(data=msg)
LOG.debug("created rcg %(name)s", {'name': rcg_name})
# Add volume to remote copy group. # Add volume to remote copy group.
rcg_targets = [] rcg_targets = []
for target in self._replication_targets: for target in self._replication_targets:
@ -5319,7 +5333,11 @@ class ModifyVolumeTask(flow_utils.CinderTask):
comment_dict = self._get_new_comment( comment_dict = self._get_new_comment(
old_comment, new_vvs, new_qos, new_type_name, new_type_id) old_comment, new_vvs, new_qos, new_type_name, new_type_id)
if new_snap_cpg != old_snap_cpg: LOG.debug("API_VERSION: %(ver_1)s, API_VERSION_2023: %(ver_2)s",
{'ver_1': common.API_VERSION,
'ver_2': API_VERSION_2023})
if (new_snap_cpg != old_snap_cpg and
common.API_VERSION < API_VERSION_2023):
# Modify the snap_cpg. This will fail with snapshots. # Modify the snap_cpg. This will fail with snapshots.
LOG.info("Modifying %(volume_name)s snap_cpg from " LOG.info("Modifying %(volume_name)s snap_cpg from "
"%(old_snap_cpg)s to %(new_snap_cpg)s.", "%(old_snap_cpg)s to %(new_snap_cpg)s.",

View File

@ -0,0 +1,4 @@
fixes:
- |
HPE 3PAR driver `Bug #2015746 <https://bugs.launchpad.net/cinder/+bug/2015746>`_:
Fixed: minor code changes to work with new wsapi.