Add vCPU cost to solver scheduler

This adds the vcpu_cost which can balance/stack the usage of vcpus in hosts.

Change-Id: I50445672a64d436f01a5e70b54ff96e101c62a51
This commit is contained in:
Xinyuan Huang 2015-07-21 18:17:26 +08:00
parent 78d9a7f643
commit 2893562105
2 changed files with 186 additions and 0 deletions

View File

@ -0,0 +1,88 @@
# Copyright (c) 2015 Cisco Systems, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""
VCPU Cost. Calculate instance placement costs by hosts' vCPU usage.
The default is to spread instances across all hosts evenly. If you prefer
stacking, you can set the 'vcpu_cost_multiplier' option to a negative
number and the cost has the opposite effect of the default.
"""
from oslo_config import cfg
from oslo_log import log as logging
from nova.i18n import _LW
from nova_solverscheduler.scheduler.solvers import costs as solver_costs
from nova_solverscheduler.scheduler.solvers.costs import utils
vcpu_cost_opts = [
cfg.FloatOpt('vcpu_cost_multiplier',
default=1.0,
help='Multiplier used for vcpu costs. Negative '
'numbers mean to stack vs spread.'),
]
CONF = cfg.CONF
CONF.register_opts(vcpu_cost_opts, group='solver_scheduler')
LOG = logging.getLogger(__name__)
class VcpuCost(solver_costs.BaseLinearCost):
def cost_multiplier(self):
return CONF.solver_scheduler.vcpu_cost_multiplier
def get_extended_cost_matrix(self, hosts, filter_properties):
num_hosts = len(hosts)
num_instances = filter_properties.get('num_instances')
instance_type = filter_properties.get('instance_type') or {}
requested_vcpus = instance_type.get('vcpus', 0)
if requested_vcpus <= 0:
LOG.warn(_LW("Requested instances\' vCPU number is 0 or invalid, "
"default value (0) is used."))
remaining_vcpus_list = []
for i in xrange(num_hosts):
vcpus_total = hosts[i].vcpus_total
vcpus_used = hosts[i].vcpus_used
if not vcpus_total:
LOG.warn(_LW("vCPUs of %(host)s not set; assuming CPU "
"collection broken."), {'host': hosts[i]})
vcpus_total = 0
remaining_vcpus = vcpus_total - vcpus_used
remaining_vcpus_list.append(remaining_vcpus)
extended_cost_matrix = [[0 for j in xrange(num_instances + 1)]
for i in xrange(num_hosts)]
if requested_vcpus == 0:
extended_cost_matrix = [
[(-remaining_vcpus_list[i])
for j in xrange(num_instances + 1)]
for i in xrange(num_hosts)]
else:
# we use int approximation here to avoid scaling problems after
# normalization, in the case that the free vcpus in all hosts are
# of very small values
extended_cost_matrix = [
[-int(remaining_vcpus_list[i] / requested_vcpus) + j
for j in xrange(num_instances + 1)]
for i in xrange(num_hosts)]
extended_cost_matrix = utils.normalize_cost_matrix(
extended_cost_matrix)
return extended_cost_matrix

View File

@ -0,0 +1,98 @@
# Copyright (c) 2015 Cisco Systems, Inc.
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
"""Test case for solver scheduler vCPU cost."""
from nova import context
from nova import test
from nova_solverscheduler.scheduler.solvers import costs
from nova_solverscheduler.scheduler.solvers.costs import vcpu_cost
from nova_solverscheduler.tests.scheduler import solver_scheduler_fakes \
as fakes
class TestVcpuCost(test.NoDBTestCase):
def setUp(self):
super(TestVcpuCost, self).setUp()
self.context = context.RequestContext('fake_usr', 'fake_proj')
self.cost_handler = costs.CostHandler()
self.cost_classes = self.cost_handler.get_matching_classes(
['nova_solverscheduler.scheduler.solvers.costs.vcpu_cost.VcpuCost']
)
def _get_all_hosts(self):
host1 = fakes.FakeSolverSchedulerHostState('host1', 'node1',
{'vcpus_total': 32, 'vcpus_used': 12})
host2 = fakes.FakeSolverSchedulerHostState('host2', 'node2',
{'vcpus_total': 16, 'vcpus_used': 6})
host3 = fakes.FakeSolverSchedulerHostState('host3', 'node3',
{'vcpus_total': 8, 'vcpus_used': 3})
host4 = fakes.FakeSolverSchedulerHostState('host4', 'node4',
{'vcpus_total': 0, 'vcpus_used': 0})
return [host1, host2, host3, host4]
def test_vcpu_cost_multiplier_1(self):
self.flags(vcpu_cost_multiplier=0.5, group='solver_scheduler')
self.assertEqual(0.5, vcpu_cost.VcpuCost().cost_multiplier())
def test_vcpu_cost_multiplier_2(self):
self.flags(vcpu_cost_multiplier=(-2), group='solver_scheduler')
self.assertEqual((-2), vcpu_cost.VcpuCost().cost_multiplier())
def test_get_extended_cost_matrix(self):
fake_hosts = self._get_all_hosts()
fake_filter_properties = {
'context': self.context,
'num_instances': 3,
'instance_type': {'vcpus': 5},
'instance_uuids': ['fake_uuid_%s' % x for x in range(3)]}
fake_cost = self.cost_classes[0]()
expected_x_cost_mat = [
[-1.0, -0.75, -0.5, -0.25],
[-0.5, -0.25, 0.0, 0.25],
[-0.25, 0.0, 0.25, 0.5],
[0.0, 0.25, 0.5, 0.75]]
x_cost_mat = fake_cost.get_extended_cost_matrix(fake_hosts,
fake_filter_properties)
expected_x_cost_mat = [[round(val, 4) for val in row]
for row in expected_x_cost_mat]
x_cost_mat = [[round(val, 4) for val in row] for row in x_cost_mat]
self.assertEqual(expected_x_cost_mat, x_cost_mat)
def test_get_extended_cost_matrix_bad_vcpu_req(self):
fake_hosts = self._get_all_hosts()
fake_filter_properties = {
'context': self.context,
'num_instances': 3,
'instance_type': {'vcpus': 0},
'instance_uuids': ['fake_uuid_%s' % x for x in range(3)]}
fake_cost = self.cost_classes[0]()
expected_x_cost_mat = [
[-1.0, -1.0, -1.0, -1.0],
[-0.5, -0.5, -0.5, -0.5],
[-0.25, -0.25, -0.25, -0.25],
[0.0, 0.0, 0.0, 0.0]]
x_cost_mat = fake_cost.get_extended_cost_matrix(fake_hosts,
fake_filter_properties)
expected_x_cost_mat = [[round(val, 4) for val in row]
for row in expected_x_cost_mat]
x_cost_mat = [[round(val, 4) for val in row] for row in x_cost_mat]
self.assertEqual(expected_x_cost_mat, x_cost_mat)