Support for oversubscription in thin provisioning
This patch adds support for oversubscription in thin provisioning in Manila. This is similar to the same feature in Cinder. Change-Id: I3e45ad23a83591bf16adff7f21c3ce19325e7e91 implements: blueprint oversubscription-in-thin-provisioning
This commit is contained in:
parent
dbf00f2e32
commit
811f496acd
@ -1,5 +1,6 @@
|
||||
# Copyright (c) 2012 Intel
|
||||
# Copyright (c) 2012 OpenStack, LLC.
|
||||
# Copyright (c) 2015 EMC Corporation
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
@ -41,19 +42,79 @@ class CapacityFilter(filters.BaseHostFilter):
|
||||
return False
|
||||
|
||||
free_space = host_state.free_capacity_gb
|
||||
if free_space == 'infinite' or free_space == 'unknown':
|
||||
total_space = host_state.total_capacity_gb
|
||||
reserved = float(host_state.reserved_percentage) / 100
|
||||
if free_space in ('infinite', 'unknown'):
|
||||
# NOTE(zhiteng) for those back-ends cannot report actual
|
||||
# available capacity, we assume it is able to serve the
|
||||
# request. Even if it was not, the retry mechanism is
|
||||
# able to handle the failure by rescheduling
|
||||
return True
|
||||
reserved = float(host_state.reserved_percentage) / 100
|
||||
free = math.floor(free_space * (1 - reserved))
|
||||
elif total_space in ('infinite', 'unknown'):
|
||||
# NOTE(xyang): If total_space is 'infinite' or 'unknown' and
|
||||
# reserved is 0, we assume the back-ends can serve the request.
|
||||
# If total_space is 'infinite' or 'unknown' and reserved
|
||||
# is not 0, we cannot calculate the reserved space.
|
||||
# float(total_space) will throw an exception. total*reserved
|
||||
# also won't work. So the back-ends cannot serve the request.
|
||||
return reserved == 0
|
||||
total = float(total_space)
|
||||
if total <= 0:
|
||||
LOG.warning(_LW("Insufficient free space for share creation. "
|
||||
"Total capacity is %(total).2f on host %(host)s."),
|
||||
{"total": total,
|
||||
"host": host_state.host})
|
||||
return False
|
||||
# NOTE(xyang): Calculate how much free space is left after taking
|
||||
# into account the reserved space.
|
||||
free = math.floor(free_space - total * reserved)
|
||||
|
||||
msg_args = {"host": host_state.host,
|
||||
"requested": share_size,
|
||||
"available": free}
|
||||
|
||||
LOG.debug("Space information for share creation "
|
||||
"on host %(host)s (requested / avail): "
|
||||
"%(requested)s/%(available)s", msg_args)
|
||||
|
||||
# NOTE(xyang): Only evaluate using max_over_subscription_ratio
|
||||
# if thin_provisioning_support is True. Check if the ratio of
|
||||
# provisioned capacity over total capacity would exceed
|
||||
# subscription ratio.
|
||||
# If max_over_subscription_ratio = 1, the provisioned_ratio
|
||||
# should still be limited by the max_over_subscription_ratio;
|
||||
# otherwise, it could result in infinite provisioning.
|
||||
if (host_state.thin_provisioning_support and
|
||||
host_state.max_over_subscription_ratio >= 1):
|
||||
provisioned_ratio = ((host_state.provisioned_capacity_gb +
|
||||
share_size) / total)
|
||||
if provisioned_ratio > host_state.max_over_subscription_ratio:
|
||||
LOG.warning(_LW(
|
||||
"Insufficient free space for thin provisioning. "
|
||||
"The ratio of provisioned capacity over total capacity "
|
||||
"%(provisioned_ratio).2f would exceed the maximum over "
|
||||
"subscription ratio %(oversub_ratio).2f on host "
|
||||
"%(host)s."),
|
||||
{"provisioned_ratio": provisioned_ratio,
|
||||
"oversub_ratio": host_state.max_over_subscription_ratio,
|
||||
"host": host_state.host})
|
||||
return False
|
||||
else:
|
||||
# NOTE(xyang): Adjust free_virtual calculation based on
|
||||
# free and max_over_subscription_ratio.
|
||||
adjusted_free_virtual = (
|
||||
free * host_state.max_over_subscription_ratio)
|
||||
return adjusted_free_virtual >= share_size
|
||||
elif host_state.thin_provisioning_support:
|
||||
LOG.error(_LE("Invalid max_over_subscription_ratio: %(ratio)s. "
|
||||
"Valid value should be >= 1."),
|
||||
{"ratio": host_state.max_over_subscription_ratio})
|
||||
return False
|
||||
|
||||
if free < share_size:
|
||||
LOG.warning(_LW("Insufficient free space for share creation "
|
||||
"(requested / avail): "
|
||||
"%(requested)s/%(available)s"),
|
||||
{'requested': share_size,
|
||||
'available': free})
|
||||
"on host %(host)s (requested / avail): "
|
||||
"%(requested)s/%(available)s"), msg_args)
|
||||
return False
|
||||
|
||||
return free >= share_size
|
||||
return True
|
||||
|
@ -1,6 +1,7 @@
|
||||
# Copyright (c) 2011 OpenStack, LLC.
|
||||
# Copyright (c) 2015 Rushil Chugh
|
||||
# Copyright (c) 2015 Clinton Knight
|
||||
# Copyright (c) 2015 EMC Corporation
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -53,6 +54,7 @@ host_manager_opts = [
|
||||
|
||||
CONF = cfg.CONF
|
||||
CONF.register_opts(host_manager_opts)
|
||||
CONF.import_opt('max_over_subscription_ratio', 'manila.share.driver')
|
||||
|
||||
LOG = log.getLogger(__name__)
|
||||
|
||||
@ -108,6 +110,15 @@ class HostState(object):
|
||||
self.total_capacity_gb = 0
|
||||
self.free_capacity_gb = None
|
||||
self.reserved_percentage = 0
|
||||
self.allocated_capacity_gb = 0
|
||||
# NOTE(xyang): The apparent allocated space indicating how much
|
||||
# capacity has been provisioned. This could be the sum of sizes
|
||||
# of all shares on a backend, which could be greater than or
|
||||
# equal to the allocated_capacity_gb.
|
||||
self.provisioned_capacity_gb = 0
|
||||
self.max_over_subscription_ratio = 1.0
|
||||
self.thin_provisioning_support = False
|
||||
self.thick_provisioning_support = False
|
||||
self.driver_handles_share_servers = False
|
||||
|
||||
# PoolState for all pools
|
||||
@ -313,6 +324,21 @@ class PoolState(HostState):
|
||||
'allocated_capacity_gb', 0)
|
||||
self.QoS_support = capability.get('QoS_support', False)
|
||||
self.reserved_percentage = capability['reserved_percentage']
|
||||
# NOTE(xyang): provisioned_capacity_gb is the apparent total
|
||||
# capacity of all the shares created on a backend, which is
|
||||
# greater than or equal to allocated_capacity_gb, which is the
|
||||
# apparent total capacity of all the shares created on a backend
|
||||
# in Manila. Using allocated_capacity_gb as the default of
|
||||
# provisioned_capacity_gb if it is not set.
|
||||
self.provisioned_capacity_gb = capability.get(
|
||||
'provisioned_capacity_gb', self.allocated_capacity_gb)
|
||||
self.max_over_subscription_ratio = capability.get(
|
||||
'max_over_subscription_ratio',
|
||||
CONF.max_over_subscription_ratio)
|
||||
self.thin_provisioning_support = capability.get(
|
||||
'thin_provisioning_support', False)
|
||||
self.thick_provisioning_support = capability.get(
|
||||
'thick_provisioning_support', False)
|
||||
|
||||
def update_pools(self, capability):
|
||||
# Do nothing, since we don't have pools within pool, yet
|
||||
|
@ -1,4 +1,6 @@
|
||||
# Copyright (c) 2012 OpenStack, LLC.
|
||||
# Copyright (c) 2015 EMC Corporation
|
||||
#
|
||||
# All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License, Version 2.0 (the "License"); you may
|
||||
@ -13,7 +15,12 @@
|
||||
# License for the specific language governing permissions and limitations
|
||||
# under the License.
|
||||
"""
|
||||
Capacity Weigher. Weigh hosts by their available capacity.
|
||||
Capacity Weigher. Weigh hosts by their virtual or actual free capacity.
|
||||
|
||||
For thin provisioning, weigh hosts by their virtual free capacity calculated
|
||||
by the total capacity multiplied by the max over subscription ratio and
|
||||
subtracting the provisioned capacity; Otherwise, weigh hosts by their actual
|
||||
free capacity, taking into account the reserved space.
|
||||
|
||||
The default is to spread shares across all hosts evenly. If you prefer
|
||||
stacking, you can set the 'capacity_weight_multiplier' option to a negative
|
||||
@ -47,10 +54,22 @@ class CapacityWeigher(weights.BaseHostWeigher):
|
||||
"""Higher weights win. We want spreading to be the default."""
|
||||
reserved = float(host_state.reserved_percentage) / 100
|
||||
free_space = host_state.free_capacity_gb
|
||||
if free_space == 'infinite' or free_space == 'unknown':
|
||||
total_space = host_state.total_capacity_gb
|
||||
if {'unknown', 'infinite'}.intersection({total_space, free_space}):
|
||||
# (zhiteng) 'infinite' and 'unknown' are treated the same
|
||||
# here, for sorting purpose.
|
||||
free = float('inf')
|
||||
else:
|
||||
free = math.floor(host_state.free_capacity_gb * (1 - reserved))
|
||||
total = float(total_space)
|
||||
if host_state.thin_provisioning_support:
|
||||
# NOTE(xyang): Calculate virtual free capacity for thin
|
||||
# provisioning.
|
||||
free = math.floor(
|
||||
total * host_state.max_over_subscription_ratio -
|
||||
host_state.provisioned_capacity_gb -
|
||||
total * reserved)
|
||||
else:
|
||||
# NOTE(xyang): Calculate how much free space is left after
|
||||
# taking into account the reserved space.
|
||||
free = math.floor(free_space - total * reserved)
|
||||
return free
|
||||
|
@ -61,6 +61,17 @@ share_opts = [
|
||||
"is able to handle share servers and it is desired mode else set "
|
||||
"False. It is set to None by default to make this choice "
|
||||
"intentional."),
|
||||
cfg.FloatOpt(
|
||||
'max_over_subscription_ratio',
|
||||
default=20.0,
|
||||
help='Float representation of the over subscription ratio '
|
||||
'when thin provisioning is involved. Default ratio is '
|
||||
'20.0, meaning provisioned capacity can be 20 times '
|
||||
'the total physical capacity. If the ratio is 10.5, it '
|
||||
'means provisioned capacity can be 10.5 times the '
|
||||
'total physical capacity. A ratio of 1.0 means '
|
||||
'provisioned capacity cannot exceed the total physical '
|
||||
'capacity. A ratio lower than 1.0 is invalid.'),
|
||||
]
|
||||
|
||||
ssh_opts = [
|
||||
@ -287,6 +298,11 @@ class ShareDriver(object):
|
||||
|
||||
def check_for_setup_error(self):
|
||||
"""Check for setup error."""
|
||||
max_ratio = self.configuration.safe_get('max_over_subscription_ratio')
|
||||
if max_ratio < 1.0:
|
||||
msg = (_("Invalid max_over_subscription_ratio '%s'. "
|
||||
"Valid value should be >= 1.0.") % max_ratio)
|
||||
raise exception.InvalidParameterValue(err=msg)
|
||||
|
||||
def do_setup(self, context):
|
||||
"""Any initialization the share driver does while starting."""
|
||||
|
@ -69,9 +69,6 @@ class FakeShareDriver(driver.ShareDriver):
|
||||
def deny_access(self, context, share, access, share_server=None):
|
||||
pass
|
||||
|
||||
def check_for_setup_error(self):
|
||||
pass
|
||||
|
||||
def get_share_stats(self, refresh=False):
|
||||
return None
|
||||
|
||||
|
@ -35,14 +35,26 @@ SERVICE_STATES_NO_POOLS = {
|
||||
'host1': dict(share_backend_name='AAA',
|
||||
total_capacity_gb=512, free_capacity_gb=200,
|
||||
timestamp=None, reserved_percentage=0,
|
||||
provisioned_capacity_gb=312,
|
||||
max_over_subscription_ratio=1.0,
|
||||
thin_provisioning_support=False,
|
||||
thick_provisioning_support=True,
|
||||
driver_handles_share_servers=False),
|
||||
'host2@back1': dict(share_backend_name='BBB',
|
||||
total_capacity_gb=256, free_capacity_gb=100,
|
||||
timestamp=None, reserved_percentage=0,
|
||||
provisioned_capacity_gb=400,
|
||||
max_over_subscription_ratio=2.0,
|
||||
thin_provisioning_support=True,
|
||||
thick_provisioning_support=False,
|
||||
driver_handles_share_servers=False),
|
||||
'host2@back2': dict(share_backend_name='CCC',
|
||||
total_capacity_gb=10000, free_capacity_gb=700,
|
||||
timestamp=None, reserved_percentage=0,
|
||||
provisioned_capacity_gb=50000,
|
||||
max_over_subscription_ratio=20.0,
|
||||
thin_provisioning_support=True,
|
||||
thick_provisioning_support=True,
|
||||
driver_handles_share_servers=False),
|
||||
}
|
||||
|
||||
@ -67,43 +79,71 @@ SHARE_SERVICE_STATES_WITH_POOLS = {
|
||||
pools=[dict(pool_name='pool1',
|
||||
total_capacity_gb=51,
|
||||
free_capacity_gb=41,
|
||||
reserved_percentage=0)]),
|
||||
reserved_percentage=0,
|
||||
provisioned_capacity_gb=10,
|
||||
max_over_subscription_ratio=1.0,
|
||||
thin_provisioning_support=False,
|
||||
thick_provisioning_support=True)]),
|
||||
'host2@BBB': dict(share_backend_name='BBB',
|
||||
timestamp=None, reserved_percentage=0,
|
||||
driver_handles_share_servers=False,
|
||||
pools=[dict(pool_name='pool2',
|
||||
total_capacity_gb=52,
|
||||
free_capacity_gb=42,
|
||||
reserved_percentage=0)]),
|
||||
reserved_percentage=0,
|
||||
provisioned_capacity_gb=60,
|
||||
max_over_subscription_ratio=2.0,
|
||||
thin_provisioning_support=True,
|
||||
thick_provisioning_support=False)]),
|
||||
'host3@CCC': dict(share_backend_name='CCC',
|
||||
timestamp=None, reserved_percentage=0,
|
||||
driver_handles_share_servers=False,
|
||||
pools=[dict(pool_name='pool3',
|
||||
total_capacity_gb=53,
|
||||
free_capacity_gb=43,
|
||||
reserved_percentage=0)]),
|
||||
reserved_percentage=0,
|
||||
provisioned_capacity_gb=100,
|
||||
max_over_subscription_ratio=20.0,
|
||||
thin_provisioning_support=True,
|
||||
thick_provisioning_support=True)]),
|
||||
'host4@DDD': dict(share_backend_name='DDD',
|
||||
timestamp=None, reserved_percentage=0,
|
||||
driver_handles_share_servers=False,
|
||||
pools=[dict(pool_name='pool4a',
|
||||
total_capacity_gb=541,
|
||||
free_capacity_gb=441,
|
||||
reserved_percentage=0),
|
||||
reserved_percentage=0,
|
||||
provisioned_capacity_gb=800,
|
||||
max_over_subscription_ratio=2.0,
|
||||
thin_provisioning_support=True,
|
||||
thick_provisioning_support=False),
|
||||
dict(pool_name='pool4b',
|
||||
total_capacity_gb=542,
|
||||
free_capacity_gb=442,
|
||||
reserved_percentage=0)]),
|
||||
reserved_percentage=0,
|
||||
provisioned_capacity_gb=2000,
|
||||
max_over_subscription_ratio=10.0,
|
||||
thin_provisioning_support=True,
|
||||
thick_provisioning_support=False)]),
|
||||
'host5@EEE': dict(share_backend_name='EEE',
|
||||
timestamp=None, reserved_percentage=0,
|
||||
driver_handles_share_servers=False,
|
||||
pools=[dict(pool_name='pool5a',
|
||||
total_capacity_gb=551,
|
||||
free_capacity_gb=451,
|
||||
reserved_percentage=0),
|
||||
reserved_percentage=0,
|
||||
provisioned_capacity_gb=100,
|
||||
max_over_subscription_ratio=1.0,
|
||||
thin_provisioning_support=False,
|
||||
thick_provisioning_support=True),
|
||||
dict(pool_name='pool5b',
|
||||
total_capacity_gb=552,
|
||||
free_capacity_gb=452,
|
||||
reserved_percentage=0)]),
|
||||
reserved_percentage=0,
|
||||
provisioned_capacity_gb=100,
|
||||
max_over_subscription_ratio=1.0,
|
||||
thin_provisioning_support=False,
|
||||
thick_provisioning_support=True)]),
|
||||
}
|
||||
|
||||
|
||||
@ -120,22 +160,47 @@ class FakeHostManager(host_manager.HostManager):
|
||||
self.service_states = {
|
||||
'host1': {'total_capacity_gb': 1024,
|
||||
'free_capacity_gb': 1024,
|
||||
'allocated_capacity_gb': 0,
|
||||
'provisioned_capacity_gb': 0,
|
||||
'max_over_subscription_ratio': 1.0,
|
||||
'thin_provisioning_support': False,
|
||||
'thick_provisioning_support': True,
|
||||
'reserved_percentage': 10,
|
||||
'timestamp': None},
|
||||
'host2': {'total_capacity_gb': 2048,
|
||||
'free_capacity_gb': 300,
|
||||
'allocated_capacity_gb': 1748,
|
||||
'provisioned_capacity_gb': 1748,
|
||||
'max_over_subscription_ratio': 2.0,
|
||||
'thin_provisioning_support': True,
|
||||
'thick_provisioning_support': False,
|
||||
'reserved_percentage': 10,
|
||||
'timestamp': None},
|
||||
'host3': {'total_capacity_gb': 512,
|
||||
'free_capacity_gb': 512,
|
||||
'free_capacity_gb': 256,
|
||||
'allocated_capacity_gb': 256,
|
||||
'provisioned_capacity_gb': 256,
|
||||
'max_over_subscription_ratio': 2.0,
|
||||
'thin_provisioning_support': False,
|
||||
'thick_provisioning_support': True,
|
||||
'reserved_percentage': 0,
|
||||
'timestamp': None},
|
||||
'host4': {'total_capacity_gb': 2048,
|
||||
'free_capacity_gb': 200,
|
||||
'allocated_capacity_gb': 1848,
|
||||
'provisioned_capacity_gb': 1848,
|
||||
'max_over_subscription_ratio': 1.0,
|
||||
'thin_provisioning_support': True,
|
||||
'thick_provisioning_support': True,
|
||||
'reserved_percentage': 5,
|
||||
'timestamp': None},
|
||||
'host5': {'total_capacity_gb': 2048,
|
||||
'free_capacity_gb': 500,
|
||||
'allocated_capacity_gb': 1548,
|
||||
'provisioned_capacity_gb': 1548,
|
||||
'max_over_subscription_ratio': 1.5,
|
||||
'thin_provisioning_support': True,
|
||||
'thick_provisioning_support': False,
|
||||
'reserved_percentage': 5,
|
||||
'timestamp': None},
|
||||
}
|
||||
|
@ -38,7 +38,7 @@ class CapacityWeigherTestCase(test.TestCase):
|
||||
|
||||
def _get_weighed_host(self, hosts, weight_properties=None):
|
||||
if weight_properties is None:
|
||||
weight_properties = {}
|
||||
weight_properties = {'size': 1}
|
||||
return self.weight_handler.get_weighed_objects(
|
||||
[capacity.CapacityWeigher],
|
||||
hosts,
|
||||
@ -54,46 +54,120 @@ class CapacityWeigherTestCase(test.TestCase):
|
||||
ctxt, CONF.share_topic)
|
||||
return host_states
|
||||
|
||||
# NOTE(xyang): If thin_provisioning_support = True and
|
||||
# max_over_subscription_ratio >= 1, use the following formula:
|
||||
# free = math.floor(total * host_state.max_over_subscription_ratio
|
||||
# - host_state.provisioned_capacity_gb
|
||||
# - total * reserved)
|
||||
# Otherwise, use the following formula:
|
||||
# free = math.floor(free_space - total * reserved)
|
||||
def test_default_of_spreading_first(self):
|
||||
hostinfo_list = self._get_all_hosts()
|
||||
|
||||
# host1: free_capacity_gb=1024, free=1024*(1-0.1)
|
||||
# host2: free_capacity_gb=300, free=300*(1-0.1)
|
||||
# host3: free_capacity_gb=512, free=512
|
||||
# host4: free_capacity_gb=200, free=200*(1-0.05)
|
||||
# host1: thin_provisioning_support = False
|
||||
# free_capacity_gb = 1024
|
||||
# free = math.floor(1024 - 1024 * 0.1) = 921.0
|
||||
# weight = 0.40
|
||||
# host2: thin_provisioning_support = True
|
||||
# max_over_subscription_ratio = 2.0
|
||||
# free_capacity_gb = 300
|
||||
# free = math.floor(2048 * 2.0 - 1748 - 2048 * 0.1)=2143.0
|
||||
# weight = 1.0
|
||||
# host3: thin_provisioning_support = False
|
||||
# free_capacity_gb = 512
|
||||
# free = math.floor(256 - 512 * 0)=256.0
|
||||
# weight = 0.08
|
||||
# host4: thin_provisioning_support = True
|
||||
# max_over_subscription_ratio = 1.0
|
||||
# free_capacity_gb = 200
|
||||
# free = math.floor(2048 * 1.0 - 1848 - 2048 * 0.05) = 97.0
|
||||
# weight = 0.0
|
||||
# host5: thin_provisioning_support = True
|
||||
# max_over_subscription_ratio = 1.5
|
||||
# free_capacity_gb = 500
|
||||
# free = math.floor(2048 * 1.5 - 1548 - 2048 * 0.05) = 1421.0
|
||||
# weight = 0.65
|
||||
|
||||
# so, host1 should win:
|
||||
# so, host2 should win:
|
||||
weighed_host = self._get_weighed_host(hostinfo_list)
|
||||
self.assertEqual(weighed_host.weight, 1.0)
|
||||
self.assertEqual(1.0, weighed_host.weight)
|
||||
self.assertEqual(
|
||||
'host1', utils.extract_host(weighed_host.obj.host))
|
||||
'host2', utils.extract_host(weighed_host.obj.host))
|
||||
|
||||
def test_capacity_weight_multiplier1(self):
|
||||
def test_capacity_weight_multiplier_negative_1(self):
|
||||
self.flags(capacity_weight_multiplier=-1.0)
|
||||
hostinfo_list = self._get_all_hosts()
|
||||
|
||||
# host1: free_capacity_gb=1024, free=-1024*(1-0.1)
|
||||
# host2: free_capacity_gb=300, free=-300*(1-0.1)
|
||||
# host3: free_capacity_gb=512, free=-512
|
||||
# host4: free_capacity_gb=200, free=-200*(1-0.05)
|
||||
# host1: thin_provisioning_support = False
|
||||
# free_capacity_gb = 1024
|
||||
# free = math.floor(1024 - 1024 * 0.1) = 921.0
|
||||
# free * (-1) = -921.0
|
||||
# weight = -0.40
|
||||
# host2: thin_provisioning_support = True
|
||||
# max_over_subscription_ratio = 2.0
|
||||
# free_capacity_gb = 300
|
||||
# free = math.floor(2048 * 2.0-1748-2048 * 0.1) = 2143.0
|
||||
# free * (-1) = -2143.0
|
||||
# weight = -1.0
|
||||
# host3: thin_provisioning_support = False
|
||||
# free_capacity_gb = 512
|
||||
# free = math.floor(256 - 512 * 0) = 256.0
|
||||
# free * (-1) = -256.0
|
||||
# weight = -0.08
|
||||
# host4: thin_provisioning_support = True
|
||||
# max_over_subscription_ratio = 1.0
|
||||
# free_capacity_gb = 200
|
||||
# free = math.floor(2048 * 1.0 - 1848 - 2048 * 0.05) = 97.0
|
||||
# free * (-1) = -97.0
|
||||
# weight = 0.0
|
||||
# host5: thin_provisioning_support = True
|
||||
# max_over_subscription_ratio = 1.5
|
||||
# free_capacity_gb = 500
|
||||
# free = math.floor(2048 * 1.5 - 1548 - 2048 * 0.05) = 1421.0
|
||||
# free * (-1) = -1421.0
|
||||
# weight = -0.65
|
||||
|
||||
# so, host4 should win:
|
||||
weighed_host = self._get_weighed_host(hostinfo_list)
|
||||
self.assertEqual(weighed_host.weight, 0.0)
|
||||
self.assertEqual(0.0, weighed_host.weight)
|
||||
self.assertEqual(
|
||||
'host4', utils.extract_host(weighed_host.obj.host))
|
||||
|
||||
def test_capacity_weight_multiplier2(self):
|
||||
def test_capacity_weight_multiplier_2(self):
|
||||
self.flags(capacity_weight_multiplier=2.0)
|
||||
hostinfo_list = self._get_all_hosts()
|
||||
|
||||
# host1: free_capacity_gb=1024, free=1024*(1-0.1)*2
|
||||
# host2: free_capacity_gb=300, free=300*(1-0.1)*2
|
||||
# host3: free_capacity_gb=512, free=512*2
|
||||
# host4: free_capacity_gb=200, free=200*(1-0.05)*2
|
||||
# host1: thin_provisioning_support = False
|
||||
# free_capacity_gb = 1024
|
||||
# free = math.floor(1024-1024*0.1) = 921.0
|
||||
# free * 2 = 1842.0
|
||||
# weight = 0.81
|
||||
# host2: thin_provisioning_support = True
|
||||
# max_over_subscription_ratio = 2.0
|
||||
# free_capacity_gb = 300
|
||||
# free = math.floor(2048 * 2.0 - 1748 - 2048 * 0.1) = 2143.0
|
||||
# free * 2 = 4286.0
|
||||
# weight = 2.0
|
||||
# host3: thin_provisioning_support = False
|
||||
# free_capacity_gb = 512
|
||||
# free = math.floor(256 - 512 * 0) = 256.0
|
||||
# free * 2 = 512.0
|
||||
# weight = 0.16
|
||||
# host4: thin_provisioning_support = True
|
||||
# max_over_subscription_ratio = 1.0
|
||||
# free_capacity_gb = 200
|
||||
# free = math.floor(2048 * 1.0 - 1848 - 2048 * 0.05) = 97.0
|
||||
# free * 2 = 194.0
|
||||
# weight = 0.0
|
||||
# host5: thin_provisioning_support = True
|
||||
# max_over_subscription_ratio = 1.5
|
||||
# free_capacity_gb = 500
|
||||
# free = math.floor(2048 * 1.5 - 1548 - 2048 * 0.05) = 1421.0
|
||||
# free * 2 = 2842.0
|
||||
# weight = 1.29
|
||||
|
||||
# so, host1 should win:
|
||||
# so, host2 should win:
|
||||
weighed_host = self._get_weighed_host(hostinfo_list)
|
||||
self.assertEqual(weighed_host.weight, 2.0)
|
||||
self.assertEqual(2.0, weighed_host.weight)
|
||||
self.assertEqual(
|
||||
'host1', utils.extract_host(weighed_host.obj.host))
|
||||
'host2', utils.extract_host(weighed_host.obj.host))
|
||||
|
@ -15,6 +15,7 @@
|
||||
Tests For Scheduler Host Filters.
|
||||
"""
|
||||
|
||||
import ddt
|
||||
from oslo_serialization import jsonutils
|
||||
|
||||
from manila import context
|
||||
@ -24,6 +25,7 @@ from manila.tests.scheduler import fakes
|
||||
from manila import utils
|
||||
|
||||
|
||||
@ddt.ddt
|
||||
class HostFiltersTestCase(test.TestCase):
|
||||
"""Test case for host filters."""
|
||||
|
||||
@ -46,62 +48,196 @@ class HostFiltersTestCase(test.TestCase):
|
||||
return ret_value
|
||||
self.mock_object(utils, 'service_is_up', fake_service_is_up)
|
||||
|
||||
def test_capacity_filter_passes(self):
|
||||
@ddt.data(
|
||||
{'size': 100, 'share_on': None, 'host': 'host1'},
|
||||
{'size': 100, 'share_on': 'host1#pool1', 'host': 'host1#pools1'})
|
||||
@ddt.unpack
|
||||
def test_capacity_filter_passes(self, size, share_on, host):
|
||||
self._stub_service_is_up(True)
|
||||
filt_cls = self.class_map['CapacityFilter']()
|
||||
filter_properties = {'size': size,
|
||||
'share_exists_on': share_on}
|
||||
service = {'disabled': False}
|
||||
host = fakes.FakeHostState(host,
|
||||
{'total_capacity_gb': 500,
|
||||
'free_capacity_gb': 200,
|
||||
'updated_at': None,
|
||||
'service': service})
|
||||
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
||||
|
||||
@ddt.data(
|
||||
{'free_capacity': 120, 'total_capacity': 200,
|
||||
'reserved': 20},
|
||||
{'free_capacity': None, 'total_capacity': None,
|
||||
'reserved': None})
|
||||
@ddt.unpack
|
||||
def test_capacity_filter_fails(self, free_capacity, total_capacity,
|
||||
reserved):
|
||||
self._stub_service_is_up(True)
|
||||
filt_cls = self.class_map['CapacityFilter']()
|
||||
filter_properties = {'size': 100}
|
||||
service = {'disabled': False}
|
||||
host = fakes.FakeHostState('host1',
|
||||
{'free_capacity_gb': 200,
|
||||
'updated_at': None,
|
||||
'service': service})
|
||||
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
||||
|
||||
def test_capacity_filter_current_host_passes(self):
|
||||
self._stub_service_is_up(True)
|
||||
filt_cls = self.class_map['CapacityFilter']()
|
||||
filter_properties = {'size': 100, 'share_exists_on': 'host1#pool1'}
|
||||
service = {'disabled': False}
|
||||
host = fakes.FakeHostState('host1#pools1',
|
||||
{'free_capacity_gb': 200,
|
||||
'updated_at': None,
|
||||
'service': service})
|
||||
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
||||
|
||||
def test_capacity_filter_fails(self):
|
||||
self._stub_service_is_up(True)
|
||||
filt_cls = self.class_map['CapacityFilter']()
|
||||
filter_properties = {'size': 100}
|
||||
service = {'disabled': False}
|
||||
host = fakes.FakeHostState('host1',
|
||||
{'free_capacity_gb': 120,
|
||||
'reserved_percentage': 20,
|
||||
{'total_capacity_gb': total_capacity,
|
||||
'free_capacity_gb': free_capacity,
|
||||
'reserved_percentage': reserved,
|
||||
'updated_at': None,
|
||||
'service': service})
|
||||
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
||||
|
||||
def test_capacity_filter_passes_infinite(self):
|
||||
@ddt.data('infinite', 'unknown')
|
||||
def test_capacity_filter_passes_infinite_unknown(self, free):
|
||||
self._stub_service_is_up(True)
|
||||
filt_cls = self.class_map['CapacityFilter']()
|
||||
filter_properties = {'size': 100}
|
||||
service = {'disabled': False}
|
||||
host = fakes.FakeHostState('host1',
|
||||
{'free_capacity_gb': 'infinite',
|
||||
{'free_capacity_gb': free,
|
||||
'updated_at': None,
|
||||
'service': service})
|
||||
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
||||
|
||||
def test_capacity_filter_passes_unknown(self):
|
||||
@ddt.data(
|
||||
{'free_capacity': 'infinite', 'total_capacity': 'infinite'},
|
||||
{'free_capacity': 'unknown', 'total_capacity': 'unknown'})
|
||||
@ddt.unpack
|
||||
def test_capacity_filter_passes_total(self, free_capacity,
|
||||
total_capacity):
|
||||
self._stub_service_is_up(True)
|
||||
filt_cls = self.class_map['CapacityFilter']()
|
||||
filter_properties = {'size': 100}
|
||||
service = {'disabled': False}
|
||||
host = fakes.FakeHostState('host1',
|
||||
{'free_capacity_gb': 'unknown',
|
||||
{'free_capacity_gb': free_capacity,
|
||||
'total_capacity_gb': total_capacity,
|
||||
'reserved_percentage': 0,
|
||||
'updated_at': None,
|
||||
'service': service})
|
||||
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
||||
|
||||
@ddt.data('infinite', 'unknown', 0)
|
||||
def test_capacity_filter_fails_total(self, total):
|
||||
self._stub_service_is_up(True)
|
||||
filt_cls = self.class_map['CapacityFilter']()
|
||||
filter_properties = {'size': 100}
|
||||
service = {'disabled': False}
|
||||
host = fakes.FakeHostState('host1',
|
||||
{'total_capacity_gb': total,
|
||||
'reserved_percentage': 5,
|
||||
'updated_at': None,
|
||||
'service': service})
|
||||
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
||||
|
||||
@ddt.data(
|
||||
{'size': 100, 'cap_thin': '<is> True', 'cap_thick': '<is> False',
|
||||
'total': 500, 'free': 200, 'provisioned': 500,
|
||||
'max_ratio': 2.0, 'reserved': 5, 'thin_prov': True,
|
||||
'thick_prov': False},
|
||||
{'size': 3000, 'cap_thin': '<is> True', 'cap_thick': '<is> False',
|
||||
'total': 500, 'free': 200, 'provisioned': 7000,
|
||||
'max_ratio': 20, 'reserved': 5, 'thin_prov': True,
|
||||
'thick_prov': False},
|
||||
{'size': 100, 'cap_thin': '<is> False', 'cap_thick': '<is> True',
|
||||
'total': 500, 'free': 200, 'provisioned': 300,
|
||||
'max_ratio': 1.0, 'reserved': 5, 'thin_prov': False,
|
||||
'thick_prov': True},
|
||||
{'size': 100, 'cap_thin': '<is> True', 'cap_thick': '<is> False',
|
||||
'total': 500, 'free': 200, 'provisioned': 400,
|
||||
'max_ratio': 1.0, 'reserved': 5, 'thin_prov': True,
|
||||
'thick_prov': False},
|
||||
{'size': 100, 'cap_thin': '<is> True', 'cap_thick': '<is> True',
|
||||
'total': 500, 'free': 125, 'provisioned': 400,
|
||||
'max_ratio': 2.0, 'reserved': 5, 'thin_prov': True,
|
||||
'thick_prov': True},
|
||||
{'size': 100, 'cap_thin': '<is> True', 'cap_thick': '<is> False',
|
||||
'total': 500, 'free': 80, 'provisioned': 600,
|
||||
'max_ratio': 2.0, 'reserved': 5, 'thin_prov': True,
|
||||
'thick_prov': False},
|
||||
{'size': 100, 'cap_thin': '<is> True', 'cap_thick': '<is> True',
|
||||
'total': 500, 'free': 100, 'provisioned': 400,
|
||||
'max_ratio': 2.0, 'reserved': 0, 'thin_prov': True,
|
||||
'thick_prov': True})
|
||||
@ddt.unpack
|
||||
def test_filter_thin_thick_passes(self, size, cap_thin, cap_thick,
|
||||
total, free, provisioned, max_ratio,
|
||||
reserved, thin_prov, thick_prov):
|
||||
self._stub_service_is_up(True)
|
||||
filt_cls = self.class_map['CapacityFilter']()
|
||||
filter_properties = {'size': size,
|
||||
'capabilities:thin_provisioning_support':
|
||||
cap_thin,
|
||||
'capabilities:thick_provisioning_support':
|
||||
cap_thick}
|
||||
service = {'disabled': False}
|
||||
host = fakes.FakeHostState('host1',
|
||||
{'total_capacity_gb': total,
|
||||
'free_capacity_gb': free,
|
||||
'provisioned_capacity_gb': provisioned,
|
||||
'max_over_subscription_ratio': max_ratio,
|
||||
'reserved_percentage': reserved,
|
||||
'thin_provisioning_support': thin_prov,
|
||||
'thick_provisioning_support': thick_prov,
|
||||
'updated_at': None,
|
||||
'service': service})
|
||||
self.assertTrue(filt_cls.host_passes(host, filter_properties))
|
||||
|
||||
@ddt.data(
|
||||
{'size': 200, 'cap_thin': '<is> True', 'cap_thick': '<is> False',
|
||||
'total': 500, 'free': 100, 'provisioned': 400,
|
||||
'max_ratio': 0.8, 'reserved': 0, 'thin_prov': True,
|
||||
'thick_prov': False},
|
||||
{'size': 100, 'cap_thin': '<is> True', 'cap_thick': '<is> False',
|
||||
'total': 500, 'free': 200, 'provisioned': 700,
|
||||
'max_ratio': 1.5, 'reserved': 5, 'thin_prov': True,
|
||||
'thick_prov': False},
|
||||
{'size': 2000, 'cap_thin': '<is> True', 'cap_thick': '<is> False',
|
||||
'total': 500, 'free': 30, 'provisioned': 9000,
|
||||
'max_ratio': 20.0, 'reserved': 0, 'thin_prov': True,
|
||||
'thick_prov': False},
|
||||
{'size': 100, 'cap_thin': '<is> True', 'cap_thick': '<is> False',
|
||||
'total': 500, 'free': 100, 'provisioned': 1000,
|
||||
'max_ratio': 2.0, 'reserved': 5, 'thin_prov': True,
|
||||
'thick_prov': False},
|
||||
{'size': 100, 'cap_thin': '<is> False', 'cap_thick': '<is> True',
|
||||
'total': 500, 'free': 100, 'provisioned': 400,
|
||||
'max_ratio': 1.0, 'reserved': 5, 'thin_prov': False,
|
||||
'thick_prov': True},
|
||||
{'size': 100, 'cap_thin': '<is> True', 'cap_thick': '<is> True',
|
||||
'total': 500, 'free': 0, 'provisioned': 800,
|
||||
'max_ratio': 2.0, 'reserved': 5, 'thin_prov': True,
|
||||
'thick_prov': True},
|
||||
{'size': 100, 'cap_thin': '<is> True', 'cap_thick': '<is> True',
|
||||
'total': 500, 'free': 99, 'provisioned': 1000,
|
||||
'max_ratio': 2.0, 'reserved': 5, 'thin_prov': True,
|
||||
'thick_prov': True},
|
||||
{'size': 400, 'cap_thin': '<is> True', 'cap_thick': '<is> False',
|
||||
'total': 500, 'free': 200, 'provisioned': 600,
|
||||
'max_ratio': 2.0, 'reserved': 5, 'thin_prov': True,
|
||||
'thick_prov': False})
|
||||
@ddt.unpack
|
||||
def test_filter_thin_thick_fails(self, size, cap_thin, cap_thick,
|
||||
total, free, provisioned, max_ratio,
|
||||
reserved, thin_prov, thick_prov):
|
||||
self._stub_service_is_up(True)
|
||||
filt_cls = self.class_map['CapacityFilter']()
|
||||
filter_properties = {'size': size,
|
||||
'capabilities:thin_provisioning_support':
|
||||
cap_thin,
|
||||
'capabilities:thick_provisioning_support':
|
||||
cap_thick}
|
||||
service = {'disabled': False}
|
||||
host = fakes.FakeHostState('host1',
|
||||
{'total_capacity_gb': total,
|
||||
'free_capacity_gb': free,
|
||||
'provisioned_capacity_gb': provisioned,
|
||||
'max_over_subscription_ratio': max_ratio,
|
||||
'reserved_percentage': reserved,
|
||||
'thin_provisioning_support': thin_prov,
|
||||
'thick_provisioning_support': thick_prov,
|
||||
'updated_at': None,
|
||||
'service': service})
|
||||
self.assertFalse(filt_cls.host_passes(host, filter_properties))
|
||||
|
||||
def test_retry_filter_disabled(self):
|
||||
# Test case where retry/re-scheduling is disabled.
|
||||
filt_cls = self.class_map['RetryFilter']()
|
||||
|
@ -188,6 +188,10 @@ class HostManagerTestCase(test.TestCase):
|
||||
'driver_version': None,
|
||||
'total_capacity_gb': 512,
|
||||
'reserved_percentage': 0,
|
||||
'provisioned_capacity_gb': 312,
|
||||
'max_over_subscription_ratio': 1.0,
|
||||
'thin_provisioning_support': False,
|
||||
'thick_provisioning_support': True,
|
||||
'vendor_name': None,
|
||||
'storage_protocol': None,
|
||||
'driver_handles_share_servers': False,
|
||||
@ -204,6 +208,10 @@ class HostManagerTestCase(test.TestCase):
|
||||
'driver_version': None,
|
||||
'total_capacity_gb': 256,
|
||||
'reserved_percentage': 0,
|
||||
'provisioned_capacity_gb': 400,
|
||||
'max_over_subscription_ratio': 2.0,
|
||||
'thin_provisioning_support': True,
|
||||
'thick_provisioning_support': False,
|
||||
'vendor_name': None,
|
||||
'storage_protocol': None,
|
||||
'driver_handles_share_servers': False,
|
||||
@ -220,6 +228,10 @@ class HostManagerTestCase(test.TestCase):
|
||||
'driver_version': None,
|
||||
'total_capacity_gb': 10000,
|
||||
'reserved_percentage': 0,
|
||||
'provisioned_capacity_gb': 50000,
|
||||
'max_over_subscription_ratio': 20.0,
|
||||
'thin_provisioning_support': True,
|
||||
'thick_provisioning_support': True,
|
||||
'vendor_name': None,
|
||||
'storage_protocol': None,
|
||||
'driver_handles_share_servers': False,
|
||||
@ -256,6 +268,10 @@ class HostManagerTestCase(test.TestCase):
|
||||
'driver_version': None,
|
||||
'total_capacity_gb': 51,
|
||||
'reserved_percentage': 0,
|
||||
'provisioned_capacity_gb': 10,
|
||||
'max_over_subscription_ratio': 1.0,
|
||||
'thin_provisioning_support': False,
|
||||
'thick_provisioning_support': True,
|
||||
'vendor_name': None,
|
||||
'storage_protocol': None,
|
||||
'driver_handles_share_servers': False,
|
||||
@ -273,6 +289,10 @@ class HostManagerTestCase(test.TestCase):
|
||||
'driver_version': None,
|
||||
'total_capacity_gb': 52,
|
||||
'reserved_percentage': 0,
|
||||
'provisioned_capacity_gb': 60,
|
||||
'max_over_subscription_ratio': 2.0,
|
||||
'thin_provisioning_support': True,
|
||||
'thick_provisioning_support': False,
|
||||
'vendor_name': None,
|
||||
'storage_protocol': None,
|
||||
'driver_handles_share_servers': False,
|
||||
@ -290,6 +310,10 @@ class HostManagerTestCase(test.TestCase):
|
||||
'driver_version': None,
|
||||
'total_capacity_gb': 53,
|
||||
'reserved_percentage': 0,
|
||||
'provisioned_capacity_gb': 100,
|
||||
'max_over_subscription_ratio': 20.0,
|
||||
'thin_provisioning_support': True,
|
||||
'thick_provisioning_support': True,
|
||||
'vendor_name': None,
|
||||
'storage_protocol': None,
|
||||
'driver_handles_share_servers': False,
|
||||
@ -307,6 +331,10 @@ class HostManagerTestCase(test.TestCase):
|
||||
'driver_version': None,
|
||||
'total_capacity_gb': 541,
|
||||
'reserved_percentage': 0,
|
||||
'provisioned_capacity_gb': 800,
|
||||
'max_over_subscription_ratio': 2.0,
|
||||
'thin_provisioning_support': True,
|
||||
'thick_provisioning_support': False,
|
||||
'vendor_name': None,
|
||||
'storage_protocol': None,
|
||||
'driver_handles_share_servers': False,
|
||||
@ -324,6 +352,10 @@ class HostManagerTestCase(test.TestCase):
|
||||
'driver_version': None,
|
||||
'total_capacity_gb': 542,
|
||||
'reserved_percentage': 0,
|
||||
'provisioned_capacity_gb': 2000,
|
||||
'max_over_subscription_ratio': 10.0,
|
||||
'thin_provisioning_support': True,
|
||||
'thick_provisioning_support': False,
|
||||
'vendor_name': None,
|
||||
'storage_protocol': None,
|
||||
'driver_handles_share_servers': False,
|
||||
@ -374,7 +406,11 @@ class HostManagerTestCase(test.TestCase):
|
||||
'total_capacity_gb': 512,
|
||||
'reserved_percentage': 0,
|
||||
'vendor_name': None,
|
||||
'storage_protocol': None
|
||||
'storage_protocol': None,
|
||||
'provisioned_capacity_gb': 312,
|
||||
'max_over_subscription_ratio': 1.0,
|
||||
'thin_provisioning_support': False,
|
||||
'thick_provisioning_support': True,
|
||||
},
|
||||
}, {
|
||||
'name': 'host2@back1#BBB',
|
||||
@ -390,7 +426,11 @@ class HostManagerTestCase(test.TestCase):
|
||||
'total_capacity_gb': 256,
|
||||
'reserved_percentage': 0,
|
||||
'vendor_name': None,
|
||||
'storage_protocol': None
|
||||
'storage_protocol': None,
|
||||
'provisioned_capacity_gb': 400,
|
||||
'max_over_subscription_ratio': 2.0,
|
||||
'thin_provisioning_support': True,
|
||||
'thick_provisioning_support': False,
|
||||
},
|
||||
},
|
||||
]
|
||||
@ -428,6 +468,10 @@ class HostManagerTestCase(test.TestCase):
|
||||
'driver_version': None,
|
||||
'total_capacity_gb': 52,
|
||||
'reserved_percentage': 0,
|
||||
'provisioned_capacity_gb': 60,
|
||||
'max_over_subscription_ratio': 2.0,
|
||||
'thin_provisioning_support': True,
|
||||
'thick_provisioning_support': False,
|
||||
'vendor_name': None,
|
||||
'storage_protocol': None
|
||||
},
|
||||
|
@ -206,3 +206,16 @@ class ShareDriverTestCase(test.TestCase):
|
||||
share_driver = driver.ShareDriver(value)
|
||||
self.assertEqual([],
|
||||
share_driver.get_share_server_pools('fake_server'))
|
||||
|
||||
@ddt.data(0.8, 1.0, 10.5, 20.0, None)
|
||||
def test_check_for_setup_error(self, value):
|
||||
driver.CONF.set_default('driver_handles_share_servers', False)
|
||||
share_driver = driver.ShareDriver(False)
|
||||
share_driver.configuration = configuration.Configuration(None)
|
||||
self.mock_object(share_driver.configuration, 'safe_get',
|
||||
mock.Mock(return_value=value))
|
||||
if value >= 1.0:
|
||||
share_driver.check_for_setup_error()
|
||||
else:
|
||||
self.assertRaises(exception.InvalidParameterValue,
|
||||
share_driver.check_for_setup_error)
|
||||
|
Loading…
Reference in New Issue
Block a user