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:
parent
d2e1b64681
commit
bf52e877e4
@ -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)
|
||||
|
@ -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()
|
||||
|
@ -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")
|
||||
|
@ -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):
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
21
cinder/volume/drivers/dell_emc/powerstore/exception.py
Normal file
21
cinder/volume/drivers/dell_emc/powerstore/exception.py
Normal 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.')
|
@ -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):
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -0,0 +1,5 @@
|
||||
---
|
||||
features:
|
||||
- |
|
||||
Dell PowerStore Driver: Added QoS (Quality of Service) support for
|
||||
PowerStore 4.0 or later versions.
|
Loading…
Reference in New Issue
Block a user