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
This commit is contained in:
Kurt Martin 2014-02-11 17:38:13 -08:00 committed by Gerrit Code Review
parent dfb1a09033
commit 6966a00065
2 changed files with 131 additions and 11 deletions

View 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 = {

View 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'])