ScaleIO over subscription support
Added support for over subscription over thin provisioning. The backend and every pool are supporting both thin and thick provisioning. DocImpact Implements: blueprint scaleio-thin-provisioning- Change-Id: I0181808d3287b8efe7805ea6cc48c8abd2575499
This commit is contained in:
parent
e27e450348
commit
49093ae469
@ -44,6 +44,8 @@ class ScaleIODriver(scaleio.ScaleIODriver):
|
|||||||
override='test_domain')
|
override='test_domain')
|
||||||
configuration.set_override('sio_storage_pools',
|
configuration.set_override('sio_storage_pools',
|
||||||
override='test_domain:test_pool')
|
override='test_domain:test_pool')
|
||||||
|
configuration.set_override('max_over_subscription_ratio',
|
||||||
|
override=5.0)
|
||||||
if 'san_thin_provision' in kwargs:
|
if 'san_thin_provision' in kwargs:
|
||||||
configuration.set_override(
|
configuration.set_override(
|
||||||
'san_thin_provision',
|
'san_thin_provision',
|
||||||
|
@ -12,12 +12,17 @@
|
|||||||
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
|
|
||||||
|
import ddt
|
||||||
|
import mock
|
||||||
|
|
||||||
from cinder import context
|
from cinder import context
|
||||||
from cinder import exception
|
from cinder import exception
|
||||||
from cinder.tests.unit import fake_volume
|
from cinder.tests.unit import fake_volume
|
||||||
from cinder.tests.unit.volume.drivers.emc import scaleio
|
from cinder.tests.unit.volume.drivers.emc import scaleio
|
||||||
|
|
||||||
|
|
||||||
|
@ddt.ddt
|
||||||
class TestCreateVolume(scaleio.TestScaleIODriver):
|
class TestCreateVolume(scaleio.TestScaleIODriver):
|
||||||
"""Test cases for ``ScaleIODriver.create_volume()``"""
|
"""Test cases for ``ScaleIODriver.create_volume()``"""
|
||||||
def setUp(self):
|
def setUp(self):
|
||||||
@ -118,3 +123,16 @@ class TestCreateVolume(scaleio.TestScaleIODriver):
|
|||||||
self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)
|
self.set_https_response_mode(self.RESPONSE_MODE.BadStatus)
|
||||||
self.assertRaises(exception.VolumeBackendAPIException,
|
self.assertRaises(exception.VolumeBackendAPIException,
|
||||||
self.test_create_volume)
|
self.test_create_volume)
|
||||||
|
|
||||||
|
@ddt.data({'provisioning:type': 'thin'}, {'provisioning:type': 'thin'})
|
||||||
|
def test_create_thin_thick_volume(self, extraspecs):
|
||||||
|
self.driver._get_volumetype_extraspecs = mock.MagicMock()
|
||||||
|
self.driver._get_volumetype_extraspecs.return_value = extraspecs
|
||||||
|
self.driver.create_volume(self.volume)
|
||||||
|
|
||||||
|
def test_create_volume_bad_provisioning_type(self):
|
||||||
|
extraspecs = {'provisioning:type': 'other'}
|
||||||
|
self.driver._get_volumetype_extraspecs = mock.MagicMock()
|
||||||
|
self.driver._get_volumetype_extraspecs.return_value = extraspecs
|
||||||
|
self.assertRaises(exception.VolumeBackendAPIException,
|
||||||
|
self.test_create_volume)
|
||||||
|
@ -64,6 +64,8 @@ class TestMisc(scaleio.TestScaleIODriver):
|
|||||||
'capacityAvailableForVolumeAllocationInKb': 5000000,
|
'capacityAvailableForVolumeAllocationInKb': 5000000,
|
||||||
'capacityLimitInKb': 16000000,
|
'capacityLimitInKb': 16000000,
|
||||||
'spareCapacityInKb': 6000000,
|
'spareCapacityInKb': 6000000,
|
||||||
|
'thickCapacityInUseInKb': 266,
|
||||||
|
'thinCapacityAllocatedInKm': 0,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
'instances/Volume::{}/action/setVolumeName'.format(
|
'instances/Volume::{}/action/setVolumeName'.format(
|
||||||
|
@ -68,7 +68,15 @@ scaleio_opts = [
|
|||||||
cfg.StrOpt('sio_storage_pool_name',
|
cfg.StrOpt('sio_storage_pool_name',
|
||||||
help='Storage Pool name.'),
|
help='Storage Pool name.'),
|
||||||
cfg.StrOpt('sio_storage_pool_id',
|
cfg.StrOpt('sio_storage_pool_id',
|
||||||
help='Storage Pool ID.')
|
help='Storage Pool ID.'),
|
||||||
|
cfg.FloatOpt('sio_max_over_subscription_ratio',
|
||||||
|
# This option exists to provide a default value for the
|
||||||
|
# ScaleIO driver which is different than the global default.
|
||||||
|
help='max_over_subscription_ratio setting for the ScaleIO '
|
||||||
|
'driver. If set, this takes precedence over the '
|
||||||
|
'general max_over_subscription_ratio option. If '
|
||||||
|
'None, the general option is used.'
|
||||||
|
'Maximum value allowed for ScaleIO is 10.0.')
|
||||||
]
|
]
|
||||||
|
|
||||||
CONF.register_opts(scaleio_opts)
|
CONF.register_opts(scaleio_opts)
|
||||||
@ -77,7 +85,8 @@ STORAGE_POOL_NAME = 'sio:sp_name'
|
|||||||
STORAGE_POOL_ID = 'sio:sp_id'
|
STORAGE_POOL_ID = 'sio:sp_id'
|
||||||
PROTECTION_DOMAIN_NAME = 'sio:pd_name'
|
PROTECTION_DOMAIN_NAME = 'sio:pd_name'
|
||||||
PROTECTION_DOMAIN_ID = 'sio:pd_id'
|
PROTECTION_DOMAIN_ID = 'sio:pd_id'
|
||||||
PROVISIONING_KEY = 'sio:provisioning_type'
|
PROVISIONING_KEY = 'provisioning:type'
|
||||||
|
OLD_PROVISIONING_KEY = 'sio:provisioning_type'
|
||||||
IOPS_LIMIT_KEY = 'sio:iops_limit'
|
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'
|
||||||
@ -93,6 +102,7 @@ OLD_VOLUME_NOT_FOUND_ERROR = 78
|
|||||||
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
|
MIN_BWS_SCALING_SIZE = 128
|
||||||
|
SIO_MAX_OVERSUBSCRIPTION_RATIO = 10.0
|
||||||
|
|
||||||
|
|
||||||
@interface.volumedriver
|
@interface.volumedriver
|
||||||
@ -165,7 +175,9 @@ class ScaleIODriver(driver.VolumeDriver):
|
|||||||
LOG.info(_LI(
|
LOG.info(_LI(
|
||||||
"Default provisioning type: %(provisioning_type)s."),
|
"Default provisioning type: %(provisioning_type)s."),
|
||||||
{'provisioning_type': self.provisioning_type})
|
{'provisioning_type': self.provisioning_type})
|
||||||
|
if self.configuration.sio_max_over_subscription_ratio is not None:
|
||||||
|
self.configuration.max_over_subscription_ratio = (
|
||||||
|
self.configuration.sio_max_over_subscription_ratio)
|
||||||
self.connector = connector.InitiatorConnector.factory(
|
self.connector = connector.InitiatorConnector.factory(
|
||||||
connector.SCALEIO, utils.get_root_helper(),
|
connector.SCALEIO, utils.get_root_helper(),
|
||||||
device_scan_attempts=
|
device_scan_attempts=
|
||||||
@ -228,6 +240,15 @@ class ScaleIODriver(driver.VolumeDriver):
|
|||||||
"sio_storage_pools."))
|
"sio_storage_pools."))
|
||||||
raise exception.InvalidInput(reason=msg)
|
raise exception.InvalidInput(reason=msg)
|
||||||
|
|
||||||
|
if (self.configuration.max_over_subscription_ratio is not None and
|
||||||
|
(self.configuration.max_over_subscription_ratio -
|
||||||
|
SIO_MAX_OVERSUBSCRIPTION_RATIO > 1)):
|
||||||
|
msg = (_("Max over subscription is configured to %(ratio)1f "
|
||||||
|
"while ScaleIO support up to %(sio_ratio)s.") %
|
||||||
|
{'sio_ratio': SIO_MAX_OVERSUBSCRIPTION_RATIO,
|
||||||
|
'ratio': self.configuration.max_over_subscription_ratio})
|
||||||
|
raise exception.InvalidInput(reason=msg)
|
||||||
|
|
||||||
def _find_storage_pool_id_from_storage_type(self, storage_type):
|
def _find_storage_pool_id_from_storage_type(self, storage_type):
|
||||||
# Default to what was configured in configuration file if not defined.
|
# Default to what was configured in configuration file if not defined.
|
||||||
return storage_type.get(STORAGE_POOL_ID,
|
return storage_type.get(STORAGE_POOL_ID,
|
||||||
@ -248,8 +269,25 @@ class ScaleIODriver(driver.VolumeDriver):
|
|||||||
self.protection_domain_name)
|
self.protection_domain_name)
|
||||||
|
|
||||||
def _find_provisioning_type(self, storage_type):
|
def _find_provisioning_type(self, storage_type):
|
||||||
return storage_type.get(PROVISIONING_KEY,
|
new_provisioning_type = storage_type.get(PROVISIONING_KEY)
|
||||||
self.provisioning_type)
|
old_provisioning_type = storage_type.get(OLD_PROVISIONING_KEY)
|
||||||
|
if new_provisioning_type is None and old_provisioning_type is not None:
|
||||||
|
LOG.info(_LI("Using sio:provisioning_type for defining "
|
||||||
|
"thin or thick volume will be deprecated in the "
|
||||||
|
"Ocata release of OpenStack. Please use "
|
||||||
|
"provisioning:type configuration option."))
|
||||||
|
provisioning_type = old_provisioning_type
|
||||||
|
else:
|
||||||
|
provisioning_type = new_provisioning_type
|
||||||
|
|
||||||
|
if provisioning_type is not None:
|
||||||
|
if provisioning_type not in ('thick', 'thin'):
|
||||||
|
msg = _("Illegal provisioning type. The supported "
|
||||||
|
"provisioning types are 'thick' or 'thin'.")
|
||||||
|
raise exception.VolumeBackendAPIException(data=msg)
|
||||||
|
return provisioning_type
|
||||||
|
else:
|
||||||
|
return self.provisioning_type
|
||||||
|
|
||||||
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)
|
||||||
@ -793,13 +831,15 @@ 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
|
||||||
|
stats['thick_provisioning_support'] = True
|
||||||
|
stats['thin_provisioning_support'] = True
|
||||||
pools = []
|
pools = []
|
||||||
|
|
||||||
verify_cert = self._get_verify_cert()
|
verify_cert = self._get_verify_cert()
|
||||||
|
|
||||||
free_capacity = 0
|
free_capacity = 0
|
||||||
total_capacity = 0
|
total_capacity = 0
|
||||||
|
provisioned_capacity = 0
|
||||||
|
|
||||||
for sp_name in self.storage_pools:
|
for sp_name in self.storage_pools:
|
||||||
splitted_name = sp_name.split(':')
|
splitted_name = sp_name.split(':')
|
||||||
@ -881,9 +921,11 @@ class ScaleIODriver(driver.VolumeDriver):
|
|||||||
request = ("https://%(server_ip)s:%(server_port)s"
|
request = ("https://%(server_ip)s:%(server_port)s"
|
||||||
"/api/types/StoragePool/instances/action/"
|
"/api/types/StoragePool/instances/action/"
|
||||||
"querySelectedStatistics") % req_vars
|
"querySelectedStatistics") % req_vars
|
||||||
|
# The 'Km' in thinCapacityAllocatedInKm is a bug in REST API
|
||||||
params = {'ids': [pool_id], 'properties': [
|
params = {'ids': [pool_id], 'properties': [
|
||||||
"capacityAvailableForVolumeAllocationInKb",
|
"capacityAvailableForVolumeAllocationInKb",
|
||||||
"capacityLimitInKb", "spareCapacityInKb"]}
|
"capacityLimitInKb", "spareCapacityInKb",
|
||||||
|
"thickCapacityInUseInKb", "thinCapacityAllocatedInKm"]}
|
||||||
r = requests.post(
|
r = requests.post(
|
||||||
request,
|
request,
|
||||||
data=json.dumps(params),
|
data=json.dumps(params),
|
||||||
@ -904,18 +946,29 @@ class ScaleIODriver(driver.VolumeDriver):
|
|||||||
# to 8 GB granularity in backend
|
# to 8 GB granularity in backend
|
||||||
free_capacity_gb = (
|
free_capacity_gb = (
|
||||||
res['capacityAvailableForVolumeAllocationInKb'] / units.Mi)
|
res['capacityAvailableForVolumeAllocationInKb'] / units.Mi)
|
||||||
|
# Divide by two because ScaleIO creates a copy for each volume
|
||||||
|
provisioned_capacity = (
|
||||||
|
((res['thickCapacityInUseInKb'] +
|
||||||
|
res['thinCapacityAllocatedInKm']) / 2) / units.Mi)
|
||||||
LOG.info(_LI(
|
LOG.info(_LI(
|
||||||
"free capacity of pool %(pool)s is: %(free)s, "
|
"free capacity of pool %(pool)s is: %(free)s, "
|
||||||
"total capacity: %(total)s."),
|
"total capacity: %(total)s, "
|
||||||
|
"provisioned capacity: %(prov)s"),
|
||||||
{'pool': pool_name,
|
{'pool': pool_name,
|
||||||
'free': free_capacity_gb,
|
'free': free_capacity_gb,
|
||||||
'total': total_capacity_gb})
|
'total': total_capacity_gb,
|
||||||
|
'prov': provisioned_capacity})
|
||||||
pool = {'pool_name': sp_name,
|
pool = {'pool_name': sp_name,
|
||||||
'total_capacity_gb': total_capacity_gb,
|
'total_capacity_gb': total_capacity_gb,
|
||||||
'free_capacity_gb': free_capacity_gb,
|
'free_capacity_gb': free_capacity_gb,
|
||||||
'QoS_support': True,
|
'QoS_support': True,
|
||||||
'consistencygroup_support': True,
|
'consistencygroup_support': True,
|
||||||
'reserved_percentage': 0
|
'reserved_percentage': 0,
|
||||||
|
'thin_provisioning_support': True,
|
||||||
|
'thick_provisioning_support': True,
|
||||||
|
'provisioned_capacity_gb': provisioned_capacity,
|
||||||
|
'max_over_subscription_ratio':
|
||||||
|
self.configuration.max_over_subscription_ratio
|
||||||
}
|
}
|
||||||
|
|
||||||
pools.append(pool)
|
pools.append(pool)
|
||||||
|
@ -0,0 +1,9 @@
|
|||||||
|
---
|
||||||
|
features:
|
||||||
|
- Added support for oversubscription in thin provisioning in the
|
||||||
|
ScaleIO driver.
|
||||||
|
Volumes should have extra_specs with the key provisioning:type with value
|
||||||
|
equals to either 'thick' or 'thin'.
|
||||||
|
max_oversubscription_ratio can be defined by the global config or for
|
||||||
|
ScaleIO specific with the config option sio_max_over_subscription_ratio.
|
||||||
|
The maximum oversubscription ratio supported at the moment is 10.0.
|
Loading…
x
Reference in New Issue
Block a user