Merge "HPE: Improved handling of QOS for Alletra MP"
This commit is contained in:
@@ -1241,6 +1241,52 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
|
||||
mock_client.assert_has_calls(expected)
|
||||
self.assertIsNone(return_model)
|
||||
|
||||
def test_qos_alletra_mp_uses_kb_bandwidth(self):
|
||||
"""Ensure Alletra MP QOS uses kB/s for bandwidth limits.
|
||||
|
||||
When the backend API version indicates Alletra MP, maxBWS should be
|
||||
converted using a 1000-based factor into bwMaxLimitKB, and only the
|
||||
max limits should be sent to createQoSRules.
|
||||
"""
|
||||
|
||||
# Create a common driver instance with Alletra MP API_VERSION.
|
||||
conf = self.setup_configuration()
|
||||
common = hpecommon.HPE3PARCommon(conf)
|
||||
mock_client = mock.Mock()
|
||||
common.client = mock_client
|
||||
common.API_VERSION = hpecommon.API_VERSION_2025
|
||||
|
||||
# Use the standard QoS specs defined for these tests.
|
||||
common._set_qos_rule(self.QOS_SPECS, 'vvs-test')
|
||||
|
||||
mock_client.createQoSRules.assert_called_once_with(
|
||||
'vvs-test',
|
||||
{'ioMaxLimit': 1000, 'bwMaxLimitKB': 50000})
|
||||
|
||||
def test_qos_alletra_mp_invalid_without_max_limits(self):
|
||||
"""Alletra MP QOS requires at least one max limit.
|
||||
|
||||
For Alletra MP backends, _set_qos_rule should raise InvalidInput
|
||||
if both maxIOPS and maxBWS are omitted from the QOS specs.
|
||||
"""
|
||||
|
||||
conf = self.setup_configuration()
|
||||
common = hpecommon.HPE3PARCommon(conf)
|
||||
mock_client = mock.Mock()
|
||||
common.client = mock_client
|
||||
# Force Alletra MP behavior based on API version.
|
||||
common.API_VERSION = hpecommon.API_VERSION_2025
|
||||
|
||||
# No maxIOPS or maxBWS provided; invalid for Alletra MP.
|
||||
invalid_qos = {}
|
||||
|
||||
self.assertRaises(exception.InvalidInput,
|
||||
common._set_qos_rule,
|
||||
invalid_qos,
|
||||
'vvs-test')
|
||||
|
||||
mock_client.createQoSRules.assert_not_called()
|
||||
|
||||
@mock.patch.object(volume_types, 'get_volume_type')
|
||||
def test_create_volume_replicated_periodic(self, _mock_volume_types):
|
||||
# setup_mock_client drive with default configuration
|
||||
@@ -2450,7 +2496,7 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
|
||||
mock.call.createQoSRules(
|
||||
'vvs-0DM4qZEVSKON-DXN-NwVpw',
|
||||
{'ioMinGoal': 100, 'ioMaxLimit': 1000,
|
||||
'bwMinGoalKB': 25600, 'bwMaxLimitKB': 51200,
|
||||
'bwMinGoalKB': 25000, 'bwMaxLimitKB': 50000,
|
||||
'priority': 3,
|
||||
'latencyGoal': 25}
|
||||
),
|
||||
@@ -2816,6 +2862,52 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
|
||||
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
@ddt.data('volume', 'volume_name_id')
|
||||
def test_create_cloned_volume_wsapi_2023_no_comment(self, volume_attr):
|
||||
src_vref = getattr(self, volume_attr)
|
||||
vol_name = getattr(self, volume_attr.upper() + '_3PAR_NAME')
|
||||
|
||||
mock_client = self.setup_driver(wsapi_version=self.wsapi_version_2023)
|
||||
mock_client.getVolume.return_value = {'name': mock.ANY}
|
||||
mock_client.copyVolume.return_value = {'taskid': 1}
|
||||
mock_client.getStorageSystemInfo.return_value = {
|
||||
'id': self.CLIENT_ID,
|
||||
'serialNumber': 'XXXXXXX'}
|
||||
|
||||
with mock.patch.object(hpecommon.HPE3PARCommon,
|
||||
'_create_client') as mock_create_client:
|
||||
mock_create_client.return_value = mock_client
|
||||
|
||||
volume = {'name': HPE3PARBaseDriver.VOLUME_NAME,
|
||||
'id': HPE3PARBaseDriver.CLONE_ID,
|
||||
'display_name': 'Foo Volume',
|
||||
'size': 2,
|
||||
'host': volume_utils.append_host(self.FAKE_HOST,
|
||||
HPE3PAR_CPG2),
|
||||
'source_volid': src_vref.id}
|
||||
|
||||
common = self.driver._login()
|
||||
model_update = common.create_cloned_volume(volume, src_vref)
|
||||
|
||||
self.assertIsNone(model_update)
|
||||
|
||||
snap_name = mock.ANY
|
||||
optional = mock.ANY
|
||||
optional_fields = {'tpvv': True,
|
||||
'tdvv': False,
|
||||
'online': True}
|
||||
|
||||
expected = [
|
||||
mock.call.createSnapshot(snap_name, vol_name, optional),
|
||||
mock.call.getVolume(snap_name),
|
||||
mock.call.copyVolume(
|
||||
snap_name,
|
||||
'osv-0DM4qZEVSKON-AAAAAAAAA',
|
||||
HPE3PAR_CPG2,
|
||||
optional_fields)]
|
||||
|
||||
mock_client.assert_has_calls(expected)
|
||||
|
||||
@mock.patch.object(volume_types, 'get_volume_type')
|
||||
def test_clone_volume_with_vvs(self, _mock_volume_types):
|
||||
# Setup_mock_client drive with default configuration
|
||||
@@ -4688,8 +4780,8 @@ class TestHPE3PARDriverBase(HPE3PARBaseDriver):
|
||||
mock.call.createQoSRules(
|
||||
vvs_matcher,
|
||||
{'ioMinGoal': 100, 'ioMaxLimit': 1000,
|
||||
'bwMinGoalKB': 25600, 'priority': 1, 'latencyGoal': 25,
|
||||
'bwMaxLimitKB': 51200}),
|
||||
'bwMinGoalKB': 25000, 'priority': 1, 'latencyGoal': 25,
|
||||
'bwMaxLimitKB': 50000}),
|
||||
mock.call.addVolumeToVolumeSet(vvs_matcher, osv_matcher),
|
||||
mock.call.tuneVolume(
|
||||
osv_matcher, 1,
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
# (c) Copyright 2012-2016 Hewlett Packard Enterprise Development LP
|
||||
# (c) Copyright 2012-2026 Hewlett Packard Enterprise Development LP
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@@ -316,11 +316,12 @@ class HPE3PARCommon(object):
|
||||
4.0.25 - Update the calculation of free_capacity
|
||||
4.0.26 - Added comment for cloned volumes. Bug #2062524
|
||||
4.0.27 - Skip license check for new WSAPI (of 2025). Bug #2119709
|
||||
4.0.28 - Improved QOS handling for Alletra MP. Bug #2143385
|
||||
|
||||
|
||||
"""
|
||||
|
||||
VERSION = "4.0.27"
|
||||
VERSION = "4.0.28"
|
||||
|
||||
stats = {}
|
||||
|
||||
@@ -2021,6 +2022,15 @@ class HPE3PARCommon(object):
|
||||
else:
|
||||
return default
|
||||
|
||||
def _is_alletra_mp(self):
|
||||
"""Check if the backend is AlletraMP based on WSAPI version.
|
||||
|
||||
AlletraMP uses WSAPI version >= 100500000 (API_VERSION_2025).
|
||||
|
||||
:returns: True if AlletraMP, False otherwise
|
||||
"""
|
||||
return self.API_VERSION >= API_VERSION_2025
|
||||
|
||||
def _get_qos_by_volume_type(self, volume_type):
|
||||
qos = {}
|
||||
qos_specs_id = volume_type.get('qos_specs_id')
|
||||
@@ -2062,37 +2072,76 @@ class HPE3PARCommon(object):
|
||||
latency = self._get_qos_value(qos, 'latency')
|
||||
priority = self._get_qos_value(qos, 'priority', 'normal')
|
||||
|
||||
# Check if backend is AlletraMP
|
||||
is_alletra_mp = self._is_alletra_mp()
|
||||
|
||||
qosRule = {}
|
||||
if min_io:
|
||||
qosRule['ioMinGoal'] = int(min_io)
|
||||
if max_io is None:
|
||||
qosRule['ioMaxLimit'] = int(min_io)
|
||||
if max_io:
|
||||
qosRule['ioMaxLimit'] = int(max_io)
|
||||
if min_io is None:
|
||||
qosRule['ioMinGoal'] = int(max_io)
|
||||
if min_bw:
|
||||
qosRule['bwMinGoalKB'] = int(min_bw) * units.Ki
|
||||
if max_bw is None:
|
||||
qosRule['bwMaxLimitKB'] = int(min_bw) * units.Ki
|
||||
if max_bw:
|
||||
qosRule['bwMaxLimitKB'] = int(max_bw) * units.Ki
|
||||
if min_bw is None:
|
||||
qosRule['bwMinGoalKB'] = int(max_bw) * units.Ki
|
||||
if latency:
|
||||
# latency could be values like 2, 5, etc or
|
||||
# small values like 0.1, 0.02, etc.
|
||||
# we are converting to float so that 0.1 doesn't become 0
|
||||
latency = float(latency)
|
||||
if latency >= 1:
|
||||
# by default, latency in millisecs
|
||||
qosRule['latencyGoal'] = int(latency)
|
||||
else:
|
||||
# latency < 1 Eg. 0.1, 0.02, etc
|
||||
# convert latency to microsecs
|
||||
qosRule['latencyGoaluSecs'] = int(latency * 1000)
|
||||
if priority:
|
||||
qosRule['priority'] = self.qos_priority_level.get(priority.lower())
|
||||
|
||||
# For Alletra MP, ioMinGoal, bwMinGoalKB, latencyGoal, and priority
|
||||
# are deprecated. Only use max limits.
|
||||
if is_alletra_mp:
|
||||
# For Alletra MP, at least one of maxIOPS or maxBWS must be
|
||||
# provided.
|
||||
if max_io is None and max_bw is None:
|
||||
err = _(
|
||||
"For Alletra MP, at least one of maxIOPS or maxBWS "
|
||||
"QOS parameters must be provided.")
|
||||
LOG.error(err)
|
||||
raise exception.InvalidInput(reason=err)
|
||||
# Alletra MP: Only set max limits, min goals are deprecated
|
||||
if max_io:
|
||||
qosRule['ioMaxLimit'] = int(max_io)
|
||||
if max_bw:
|
||||
# Alletra MP expects bandwidth in kB/s
|
||||
qosRule['bwMaxLimitKB'] = int(max_bw) * units.k
|
||||
if min_io:
|
||||
LOG.warning(
|
||||
"minIOPS QOS parameter is deprecated for "
|
||||
"Alletra MP and will be ignored.")
|
||||
if min_bw:
|
||||
LOG.warning(
|
||||
"minBWS QOS parameter is deprecated for "
|
||||
"Alletra MP and will be ignored.")
|
||||
if latency:
|
||||
LOG.warning("latency QOS parameter is deprecated for "
|
||||
"Alletra MP and will be ignored.")
|
||||
if priority:
|
||||
LOG.warning("priority QOS parameter is deprecated for "
|
||||
"Alletra MP and will be ignored.")
|
||||
else:
|
||||
# 3PAR/Primera/Alletra 9k: Use traditional QoS parameters
|
||||
if min_io:
|
||||
qosRule['ioMinGoal'] = int(min_io)
|
||||
if max_io is None:
|
||||
qosRule['ioMaxLimit'] = int(min_io)
|
||||
if max_io:
|
||||
qosRule['ioMaxLimit'] = int(max_io)
|
||||
if min_io is None:
|
||||
qosRule['ioMinGoal'] = int(max_io)
|
||||
if min_bw:
|
||||
# 3PAR/Primera/Alletra 9k expect bandwidth in kB/s
|
||||
qosRule['bwMinGoalKB'] = int(min_bw) * units.k
|
||||
if max_bw is None:
|
||||
qosRule['bwMaxLimitKB'] = int(min_bw) * units.k
|
||||
if max_bw:
|
||||
qosRule['bwMaxLimitKB'] = int(max_bw) * units.k
|
||||
if min_bw is None:
|
||||
qosRule['bwMinGoalKB'] = int(max_bw) * units.k
|
||||
if latency:
|
||||
# latency could be values like 2, 5, etc or
|
||||
# small values like 0.1, 0.02, etc.
|
||||
# we are converting to float so that 0.1 doesn't become 0
|
||||
latency = float(latency)
|
||||
if latency >= 1:
|
||||
# by default, latency in millisecs
|
||||
qosRule['latencyGoal'] = int(latency)
|
||||
else:
|
||||
# latency < 1 Eg. 0.1, 0.02, etc
|
||||
# convert latency to microsecs
|
||||
qosRule['latencyGoaluSecs'] = int(latency * 1000)
|
||||
if priority:
|
||||
qosRule['priority'] = (
|
||||
self.qos_priority_level.get(priority.lower()))
|
||||
|
||||
try:
|
||||
self.client.createQoSRules(vvs_name, qosRule)
|
||||
@@ -2659,8 +2708,10 @@ class HPE3PARCommon(object):
|
||||
LOG.info("array version: %(ver)s",
|
||||
{'ver': self.API_VERSION})
|
||||
comment_line = None
|
||||
if self.API_VERSION >= 40600000:
|
||||
# comment can be added
|
||||
if self.API_VERSION >= 40600000 and \
|
||||
self.API_VERSION < API_VERSION_2023:
|
||||
# comment added for online copy only for
|
||||
# Alletra9k version
|
||||
comments = {'volume_id': volume['id'],
|
||||
'name': volume['name'],
|
||||
'type': 'OpenStack'}
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
fixes:
|
||||
- |
|
||||
HPE driver `Bug #2143385 <https://bugs.launchpad.net/cinder/+bug/2143385>`_:
|
||||
Fixed: Handled deprecation of QOS parameters and comments for
|
||||
online copy. While passing QOS params use units.k(1000) as multiplier,
|
||||
as backend is expecting values in KB.
|
||||
Reference in New Issue
Block a user