From 67f52ab36df0f029d745851fc45607736c03e474 Mon Sep 17 00:00:00 2001 From: Stephen Finucane Date: Thu, 29 Sep 2016 11:13:37 +0100 Subject: [PATCH] Add CPUWeigher nova provides many different types of filter, providing both resource stacking/spreading and affinity/anti-affinity patterns. Curiously enough, however, there is no way to configure a stack or spread policy of CPUs. Add one. Change-Id: I90ee8a2081c2a0465441a8d81d161f4887b4e1fb Implements: bp vcpu-weighter --- doc/source/user/filter-scheduler.rst | 9 +- nova/conf/scheduler.py | 25 +++- nova/scheduler/weights/cpu.py | 41 ++++++ .../scheduler/weights/test_weights_cpu.py | 129 ++++++++++++++++++ .../add-cpu-weigher-2e982c9f9751d631.yaml | 6 + 5 files changed, 208 insertions(+), 2 deletions(-) create mode 100644 nova/scheduler/weights/cpu.py create mode 100644 nova/tests/unit/scheduler/weights/test_weights_cpu.py create mode 100644 releasenotes/notes/add-cpu-weigher-2e982c9f9751d631.yaml diff --git a/doc/source/user/filter-scheduler.rst b/doc/source/user/filter-scheduler.rst index e9113adb4136..4622d7655fa8 100644 --- a/doc/source/user/filter-scheduler.rst +++ b/doc/source/user/filter-scheduler.rst @@ -397,9 +397,15 @@ The Filter Scheduler weighs hosts based on the config option `nova.scheduler.weights.all_weighers`, which selects the following weighers: * |RAMWeigher| Compute weight based on available RAM on the compute node. - Sort with the largest weight winning. If the multiplier is negative, the + Sort with the largest weight winning. If the multiplier, + :oslo.config:opt:`filter_scheduler.ram_weight_multiplier`, is negative, the host with least RAM available will win (useful for stacking hosts, instead of spreading). +* |CPUWeigher| Compute weight based on available vCPUs on the compute node. + Sort with the largest weight winning. If the multiplier, + :oslo.config:opt:`filter_scheduler.cpu_weight_multiplier`, is negative, the + host with least CPUs available will win (useful for stacking hosts, instead + of spreading). * |DiskWeigher| Hosts are weighted and sorted by free disk space with the largest weight winning. If the multiplier is negative, the host with less disk space available will win (useful for stacking hosts, instead of spreading). @@ -489,6 +495,7 @@ in :mod:`nova.tests.scheduler`. .. |AggregateMultiTenancyIsolation| replace:: :class:`AggregateMultiTenancyIsolation ` .. |NUMATopologyFilter| replace:: :class:`NUMATopologyFilter ` .. |RAMWeigher| replace:: :class:`RAMWeigher ` +.. |CPUWeigher| replace:: :class:`CPUWeigher ` .. |AggregateImagePropertiesIsolation| replace:: :class:`AggregateImagePropertiesIsolation ` .. |MetricsFilter| replace:: :class:`MetricsFilter ` .. |MetricsWeigher| replace:: :class:`MetricsWeigher ` diff --git a/nova/conf/scheduler.py b/nova/conf/scheduler.py index cdbe4e5c812f..080fd7f67f6c 100644 --- a/nova/conf/scheduler.py +++ b/nova/conf/scheduler.py @@ -431,7 +431,7 @@ Possible values: default=1.0, deprecated_group="DEFAULT", help=""" -Ram weight multipler ratio. +RAM weight multipler ratio. This option determines how hosts with more or less available RAM are weighed. A positive value will result in the scheduler preferring hosts with more @@ -450,6 +450,29 @@ Possible values: * An integer or float value, where the value corresponds to the multipler ratio for this weigher. +"""), + cfg.FloatOpt("cpu_weight_multiplier", + default=1.0, + help=""" +CPU weight multiplier ratio. + +Multiplier used for weighting free vCPUs. Negative numbers indicate stacking +rather than spreading. + +This option is only used by the FilterScheduler and its subclasses; if you use +a different scheduler, this option has no effect. Also note that this setting +only affects scheduling if the 'cpu' weigher is enabled. + +Possible values: + +* An integer or float value, where the value corresponds to the multipler + ratio for this weigher. + +Related options: + +* ``filter_scheduler.weight_classes``: This weigher must be added to list of + enabled weight classes if the ``weight_classes`` setting is set to a + non-default value. """), cfg.FloatOpt("disk_weight_multiplier", default=1.0, diff --git a/nova/scheduler/weights/cpu.py b/nova/scheduler/weights/cpu.py new file mode 100644 index 000000000000..bd9aeea2116a --- /dev/null +++ b/nova/scheduler/weights/cpu.py @@ -0,0 +1,41 @@ +# Copyright (c) 2016, Red Hat 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. + +""" +CPU Weigher. Weigh hosts by their CPU usage. + +The default is to spread instances across all hosts evenly. If you prefer +stacking, you can set the 'cpu_weight_multiplier' option to a negative +number and the weighing has the opposite effect of the default. +""" + +import nova.conf +from nova.scheduler import weights + +CONF = nova.conf.CONF + + +class CPUWeigher(weights.BaseHostWeigher): + minval = 0 + + def weight_multiplier(self): + """Override the weight multiplier.""" + return CONF.filter_scheduler.cpu_weight_multiplier + + def _weigh_object(self, host_state, weight_properties): + """Higher weights win. We want spreading to be the default.""" + vcpus_free = (host_state.vcpus_total * host_state.cpu_allocation_ratio + - host_state.vcpus_used) + return vcpus_free diff --git a/nova/tests/unit/scheduler/weights/test_weights_cpu.py b/nova/tests/unit/scheduler/weights/test_weights_cpu.py new file mode 100644 index 000000000000..207cea71e438 --- /dev/null +++ b/nova/tests/unit/scheduler/weights/test_weights_cpu.py @@ -0,0 +1,129 @@ +# Copyright 2016, Red Hat 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. +""" +Tests For Scheduler CPU weights. +""" + +from nova.scheduler import weights +from nova.scheduler.weights import cpu +from nova import test +from nova.tests.unit.scheduler import fakes + + +class CPUWeigherTestCase(test.NoDBTestCase): + def setUp(self): + super(CPUWeigherTestCase, self).setUp() + self.weight_handler = weights.HostWeightHandler() + self.weighers = [cpu.CPUWeigher()] + + def _get_weighed_host(self, hosts, weight_properties=None): + if weight_properties is None: + weight_properties = {} + return self.weight_handler.get_weighed_objects(self.weighers, + hosts, weight_properties)[0] + + def _get_all_hosts(self): + host_values = [ + ('host1', 'node1', {'vcpus_total': 8, 'vcpus_used': 8, + 'cpu_allocation_ratio': 1.0}), # 0 free + ('host2', 'node2', {'vcpus_total': 4, 'vcpus_used': 2, + 'cpu_allocation_ratio': 1.0}), # 2 free + ('host3', 'node3', {'vcpus_total': 6, 'vcpus_used': 0, + 'cpu_allocation_ratio': 1.0}), # 6 free + ('host4', 'node4', {'vcpus_total': 8, 'vcpus_used': 0, + 'cpu_allocation_ratio': 2.0}), # 16 free + ] + return [fakes.FakeHostState(host, node, values) + for host, node, values in host_values] + + def test_multiplier_default(self): + hostinfo_list = self._get_all_hosts() + + # host1: vcpus_free=0 + # host2: vcpus_free=2 + # host3: vcpus_free=6 + # host4: vcpus_free=16 + + # so, host4 should win: + weighed_host = self._get_weighed_host(hostinfo_list) + self.assertEqual(1.0, weighed_host.weight) + self.assertEqual('host4', weighed_host.obj.host) + + def test_multiplier_none(self): + self.flags(cpu_weight_multiplier=0.0, group='filter_scheduler') + hostinfo_list = self._get_all_hosts() + + # host1: vcpus_free=0 + # host2: vcpus_free=2 + # host3: vcpus_free=6 + # host4: vcpus_free=16 + + # We do not know the host, all have same weight. + weighed_host = self._get_weighed_host(hostinfo_list) + self.assertEqual(0.0, weighed_host.weight) + + def test_multiplier_positive(self): + self.flags(cpu_weight_multiplier=2.0, group='filter_scheduler') + hostinfo_list = self._get_all_hosts() + + # host1: vcpus_free=0 + # host2: vcpus_free=2 + # host3: vcpus_free=6 + # host4: vcpus_free=16 + + # so, host4 should win: + weighed_host = self._get_weighed_host(hostinfo_list) + self.assertEqual(1.0 * 2, weighed_host.weight) + self.assertEqual('host4', weighed_host.obj.host) + + def test_multiplier_negative(self): + self.flags(cpu_weight_multiplier=-1.0, group='filter_scheduler') + hostinfo_list = self._get_all_hosts() + + # host1: vcpus_free=0 + # host2: vcpus_free=2 + # host3: vcpus_free=6 + # host4: vcpus_free=16 + + # so, host1 should win: + weighed_host = self._get_weighed_host(hostinfo_list) + self.assertEqual('host1', weighed_host.obj.host) + + def test_negative_host(self): + self.flags(cpu_weight_multiplier=1.0, group='filter_scheduler') + hostinfo_list = self._get_all_hosts() + host_attr = {'vcpus_total': 4, 'vcpus_used': 6, + 'cpu_allocation_ratio': 1.0} + host_state = fakes.FakeHostState('negative', 'negative', host_attr) + hostinfo_list = list(hostinfo_list) + [host_state] + + # host1: vcpus_free=0 + # host2: vcpus_free=2 + # host3: vcpus_free=6 + # host4: vcpus_free=16 + # negative: vcpus_free=-2 + + # so, host4 should win + weights = self.weight_handler.get_weighed_objects(self.weighers, + hostinfo_list, {}) + + weighed_host = weights[0] + self.assertEqual(1, weighed_host.weight) + self.assertEqual('host4', weighed_host.obj.host) + + # and negativehost should lose + weighed_host = weights[-1] + self.assertEqual(0, weighed_host.weight) + self.assertEqual('negative', weighed_host.obj.host) diff --git a/releasenotes/notes/add-cpu-weigher-2e982c9f9751d631.yaml b/releasenotes/notes/add-cpu-weigher-2e982c9f9751d631.yaml new file mode 100644 index 000000000000..15f7f5255305 --- /dev/null +++ b/releasenotes/notes/add-cpu-weigher-2e982c9f9751d631.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Add ``CPUWeigher`` weigher. This can be used to spread (default) or pack + workloads on hosts based on their vCPU usage. This can be configured using + the ``[filter_scheduler] cpu_weight_multiplier`` configuration option.