From 6966a00065c9e092d90161e1f40e9455174bd4d9 Mon Sep 17 00:00:00 2001 From: Kurt Martin Date: Tue, 11 Feb 2014 17:38:13 -0800 Subject: [PATCH] Add support for qos_specs feature to 3PAR drivers This patch includes the ability to set the qos-specs for the existing 3PAR qos settings (maxIOP and maxBWS) in addition to the new settingis listed below. We would prefer that the new qos-specs associated to a volume type be used, but this patch also supports setting these values the old way in extra-specs for backwards compatibility. This patch also adds two additional personas to the list of valid personas that are now support by the 3PAR backends. DocImpact: Implements new qos settings minIOPS - The minimum IOPs per second minBWS - The minimum bandwidth per second latency - The QOS I/O target latency priority - The QOS scheduling priority (high, normal, low) The priority defaults to normal Two new personas added include HPUX and WindowsServer Change-Id: I7f94a493919dc2d34daac91d684369d1f51c974c Implements: blueprint add-qosspec-support-to-3par-drivers --- cinder/tests/test_hp3par.py | 71 ++++++++++++++++++- .../volume/drivers/san/hp/hp_3par_common.py | 71 ++++++++++++++++--- 2 files changed, 131 insertions(+), 11 deletions(-) diff --git a/cinder/tests/test_hp3par.py b/cinder/tests/test_hp3par.py index 7d7484b26..30e2ea888 100644 --- a/cinder/tests/test_hp3par.py +++ b/cinder/tests/test_hp3par.py @@ -26,6 +26,7 @@ from cinder.openstack.common import log as logging from cinder import test from cinder.volume.drivers.san.hp import hp_3par_fc as hpfcdriver from cinder.volume.drivers.san.hp import hp_3par_iscsi as hpdriver +from cinder.volume import qos_specs from cinder.volume import volume_types LOG = logging.getLogger(__name__) @@ -65,7 +66,12 @@ class HP3PARBaseDriver(object): 'protocol': 1, 'mode': 2, 'linkState': 4}] - QOS = {'qos:maxIOPS': '1000', 'qos:maxBWS': '50'} + QOS = {'qos:maxIOPS': '1000', 'qos:maxBWS': '50', + 'qos:minIOPS': '100', 'qos:minBWS': '25', + 'qos:latency': '25', 'qos:priority': 'low'} + QOS_SPECS = {'maxIOPS': '1000', 'maxBWS': '50', + 'minIOPS': '100', 'minBWS': '25', + 'latency': '25', 'priority': 'low'} VVS_NAME = "myvvs" FAKE_ISCSI_PORT = {'portPos': {'node': 8, 'slot': 1, 'cardPort': 1}, 'protocol': 2, @@ -113,8 +119,12 @@ class HP3PARBaseDriver(object): volume_type = {'name': 'gold', 'deleted': False, 'updated_at': None, - 'extra_specs': {'qos:maxBWS': '50', - 'qos:maxIOPS': '1000'}, + 'extra_specs': {'qos:maxIOPS': '1000', + 'qos:maxBWS': '50', + 'qos:minIOPS': '100', + 'qos:minBWS': '25', + 'qos:latency': '25', + 'qos:priority': 'low'}, 'deleted_at': None, 'id': 'gold'} @@ -575,6 +585,59 @@ class HP3PARBaseDriver(object): ports = self.driver.common.get_ports()['members'] self.assertEqual(len(ports), 3) + def test_get_by_qos_spec_with_scoping(self): + self.setup_driver() + qos_ref = qos_specs.create(self.ctxt, 'qos-specs-1', self.QOS) + type_ref = volume_types.create(self.ctxt, + "type1", {"qos:maxIOPS": "100", + "qos:maxBWS": "50", + "qos:minIOPS": "10", + "qos:minBWS": "20", + "qos:latency": "5", + "qos:priority": "high"}) + qos_specs.associate_qos_with_type(self.ctxt, + qos_ref['id'], + type_ref['id']) + type_ref = volume_types.get_volume_type(self.ctxt, type_ref['id']) + qos = self.driver.common._get_qos_by_volume_type(type_ref) + self.assertEqual(qos, {'maxIOPS': '1000', 'maxBWS': '50', + 'minIOPS': '100', 'minBWS': '25', + 'latency': '25', 'priority': 'low'}) + + def test_get_by_qos_spec(self): + self.setup_driver() + qos_ref = qos_specs.create(self.ctxt, 'qos-specs-1', self.QOS_SPECS) + type_ref = volume_types.create(self.ctxt, + "type1", {"qos:maxIOPS": "100", + "qos:maxBWS": "50", + "qos:minIOPS": "10", + "qos:minBWS": "20", + "qos:latency": "5", + "qos:priority": "high"}) + qos_specs.associate_qos_with_type(self.ctxt, + qos_ref['id'], + type_ref['id']) + type_ref = volume_types.get_volume_type(self.ctxt, type_ref['id']) + qos = self.driver.common._get_qos_by_volume_type(type_ref) + self.assertEqual(qos, {'maxIOPS': '1000', 'maxBWS': '50', + 'minIOPS': '100', 'minBWS': '25', + 'latency': '25', 'priority': 'low'}) + + def test_get_by_qos_by_type_only(self): + self.setup_driver() + type_ref = volume_types.create(self.ctxt, + "type1", {"qos:maxIOPS": "100", + "qos:maxBWS": "50", + "qos:minIOPS": "10", + "qos:minBWS": "20", + "qos:latency": "5", + "qos:priority": "high"}) + type_ref = volume_types.get_volume_type(self.ctxt, type_ref['id']) + qos = self.driver.common._get_qos_by_volume_type(type_ref) + self.assertEqual(qos, {'maxIOPS': '100', 'maxBWS': '50', + 'minIOPS': '10', 'minBWS': '20', + 'latency': '5', 'priority': 'high'}) + class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase): @@ -593,6 +656,7 @@ class TestHP3PARFCDriver(HP3PARBaseDriver, test.TestCase): def setup_driver(self, config=None, mock_conf=None): + self.ctxt = context.get_admin_context() mock_client = self.setup_mock_client( conf=config, m_conf=mock_conf, @@ -888,6 +952,7 @@ class TestHP3PARISCSIDriver(HP3PARBaseDriver, test.TestCase): def setup_driver(self, config=None, mock_conf=None): + self.ctxt = context.get_admin_context() # setup_mock_client default config, if necessary if mock_conf is None: mock_conf = { diff --git a/cinder/volume/drivers/san/hp/hp_3par_common.py b/cinder/volume/drivers/san/hp/hp_3par_common.py index 82f14939e..435d5146e 100644 --- a/cinder/volume/drivers/san/hp/hp_3par_common.py +++ b/cinder/volume/drivers/san/hp/hp_3par_common.py @@ -50,6 +50,8 @@ from cinder import context from cinder import exception from cinder.openstack.common import excutils from cinder.openstack.common import log as logging +from cinder import units +from cinder.volume import qos_specs from cinder.volume import volume_types @@ -111,10 +113,11 @@ class HP3PARCommon(object): This update now requires 3.1.2 MU3 firmware 1.3.0 - Removed all SSH code. We rely on the hp3parclient now. 2.0.0 - Update hp3parclient API uses 3.0.x + 2.0.1 - Updated to use qos_specs, added new qos settings and personas """ - VERSION = "2.0.0" + VERSION = "2.0.1" stats = {} @@ -136,8 +139,12 @@ class HP3PARCommon(object): '9 - EGENERA', '10 - ONTAP-legacy', '11 - VMware', - '12 - OpenVMS'] - hp_qos_keys = ['maxIOPS', 'maxBWS'] + '12 - OpenVMS', + '13 - HPUX', + '15 - WindowsServer'] + hp_qos_keys = ['minIOPS', 'maxIOPS', 'minBWS', 'maxBWS', 'latency', + 'priority'] + qos_priority_level = {'low': 1, 'normal': 2, 'high': 3} hp3par_valid_keys = ['cpg', 'snap_cpg', 'provisioning', 'persona', 'vvs'] def __init__(self, config): @@ -462,13 +469,24 @@ class HP3PARCommon(object): def _get_qos_by_volume_type(self, volume_type): qos = {} + qos_specs_id = volume_type.get('qos_specs_id') specs = volume_type.get('extra_specs') - for key, value in specs.iteritems(): + + #NOTE(kmartin): We prefer the qos_specs association + # and override any existing extra-specs settings + # if present. + if qos_specs_id is not None: + kvs = qos_specs.get_qos_specs(context.get_admin_context(), + qos_specs_id)['specs'] + else: + kvs = specs + + for key, value in kvs.iteritems(): if 'qos:' in key: fields = key.split(':') key = fields[1] if key in self.hp_qos_keys: - qos[key] = int(value) + qos[key] = value return qos def _get_keys_by_volume_type(self, volume_type): @@ -483,9 +501,40 @@ class HP3PARCommon(object): return hp3par_keys def _set_qos_rule(self, qos, vvs_name): + min_io = self._get_qos_value(qos, 'minIOPS') max_io = self._get_qos_value(qos, 'maxIOPS') + min_bw = self._get_qos_value(qos, 'minBWS') max_bw = self._get_qos_value(qos, 'maxBWS') - self.client.setQOSRule(vvs_name, max_io, max_bw) + latency = self._get_qos_value(qos, 'latency') + priority = self._get_qos_value(qos, 'priority', 'normal') + + qosRule = {} + if min_io: + qosRule['ioMinGoal'] = int(min_io) + if max_io is None: + qosRule['ioMaxLimit'] = int(min_io) + if max_io: + qosRule['ioMaxLimit'] = int(max_io) + if min_io is None: + qosRule['ioMinGoal'] = int(max_io) + if min_bw: + qosRule['bwMinGoalKB'] = int(min_bw) * units.KiB + if max_bw is None: + qosRule['bwMaxLimitKB'] = int(min_bw) * units.KiB + if max_bw: + qosRule['bwMaxLimitKB'] = int(max_bw) * units.KiB + if min_bw is None: + qosRule['bwMinGoalKB'] = int(max_bw) * units.KiB + if latency: + qosRule['latencyGoal'] = int(latency) + if priority: + qosRule['priority'] = self.qos_priority_level.get(priority.lower()) + + try: + self.client.createQoSRules(vvs_name, qosRule) + except Exception: + with excutils.save_and_reraise_exception(): + LOG.error(_("Error creating QOS rule %s") % qosRule) def _add_volume_to_volume_set(self, volume, volume_name, cpg, vvs_name, qos): @@ -501,8 +550,14 @@ class HP3PARCommon(object): vvs_name = self._get_3par_vvs_name(volume['id']) domain = self.get_domain(cpg) self.client.createVolumeSet(vvs_name, domain) - self._set_qos_rule(qos, vvs_name) - self.client.addVolumeToVolumeSet(vvs_name, volume_name) + try: + self._set_qos_rule(qos, vvs_name) + self.client.addVolumeToVolumeSet(vvs_name, volume_name) + except Exception as ex: + # Cleanup the volume set if unable to create the qos rule + # or add the volume to the volume set + self.client.deleteVolumeSet(vvs_name) + raise exception.CinderException(ex.get_description()) def get_cpg(self, volume, allowSnap=False): volume_name = self._get_3par_vol_name(volume['id']) -- 2.45.2