Merge "Adding Scaling QoS for ScaleIO driver"

This commit is contained in:
Jenkins 2016-06-29 10:40:04 +00:00 committed by Gerrit Code Review
commit 483bc008f3
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

@ -81,12 +81,15 @@ 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
@interface.volumedriver
@ -94,7 +97,8 @@ 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)
@ -234,8 +238,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."))
@ -418,7 +424,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}
@ -561,20 +567,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.")
@ -583,10 +588,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."""
@ -696,18 +701,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.")
@ -724,6 +777,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.