Merge "[Pure Storage] Add capacity based backend QoS options"
This commit is contained in:
@@ -1198,10 +1198,14 @@ MPS_REFS = ValidResponse(200, None, 3,
|
||||
DotNotation(MANAGEABLE_PURE_SNAP_REFS[2])], {})
|
||||
|
||||
# unit for maxBWS is MB
|
||||
QOS_IOPS_BWS = {"maxIOPS": "100", "maxBWS": "1"}
|
||||
QOS_IOPS_BWS_2 = {"maxIOPS": "1000", "maxBWS": "10"}
|
||||
QOS_INVALID = {"maxIOPS": "100", "maxBWS": str(512 * 1024 + 1)}
|
||||
QOS_ZEROS = {"maxIOPS": "0", "maxBWS": "0"}
|
||||
QOS_IOPS_BWS = {"maxIOPS": "100", "maxBWS": "1",
|
||||
"maxIOPS_per_GB": "0", "maxBWS_per_GB": "0"}
|
||||
QOS_IOPS_BWS_2 = {"maxIOPS": "1000", "maxBWS": "10",
|
||||
"maxIOPS_per_GB": "0", "maxBWS_per_GB": "0"}
|
||||
QOS_INVALID = {"maxIOPS": "100", "maxBWS": str(512 * 1024 + 1),
|
||||
"maxIOPS_per_GB": "0", "maxBWS_per_GB": "0"}
|
||||
QOS_ZEROS = {"maxIOPS": "0", "maxBWS": "0",
|
||||
"maxIOPS_per_GB": "0", "maxBWS_per_GB": "0"}
|
||||
QOS_IOPS = {"maxIOPS": "100"}
|
||||
QOS_BWS = {"maxBWS": "1"}
|
||||
MAX_IOPS = 100000000
|
||||
@@ -2132,11 +2136,14 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
||||
@mock.patch(DRIVER_PATH + ".flasharray.VolumePost")
|
||||
@mock.patch(BASE_DRIVER_OBJ + "._add_to_group_if_needed")
|
||||
@mock.patch(BASE_DRIVER_OBJ + "._get_replication_type_from_vol_type")
|
||||
def test_create_cloned_volume(self, mock_get_replication_type,
|
||||
@mock.patch.object(volume_types, 'get_volume_type')
|
||||
def test_create_cloned_volume(self, mock_get_volume_type,
|
||||
mock_get_replication_type,
|
||||
mock_add_to_group,
|
||||
mock_fa):
|
||||
vol, vol_name = self.new_fake_vol(set_provider_id=False)
|
||||
src_vol, src_name = self.new_fake_vol()
|
||||
mock_get_volume_type.return_value = vol.volume_type
|
||||
mock_data = self.array.flasharray.VolumePost(names=[vol_name],
|
||||
source=
|
||||
pure.flasharray.
|
||||
@@ -2178,7 +2185,9 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
||||
@mock.patch(BASE_DRIVER_OBJ + "._get_qos_settings")
|
||||
@mock.patch(BASE_DRIVER_OBJ + ".set_qos")
|
||||
@mock.patch(DRIVER_PATH + ".flasharray.VolumePost")
|
||||
@mock.patch.object(volume_types, 'get_volume_type')
|
||||
def test_create_cloned_volume_qos(self, qos_info,
|
||||
mock_get_volume_type,
|
||||
mock_fa,
|
||||
mock_qos,
|
||||
mock_qos_specs):
|
||||
@@ -2190,6 +2199,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
||||
vol, vol_name = self.new_fake_vol(set_provider_id=False)
|
||||
src_vol, src_name = self.new_fake_vol(spec={"size": 1},
|
||||
type_qos_specs_id=qos.id)
|
||||
mock_get_volume_type.return_value = vol.volume_type
|
||||
mock_data = self.array.flasharray.VolumePost(names=[vol_name],
|
||||
source=
|
||||
pure.flasharray.
|
||||
@@ -2200,10 +2210,16 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
||||
self.mock_object(self.driver, '_get_volume_type_extra_spec',
|
||||
return_value={})
|
||||
self.driver.create_cloned_volume(vol, src_vol)
|
||||
self.driver.set_qos.assert_called_with(self.array, vol_name, qos)
|
||||
self.driver.set_qos.assert_called_with(self.array,
|
||||
vol_name,
|
||||
vol["size"],
|
||||
qos)
|
||||
|
||||
@mock.patch(DRIVER_PATH + ".flasharray.VolumePost")
|
||||
def test_create_cloned_volume_sync_rep(self, mock_fa):
|
||||
@mock.patch.object(volume_types, 'get_volume_type')
|
||||
def test_create_cloned_volume_sync_rep(self,
|
||||
mock_get_volume_type,
|
||||
mock_fa):
|
||||
repl_extra_specs = {
|
||||
'replication_type': '<in> sync',
|
||||
'replication_enabled': '<is> true',
|
||||
@@ -2212,6 +2228,7 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
||||
type_extra_specs=repl_extra_specs)
|
||||
vol, vol_name = self.new_fake_vol(set_provider_id=False,
|
||||
type_extra_specs=repl_extra_specs)
|
||||
mock_get_volume_type.return_value = vol.volume_type
|
||||
mock_data = self.array.flasharray.VolumePost(names=[vol_name],
|
||||
source=pure.flasharray.
|
||||
reference(name=src_name))
|
||||
@@ -2228,11 +2245,14 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
||||
@mock.patch(DRIVER_PATH + ".flasharray.VolumePost")
|
||||
@mock.patch(BASE_DRIVER_OBJ + "._add_to_group_if_needed")
|
||||
@mock.patch(BASE_DRIVER_OBJ + "._get_replication_type_from_vol_type")
|
||||
def test_create_cloned_volume_and_extend(self, mock_get_replication_type,
|
||||
@mock.patch.object(volume_types, 'get_volume_type')
|
||||
def test_create_cloned_volume_and_extend(self, mock_get_volume_type,
|
||||
mock_get_replication_type,
|
||||
mock_add_to_group,
|
||||
mock_fa, mock_extend):
|
||||
vol, vol_name = self.new_fake_vol(set_provider_id=False,
|
||||
spec={"size": 2})
|
||||
mock_get_volume_type.return_value = vol.volume_type
|
||||
src_vol, src_name = self.new_fake_vol()
|
||||
mock_get_replication_type.return_value = None
|
||||
mock_data = self.array.flasharray.VolumePost(names=[vol_name],
|
||||
@@ -2252,9 +2272,12 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
||||
# Tests cloning a volume that is part of a consistency group
|
||||
@mock.patch(BASE_DRIVER_OBJ + "._add_to_group_if_needed")
|
||||
@mock.patch(BASE_DRIVER_OBJ + "._get_replication_type_from_vol_type")
|
||||
def test_create_cloned_volume_with_cgroup(self, mock_get_replication_type,
|
||||
@mock.patch.object(volume_types, 'get_volume_type')
|
||||
def test_create_cloned_volume_with_cgroup(self, mock_get_volume_type,
|
||||
mock_get_replication_type,
|
||||
mock_add_to_group):
|
||||
vol, vol_name = self.new_fake_vol(set_provider_id=False)
|
||||
mock_get_volume_type.return_value = vol.volume_type
|
||||
group = fake_group.fake_group_obj(mock.MagicMock())
|
||||
self.driver._get_volume_type_extra_spec = mock.Mock(
|
||||
return_value={})
|
||||
@@ -2625,8 +2648,10 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
||||
self.array.delete_connections.assert_not_called()
|
||||
|
||||
@mock.patch(DRIVER_PATH + ".flasharray.VolumePatch")
|
||||
def test_extend_volume(self, mock_fa):
|
||||
@mock.patch.object(volume_types, 'get_volume_type')
|
||||
def test_extend_volume(self, mock_get_volume_type, mock_fa):
|
||||
vol, vol_name = self.new_fake_vol(spec={"size": 1})
|
||||
mock_get_volume_type.return_value = vol.volume_type
|
||||
mock_data = self.flasharray.VolumePatch(provisioned=3 * units.Gi)
|
||||
self.driver.extend_volume(vol, 3)
|
||||
self.array.patch_volumes.\
|
||||
@@ -4511,7 +4536,10 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
||||
Reference(name=vol_name),
|
||||
name=vol_name,
|
||||
qos={'maxIOPS': 100,
|
||||
'maxBWS': 1048576})
|
||||
'maxBWS': 1048576,
|
||||
'maxBWS': 1048576,
|
||||
'maxIOPS_per_GB': 0,
|
||||
'maxBWS_per_GB': 0})
|
||||
mock_fa.return_value = mock_data
|
||||
|
||||
mock_get_volume_type.return_value = vol.volume_type
|
||||
@@ -4523,9 +4551,11 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
||||
with_default_protection=
|
||||
False,
|
||||
volume=mock_data)
|
||||
self.driver.set_qos.assert_called_with(self.array, vol_name,
|
||||
self.driver.set_qos.assert_called_with(self.array, vol_name, 1,
|
||||
{'maxIOPS': 100,
|
||||
'maxBWS': 1048576})
|
||||
'maxBWS': 1048576,
|
||||
'maxIOPS_per_GB': 0,
|
||||
'maxBWS_per_GB': 0})
|
||||
self.assertFalse(self.array.extend_volume.called)
|
||||
mock_add_to_group.assert_called_once_with(vol, vol_name)
|
||||
self.assert_error_propagates(
|
||||
@@ -4552,9 +4582,11 @@ class PureBaseVolumeDriverTestCase(PureBaseSharedDriverTestCase):
|
||||
self.array.get_volumes.return_value = MPV
|
||||
|
||||
self.driver.manage_existing(vol, volume_ref)
|
||||
mock_qos.assert_called_with(self.array, vol_name,
|
||||
mock_qos.assert_called_with(self.array, vol_name, 3,
|
||||
{'maxIOPS': 100,
|
||||
'maxBWS': 1048576})
|
||||
'maxBWS': 1048576,
|
||||
'maxIOPS_per_GB': 0,
|
||||
'maxBWS_per_GB': 0})
|
||||
|
||||
@mock.patch(DRIVER_PATH + ".flasharray.VolumePatch")
|
||||
def test_retype_qos(self, mock_fa):
|
||||
|
@@ -234,7 +234,7 @@ class PureBaseVolumeDriver(san.SanDriver):
|
||||
"""Performs volume management on Pure Storage FlashArray."""
|
||||
|
||||
SUPPORTS_ACTIVE_ACTIVE = True
|
||||
PURE_QOS_KEYS = ['maxIOPS', 'maxBWS']
|
||||
PURE_QOS_KEYS = ['maxIOPS', 'maxBWS', 'maxIOPS_per_GB', 'maxBWS_per_GB']
|
||||
# ThirdPartySystems wiki page
|
||||
CI_WIKI_NAME = "Pure_Storage_CI"
|
||||
|
||||
@@ -352,7 +352,19 @@ class PureBaseVolumeDriver(san.SanDriver):
|
||||
target_array)
|
||||
|
||||
@pure_driver_debug_trace
|
||||
def set_qos(self, array, vol_name, qos):
|
||||
def set_qos(self, array, vol_name, vol_size, qos):
|
||||
# max_IOPS and max_BWS override the per GB IOPS and BW values if
|
||||
# both are provided. If only a per GB value is provided then
|
||||
# we must ensure, based on volume size, the IOPS or BW values
|
||||
# do not exceed the maximum limits for these values allowed per
|
||||
# volume.
|
||||
if qos['maxIOPS'] == 0 and qos['maxIOPS_per_GB']:
|
||||
qos['maxIOPS'] = min(MAX_IOPS,
|
||||
int(qos['maxIOPS_per_GB']) * vol_size)
|
||||
if qos['maxBWS'] == 0 and qos['maxBWS_per_GB']:
|
||||
qos['maxBWS'] = min(MAX_BWS,
|
||||
int(qos['maxBWS_per_GB']) * vol_size)
|
||||
|
||||
if qos['maxIOPS'] == 0 and qos['maxBWS'] == 0:
|
||||
array.patch_volumes(names=[vol_name],
|
||||
volume=flasharray.VolumePatch(
|
||||
@@ -450,6 +462,19 @@ class PureBaseVolumeDriver(san.SanDriver):
|
||||
|
||||
@pure_driver_debug_trace
|
||||
def create_with_qos(self, array, vol_name, vol_size, qos):
|
||||
# max_IOPS and max_BWS override the per GB IOPS and BW values if
|
||||
# both are provided. Iif only a per GB value is provided then
|
||||
# we must ensure, based on volume size, the IOPS or BW values
|
||||
# do not exceed the maximum limits for these values allowed per
|
||||
# volume.
|
||||
gb_size = vol_size / units.Gi
|
||||
if qos['maxIOPS'] == 0 and qos['maxIOPS_per_GB']:
|
||||
qos['maxIOPS'] = min(MAX_IOPS,
|
||||
int(qos['maxIOPS_per_GB']) * gb_size)
|
||||
if qos['maxBWS'] == 0 and qos['maxBWS_per_GB']:
|
||||
qos['maxBWS'] = min(MAX_BWS,
|
||||
int(qos['maxBWS_per_GB']) * gb_size)
|
||||
|
||||
if self._array.safemode:
|
||||
if qos['maxIOPS'] == 0 and qos['maxBWS'] == 0:
|
||||
array.post_volumes(names=[vol_name],
|
||||
@@ -832,7 +857,7 @@ class PureBaseVolumeDriver(san.SanDriver):
|
||||
snapshot["volume_size"],
|
||||
volume["size"])
|
||||
if qos is not None:
|
||||
self.set_qos(current_array, vol_name, qos)
|
||||
self.set_qos(current_array, vol_name, snapshot["volume_size"], qos)
|
||||
else:
|
||||
current_array.patch_volumes(names=[vol_name],
|
||||
volume=flasharray.VolumePatch(
|
||||
@@ -948,11 +973,15 @@ class PureBaseVolumeDriver(san.SanDriver):
|
||||
vol_name,
|
||||
src_vref["size"],
|
||||
volume["size"])
|
||||
# Check if the volume_type has QoS settings and if so
|
||||
# apply them to the newly created volume
|
||||
qos = self._get_qos_settings(volume.volume_type)
|
||||
if qos:
|
||||
self.set_qos(current_array, vol_name, qos)
|
||||
type_id = volume.get('volume_type_id')
|
||||
ctxt = context.get_admin_context()
|
||||
if type_id is not None:
|
||||
volume_type = volume_types.get_volume_type(ctxt, type_id)
|
||||
# Check if the volume_type has QoS settings and if so
|
||||
# apply them to the newly created volume
|
||||
qos = self._get_qos_settings(volume_type)
|
||||
if qos is not None:
|
||||
self.set_qos(current_array, vol_name, volume["size"], qos)
|
||||
|
||||
return self._setup_volume(current_array, volume, vol_name)
|
||||
|
||||
@@ -1397,17 +1426,25 @@ class PureBaseVolumeDriver(san.SanDriver):
|
||||
return thin_provisioning
|
||||
|
||||
@pure_driver_debug_trace
|
||||
def extend_volume(self, volume, new_size):
|
||||
def extend_volume(self, volume, new_size_gb):
|
||||
"""Extend volume to new_size."""
|
||||
|
||||
# Get current array in case we have failed over via replication.
|
||||
current_array = self._get_current_array()
|
||||
|
||||
vol_name = self._get_vol_name(volume)
|
||||
new_size = new_size * units.Gi
|
||||
new_size = new_size_gb * units.Gi
|
||||
current_array.patch_volumes(names=[vol_name],
|
||||
volume=flasharray.VolumePatch(
|
||||
provisioned=new_size))
|
||||
ctxt = context.get_admin_context()
|
||||
type_id = volume.get('volume_type_id')
|
||||
if type_id is not None:
|
||||
volume_type = volume_types.get_volume_type(ctxt, type_id)
|
||||
LOG.debug("QOS volume type: '%s'", volume_type)
|
||||
qos = self._get_qos_settings(volume_type)
|
||||
if qos is not None:
|
||||
self.set_qos(current_array, vol_name, new_size, qos)
|
||||
|
||||
def _add_volume_to_consistency_group(self, group, vol_name):
|
||||
pgroup_name = self._get_pgroup_name(group)
|
||||
@@ -1969,7 +2006,8 @@ class PureBaseVolumeDriver(san.SanDriver):
|
||||
qos = None
|
||||
qos = self._get_qos_settings(volume.volume_type)
|
||||
if qos:
|
||||
self.set_qos(current_array, new_vol_name, qos)
|
||||
vol_size = int(volume_data.provisioned / units.Gi)
|
||||
self.set_qos(current_array, new_vol_name, vol_size, qos)
|
||||
volume.provider_id = new_vol_name
|
||||
async_enabled = self._enable_async_replication_if_needed(current_array,
|
||||
volume)
|
||||
@@ -2449,7 +2487,7 @@ class PureBaseVolumeDriver(san.SanDriver):
|
||||
if qos == {}:
|
||||
return None
|
||||
else:
|
||||
# Check set vslues are within limits
|
||||
# Check set values are within limits
|
||||
iops_qos = int(qos.get('maxIOPS', 0))
|
||||
bw_qos = int(qos.get('maxBWS', 0)) * MIN_BWS
|
||||
if iops_qos != 0 and not (MIN_IOPS <= iops_qos <= MAX_IOPS):
|
||||
@@ -2465,6 +2503,8 @@ class PureBaseVolumeDriver(san.SanDriver):
|
||||
|
||||
qos['maxIOPS'] = iops_qos
|
||||
qos['maxBWS'] = bw_qos
|
||||
qos['maxIOPS_per_GB'] = int(qos.get('maxIOPS_per_GB', 0))
|
||||
qos['maxBWS_per_GB'] = int(qos.get('maxBWS_per_GB', 0)) * MIN_BWS
|
||||
return qos
|
||||
|
||||
def _generate_purity_vol_name(self, volume):
|
||||
@@ -2815,7 +2855,7 @@ class PureBaseVolumeDriver(san.SanDriver):
|
||||
qos = self._get_qos_settings(new_type)
|
||||
vol_name = self._generate_purity_vol_name(volume)
|
||||
if qos is not None:
|
||||
self.set_qos(current_array, vol_name, qos)
|
||||
self.set_qos(current_array, vol_name, volume["size"], qos)
|
||||
else:
|
||||
current_array.patch_volumes(names=[vol_name],
|
||||
volume=flasharray.VolumePatch(
|
||||
|
@@ -59,12 +59,27 @@ following capabilities in the OpenStack Block Storage API
|
||||
|
||||
* **maxBWS** - Maximum bandwidth limit in MB/s. Range: 1 - 524288 (512GB/s)
|
||||
|
||||
* **maxIOPS_per_GB** - Maximum number of IOPs allowed for volume based on
|
||||
capacity. Range: 100 - 100M
|
||||
|
||||
* **maxBWS_per_GB** - Maximum bandwidth limit in MB/s based on capacity.
|
||||
Range: 1 - 524288 (512GB/s)
|
||||
|
||||
If both max and per_GB values are provided for a QoS type, the max value will
|
||||
take precedence.
|
||||
|
||||
If the calculated per_GB value for a volume based on capacity is greater
|
||||
than the maximum allowed value, the maximum allowed values will be applied.
|
||||
|
||||
The qos keys above must be created and asscoiated to a volume type. For
|
||||
information on how to set the key-value pairs and associate them with a
|
||||
volume type see the `volume qos
|
||||
<https://docs.openstack.org/python-openstackclient/latest/cli/command-objects/volume-qos.html>`_
|
||||
section in the OpenStack Client command list.
|
||||
|
||||
QoS settings are not applied to any volume in a volume group as these are
|
||||
controlled at the volume group level.
|
||||
|
||||
Configure OpenStack and Purity
|
||||
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
|
||||
|
||||
|
7
releasenotes/notes/pure_per_gb_qos-0b96279d615b81a1.yaml
Normal file
7
releasenotes/notes/pure_per_gb_qos-0b96279d615b81a1.yaml
Normal file
@@ -0,0 +1,7 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
[Pure Storage] Added new QoS spec parameters to support QoS per GB.
|
||||
New spec options are ``maxIOPS_per_GB`` and ``maxBWS_per_GB``. If
|
||||
either of these are provided with the equivalent ``max`` value, the
|
||||
``max`` value will take precedence.
|
Reference in New Issue
Block a user