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:
parent
ab236e32d6
commit
d5030ca7d5
@ -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()
|
||||
|
@ -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
|
||||
|
||||
|
4
releasenotes/notes/infinidat-qos-50d743591543db98.yaml
Normal file
4
releasenotes/notes/infinidat-qos-50d743591543db98.yaml
Normal file
@ -0,0 +1,4 @@
|
||||
---
|
||||
features:
|
||||
- Added support for QoS in the INFINIDAT InfiniBox driver.
|
||||
QoS is available on InfiniBox 4.0 onward.
|
Loading…
Reference in New Issue
Block a user