Adding Scaling QoS for ScaleIO driver

Add 2 new qos keys for ScaleIO: maxIOPSperGB and maxBWperGB.
The user can specify those in order to get QoS correlated with
the volume size.
the driver will always choose the minimum between the scaling QoS
keys and the pertinent maximum limitation key: maxIOPS, maxBWS.

Change-Id: I089e1a24af7925ed9b5f4e791d4ff30c0bd418e5
DocImpact:
Implements: blueprint scaleio-scaling-qos
This commit is contained in:
Matan Sabag 2016-05-25 04:03:42 -07:00
parent a17d3e2961
commit 17d7712fd1
3 changed files with 123 additions and 27 deletions

View File

@ -30,12 +30,12 @@ class TestInitializeConnection(scaleio.TestScaleIODriver):
self.volume = fake_volume.fake_volume_obj(self.ctx)
def test_only_qos(self):
qos = {'maxIOPS': 1000, 'maxBWS': 3000}
qos = {'maxIOPS': 1000, 'maxBWS': 2048}
extraspecs = {}
connection_properties = (
self._initialize_connection(qos, extraspecs)['data'])
self.assertEqual(1000, connection_properties['iopsLimit'])
self.assertEqual(3000, connection_properties['bandwidthLimit'])
self.assertEqual(1000, int(connection_properties['iopsLimit']))
self.assertEqual(2048, int(connection_properties['bandwidthLimit']))
def test_no_qos(self):
qos = {}
@ -47,19 +47,57 @@ class TestInitializeConnection(scaleio.TestScaleIODriver):
def test_only_extraspecs(self):
qos = {}
extraspecs = {'sio:iops_limit': 2000, 'sio:bandwidth_limit': 4000}
extraspecs = {'sio:iops_limit': 2000, 'sio:bandwidth_limit': 4096}
connection_properties = (
self._initialize_connection(qos, extraspecs)['data'])
self.assertEqual(2000, connection_properties['iopsLimit'])
self.assertEqual(4000, connection_properties['bandwidthLimit'])
self.assertEqual(2000, int(connection_properties['iopsLimit']))
self.assertEqual(4096, int(connection_properties['bandwidthLimit']))
def test_qos_and_extraspecs(self):
qos = {'maxIOPS': 1000, 'maxBWS': 3000}
qos = {'maxIOPS': 1000, 'maxBWS': 3072}
extraspecs = {'sio:iops_limit': 2000, 'sio:bandwidth_limit': 4000}
connection_properties = (
self._initialize_connection(qos, extraspecs)['data'])
self.assertEqual(1000, connection_properties['iopsLimit'])
self.assertEqual(3000, connection_properties['bandwidthLimit'])
self.assertEqual(1000, int(connection_properties['iopsLimit']))
self.assertEqual(3072, int(connection_properties['bandwidthLimit']))
def test_qos_scaling_and_max(self):
qos = {'maxIOPS': 100, 'maxBWS': 2048, 'maxIOPSperGB': 10,
'maxBWSperGB': 128}
extraspecs = {}
self.volume.size = 8
connection_properties = (
self._initialize_connection(qos, extraspecs)['data'])
self.assertEqual(80, int(connection_properties['iopsLimit']))
self.assertEqual(1024, int(connection_properties['bandwidthLimit']))
self.volume.size = 24
connection_properties = (
self._initialize_connection(qos, extraspecs)['data'])
self.assertEqual(100, int(connection_properties['iopsLimit']))
self.assertEqual(2048, int(connection_properties['bandwidthLimit']))
def test_qos_scaling_no_max(self):
qos = {'maxIOPSperGB': 10, 'maxBWSperGB': 128}
extraspecs = {}
self.volume.size = 8
connection_properties = (
self._initialize_connection(qos, extraspecs)['data'])
self.assertEqual(80, int(connection_properties['iopsLimit']))
self.assertEqual(1024, int(connection_properties['bandwidthLimit']))
def test_qos_round_up(self):
qos = {'maxBWS': 2000, 'maxBWSperGB': 100}
extraspecs = {}
self.volume.size = 8
connection_properties = (
self._initialize_connection(qos, extraspecs)['data'])
self.assertEqual(1024, int(connection_properties['bandwidthLimit']))
self.volume.size = 24
connection_properties = (
self._initialize_connection(qos, extraspecs)['data'])
self.assertEqual(2048, int(connection_properties['bandwidthLimit']))
def _initialize_connection(self, qos, extraspecs):
self.driver._get_volumetype_qos = mock.MagicMock()

View File

@ -80,19 +80,23 @@ IOPS_LIMIT_KEY = 'sio:iops_limit'
BANDWIDTH_LIMIT = 'sio:bandwidth_limit'
QOS_IOPS_LIMIT_KEY = 'maxIOPS'
QOS_BANDWIDTH_LIMIT = 'maxBWS'
QOS_IOPS_PER_GB = 'maxIOPSperGB'
QOS_BANDWIDTH_PER_GB = 'maxBWSperGB'
BLOCK_SIZE = 8
OK_STATUS_CODE = 200
VOLUME_NOT_FOUND_ERROR = 79
VOLUME_NOT_MAPPED_ERROR = 84
VOLUME_ALREADY_MAPPED_ERROR = 81
MIN_BWS_SCALING_SIZE = 128
class ScaleIODriver(driver.VolumeDriver):
"""EMC ScaleIO Driver."""
VERSION = "2.0"
scaleio_qos_keys = (QOS_IOPS_LIMIT_KEY, QOS_BANDWIDTH_LIMIT)
scaleio_qos_keys = (QOS_IOPS_LIMIT_KEY, QOS_BANDWIDTH_LIMIT,
QOS_IOPS_PER_GB, QOS_BANDWIDTH_PER_GB)
def __init__(self, *args, **kwargs):
super(ScaleIODriver, self).__init__(*args, **kwargs)
@ -232,8 +236,10 @@ class ScaleIODriver(driver.VolumeDriver):
return storage_type.get(PROVISIONING_KEY)
def _find_limit(self, storage_type, qos_key, extraspecs_key):
qos_limit = storage_type.get(qos_key)
extraspecs_limit = storage_type.get(extraspecs_key)
qos_limit = (storage_type.get(qos_key)
if qos_key is not None else None)
extraspecs_limit = (storage_type.get(extraspecs_key)
if extraspecs_key is not None else None)
if extraspecs_limit is not None:
if qos_limit is not None:
LOG.warning(_LW("QoS specs are overriding extra_specs."))
@ -416,7 +422,7 @@ class ScaleIODriver(driver.VolumeDriver):
LOG.info(_LI("Created volume %(volname)s, volume id %(volid)s."),
{'volname': volname, 'volid': volume.id})
real_size = int(self._round_to_8_gran(volume.size))
real_size = int(self._round_to_num_gran(volume.size))
return {'provider_id': response['id'], 'size': real_size}
@ -559,20 +565,19 @@ class ScaleIODriver(driver.VolumeDriver):
# Round up the volume size so that it is a granularity of 8 GBs
# because ScaleIO only supports volumes with a granularity of 8 GBs.
volume_new_size = self._round_to_8_gran(new_size)
volume_real_old_size = self._round_to_8_gran(old_size)
volume_new_size = self._round_to_num_gran(new_size)
volume_real_old_size = self._round_to_num_gran(old_size)
if volume_real_old_size == volume_new_size:
return
round_volume_capacity = self.configuration.sio_round_volume_capacity
if (not round_volume_capacity and not new_size % 8 == 0):
if not round_volume_capacity and not new_size % 8 == 0:
LOG.warning(_LW("ScaleIO only supports volumes with a granularity "
"of 8 GBs. The new volume size is: %d."),
volume_new_size)
params = {'sizeInGB': six.text_type(volume_new_size)}
r, response = self._execute_scaleio_post_request(params, request)
if r.status_code != OK_STATUS_CODE:
response = r.json()
msg = (_("Error extending volume %(vol)s: %(err)s.")
@ -581,10 +586,10 @@ class ScaleIODriver(driver.VolumeDriver):
LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg)
def _round_to_8_gran(self, size):
if size % 8 == 0:
def _round_to_num_gran(self, size, num=8):
if size % num == 0:
return size
return size + 8 - (size % 8)
return size + num - (size % num)
def create_cloned_volume(self, volume, src_vref):
"""Creates a cloned volume."""
@ -694,18 +699,66 @@ class ScaleIODriver(driver.VolumeDriver):
storage_type = extra_specs.copy()
storage_type.update(qos_specs)
LOG.info(_LI("Volume type is %s."), storage_type)
iops_limit = self._find_limit(storage_type, QOS_IOPS_LIMIT_KEY,
IOPS_LIMIT_KEY)
LOG.info(_LI("iops limit is: %s."), iops_limit)
bandwidth_limit = self._find_limit(storage_type, QOS_BANDWIDTH_LIMIT,
BANDWIDTH_LIMIT)
LOG.info(_LI("Bandwidth limit is: %s."), bandwidth_limit)
round_volume_size = self._round_to_num_gran(volume.size)
iops_limit = self._get_iops_limit(round_volume_size, storage_type)
bandwidth_limit = self._get_bandwidth_limit(round_volume_size,
storage_type)
LOG.info(_LI("iops limit is %s"), iops_limit)
LOG.info(_LI("bandwidth limit is %s"), bandwidth_limit)
connection_properties['iopsLimit'] = iops_limit
connection_properties['bandwidthLimit'] = bandwidth_limit
return {'driver_volume_type': 'scaleio',
'data': connection_properties}
def _get_bandwidth_limit(self, size, storage_type):
try:
max_bandwidth = self._find_limit(storage_type, QOS_BANDWIDTH_LIMIT,
BANDWIDTH_LIMIT)
if max_bandwidth is not None:
max_bandwidth = (self._round_to_num_gran(int(max_bandwidth),
units.Ki))
max_bandwidth = six.text_type(max_bandwidth)
LOG.info(_LI("max bandwidth is: %s"), max_bandwidth)
bw_per_gb = self._find_limit(storage_type, QOS_BANDWIDTH_PER_GB,
None)
LOG.info(_LI("bandwidth per gb is: %s"), bw_per_gb)
if bw_per_gb is None:
return max_bandwidth
# Since ScaleIO volumes size is in 8GB granularity
# and BWS limitation is in 1024 KBs granularity, we need to make
# sure that scaled_bw_limit is in 128 granularity.
scaled_bw_limit = (size *
self._round_to_num_gran(int(bw_per_gb),
MIN_BWS_SCALING_SIZE))
if max_bandwidth is None or scaled_bw_limit < int(max_bandwidth):
return six.text_type(scaled_bw_limit)
else:
return max_bandwidth
except ValueError:
msg = _("None numeric BWS QoS limitation")
raise exception.InvalidInput(reason=msg)
def _get_iops_limit(self, size, storage_type):
max_iops = self._find_limit(storage_type, QOS_IOPS_LIMIT_KEY,
IOPS_LIMIT_KEY)
LOG.info(_LI("max iops is: %s"), max_iops)
iops_per_gb = self._find_limit(storage_type, QOS_IOPS_PER_GB, None)
LOG.info(_LI("iops per gb is: %s"), iops_per_gb)
try:
if iops_per_gb is None:
if max_iops is not None:
return six.text_type(max_iops)
else:
return None
scaled_iops_limit = size * int(iops_per_gb)
if max_iops is None or scaled_iops_limit < int(max_iops):
return six.text_type(scaled_iops_limit)
else:
return six.text_type(max_iops)
except ValueError:
msg = _("None numeric IOPS QoS limitation")
raise exception.InvalidInput(reason=msg)
def terminate_connection(self, volume, connector, **kwargs):
LOG.debug("scaleio driver terminate connection.")
@ -722,6 +775,7 @@ class ScaleIODriver(driver.VolumeDriver):
stats['reserved_percentage'] = 0
stats['QoS_support'] = True
stats['consistencygroup_support'] = True
pools = []
verify_cert = self._get_verify_cert()

View File

@ -0,0 +1,4 @@
---
features:
- Added support for scaling QoS in the ScaleIO driver.
The new QoS keys are maxIOPSperGB and maxBWSperGB.