Merge "Adding Scaling QoS for ScaleIO driver"
This commit is contained in:
commit
483bc008f3
@ -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()
|
||||||
|
@ -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()
|
||||||
|
@ -0,0 +1,4 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Added support for scaling QoS in the ScaleIO driver.
|
||||||
|
The new QoS keys are maxIOPSperGB and maxBWSperGB.
|
Loading…
Reference in New Issue
Block a user