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:
parent
dfb1a09033
commit
6966a00065
|
@ -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 = {
|
||||
|
|
|
@ -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'])
|
||||
|
|
Loading…
Reference in New Issue