From bf52e877e46e55e2cfe2640b0aeb8add47672011 Mon Sep 17 00:00:00 2001 From: Nilesh Thathagar Date: Tue, 11 Jun 2024 06:50:17 +0000 Subject: [PATCH] Dell PowerStore driver: QoS support Added QoS support for Dell PowerStore driver. Implements: blueprint dell-powerstore-qos Change-Id: Id8f9b78671047eb24a4d0fa3a55a4fdb584d71db --- .../dell_emc/powerstore/test_client.py | 177 ++++++++++++++++++ .../powerstore/test_volume_attach_detach.py | 147 ++++++++++++++- .../test_volume_create_delete_extend.py | 12 +- .../drivers/dell_emc/powerstore/adapter.py | 99 ++++++++++ .../drivers/dell_emc/powerstore/client.py | 90 ++++++++- .../drivers/dell_emc/powerstore/driver.py | 3 +- .../drivers/dell_emc/powerstore/exception.py | 21 +++ .../drivers/dell_emc/powerstore/utils.py | 2 + .../drivers/dell-emc-powerstore-driver.rst | 70 +++++++ doc/source/reference/support-matrix.ini | 2 +- ...-dell-powerstore-qos-1532737fa1bb2664.yaml | 5 + 11 files changed, 622 insertions(+), 6 deletions(-) create mode 100644 cinder/volume/drivers/dell_emc/powerstore/exception.py create mode 100644 releasenotes/notes/bp-dell-powerstore-qos-1532737fa1bb2664.yaml diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_client.py b/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_client.py index 4c42c78ffaa..b8f6eed93f1 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_client.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_client.py @@ -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) diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_attach_detach.py b/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_attach_detach.py index 1e3e465208c..936c28ace0c 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_attach_detach.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_attach_detach.py @@ -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() diff --git a/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_create_delete_extend.py b/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_create_delete_extend.py index 90ecc4b1632..5948c9f7f47 100644 --- a/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_create_delete_extend.py +++ b/cinder/tests/unit/volume/drivers/dell_emc/powerstore/test_volume_create_delete_extend.py @@ -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") diff --git a/cinder/volume/drivers/dell_emc/powerstore/adapter.py b/cinder/volume/drivers/dell_emc/powerstore/adapter.py index 553e1e7c253..37ae21e7e98 100644 --- a/cinder/volume/drivers/dell_emc/powerstore/adapter.py +++ b/cinder/volume/drivers/dell_emc/powerstore/adapter.py @@ -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): diff --git a/cinder/volume/drivers/dell_emc/powerstore/client.py b/cinder/volume/drivers/dell_emc/powerstore/client.py index a98df5bca94..987683c8115 100644 --- a/cinder/volume/drivers/dell_emc/powerstore/client.py +++ b/cinder/volume/drivers/dell_emc/powerstore/client.py @@ -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) diff --git a/cinder/volume/drivers/dell_emc/powerstore/driver.py b/cinder/volume/drivers/dell_emc/powerstore/driver.py index 91ed3f91e21..6cc64992bd3 100644 --- a/cinder/volume/drivers/dell_emc/powerstore/driver.py +++ b/cinder/volume/drivers/dell_emc/powerstore/driver.py @@ -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 diff --git a/cinder/volume/drivers/dell_emc/powerstore/exception.py b/cinder/volume/drivers/dell_emc/powerstore/exception.py new file mode 100644 index 00000000000..79ffceefcab --- /dev/null +++ b/cinder/volume/drivers/dell_emc/powerstore/exception.py @@ -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.') diff --git a/cinder/volume/drivers/dell_emc/powerstore/utils.py b/cinder/volume/drivers/dell_emc/powerstore/utils.py index 784d06d2ee1..9370788ca4b 100644 --- a/cinder/volume/drivers/dell_emc/powerstore/utils.py +++ b/cinder/volume/drivers/dell_emc/powerstore/utils.py @@ -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): diff --git a/doc/source/configuration/block-storage/drivers/dell-emc-powerstore-driver.rst b/doc/source/configuration/block-storage/drivers/dell-emc-powerstore-driver.rst index 016ac9a20e5..b5f9ededa2e 100644 --- a/doc/source/configuration/block-storage/drivers/dell-emc-powerstore-driver.rst +++ b/doc/source/configuration/block-storage/drivers/dell-emc-powerstore-driver.rst @@ -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. diff --git a/doc/source/reference/support-matrix.ini b/doc/source/reference/support-matrix.ini index 373bea0b79e..3b7bc51976c 100644 --- a/doc/source/reference/support-matrix.ini +++ b/doc/source/reference/support-matrix.ini @@ -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 diff --git a/releasenotes/notes/bp-dell-powerstore-qos-1532737fa1bb2664.yaml b/releasenotes/notes/bp-dell-powerstore-qos-1532737fa1bb2664.yaml new file mode 100644 index 00000000000..1c5777e3d09 --- /dev/null +++ b/releasenotes/notes/bp-dell-powerstore-qos-1532737fa1bb2664.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Dell PowerStore Driver: Added QoS (Quality of Service) support for + PowerStore 4.0 or later versions.