From fd4c33bfc0443a3c2eb9a382c9fac20b65213ec7 Mon Sep 17 00:00:00 2001 From: Kiran Pawar Date: Thu, 10 Mar 2022 17:46:50 +0000 Subject: [PATCH] Add PreferSameShardOnResizeWeigher. This filter enable to select same host-aggregate/shard/VC for instance resize because it could take more time to migrate the volumes over other shards. --- nova/conf/scheduler.py | 20 +++ nova/scheduler/weights/resize_same_shard.py | 68 ++++++++++ .../weights/test_weights_resize_same_shard.py | 125 ++++++++++++++++++ 3 files changed, 213 insertions(+) create mode 100644 nova/scheduler/weights/resize_same_shard.py create mode 100644 nova/tests/unit/scheduler/weights/test_weights_resize_same_shard.py diff --git a/nova/conf/scheduler.py b/nova/conf/scheduler.py index 45986030398..d554f3f6e2a 100644 --- a/nova/conf/scheduler.py +++ b/nova/conf/scheduler.py @@ -420,6 +420,26 @@ Possible values: +* An integer or float value, where the value corresponds to the multipler + ratio for this weigher. +"""), + cfg.FloatOpt("prefer_same_shard_resize_weight_multiplier", + default=1.0, + help=""" +Prefer scheduling on same-shard on resize weight multiplier. + +This option determines how strongly the previous shard should be preferred for +scheduling a resizing instance. A positive value will result in the scheduler +preferring the same shard that the instance was previously running on. A +negative value would prefer all other shards over the instance's previous +aggregate. + +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 'resize_same_shard' weigher is enabled. + +Possible values: + * An integer or float value, where the value corresponds to the multipler ratio for this weigher. """), diff --git a/nova/scheduler/weights/resize_same_shard.py b/nova/scheduler/weights/resize_same_shard.py new file mode 100644 index 00000000000..4e7ad8ae9c3 --- /dev/null +++ b/nova/scheduler/weights/resize_same_shard.py @@ -0,0 +1,68 @@ +# Copyright (c) 2022 SAP +# 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. +""" +Prefer resize to same shard weigher. Resizing an instance gives the current +shard aggregate a higher priority. + +This weigher ignores any instances which are: + * new instances (or unshelving) + * not resizing (a.k.a. migrating or rebuilding) +""" +from oslo_log import log as logging + +import nova.conf +from nova.scheduler import utils +from nova.scheduler import weights + +LOG = logging.getLogger(__name__) + +CONF = nova.conf.CONF + + +class PreferSameShardOnResizeWeigher(weights.BaseHostWeigher): + minval = 0 + _SHARD_PREFIX = 'vc-' + + def weight_multiplier(self): + """Override the weight multiplier.""" + return (CONF.filter_scheduler. + prefer_same_shard_resize_weight_multiplier) + + def _weigh_object(self, host_state, request_spec): + """Return 1 for about-to-be-resized instances where the shard is the + instance's current shard. Return 0 otherwise. + """ + if not utils.request_is_resize(request_spec): + return 0.0 + + host_shard_aggrs = [aggr for aggr in host_state.aggregates + if aggr.name.startswith(self._SHARD_PREFIX)] + if not host_shard_aggrs: + LOG.warning('%(host_state)s is not in an aggregate starting with ' + '%(shard_prefix)s.', + {'host_state': host_state, + 'shard_prefix': self._SHARD_PREFIX}) + return 0.0 + + if len(host_shard_aggrs) > 1: + LOG.warning('More than one host aggregates found for ' + 'host %(host)s, selecting first.', + {'host': host_state.host}) + host_shard_aggr = host_shard_aggrs[0] + + instance_host = request_spec.get_scheduler_hint('source_host') + if instance_host in host_shard_aggr.hosts: + return 1.0 + return 0.0 diff --git a/nova/tests/unit/scheduler/weights/test_weights_resize_same_shard.py b/nova/tests/unit/scheduler/weights/test_weights_resize_same_shard.py new file mode 100644 index 00000000000..c2645e313da --- /dev/null +++ b/nova/tests/unit/scheduler/weights/test_weights_resize_same_shard.py @@ -0,0 +1,125 @@ +# Copyright 2022 SAP +# 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 prefer-resize-to-same-host weigher. +""" +from nova import objects +from nova.scheduler import weights +from nova.scheduler.weights import resize_same_shard as same_shard +from nova import test +from nova.tests.unit.scheduler import fakes +from nova.tests import uuidsentinel as uuids + + +class PreferSameShardOnResizeWeigherTestCase(test.NoDBTestCase): + def setUp(self): + super(PreferSameShardOnResizeWeigherTestCase, self).setUp() + self.weight_handler = weights.HostWeightHandler() + self.weighers = [same_shard.PreferSameShardOnResizeWeigher()] + + self.instance1 = objects.Instance(host='host1', uuid=uuids.instance_1) + self.instance2 = objects.Instance(host='host1', uuid=uuids.instance_2) + self.instance3 = objects.Instance(host='host2', uuid=uuids.instance_3) + self.instance4 = objects.Instance(host='host3', uuid=uuids.instance_4) + self.aggs1 = [objects.Aggregate(id=1, name='az-a', hosts=['host1', + 'host3']), + objects.Aggregate(id=1, name='vc-a-0', hosts=['host1', + 'host3'])] + self.aggs2 = [objects.Aggregate(id=1, name='vc-a-1', hosts=['host2'])] + + self.hosts = [ + fakes.FakeHostState('host1', 'compute', {'aggregates': self.aggs1, + 'instances': { + self.instance1.uuid: self.instance1, + self.instance2.uuid: self.instance2, + }}), + fakes.FakeHostState('host2', 'compute', {'aggregates': self.aggs2, + 'instances': { + self.instance3.uuid: self.instance3, + }}), + fakes.FakeHostState('host3', 'compute', {'aggregates': self.aggs1, + 'instances': { + self.instance4.uuid: self.instance4, + }}), + ] + + def test_prefer_resize_to_same_shard(self): + self.flags(prefer_same_shard_resize_weight_multiplier=1.0, + group='filter_scheduler') + request_spec = objects.RequestSpec( + instance_uuid=self.instance1.uuid, + scheduler_hints=dict(_nova_check_type=['resize'], + source_host=['host1'])) + weighed_hosts = self.weight_handler.get_weighed_objects( + self.weighers, self.hosts, request_spec) + self.assertEqual(1.0, weighed_hosts[0].weight) + self.assertEqual(1.0, weighed_hosts[1].weight) + self.assertEqual(0.0, weighed_hosts[2].weight) + self.assertEqual('host1', weighed_hosts[0].obj.host) + self.assertEqual('host3', weighed_hosts[1].obj.host) + self.assertEqual('host2', weighed_hosts[2].obj.host) + + def test_prefer_resize_to_different_shard(self): + self.flags(prefer_same_shard_resize_weight_multiplier=-1.0, + group='filter_scheduler') + request_spec = objects.RequestSpec( + instance_uuid=self.instance1.uuid, + scheduler_hints=dict(_nova_check_type=['resize'], + source_host=['host1'])) + weighed_hosts = self.weight_handler.get_weighed_objects( + self.weighers, self.hosts, request_spec) + self.assertEqual(0.0, weighed_hosts[0].weight) + self.assertEqual(-1.0, weighed_hosts[1].weight) + self.assertEqual(-1.0, weighed_hosts[2].weight) + self.assertEqual('host2', weighed_hosts[0].obj.host) + self.assertEqual('host1', weighed_hosts[1].obj.host) + self.assertEqual('host3', weighed_hosts[2].obj.host) + + def test_ignore_scheduling_new_instance(self): + # Explicitly calling resize with new instance and test + # should fail as 'source_host' is missing. + self.flags(prefer_same_shard_resize_weight_multiplier=1.0, + group='filter_scheduler') + request_spec = objects.RequestSpec( + instance_uuid=uuids.fake_new_instance, + scheduler_hints={'_nova_check_type': ['resize']}) + weighed_hosts = self.weight_handler.get_weighed_objects( + self.weighers, self.hosts, request_spec) + self.assertEqual(0.0, weighed_hosts[0].weight) + self.assertEqual(0.0, weighed_hosts[1].weight) + self.assertEqual(0.0, weighed_hosts[2].weight) + + def test_ignore_rebuilding_instance(self): + self.flags(prefer_same_shard_resize_weight_multiplier=1.0, + group='filter_scheduler') + request_spec = objects.RequestSpec( + instance_uuid=self.instance1.uuid, + scheduler_hints={'_nova_check_type': ['rebuild']}) + weighed_hosts = self.weight_handler.get_weighed_objects( + self.weighers, self.hosts, request_spec) + self.assertEqual(0.0, weighed_hosts[0].weight) + self.assertEqual(0.0, weighed_hosts[1].weight) + self.assertEqual(0.0, weighed_hosts[2].weight) + + def test_ignore_non_resizing_instance(self): + self.flags(prefer_same_shard_resize_weight_multiplier=1.0, + group='filter_scheduler') + request_spec = objects.RequestSpec( + instance_uuid=self.instance1.uuid) + weighed_hosts = self.weight_handler.get_weighed_objects( + self.weighers, self.hosts, request_spec) + self.assertEqual(0.0, weighed_hosts[0].weight) + self.assertEqual(0.0, weighed_hosts[1].weight) + self.assertEqual(0.0, weighed_hosts[2].weight)