INFINIDAT: Add QoS support

QoS is available in InfiniBox starting with version 4.0.
Older systems will report QoS_support=False.
If QoS is supported and there are QoS specs defined on the
volume type, the driver will apply them on the storage system.

Change-Id: Ic8c702eb95a8e8d5b598508e3072bf825b6f3de1
Implements: blueprint infinidat-qos
This commit is contained in:
Arnon Yaari 2017-07-10 13:05:51 +03:00
parent ab236e32d6
commit d5030ca7d5
3 changed files with 152 additions and 5 deletions

View File

@ -26,7 +26,7 @@ from cinder.volume.drivers import infinidat
TEST_WWN_1 = '00:11:22:33:44:55:66:77'
TEST_WWN_2 = '11:11:22:33:44:55:66:77'
test_volume = mock.Mock(id=1, size=1)
test_volume = mock.Mock(id=1, size=1, volume_type_id=1)
test_snapshot = mock.Mock(id=2, volume=test_volume, volume_id='1')
test_clone = mock.Mock(id=3, size=1)
test_group = mock.Mock(id=4)
@ -89,6 +89,7 @@ class InfiniboxDriverTestCaseBase(test.TestCase):
self._mock_ns = mock.Mock()
self._mock_ns.get_ips.return_value = [mock.Mock(ip_address='1.1.1.1')]
self._mock_group = mock.Mock()
self._mock_qos_policy = mock.Mock()
result.volumes.safe_get.return_value = self._mock_volume
result.volumes.create.return_value = self._mock_volume
result.pools.safe_get.return_value = self._mock_pool
@ -98,6 +99,8 @@ class InfiniboxDriverTestCaseBase(test.TestCase):
result.hosts.create.return_value = self._mock_host
result.network_spaces.safe_get.return_value = self._mock_ns
result.components.nodes.get_all.return_value = []
result.qos_policies.create.return_value = self._mock_qos_policy
result.qos_policies.safe_get.return_value = None
return result
def _raise_infinisdk(self, *args, **kwargs):
@ -186,7 +189,8 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
self.assertRaises(exception.VolumeDriverException,
self.driver.get_volume_stats)
def test_create_volume(self):
@mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs")
def test_create_volume(self, *mocks):
self.driver.create_volume(test_volume)
def test_create_volume_pool_not_found(self):
@ -199,7 +203,8 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
self.assertRaises(exception.VolumeBackendAPIException,
self.driver.create_volume, test_volume)
def test_create_volume_metadata(self):
@mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs")
def test_create_volume_metadata(self, *mocks):
self.driver.create_volume(test_volume)
self._mock_volume.set_metadata_from_dict.assert_called_once()
@ -246,6 +251,7 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
@mock.patch("cinder.utils.brick_get_connector")
@mock.patch("cinder.utils.brick_get_connector_properties",
return_value=test_connector)
@mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs")
def test_create_volume_from_snapshot(self, *mocks):
self.driver.create_volume_from_snapshot(test_clone, test_snapshot)
@ -263,6 +269,7 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
@mock.patch("cinder.utils.brick_get_connector_properties",
return_value=test_connector)
@mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs")
def test_create_volume_from_snapshot_map_fails(self, *mocks):
self._mock_host.map_volume.side_effect = self._raise_infinisdk
self.assertRaises(exception.VolumeBackendAPIException,
@ -296,6 +303,7 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
@mock.patch("cinder.utils.brick_get_connector")
@mock.patch("cinder.utils.brick_get_connector_properties",
return_value=test_connector)
@mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs")
def test_create_cloned_volume(self, *mocks):
self.driver.create_cloned_volume(test_clone, test_volume)
@ -315,6 +323,7 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
@mock.patch("cinder.utils.brick_get_connector_properties",
return_value=test_connector)
@mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs")
def test_create_cloned_volume_map_fails(self, *mocks):
self._mock_host.map_volume.side_effect = self._raise_infinisdk
self.assertRaises(exception.VolumeBackendAPIException,
@ -386,6 +395,7 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
return_value=test_connector)
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type',
return_value=True)
@mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs")
def test_create_group_from_src_snaps(self, *mocks):
self.driver.create_group_from_src(None, test_group, [test_volume],
test_snapgroup, [test_snapshot],
@ -397,6 +407,7 @@ class InfiniboxDriverTestCase(InfiniboxDriverTestCaseBase):
return_value=test_connector)
@mock.patch('cinder.volume.utils.is_group_a_cg_snapshot_type',
return_value=True)
@mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs")
def test_create_group_from_src_vols(self, *mocks):
self.driver.create_group_from_src(None, test_group, [test_volume],
None, None,
@ -509,3 +520,85 @@ class InfiniboxDriverTestCaseISCSI(InfiniboxDriverTestCaseBase):
def test_terminate_connection(self):
self.driver.terminate_connection(test_volume, test_connector)
class InfiniboxDriverTestCaseQoS(InfiniboxDriverTestCaseBase):
@mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs")
def test_qos_max_ipos(self, qos_specs):
qos_specs.return_value = {'qos_specs': {'id': 'qos_name',
'consumer': 'back-end',
'specs': {'maxIOPS': 1000,
'maxBWS': None}}}
self.driver.create_volume(test_volume)
self._system.qos_policies.create.assert_called_once()
self._mock_qos_policy.assign_entity.assert_called_once()
@mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs")
def test_qos_max_bws(self, qos_specs):
qos_specs.return_value = {'qos_specs': {'id': 'qos_name',
'consumer': 'back-end',
'specs': {'maxIOPS': None,
'maxBWS': 10000}}}
self.driver.create_volume(test_volume)
self._system.qos_policies.create.assert_called_once()
self._mock_qos_policy.assign_entity.assert_called_once()
@mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs")
def test_qos_no_compat(self, qos_specs):
qos_specs.return_value = {'qos_specs': {'id': 'qos_name',
'consumer': 'back-end',
'specs': {'maxIOPS': 1000,
'maxBWS': 10000}}}
self._system.compat.has_qos.return_value = False
self.driver.create_volume(test_volume)
self._system.qos_policies.create.assert_not_called()
self._mock_qos_policy.assign_entity.assert_not_called()
@mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs")
def test_qos_volume_type_id_none(self, qos_specs):
qos_specs.return_value = {'qos_specs': {'id': 'qos_name',
'consumer': 'back-end',
'specs': {'maxIOPS': 1000,
'maxBWS': 10000}}}
test_volume = mock.Mock(id=1, size=1, volume_type_id=None)
self.driver.create_volume(test_volume)
self._system.qos_policies.create.assert_not_called()
self._mock_qos_policy.assign_entity.assert_not_called()
@mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs")
def test_qos_no_specs(self, qos_specs):
qos_specs.return_value = {'qos_specs': None}
self.driver.create_volume(test_volume)
self._system.qos_policies.create.assert_not_called()
self._mock_qos_policy.assign_entity.assert_not_called()
@mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs")
def test_qos_front_end(self, qos_specs):
qos_specs.return_value = {'qos_specs': {'id': 'qos_name',
'consumer': 'front-end',
'specs': {'maxIOPS': 1000,
'maxBWS': 10000}}}
self.driver.create_volume(test_volume)
self._system.qos_policies.create.assert_not_called()
self._mock_qos_policy.assign_entity.assert_not_called()
@mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs")
def test_qos_specs_empty(self, qos_specs):
qos_specs.return_value = {'qos_specs': {'id': 'qos_name',
'consumer': 'back-end',
'specs': {'maxIOPS': None,
'maxBWS': None}}}
self.driver.create_volume(test_volume)
self._system.qos_policies.create.assert_not_called()
self._mock_qos_policy.assign_entity.assert_not_called()
@mock.patch("cinder.volume.volume_types.get_volume_type_qos_specs")
def test_qos_policy_exists(self, qos_specs):
qos_specs.return_value = {'qos_specs': {'id': 'qos_name',
'consumer': 'back-end',
'specs': {'maxIOPS': 1000,
'maxBWS': 10000}}}
self._system.qos_policies.safe_get.return_value = self._mock_qos_policy
self.driver.create_volume(test_volume)
self._system.qos_policies.create.assert_not_called()
self._mock_qos_policy.assign_entity.assert_called()

View File

@ -36,6 +36,7 @@ from cinder import version
from cinder.volume import configuration
from cinder.volume.drivers.san import san
from cinder.volume import utils as vol_utils
from cinder.volume import volume_types
from cinder.zonemanager import utils as fczm_utils
try:
@ -56,6 +57,9 @@ except ImportError:
LOG = logging.getLogger(__name__)
VENDOR_NAME = 'INFINIDAT'
BACKEND_QOS_CONSUMERS = frozenset(['back-end', 'both'])
QOS_MAX_IOPS = 'maxIOPS'
QOS_MAX_BWS = 'maxBWS'
infinidat_opts = [
cfg.StrOpt('infinidat_pool_name',
@ -93,7 +97,7 @@ def infinisdk_to_cinder_exceptions(func):
@interface.volumedriver
class InfiniboxVolumeDriver(san.SanISCSIDriver):
VERSION = '1.3'
VERSION = '1.4'
# ThirdPartySystems wiki page
CI_WIKI_NAME = "INFINIDAT_Cinder_CI"
@ -219,6 +223,48 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
# volume not mapped. map it
return host.map_volume(volume)
def _get_backend_qos_specs(self, cinder_volume):
type_id = cinder_volume.volume_type_id
if type_id is None:
return None
qos_specs = volume_types.get_volume_type_qos_specs(type_id)
if qos_specs is None:
return None
qos_specs = qos_specs['qos_specs']
if qos_specs is None:
return None
consumer = qos_specs['consumer']
# Front end QoS specs are handled by nova. We ignore them here.
if consumer not in BACKEND_QOS_CONSUMERS:
return None
max_iops = qos_specs['specs'].get(QOS_MAX_IOPS)
max_bws = qos_specs['specs'].get(QOS_MAX_BWS)
if max_iops is None and max_bws is None:
return None
return {
'id': qos_specs['id'],
QOS_MAX_IOPS: max_iops,
QOS_MAX_BWS: max_bws,
}
def _get_or_create_qos_policy(self, qos_specs):
qos_policy = self._system.qos_policies.safe_get(name=qos_specs['id'])
if qos_policy is None:
qos_policy = self._system.qos_policies.create(
name=qos_specs['id'],
type="VOLUME",
max_ops=qos_specs[QOS_MAX_IOPS],
max_bps=qos_specs[QOS_MAX_BWS])
return qos_policy
def _set_qos(self, cinder_volume, infinidat_volume):
if (hasattr(self._system.compat, "has_qos") and
self._system.compat.has_qos()):
qos_specs = self._get_backend_qos_specs(cinder_volume)
if qos_specs:
policy = self._get_or_create_qos_policy(qos_specs)
policy.assign_entity(infinidat_volume)
def _get_online_fc_ports(self):
nodes = self._system.components.nodes.get_all()
for node in nodes:
@ -365,6 +411,8 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
capacity.byte)
free_capacity_gb = float(free_capacity_bytes) / units.Gi
total_capacity_gb = float(physical_capacity_bytes) / units.Gi
qos_support = (hasattr(self._system.compat, "has_qos") and
self._system.compat.has_qos())
self._volume_stats = dict(volume_backend_name=self._backend_name,
vendor_name=VENDOR_NAME,
driver_version=self.VERSION,
@ -372,7 +420,8 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
consistencygroup_support=False,
total_capacity_gb=total_capacity_gb,
free_capacity_gb=free_capacity_gb,
consistent_group_snapshot_enabled=True)
consistent_group_snapshot_enabled=True,
QoS_support=qos_support)
return self._volume_stats
def _create_volume(self, volume):
@ -384,6 +433,7 @@ class InfiniboxVolumeDriver(san.SanISCSIDriver):
pool=pool,
provtype=provtype,
size=size)
self._set_qos(volume, infinidat_volume)
self._set_cinder_object_metadata(infinidat_volume, volume)
return infinidat_volume

View File

@ -0,0 +1,4 @@
---
features:
- Added support for QoS in the INFINIDAT InfiniBox driver.
QoS is available on InfiniBox 4.0 onward.