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) self.volume = fake_volume.fake_volume_obj(self.ctx)
def test_only_qos(self): def test_only_qos(self):
qos = {'maxIOPS': 1000, 'maxBWS': 3000} qos = {'maxIOPS': 1000, 'maxBWS': 2048}
extraspecs = {} extraspecs = {}
connection_properties = ( connection_properties = (
self._initialize_connection(qos, extraspecs)['data']) self._initialize_connection(qos, extraspecs)['data'])
self.assertEqual(1000, connection_properties['iopsLimit']) self.assertEqual(1000, int(connection_properties['iopsLimit']))
self.assertEqual(3000, connection_properties['bandwidthLimit']) self.assertEqual(2048, int(connection_properties['bandwidthLimit']))
def test_no_qos(self): def test_no_qos(self):
qos = {} qos = {}
@ -47,19 +47,57 @@ class TestInitializeConnection(scaleio.TestScaleIODriver):
def test_only_extraspecs(self): def test_only_extraspecs(self):
qos = {} qos = {}
extraspecs = {'sio:iops_limit': 2000, 'sio:bandwidth_limit': 4000} extraspecs = {'sio:iops_limit': 2000, 'sio:bandwidth_limit': 4096}
connection_properties = ( connection_properties = (
self._initialize_connection(qos, extraspecs)['data']) self._initialize_connection(qos, extraspecs)['data'])
self.assertEqual(2000, connection_properties['iopsLimit']) self.assertEqual(2000, int(connection_properties['iopsLimit']))
self.assertEqual(4000, connection_properties['bandwidthLimit']) self.assertEqual(4096, int(connection_properties['bandwidthLimit']))
def test_qos_and_extraspecs(self): 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} extraspecs = {'sio:iops_limit': 2000, 'sio:bandwidth_limit': 4000}
connection_properties = ( connection_properties = (
self._initialize_connection(qos, extraspecs)['data']) self._initialize_connection(qos, extraspecs)['data'])
self.assertEqual(1000, connection_properties['iopsLimit']) self.assertEqual(1000, int(connection_properties['iopsLimit']))
self.assertEqual(3000, connection_properties['bandwidthLimit']) 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): def _initialize_connection(self, qos, extraspecs):
self.driver._get_volumetype_qos = mock.MagicMock() self.driver._get_volumetype_qos = mock.MagicMock()

View File

@ -81,12 +81,15 @@ IOPS_LIMIT_KEY = 'sio:iops_limit'
BANDWIDTH_LIMIT = 'sio:bandwidth_limit' BANDWIDTH_LIMIT = 'sio:bandwidth_limit'
QOS_IOPS_LIMIT_KEY = 'maxIOPS' QOS_IOPS_LIMIT_KEY = 'maxIOPS'
QOS_BANDWIDTH_LIMIT = 'maxBWS' QOS_BANDWIDTH_LIMIT = 'maxBWS'
QOS_IOPS_PER_GB = 'maxIOPSperGB'
QOS_BANDWIDTH_PER_GB = 'maxBWSperGB'
BLOCK_SIZE = 8 BLOCK_SIZE = 8
OK_STATUS_CODE = 200 OK_STATUS_CODE = 200
VOLUME_NOT_FOUND_ERROR = 79 VOLUME_NOT_FOUND_ERROR = 79
VOLUME_NOT_MAPPED_ERROR = 84 VOLUME_NOT_MAPPED_ERROR = 84
VOLUME_ALREADY_MAPPED_ERROR = 81 VOLUME_ALREADY_MAPPED_ERROR = 81
MIN_BWS_SCALING_SIZE = 128
@interface.volumedriver @interface.volumedriver
@ -94,7 +97,8 @@ class ScaleIODriver(driver.VolumeDriver):
"""EMC ScaleIO Driver.""" """EMC ScaleIO Driver."""
VERSION = "2.0" 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): def __init__(self, *args, **kwargs):
super(ScaleIODriver, self).__init__(*args, **kwargs) super(ScaleIODriver, self).__init__(*args, **kwargs)
@ -234,8 +238,10 @@ class ScaleIODriver(driver.VolumeDriver):
return storage_type.get(PROVISIONING_KEY) return storage_type.get(PROVISIONING_KEY)
def _find_limit(self, storage_type, qos_key, extraspecs_key): def _find_limit(self, storage_type, qos_key, extraspecs_key):
qos_limit = storage_type.get(qos_key) qos_limit = (storage_type.get(qos_key)
extraspecs_limit = storage_type.get(extraspecs_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 extraspecs_limit is not None:
if qos_limit is not None: if qos_limit is not None:
LOG.warning(_LW("QoS specs are overriding extra_specs.")) 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."), LOG.info(_LI("Created volume %(volname)s, volume id %(volid)s."),
{'volname': volname, 'volid': volume.id}) {'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} 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 # 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. # because ScaleIO only supports volumes with a granularity of 8 GBs.
volume_new_size = self._round_to_8_gran(new_size) volume_new_size = self._round_to_num_gran(new_size)
volume_real_old_size = self._round_to_8_gran(old_size) volume_real_old_size = self._round_to_num_gran(old_size)
if volume_real_old_size == volume_new_size: if volume_real_old_size == volume_new_size:
return return
round_volume_capacity = self.configuration.sio_round_volume_capacity 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 " LOG.warning(_LW("ScaleIO only supports volumes with a granularity "
"of 8 GBs. The new volume size is: %d."), "of 8 GBs. The new volume size is: %d."),
volume_new_size) volume_new_size)
params = {'sizeInGB': six.text_type(volume_new_size)} params = {'sizeInGB': six.text_type(volume_new_size)}
r, response = self._execute_scaleio_post_request(params, request) r, response = self._execute_scaleio_post_request(params, request)
if r.status_code != OK_STATUS_CODE: if r.status_code != OK_STATUS_CODE:
response = r.json() response = r.json()
msg = (_("Error extending volume %(vol)s: %(err)s.") msg = (_("Error extending volume %(vol)s: %(err)s.")
@ -583,10 +588,10 @@ class ScaleIODriver(driver.VolumeDriver):
LOG.error(msg) LOG.error(msg)
raise exception.VolumeBackendAPIException(data=msg) raise exception.VolumeBackendAPIException(data=msg)
def _round_to_8_gran(self, size): def _round_to_num_gran(self, size, num=8):
if size % 8 == 0: if size % num == 0:
return size return size
return size + 8 - (size % 8) return size + num - (size % num)
def create_cloned_volume(self, volume, src_vref): def create_cloned_volume(self, volume, src_vref):
"""Creates a cloned volume.""" """Creates a cloned volume."""
@ -696,18 +701,66 @@ class ScaleIODriver(driver.VolumeDriver):
storage_type = extra_specs.copy() storage_type = extra_specs.copy()
storage_type.update(qos_specs) storage_type.update(qos_specs)
LOG.info(_LI("Volume type is %s."), storage_type) LOG.info(_LI("Volume type is %s."), storage_type)
iops_limit = self._find_limit(storage_type, QOS_IOPS_LIMIT_KEY, round_volume_size = self._round_to_num_gran(volume.size)
IOPS_LIMIT_KEY) iops_limit = self._get_iops_limit(round_volume_size, storage_type)
LOG.info(_LI("iops limit is: %s."), iops_limit) bandwidth_limit = self._get_bandwidth_limit(round_volume_size,
bandwidth_limit = self._find_limit(storage_type, QOS_BANDWIDTH_LIMIT, storage_type)
BANDWIDTH_LIMIT) LOG.info(_LI("iops limit is %s"), iops_limit)
LOG.info(_LI("Bandwidth limit is: %s."), bandwidth_limit) LOG.info(_LI("bandwidth limit is %s"), bandwidth_limit)
connection_properties['iopsLimit'] = iops_limit connection_properties['iopsLimit'] = iops_limit
connection_properties['bandwidthLimit'] = bandwidth_limit connection_properties['bandwidthLimit'] = bandwidth_limit
return {'driver_volume_type': 'scaleio', return {'driver_volume_type': 'scaleio',
'data': connection_properties} '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): def terminate_connection(self, volume, connector, **kwargs):
LOG.debug("scaleio driver terminate connection.") LOG.debug("scaleio driver terminate connection.")
@ -724,6 +777,7 @@ class ScaleIODriver(driver.VolumeDriver):
stats['reserved_percentage'] = 0 stats['reserved_percentage'] = 0
stats['QoS_support'] = True stats['QoS_support'] = True
stats['consistencygroup_support'] = True stats['consistencygroup_support'] = True
pools = [] pools = []
verify_cert = self._get_verify_cert() 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.