From d5030ca7d57532957bb4c1e6a395fe0f3e091cb6 Mon Sep 17 00:00:00 2001 From: Arnon Yaari Date: Mon, 10 Jul 2017 13:05:51 +0300 Subject: [PATCH] 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 --- .../unit/volume/drivers/test_infinidat.py | 99 ++++++++++++++++++- cinder/volume/drivers/infinidat.py | 54 +++++++++- .../notes/infinidat-qos-50d743591543db98.yaml | 4 + 3 files changed, 152 insertions(+), 5 deletions(-) create mode 100644 releasenotes/notes/infinidat-qos-50d743591543db98.yaml diff --git a/cinder/tests/unit/volume/drivers/test_infinidat.py b/cinder/tests/unit/volume/drivers/test_infinidat.py index 9b11164218f..1539f65f0d5 100644 --- a/cinder/tests/unit/volume/drivers/test_infinidat.py +++ b/cinder/tests/unit/volume/drivers/test_infinidat.py @@ -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() diff --git a/cinder/volume/drivers/infinidat.py b/cinder/volume/drivers/infinidat.py index 4e641f54441..bb7e1a21eb8 100644 --- a/cinder/volume/drivers/infinidat.py +++ b/cinder/volume/drivers/infinidat.py @@ -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 diff --git a/releasenotes/notes/infinidat-qos-50d743591543db98.yaml b/releasenotes/notes/infinidat-qos-50d743591543db98.yaml new file mode 100644 index 00000000000..956ec09e7cd --- /dev/null +++ b/releasenotes/notes/infinidat-qos-50d743591543db98.yaml @@ -0,0 +1,4 @@ +--- +features: + - Added support for QoS in the INFINIDAT InfiniBox driver. + QoS is available on InfiniBox 4.0 onward.