Dell PowerStore driver: QoS support

Added QoS support for Dell PowerStore driver.

Implements: blueprint dell-powerstore-qos
Change-Id: Id8f9b78671047eb24a4d0fa3a55a4fdb584d71db
This commit is contained in:
Nilesh Thathagar 2024-06-11 06:50:17 +00:00
parent d2e1b64681
commit bf52e877e4
11 changed files with 622 additions and 6 deletions

View File

@ -18,8 +18,11 @@ import uuid
import ddt
from cinder import exception
from cinder.tests.unit import test
from cinder.tests.unit.volume.drivers.dell_emc.powerstore import MockResponse
from cinder.volume.drivers.dell_emc.powerstore import (
exception as powerstore_exception)
from cinder.volume.drivers.dell_emc.powerstore import client
@ -57,6 +60,26 @@ NVME_IP_POOL_RESP = [
}
]
QOS_IO_RULE_PARAMS = {
"name": "io-rule-6b6e5489-4b5b-4468-a1f7-32cec2ffa3bf",
"type": "Absolute",
"max_iops": "200",
"max_bw": "18000",
"burst_percentage": "50"
}
QOS_POLICY_PARAMS = {
"name": "qos-policy-6b6e5489-4b5b-4468-a1f7-32cec2ffa3bf",
"io_limit_rule_id": "9beb10ff-a00c-4d88-a7d9-692be2b3073f"
}
QOS_UPDATE_IO_RULE_PARAMS = {
"type": "Absolute",
"max_iops": "500",
"max_bw": "225000",
"burst_percentage": "89"
}
@ddt.ddt
class TestClient(test.TestCase):
@ -100,3 +123,157 @@ class TestClient(test.TestCase):
)
self.assertEqual(self.client.get_array_version(),
"3.0.0.0")
@mock.patch("requests.request")
def test_get_qos_policy_id_by_name(self, mock_request):
mock_request.return_value = MockResponse(
content=[
{
"id": "d69f7131-4617-4bae-89f8-a540a6bda94b",
}
],
rc=200
)
self.assertEqual(
self.client.get_qos_policy_id_by_name("qos-"
"policy-6b6e5489"
"-4b5b-4468-a1f7-"
"32cec2ffa3bf"),
"d69f7131-4617-4bae-89f8-a540a6bda94b")
@mock.patch("requests.request")
def test_get_qos_policy_id_by_name_exception(self, mock_request):
mock_request.return_value = MockResponse(rc=400)
self.assertRaises(
exception.VolumeBackendAPIException,
self.client.get_qos_policy_id_by_name,
"qos-policy-6b6e5489-4b5b-4468-a1f7-32cec2ffa3bf")
@mock.patch("requests.request")
def test_create_qos_io_rule(self, mock_request):
mock_request.return_value = MockResponse(
content={
"id": "9beb10ff-a00c-4d88-a7d9-692be2b3073f"
},
rc=200
)
self.assertEqual(
self.client.create_qos_io_rule(QOS_IO_RULE_PARAMS),
"9beb10ff-a00c-4d88-a7d9-692be2b3073f")
@mock.patch("requests.request")
def test_create_duplicate_qos_io_rule(self, mock_request):
mock_request.return_value = MockResponse(
content={
"messages": [
{
"code": "0xE0A0E0010009",
"severity": "Error",
"message_l10n": "The rule name "
"io-rule-9899a65f-70fe-46c9-8f6c-22625c7e19df "
"is already used by another rule. "
"It needs to be unique (case-insensitive). "
"Please use a different name.",
"arguments": [
"io-rule-6b6e5489-4b5b-4468-a1f7-32cec2ffa3bf"
]
}
]
},
rc=400
)
self.assertRaises(
powerstore_exception.DellPowerStoreQoSIORuleExists,
self.client.create_qos_io_rule,
QOS_IO_RULE_PARAMS)
@mock.patch("requests.request")
def test_create_duplicate_qos_io_rule_with_unexpected_error(
self, mock_request):
mock_request.return_value = MockResponse(
content={
"messages": [
{
"code": "0xE0101001000C",
"severity": "Error",
"message_l10n": "The system encountered unexpected "
"backend errors. "
"Please contact support."
}
]
},
rc=400
)
self.assertRaises(
powerstore_exception.DellPowerStoreQoSIORuleExists,
self.client.create_qos_io_rule,
QOS_IO_RULE_PARAMS)
@mock.patch("requests.request")
def test_create_qos_policy(self, mock_request):
mock_request.return_value = MockResponse(
content={
"id": "d69f7131-4617-4bae-89f8-a540a6bda94b",
},
rc=200
)
self.assertEqual(
self.client.create_qos_policy(QOS_POLICY_PARAMS),
"d69f7131-4617-4bae-89f8-a540a6bda94b")
@mock.patch("requests.request")
def test_create_duplicate_qos_policy(self, mock_request):
mock_request.return_value = MockResponse(
content={
"messages": [
{
"code": "0xE02020010004",
"severity": "Error",
"message_l10n": "The new policy name qos-policy-"
"6b6e5489-4b5b-4468-a1f7-32cec2ffa3bf "
"is in use. It must be unique "
"regardless of character cases.",
"arguments": [
"qos-policy-6b6e5489-4b5b-4468-a1f7-32cec2ffa3bf"
]
}
]
},
rc=400
)
self.assertRaises(
powerstore_exception.DellPowerStoreQoSPolicyExists,
self.client.create_qos_policy,
QOS_POLICY_PARAMS)
@mock.patch("requests.request")
def test_update_volume_with_qos_policy(self, mock_request):
mock_request.return_value = MockResponse(rc=200)
self.client.update_volume_with_qos_policy(
"fake_volume_id",
"qos-policy-6b6e5489-4b5b-4468-a1f7-32cec2ffa3bf")
mock_request.assert_called_once()
@mock.patch("requests.request")
def test_update_volume_with_qos_policy_exception(self, mock_request):
mock_request.return_value = MockResponse(rc=400)
self.assertRaises(exception.VolumeBackendAPIException,
self.client.update_volume_with_qos_policy,
"fake_volume_id",
"qos-policy-6b6e5489-4b5b-4468-a1f7-32cec2ffa3bf")
@mock.patch("requests.request")
def test_update_qos_io_rule(self, mock_request):
mock_request.return_value = MockResponse(rc=200)
self.client.update_qos_io_rule(
"io-rule-6b6e5489-4b5b-4468-a1f7-32cec2ffa3bf",
QOS_UPDATE_IO_RULE_PARAMS)
mock_request.assert_called_once()
@mock.patch("requests.request")
def test_update_qos_io_rule_exception(self, mock_request):
mock_request.return_value = MockResponse(rc=400)
self.assertRaises(exception.VolumeBackendAPIException,
self.client.update_qos_io_rule,
"io-rule-6b6e5489-4b5b-4468-a1f7-32cec2ffa3bf",
QOS_UPDATE_IO_RULE_PARAMS)

View File

@ -22,8 +22,29 @@ from cinder.tests.unit import fake_volume
from cinder.tests.unit.volume.drivers.dell_emc import powerstore
from cinder.volume.drivers.dell_emc.powerstore import utils
FAKE_HOST = {
"name": "fake_host",
"id": "fake_id"
}
class TestVolumeAttachDetach(powerstore.TestPowerStoreDriver):
QOS_SPECS = {
'qos_specs': {
'name': 'powerstore_qos',
'id': 'd8c88f5a-4c6f-4f89-97c5-da1ef059006e',
'created_at': 'fake_date',
'consumer': 'back-end',
'specs': {
'max_bw': '104857600',
'max_iops': '500',
'bandwidth_limit_type': 'Absolute',
'burst_percentage': '50'
}
}
}
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_chap_config")
def setUp(self, mock_chap):
@ -41,7 +62,8 @@ class TestVolumeAttachDetach(powerstore.TestPowerStoreDriver):
self.context,
host="host@backend",
provider_id="fake_id",
size=8
size=8,
volume_type_id="fake_volume_type_id"
)
self.volume.volume_attachment = (
volume_attachment.VolumeAttachmentList()
@ -208,3 +230,126 @@ class TestVolumeAttachDetach(powerstore.TestPowerStoreDriver):
self.fake_connector)
mock_filter_hosts.assert_called_once()
mock_detach.assert_called_once()
@mock.patch('cinder.volume.volume_types.'
'get_volume_type_qos_specs',
return_value=QOS_SPECS)
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.attach_volume_to_host")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_volume_lun")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_array_version",
return_value='4.0')
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_qos_policy_id_by_name",
return_value=None)
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.create_qos_io_rule")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.create_qos_policy")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.update_volume_with_qos_policy")
def test_volume_qos_policy_create(self,
mock_volume_types,
mock_attach_volume,
mock_get_volume_lun,
mock_get_array_version,
mock_get_qos_policy,
mock_qos_io_rule,
mock_qos_policy,
mock_volume_qos_update):
self.iscsi_driver.adapter.use_chap_auth = False
self.mock_object(self.iscsi_driver.adapter,
"_create_host_if_not_exist",
return_value=(
FAKE_HOST,
utils.get_chap_credentials(),
))
self.iscsi_driver.initialize_connection(
self.volume,
self.fake_connector
)
mock_get_volume_lun.return_value = "fake_volume_identifier"
mock_qos_io_rule.return_value = "9beb10ff-a00c-4d88-a7d9-692be2b3073f"
mock_qos_policy.return_value = "d69f7131-4617-4bae-89f8-a540a6bda94b"
mock_volume_types.assert_called_once()
mock_get_array_version.assert_called_once()
mock_get_qos_policy.assert_called_once()
mock_attach_volume.assert_called_once()
mock_qos_policy.assert_called_once()
mock_volume_qos_update.assert_called_once()
@mock.patch('cinder.volume.volume_types.'
'get_volume_type_qos_specs',
return_value=QOS_SPECS)
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.attach_volume_to_host")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_volume_lun")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_array_version",
return_value='4.0')
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_qos_policy_id_by_name")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.update_qos_io_rule")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.update_volume_with_qos_policy")
def test_volume_qos_io_rule_update(self,
mock_volume_types,
mock_attach_volume,
mock_get_volume_lun,
mock_get_array_version,
mock_get_qos_policy,
mock_update_qos_io_rule,
mock_volume_qos_update):
self.iscsi_driver.adapter.use_chap_auth = False
self.mock_object(self.iscsi_driver.adapter,
"_create_host_if_not_exist",
return_value=(
FAKE_HOST,
utils.get_chap_credentials(),
))
self.iscsi_driver.initialize_connection(
self.volume,
self.fake_connector
)
mock_get_volume_lun.return_value = "fake_volume_identifier"
mock_get_qos_policy.return_value = ("d69f7131-"
"4617-4bae-89f8-a540a6bda94b")
mock_volume_types.assert_called_once()
mock_attach_volume.assert_called_once()
mock_get_array_version.assert_called_once()
mock_update_qos_io_rule.assert_called_once()
mock_volume_qos_update.assert_called_once()
@mock.patch('cinder.volume.volume_types.'
'get_volume_type_qos_specs',
return_value=QOS_SPECS)
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.utils."
"is_multiattached_to_host", return_value=False)
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.detach_volume_from_host")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_array_version",
return_value='4.0')
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.update_volume_with_qos_policy")
def test_volume_qos_policy_update(self,
mock_volume_types,
mock_multi_attached_host,
mock_detach_volume,
mock_get_array_version,
mock_volume_qos_update):
self.mock_object(self.iscsi_driver.adapter,
"_filter_hosts_by_initiators",
return_value=FAKE_HOST)
self.iscsi_driver.terminate_connection(self.volume,
self.fake_connector)
mock_volume_types.assert_called_once()
mock_multi_attached_host.assert_called_once()
mock_detach_volume.assert_called_once()
mock_get_array_version.assert_called_once()
mock_volume_qos_update.assert_called_once()

View File

@ -77,26 +77,33 @@ class TestVolumeCreateDeleteExtend(powerstore.TestPowerStoreDriver):
"PowerStoreClient.get_volume_mapped_hosts")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.delete_volume_or_snapshot")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.adapter."
"CommonAdapter._create_or_update_volume_qos_policy")
def test_delete_volume_detach_not_found(self,
mock_delete,
mock_mapped_hosts,
mock_detach_request):
mock_detach_request,
mock_qos_policy):
mock_mapped_hosts.return_value = ["fake_host_id"]
mock_detach_request.return_value = powerstore.MockResponse(
content={},
rc=404
)
self.driver.delete_volume(self.volume)
mock_qos_policy.assert_not_called()
@mock.patch("requests.request")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.get_volume_mapped_hosts")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.client."
"PowerStoreClient.delete_volume_or_snapshot")
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.adapter."
"CommonAdapter._create_or_update_volume_qos_policy")
def test_delete_volume_detach_not_mapped(self,
mock_delete,
mock_mapped_hosts,
mock_detach_request):
mock_detach_request,
mock_qos_policy):
mock_mapped_hosts.return_value = ["fake_host_id"]
mock_detach_request.return_value = powerstore.MockResponse(
content={
@ -109,6 +116,7 @@ class TestVolumeCreateDeleteExtend(powerstore.TestPowerStoreDriver):
rc=422
)
self.driver.delete_volume(self.volume)
mock_qos_policy.assert_not_called()
@mock.patch("cinder.volume.drivers.dell_emc.powerstore.adapter."
"CommonAdapter._detach_volume_from_hosts")

View File

@ -24,15 +24,22 @@ from cinder.i18n import _
from cinder.objects import fields
from cinder.objects.group_snapshot import GroupSnapshot
from cinder.objects.snapshot import Snapshot
from cinder.utils import retry
from cinder.volume.drivers.dell_emc.powerstore import (
exception as powerstore_exception)
from cinder.volume.drivers.dell_emc.powerstore import client
from cinder.volume.drivers.dell_emc.powerstore import utils
from cinder.volume import manager
from cinder.volume import volume_types
from cinder.volume import volume_utils
LOG = logging.getLogger(__name__)
CHAP_MODE_SINGLE = "Single"
POWERSTORE_NVME_VERSION_SUPPORT = "3.0"
POWERSTORE_QOS_VERSION_SUPPORT = "4.0"
retry_exc_tuple = (powerstore_exception.DellPowerStoreQoSIORuleExists,
powerstore_exception.DellPowerStoreQoSPolicyExists)
class CommonAdapter(object):
@ -592,6 +599,8 @@ class CommonAdapter(object):
"host_provider_id": host["id"],
"volume_identifier": volume_identifier,
})
self._create_or_update_volume_qos_policy(volume, provider_id,
utils.VOLUME_ATTACH_OPERATION)
return volume_identifier
def _create_host_and_attach(self, connector, volume):
@ -668,6 +677,8 @@ class CommonAdapter(object):
"volume_provider_id": provider_id,
"hosts_provider_ids": hosts_to_detach,
})
self._create_or_update_volume_qos_policy(volume, provider_id,
utils.VOLUME_DETACH_OPERATION)
def _disconnect_volume(self, volume, connector):
"""Detach PowerStore volume.
@ -1020,6 +1031,94 @@ class CommonAdapter(object):
updates.append(volume_updates)
return None, updates
def _create_or_update_volume_qos_policy(self, volume,
provider_id, operation):
"""Create or update volume QoS policy
@param volume: OpenStack volume object
@param provider_id: Volume provider Id
@param operation: QoS create or update operation
"""
volume_type_id = volume["volume_type_id"]
specs = volume_types.get_volume_type_qos_specs(volume_type_id)
qos_specs = specs['qos_specs']
if (qos_specs is not None and (qos_specs["consumer"] == "back-end" or
qos_specs["consumer"] == "both")
and self._check_qos_support()):
if operation == utils.VOLUME_ATTACH_OPERATION:
qos_policy_id = self._get_or_create_qos_policy(qos_specs)
self.client.update_volume_with_qos_policy(provider_id,
qos_policy_id)
else:
self.client.update_volume_with_qos_policy(provider_id,
None)
def _check_qos_support(self):
"""Check PowerStore array support QoS or not
@return: Version is supported or not in boolean
"""
array_version = self.client.get_array_version()
if not utils.version_gte(
array_version,
POWERSTORE_QOS_VERSION_SUPPORT
):
msg = (_("PowerStore arrays support QoS starting from version "
"%(qos_support_version)s. Current PowerStore array "
"version: %(current_version)s.")
% {"qos_support_version": POWERSTORE_QOS_VERSION_SUPPORT,
"current_version": array_version})
LOG.error(msg)
else:
return True
return False
@retry(retry_exc_tuple,
interval=1,
retries=3,
backoff_rate=2)
def _get_or_create_qos_policy(self, qos_specs):
"""Get or create QoS policy
1. Create operation: It will verify if a QoS policy is created for the
volume type. If not, it will create an I/O limit rule, establish
a policy with this rule, and attach the policy to the volume.
2. Update operation: It will verify if a QoS policy is created for the
volume type. If it is, it will update the existing I/O limit rule with
the specified QoS values.
@param qos_specs: Volume QoS specs
@return: QoS policy id
"""
qos_id = qos_specs["id"]
policy_name = "qos-policy-%s" % qos_id
io_rule_name = "io-rule-%s" % qos_id
specs = qos_specs["specs"]
io_rule_params = {
"type": (specs["bandwidth_limit_type"]
if "bandwidth_limit_type" in specs else None),
"max_iops":
int(specs["max_iops"]) if "max_iops" in specs else None,
"max_bw":
int(specs["max_bw"]) if "max_bw" in specs else None,
"burst_percentage":
(int(specs["burst_percentage"])
if "burst_percentage" in specs else None)
}
policy_id = self.client.get_qos_policy_id_by_name(policy_name)
if policy_id is None:
io_rule_params["name"] = io_rule_name
io_rule_id = self.client.create_qos_io_rule(io_rule_params)
policy_params = {
"name": policy_name,
"io_limit_rule_id": io_rule_id
}
return self.client.create_qos_policy(policy_params)
else:
self.client.update_qos_io_rule(io_rule_name, io_rule_params)
return policy_id
class FibreChannelAdapter(CommonAdapter):
def __init__(self, **kwargs):

View File

@ -25,14 +25,18 @@ import requests
from cinder import exception
from cinder.i18n import _
from cinder import utils as cinder_utils
from cinder.volume.drivers.dell_emc.powerstore import (
exception as powerstore_exception)
from cinder.volume.drivers.dell_emc.powerstore import utils
LOG = logging.getLogger(__name__)
VOLUME_NOT_MAPPED_ERROR = "0xE0A08001000F"
SESSION_ALREADY_FAILED_OVER_ERROR = "0xE0201005000C"
TOO_MANY_SNAPS_ERROR = "0xE0A040010003"
MAX_SNAPS_IN_VTREE = 32
QOS_IO_RULE_EXISTS_ERROR = "0xE0A0E0010009"
QOS_POLICY_EXISTS_ERROR = "0xE02020010004"
QOS_UNEXPECTED_RESPONSE_ERROR = "0xE0101001000C"
class PowerStoreClient(object):
@ -803,3 +807,87 @@ class PowerStoreClient(object):
raise exception.VolumeBackendAPIException(data=msg)
nguid = response["nguid"].split('.')[1]
return nguid
def get_qos_policy_id_by_name(self, name):
r, response = self._send_get_request(
"/policy",
params={
"name": "eq.%s" % name,
"type": "eq.QoS",
}
)
if r.status_code not in self.ok_codes:
msg = _("Failed to query PowerStore QoS policy "
"with name %s." % name)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
if len(response) > 0:
qos_policy_id = response[0].get("id")
return qos_policy_id
return None
def create_qos_io_rule(self, io_rule_params):
r, response = self._send_post_request(
"/io_limit_rule",
payload=io_rule_params
)
if r.status_code not in self.ok_codes:
msg = _("Failed to create PowerStore I/O limit "
"rule %s." % io_rule_params["name"])
LOG.error(msg)
if ("messages" in response and
(response["messages"][0]["code"] ==
QOS_IO_RULE_EXISTS_ERROR or
response["messages"][0]["code"] ==
QOS_UNEXPECTED_RESPONSE_ERROR)):
raise (
powerstore_exception.
DellPowerStoreQoSIORuleExists(name=io_rule_params["name"]))
raise exception.VolumeBackendAPIException(data=msg)
return response["id"]
def create_qos_policy(self, policy_params):
r, response = self._send_post_request(
"/policy",
payload=policy_params
)
if r.status_code not in self.ok_codes:
msg = _("Failed to create PowerStore QoS "
"policy %s." % policy_params["name"])
LOG.error(msg)
if ("messages" in response and
(response["messages"][0]["code"] ==
QOS_POLICY_EXISTS_ERROR or
response["messages"][0]["code"] ==
QOS_UNEXPECTED_RESPONSE_ERROR)):
raise (
powerstore_exception.
DellPowerStoreQoSPolicyExists(name=policy_params["name"]))
raise exception.VolumeBackendAPIException(data=msg)
return response["id"]
def update_volume_with_qos_policy(self, provider_id, qos_policy_id):
r, response = self._send_patch_request(
"/volume/%s" % provider_id,
payload={
"qos_performance_policy_id": qos_policy_id,
}
)
if r.status_code not in self.ok_codes:
msg = _("Failed to update PowerStore volume %(volume_id)s with "
"QoS policy %(policy_id)s."
% {"volume_id": provider_id,
"policy_id": qos_policy_id})
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def update_qos_io_rule(self, io_rule_name, io_rule_params):
r, response = self._send_patch_request(
"/io_limit_rule/name:%s" % io_rule_name,
payload=io_rule_params
)
if r.status_code not in self.ok_codes:
msg = (_("Failed to update PowerStore I/O limit rule %s.")
% io_rule_name)
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)

View File

@ -52,9 +52,10 @@ class PowerStoreDriver(driver.VolumeDriver):
(iSCSI target, Replication target, etc.)
1.2.0 - Add NVMe-OF support
1.2.1 - Report trim/discard support
1.2.2 - QoS (Quality of Service) support
"""
VERSION = "1.2.1"
VERSION = "1.2.2"
VENDOR = "Dell EMC"
# ThirdPartySystems wiki page

View File

@ -0,0 +1,21 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
from cinder import exception
from cinder.i18n import _
class DellPowerStoreQoSIORuleExists(exception.VolumeDriverException):
message = _('QoS I/O Rule %(name)s already exists.')
class DellPowerStoreQoSPolicyExists(exception.VolumeDriverException):
message = _('QoS policy %(name)s already exists.')

View File

@ -36,6 +36,8 @@ PROTOCOL_FC = constants.FC
PROTOCOL_ISCSI = constants.ISCSI
PROTOCOL_NVME = "NVMe"
POWERSTORE_PP_KEY = "powerstore:protection_policy"
VOLUME_ATTACH_OPERATION = 1
VOLUME_DETACH_OPERATION = 2
def bytes_to_gib(size_in_bytes):

View File

@ -23,6 +23,7 @@ Supported operations
- Create, delete Consistency Groups snapshots.
- Clone a Consistency Group.
- Create a Consistency Group from a Consistency Group snapshot.
- Quality of Service (QoS)
Driver configuration
~~~~~~~~~~~~~~~~~~~~
@ -221,3 +222,72 @@ snapshot enabled.
.. note:: Currently driver does not support Consistency Groups replication.
Adding volume to Consistency Group and creating volume in Consistency Group
will fail if volume is replicated.
QoS (Quality of Service) support
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
.. note:: QoS is supported in PowerStore version 4.0 or later.
The PowerStore driver supports Quality of Service (QoS) by
enabling the following capabilities:
``bandwidth_limit_type``
The QoS bandwidth limit type. This type setting determines
how the max_iops and max_bw attributes are used.
This has the following two values:
1. ``Absolute`` - Limits are absolute values specified,
either I/O operations per second or bandwidth.
2. ``Density`` - Limits specified are per GB,
e.g. I/O operations per second per GB.
.. note:: This (bandwidth_limit_type) property is mandatory when creating QoS.
``max_iops``
Maximum I/O operations in either I/O operations per second (IOPS) or
I/O operations per second per GB. The specification of the type
attribute determines which metric is used.
If type is set to absolute, max_iops is specified in IOPS.
If type is set to density, max_iops is specified in IOPS per GB.
If both max_iops and max_bw are specified,
the system will limit I/O if either value is exceeded.
The value must be within the range of 1 to 2147483646.
``max_bw``
Maximum I/O bandwidth measured in either Kilobytes per second or Kilobytes
per second / per GB. The specification of the type attribute determines
which measurement is used. If type is set to absolute, max_bw is specified
in Kilobytes per second. If type is set to density max_bw is specified
in Kilobytes per second / per GB.
If both max_iops and max_bw are specified, the system will
limit I/O if either value is exceeded.
The value must be within the range of 2000 to 2147483646.
``burst_percentage``
Percentage indicating by how much the limit may be exceeded. If I/O
normally runs below the specified limit, then the volume or volume_group
will accumulate burst credits that can be used to exceed the limit for
a short period (a few seconds, but will not exceed the burst limit).
This burst percentage applies to both max_iops and max_bw and
is independent of the type setting.
The value must be within the range of 0 to 100.
If this property is not specified during QoS creation,
a default value of 0 will be used.
.. note:: When creating QoS, you must define either ``max_iops`` or ``max_bw``, or you can define both.
.. code-block:: console
$ openstack volume qos create --consumer back-end --property max_iops=100 --property max_bw=50000 --property bandwidth_limit_type=Absolute --property burst_percentage=80 powerstore_qos
$ openstack volume type create --property volume_backend_name=powerstore powerstore
$ openstack volume qos associate powerstore_qos powerstore
.. note:: There are two approaches for updating QoS properties in PowerStore:
#. ``Retype the Volume``:
This involves retyping the volume with the different QoS settings and migrating the volume to the new type.
#. ``Modify Existing QoS Properties`` (Recommended):
This method entails changing the existing QoS properties and creating a new instance or image
volume to update the QoS policy in PowerStore. This will also update the QoS properties of existing attached volumes,
created with the same volume type.

View File

@ -408,7 +408,7 @@ notes=Vendor drivers that support Quality of Service (QoS) at the
driver.datacore=missing
driver.datera=complete
driver.dell_emc_powermax=complete
driver.dell_emc_powerstore=missing
driver.dell_emc_powerstore=complete
driver.dell_emc_powerstore_nfs=missing
driver.dell_emc_powervault=missing
driver.dell_emc_sc=complete

View File

@ -0,0 +1,5 @@
---
features:
- |
Dell PowerStore Driver: Added QoS (Quality of Service) support for
PowerStore 4.0 or later versions.