diff --git a/doc/source/admin/scheduling.rst b/doc/source/admin/scheduling.rst index 353514ab5543..fa7f7b7deb92 100644 --- a/doc/source/admin/scheduling.rst +++ b/doc/source/admin/scheduling.rst @@ -1081,6 +1081,21 @@ If you prefer to invert the behavior set the :oslo.config:option:`filter_scheduler.hypervisor_version_weight_multiplier` option to a negative number and the weighing has the opposite effect of the default. + +``NumInstancesWeigher`` +~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +.. versionadded:: 28.0.0 (Bobcat) + +This weigher compares hosts and orders them based on their number of instances +respectively. By default the weigher is doing nothing but you +can change its behaviour by modifiying the value of +:oslo.config:option:`filter_scheduler.num_instances_weight_multiplier`. +A positive value will favor hosts with a larger number of instances (packing +strategy) while a negative value will follow a spread strategy that will +favor hosts with the lesser number of instances. + + Utilization-aware scheduling ---------------------------- diff --git a/nova/conf/scheduler.py b/nova/conf/scheduler.py index c7aa2ad76d43..6d650f282be2 100644 --- a/nova/conf/scheduler.py +++ b/nova/conf/scheduler.py @@ -505,6 +505,51 @@ Example: Related options: +* ``[filter_scheduler] weight_classes`` +"""), + cfg.FloatOpt("num_instances_weight_multiplier", + default=0.0, + help=""" +Number of instances weight multiplier ratio. + +The multiplier is used for weighting hosts based on the reported +number of instances they have. +Negative numbers indicate preferring hosts with fewer instances (i.e. choosing +to spread instances), while positive numbers mean preferring hosts with more +hosts (ie. choosing to pack). +The default is 0.0 which means that you have to choose a strategy if you want +to use it. + +Possible values: + +* An integer or float value, where the value corresponds to the multiplier + ratio for this weigher. + +Example: + +* Strongly prefer to pack instances to hosts. + + .. code-block:: ini + + [filter_scheduler] + num_instances_weight_multiplier=1000 + +* Softly prefer to spread instances between hosts. + + .. code-block:: ini + + [filter_scheduler] + num_instances_weight_multiplier=1.0 + +* Disable weigher influence + + .. code-block:: ini + + [filter_scheduler] + num_instances_weight_multiplier=0 + +Related options: + * ``[filter_scheduler] weight_classes`` """), cfg.FloatOpt("io_ops_weight_multiplier", diff --git a/nova/scheduler/weights/num_instances.py b/nova/scheduler/weights/num_instances.py new file mode 100644 index 000000000000..ec5b06cd85ac --- /dev/null +++ b/nova/scheduler/weights/num_instances.py @@ -0,0 +1,41 @@ +# 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. + +""" +Num instances Weigher. Weigh hosts by their number of instances. + +The default is to select hosts with less instances for a spreading strategy. +If you prefer to invert this behavior set the 'num_instances_weight_multiplier' +option to a positive number and the weighing has the opposite effect of the +default. +""" + +import nova.conf +from nova.scheduler import utils +from nova.scheduler import weights + +CONF = nova.conf.CONF + + +class NumInstancesWeigher(weights.BaseHostWeigher): + + def weight_multiplier(self, host_state): + """Override the weight multiplier.""" + return utils.get_weight_multiplier( + host_state, 'num_instances_weight_multiplier', + CONF.filter_scheduler.num_instances_weight_multiplier) + + def _weigh_object(self, host_state, weight_properties): + """Higher weights win. We want to chooose hosts with fewer instances + as the default, hence the negative value of the multiplier. + """ + return host_state.num_instances diff --git a/nova/tests/unit/scheduler/weights/test_weights_num_instances.py b/nova/tests/unit/scheduler/weights/test_weights_num_instances.py new file mode 100644 index 000000000000..2af8a99e881a --- /dev/null +++ b/nova/tests/unit/scheduler/weights/test_weights_num_instances.py @@ -0,0 +1,71 @@ +# 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 hypervisor version weights. +""" + +from nova.scheduler import weights +from nova.scheduler.weights import num_instances +from nova import test +from nova.tests.unit.scheduler import fakes + + +class NumInstancesWeigherTestCase(test.NoDBTestCase): + def setUp(self): + super().setUp() + self.weight_handler = weights.HostWeightHandler() + self.weighers = [num_instances.NumInstancesWeigher()] + + 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', {'num_instances': 2}), + ('host2', 'node2', {'num_instances': 5}), + ('host3', 'node3', {'num_instances': 4}), + ('host4', 'node4', {'num_instances': 10}), + ] + return [fakes.FakeHostState(host, node, values) + for host, node, values in host_values] + + def test_multiplier_default(self): + hostinfo_list = self._get_all_hosts() + weighed_host = self._get_weighed_host(hostinfo_list) + self.assertEqual(0.0, weighed_host.weight) + # Nothing changes, we just returns the first in the list + self.assertEqual('host1', weighed_host.obj.host) + + def test_multiplier_positive(self): + self.flags( + num_instances_weight_multiplier=2.0, + group='filter_scheduler' + ) + hostinfo_list = self._get_all_hosts() + weighed_host = self._get_weighed_host(hostinfo_list) + self.assertEqual(2.0, weighed_host.weight) + # Host4 wins because it has the most instances + self.assertEqual('host4', weighed_host.obj.host) + + def test_multiplier_negative(self): + self.flags( + num_instances_weight_multiplier=-2.0, + group='filter_scheduler' + ) + hostinfo_list = self._get_all_hosts() + weighed_host = self._get_weighed_host(hostinfo_list) + self.assertEqual(0.0, weighed_host.weight) + # Host1 wins because it has the less instances + self.assertEqual('host1', weighed_host.obj.host) diff --git a/releasenotes/notes/bp-num_instances_weigher-bba342c82aac5509.yaml b/releasenotes/notes/bp-num_instances_weigher-bba342c82aac5509.yaml new file mode 100644 index 000000000000..46a32d64ee6b --- /dev/null +++ b/releasenotes/notes/bp-num_instances_weigher-bba342c82aac5509.yaml @@ -0,0 +1,13 @@ +--- +features: + - | + A new `num_instances_weigher` weigher has been added. This weigher will + compare the number of instances between each node and order the list of + filtered results by its number, By default, this weigher is enabled but with + a default of 0.0 which doesn't change the current behavior. + In order to use it, please change the value of + ``[filter_scheduler]/num_instances_weight_multiplier`` config option where + a positive value will favor the host with the higher number of instances + (ie. packing strategy) vs. a negative value that will spread instances + between hosts. As a side note, this weigher will count *all* of the existing + instances on the host, even the stopped or shelved ones.