]> review.fuel-infra Code Review - openstack-build/cinder-build.git/commitdiff
Add support for qos_specs feature to 3PAR drivers
authorKurt Martin <kurt.f.martin@hp.com>
Wed, 12 Feb 2014 01:38:13 +0000 (17:38 -0800)
committerGerrit Code Review <review@openstack.org>
Wed, 19 Feb 2014 17:57:05 +0000 (17:57 +0000)
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
cinder/volume/drivers/san/hp/hp_3par_common.py

index 7d7484b261a6f197be0c35a20f5b8a55123d07b0..30e2ea88820fd589302cb9ba4cd958616aaf1600 100644 (file)
@@ -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 = {
index 82f14939e50d0dfe8ddd2512f893132d726117bd..435d5146ee2f0d908bece49b84dad7c4cd04bb16 100644 (file)
@@ -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'])