From e63eaf53ce95112ae9cd3f214fd9dafd4230ca98 Mon Sep 17 00:00:00 2001 From: Xinyuan Huang Date: Mon, 10 Aug 2015 22:42:11 +0800 Subject: [PATCH] Add NUMA topology constraint to solver scheduler This adds the numa topology constraint that matches the numa topology filter in filter scheduler. Change-Id: Id80f0a7364132204c82f50e61b0c77913485ae04 --- .../constraints/numa_topology_constraint.py | 72 +++++++++++ .../test_numa_topology_constraint.py | 112 ++++++++++++++++++ 2 files changed, 184 insertions(+) create mode 100644 nova_solverscheduler/scheduler/solvers/constraints/numa_topology_constraint.py create mode 100644 nova_solverscheduler/tests/scheduler/solvers/constraints/test_numa_topology_constraint.py diff --git a/nova_solverscheduler/scheduler/solvers/constraints/numa_topology_constraint.py b/nova_solverscheduler/scheduler/solvers/constraints/numa_topology_constraint.py new file mode 100644 index 0000000..3808cd5 --- /dev/null +++ b/nova_solverscheduler/scheduler/solvers/constraints/numa_topology_constraint.py @@ -0,0 +1,72 @@ +# 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 copy + +from oslo_log import log as logging + +from nova.scheduler.filters import numa_topology_filter +from nova_solverscheduler.scheduler.solvers import constraints + +LOG = logging.getLogger(__name__) + + +class NUMATopologyConstraint(constraints.BaseLinearConstraint): + """Constraint on requested NUMA topology.""" + + def __init__(self): + super(NUMATopologyConstraint, self).__init__() + self.host_filter = numa_topology_filter.NUMATopologyFilter() + + def _get_acceptable_instance_num(self, host_state, filter_properties, + max_num): + instance = filter_properties['request_spec']['instance_properties'] + acceptable_num = 0 + while acceptable_num < max_num: + if self.host_filter.host_passes(host_state, filter_properties): + acceptable_num += 1 + host_state.consume_from_instance(instance) + else: + break + return acceptable_num + + def get_constraint_matrix(self, hosts, filter_properties): + num_hosts = len(hosts) + num_instances = filter_properties.get('num_instances') + + constraint_matrix = [[True for j in xrange(num_instances)] + for i in xrange(num_hosts)] + + for i in xrange(num_hosts): + host_state = copy.deepcopy(hosts[i]) + acceptable_instance_num = self._get_acceptable_instance_num( + host_state, filter_properties, num_instances) + + if acceptable_instance_num < num_instances: + inacceptable_num = num_instances - acceptable_instance_num + constraint_matrix[i] = ( + [True for j in xrange(acceptable_instance_num)] + + [False for j in xrange(inacceptable_num)]) + + LOG.debug("%(host)s can accept %(num)s requested instances " + "according to NUMATopologyConstraint.", + {'host': hosts[i], + 'num': acceptable_instance_num}) + + numa_topology_limit = host_state.limits.get('numa_topology') + if numa_topology_limit: + hosts[i].limits['numa_topology'] = numa_topology_limit + + return constraint_matrix diff --git a/nova_solverscheduler/tests/scheduler/solvers/constraints/test_numa_topology_constraint.py b/nova_solverscheduler/tests/scheduler/solvers/constraints/test_numa_topology_constraint.py new file mode 100644 index 0000000..09f9ba6 --- /dev/null +++ b/nova_solverscheduler/tests/scheduler/solvers/constraints/test_numa_topology_constraint.py @@ -0,0 +1,112 @@ +# 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 oslo_serialization import jsonutils + +from nova import objects +from nova.objects import base as obj_base +from nova import test +from nova.tests.unit import fake_instance +from nova_solverscheduler.scheduler.solvers.constraints \ + import numa_topology_constraint +from nova_solverscheduler.tests.scheduler import solver_scheduler_fakes \ + as fakes + +NUMA_TOPOLOGY = objects.NUMATopology( + cells=[ + objects.NUMACell(id=0, cpuset=set([1, 2]), memory=512, + cpu_usage=0, memory_usage=0, mempages=[], + siblings=[], pinned_cpus=set([])), + objects.NUMACell(id=1, cpuset=set([3, 4]), memory=512, + cpu_usage=0, memory_usage=0, mempages=[], + siblings=[], pinned_cpus=set([])) + ] +) + + +class TestNUMATopologyConstraint(test.NoDBTestCase): + + def setUp(self): + super(TestNUMATopologyConstraint, self).setUp() + self.constraint_cls = numa_topology_constraint.NUMATopologyConstraint + + def _gen_fake_hosts(self): + host1 = fakes.FakeSolverSchedulerHostState('host1', 'node1', + { + 'numa_topology': objects.NUMATopology( + cells=[ + objects.NUMACell(id=0, cpuset=set([1, 2]), + memory=1024, cpu_usage=0, memory_usage=0, + mempages=[], siblings=[], pinned_cpus=set([])), + objects.NUMACell(id=1, cpuset=set([3, 4]), + memory=1024, cpu_usage=0, memory_usage=0, + mempages=[], siblings=[], pinned_cpus=set([]))]), + 'pci_stats': None + }) + host2 = fakes.FakeSolverSchedulerHostState('host2', 'node1', + { + 'numa_topology': objects.NUMATopology( + cells=[ + objects.NUMACell(id=0, cpuset=set([1, 2]), + memory=1024, cpu_usage=0, memory_usage=0, + mempages=[], siblings=[], pinned_cpus=set([])), + objects.NUMACell(id=1, cpuset=set([3, 4]), + memory=512, cpu_usage=0, memory_usage=0, + mempages=[], siblings=[], pinned_cpus=set([]))]), + 'pci_stats': None + }) + host3 = fakes.FakeSolverSchedulerHostState('host3', 'node1', + { + 'numa_topology': objects.NUMATopology( + cells=[ + objects.NUMACell(id=0, cpuset=set([1, 2]), + memory=512, cpu_usage=0, memory_usage=0, + mempages=[], siblings=[], pinned_cpus=set([])), + objects.NUMACell(id=1, cpuset=set([3]), + memory=512, cpu_usage=0, memory_usage=0, + mempages=[], siblings=[], pinned_cpus=set([]))]), + 'pci_stats': None + }) + hosts = [host1, host2, host3] + return hosts + + def test_get_constraint_matrix(self): + self.flags(ram_allocation_ratio=1) + self.flags(cpu_allocation_ratio=2) + + instance_topology = objects.InstanceNUMATopology(cells=[ + objects.InstanceNUMACell(id=0, cpuset=set([1, 2]), memory=512), + objects.InstanceNUMACell(id=1, cpuset=set([3, 4]), memory=512)] + ) + instance = fake_instance.fake_instance_obj(mock.sentinel.ctx, + root_gb=0, ephemeral_gb=0, memory_mb=0, vcpus=0,) + instance.numa_topology = instance_topology + filter_properties = { + 'request_spec': { + 'instance_properties': jsonutils.to_primitive( + obj_base.obj_to_primitive(instance))}, + 'num_instances': 2} + fake_hosts = self._gen_fake_hosts() + + expected_cons_mat = [ + [True, True], + [True, False], + [False, False]] + + cons_mat = self.constraint_cls().get_constraint_matrix( + fake_hosts, filter_properties) + + self.assertEqual(expected_cons_mat, cons_mat)