diff --git a/nova_solverscheduler/scheduler/solvers/constraints/aggregate_num_instances.py b/nova_solverscheduler/scheduler/solvers/constraints/aggregate_num_instances.py new file mode 100644 index 0000000..f98db6f --- /dev/null +++ b/nova_solverscheduler/scheduler/solvers/constraints/aggregate_num_instances.py @@ -0,0 +1,52 @@ +# 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. + +from oslo_config import cfg +from oslo_log import log as logging + +from nova.i18n import _LW +from nova_solverscheduler.scheduler.solvers.constraints \ + import num_instances_constraint +from nova_solverscheduler.scheduler.solvers import utils + +CONF = cfg.CONF +CONF.import_opt('max_instances_per_host', + 'nova.scheduler.filters.num_instances_filter') + +LOG = logging.getLogger(__name__) + + +class AggregateNumInstancesConstraint( + num_instances_constraint.NumInstancesConstraint): + """AggregateNumInstancesConstraint with per-aggregate max num instances + per host. + + Fall back to global max_instances_per_host if no per-aggregate setting + found. + """ + + def _get_max_instances_per_host(self, host_state, filter_properties): + aggregate_vals = utils.aggregate_values_from_key( + host_state, 'max_instances_per_host') + + try: + value = utils.validate_num_values( + aggregate_vals, CONF.max_instances_per_host, cast_to=int) + except ValueError as e: + LOG.warning(_LW("Could not decode max_instances_per_host: '%s'"), + e) + value = CONF.max_instances_per_host + + return value diff --git a/nova_solverscheduler/scheduler/solvers/constraints/num_instances_constraint.py b/nova_solverscheduler/scheduler/solvers/constraints/num_instances_constraint.py index 9230fd9..eb03311 100644 --- a/nova_solverscheduler/scheduler/solvers/constraints/num_instances_constraint.py +++ b/nova_solverscheduler/scheduler/solvers/constraints/num_instances_constraint.py @@ -30,6 +30,9 @@ class NumInstancesConstraint(constraints.BaseLinearConstraint): each host can launch. """ + def _get_max_instances_per_host(self, host_state, filter_properties): + return CONF.max_instances_per_host + def get_constraint_matrix(self, hosts, filter_properties): num_hosts = len(hosts) num_instances = filter_properties.get('num_instances') @@ -37,9 +40,9 @@ class NumInstancesConstraint(constraints.BaseLinearConstraint): constraint_matrix = [[True for j in xrange(num_instances)] for i in xrange(num_hosts)] - max_instances = CONF.max_instances_per_host - for i in xrange(num_hosts): + max_instances = self._get_max_instances_per_host(hosts[i], + filter_properties) num_host_instances = hosts[i].num_instances acceptable_num_instances = int(max_instances - num_host_instances) if acceptable_num_instances < 0: diff --git a/nova_solverscheduler/tests/scheduler/solvers/constraints/test_aggregate_num_instances.py b/nova_solverscheduler/tests/scheduler/solvers/constraints/test_aggregate_num_instances.py new file mode 100644 index 0000000..d150f25 --- /dev/null +++ b/nova_solverscheduler/tests/scheduler/solvers/constraints/test_aggregate_num_instances.py @@ -0,0 +1,71 @@ +# 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. + +import mock + +from nova import context +from nova import test +from nova_solverscheduler.scheduler.solvers.constraints \ + import aggregate_num_instances +from nova_solverscheduler.tests.scheduler import solver_scheduler_fakes \ + as fakes + + +class TestAggregateNumInstancesConstraint(test.NoDBTestCase): + def setUp(self): + super(TestAggregateNumInstancesConstraint, self).setUp() + self.constraint_cls = aggregate_num_instances.\ + AggregateNumInstancesConstraint + self.context = context.RequestContext('fake', 'fake') + self._generate_fake_constraint_input() + + def _generate_fake_constraint_input(self): + self.fake_filter_properties = { + 'context': self.context, + 'num_instances': 3} + host1 = fakes.FakeSolverSchedulerHostState('host1', 'node1', {}) + host2 = fakes.FakeSolverSchedulerHostState('host2', 'node1', {}) + host3 = fakes.FakeSolverSchedulerHostState('host3', 'node1', {}) + host4 = fakes.FakeSolverSchedulerHostState('host4', 'node1', {}) + self.fake_hosts = [host1, host2, host3, host4] + + @mock.patch('nova_solverscheduler.scheduler.solvers.utils.' + 'aggregate_values_from_key') + def test_get_constraint_matrix(self, agg_mock): + self.flags(max_instances_per_host=1) + + def _agg_mock_side_effect(*args, **kwargs): + if args[0].host == 'host1': + return set(['2', '3']) + if args[0].host == 'host2': + return set(['4']) + if args[0].host == 'host3': + return set([]) + if args[0].host == 'host4': + return set(['invalidval']) + agg_mock.side_effect = _agg_mock_side_effect + + expected_cons_mat = [ + [True, True, False], + [True, True, True], + [True, False, False], + [True, False, False]] + cons_mat = self.constraint_cls().get_constraint_matrix( + self.fake_hosts, self.fake_filter_properties) + agg_mock.assert_any_call(self.fake_hosts[0], 'max_instances_per_host') + agg_mock.assert_any_call(self.fake_hosts[1], 'max_instances_per_host') + agg_mock.assert_any_call(self.fake_hosts[2], 'max_instances_per_host') + agg_mock.assert_any_call(self.fake_hosts[3], 'max_instances_per_host') + self.assertEqual(expected_cons_mat, cons_mat)