diff --git a/nova_solverscheduler/scheduler/solvers/costs/rack_affinity_cost.py b/nova_solverscheduler/scheduler/solvers/costs/rack_affinity_cost.py new file mode 100644 index 0000000..66a4793 --- /dev/null +++ b/nova_solverscheduler/scheduler/solvers/costs/rack_affinity_cost.py @@ -0,0 +1,86 @@ +# 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 _ +from nova_solverscheduler.scheduler.solvers import costs as solver_costs +from nova_solverscheduler.scheduler.solvers import utils as solver_utils + +affinity_cost_opts = [ + cfg.FloatOpt('tenant_rack_affinity_cost_multiplier', + default=1.0, + help='Multiplier used for tenant rack affinity cost. ' + 'Must be a positive number.'), +] + +CONF = cfg.CONF +CONF.register_opts(affinity_cost_opts, group='solver_scheduler') + +LOG = logging.getLogger(__name__) + + +class TenantRackAffinityCost(solver_costs.BaseLinearCost): + """Tenant Rack Affinity Cost tends to let scheduler place instances in + the racks that contain existing instances of the tenant. + If a rack has existing instances of the same tenant as that making request, + then the hosts in the rack will have a lower cost value. + """ + + def __init__(self): + super(TenantRackAffinityCost, self).__init__() + + def cost_multiplier(self): + return CONF.solver_scheduler.tenant_rack_affinity_cost_multiplier + + def get_extended_cost_matrix(self, hosts, filter_properties): + num_hosts = len(hosts) + num_instances = filter_properties.get('num_instances') + + extended_cost_matrix = [[0 for j in xrange(num_instances + 1)] + for i in xrange(num_hosts)] + + project_id = filter_properties['project_id'] + + host_racks_map = solver_utils.get_host_racks_map(hosts) + + affinity_racks = set([]) + affinity_hosts = set([]) + + # get affinity racks/hosts + for i in xrange(num_hosts): + host_name = hosts[i].host + host_racks = host_racks_map.get(host_name, set([])) + + # if tenant not in host state then tenant network does not exist + # there, hence no need for further check + if project_id in hosts[i].projects: + affinity_hosts.add(host_name) + affinity_racks = affinity_racks.union(host_racks) + + # check each hosts for affinity + for i in xrange(num_hosts): + host_name = hosts[i].host + host_racks = host_racks_map.get(host_name, set([])) + if (not any([rack in affinity_racks for rack in host_racks])) and ( + host_name not in affinity_hosts): + extended_cost_matrix[i] = [1 for j + in xrange(num_instances + 1)] + else: + LOG.debug(_("%(host)s is in tenant affinity rack."), + {'host': host_name}) + + return extended_cost_matrix diff --git a/nova_solverscheduler/tests/scheduler/solvers/costs/test_rack_affinity_cost.py b/nova_solverscheduler/tests/scheduler/solvers/costs/test_rack_affinity_cost.py new file mode 100644 index 0000000..a1969c6 --- /dev/null +++ b/nova_solverscheduler/tests/scheduler/solvers/costs/test_rack_affinity_cost.py @@ -0,0 +1,163 @@ +# Copyright (c) 2014 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. + +"""Test case for solver scheduler rack affinity cost.""" + +import mock + +from nova import context +from nova import test +from nova_solverscheduler.scheduler.solvers import costs +from nova_solverscheduler.scheduler.solvers.costs import rack_affinity_cost +from nova_solverscheduler.tests.scheduler import solver_scheduler_fakes \ + as fakes + + +@mock.patch('nova_solverscheduler.scheduler.solvers.utils.get_host_racks_map') +class TestTenantRackAffinityCost(test.NoDBTestCase): + def setUp(self): + super(TestTenantRackAffinityCost, self).setUp() + self.context = context.RequestContext('fake_usr', 'fake_proj') + self.cost_handler = costs.CostHandler() + self.cost_classes = self.cost_handler.get_matching_classes( + ['nova_solverscheduler.scheduler.solvers.costs.' + 'rack_affinity_cost.TenantRackAffinityCost']) + + def _get_fake_hosts(self): + host1 = fakes.FakeSolverSchedulerHostState('host1', 'node1', + {'projects': [self.context.project_id]}) + host2 = fakes.FakeSolverSchedulerHostState('host2', 'node2', + {'Projects': []}) + host3 = fakes.FakeSolverSchedulerHostState('host3', 'node3', + {'projects': [self.context.project_id]}) + host4 = fakes.FakeSolverSchedulerHostState('host4', 'node4', + {'projects': []}) + host5 = fakes.FakeSolverSchedulerHostState('host5', 'node5', + {'projects': []}) + host6 = fakes.FakeSolverSchedulerHostState('host6', 'node6', + {'projects': []}) + return [host1, host2, host3, host4, host5, host6] + + def test_cost_multiplier(self, racks_mock): + self.flags(tenant_rack_affinity_cost_multiplier=0.5, + group='solver_scheduler') + self.assertEqual(0.5, + rack_affinity_cost.TenantRackAffinityCost().cost_multiplier()) + + def test_get_extended_cost_matrix_normal(self, racks_mock): + fake_hosts = self._get_fake_hosts() + fake_cost = self.cost_classes[0]() + fake_filter_properties = { + 'context': self.context, + 'project_id': self.context.project_id, + 'num_instances': 3 + } + racks_mock.return_value = { + 'host1': set(['rack1']), + 'host2': set(['rack1']), + 'host3': set(['rack2']), + 'host4': set(['rack2']), + 'host5': set(['rack3']), + 'host6': set(['rack3']) + } + expected_x_cost_mat = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [1, 1, 1, 1], + [1, 1, 1, 1]] + x_cost_mat = fake_cost.get_extended_cost_matrix(fake_hosts, + fake_filter_properties) + self.assertEqual(expected_x_cost_mat, x_cost_mat) + + def test_get_extended_cost_matrix_cross_rack_host_1(self, racks_mock): + fake_hosts = self._get_fake_hosts() + fake_cost = self.cost_classes[0]() + fake_filter_properties = { + 'context': self.context, + 'project_id': self.context.project_id, + 'num_instances': 3 + } + racks_mock.return_value = { + 'host1': set(['rack1']), + 'host2': set(['rack1']), + 'host3': set(['rack2']), + 'host4': set(['rack2']), + 'host5': set(['rack3', 'rack1']), + 'host6': set(['rack3']) + } + expected_x_cost_mat = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [1, 1, 1, 1]] + x_cost_mat = fake_cost.get_extended_cost_matrix(fake_hosts, + fake_filter_properties) + self.assertEqual(expected_x_cost_mat, x_cost_mat) + + def test_get_extended_cost_matrix_cross_rack_host_2(self, racks_mock): + fake_hosts = self._get_fake_hosts() + fake_cost = self.cost_classes[0]() + fake_filter_properties = { + 'context': self.context, + 'project_id': self.context.project_id, + 'num_instances': 3 + } + racks_mock.return_value = { + 'host1': set(['rack1', 'rack3']), + 'host2': set(['rack1']), + 'host3': set(['rack2']), + 'host4': set(['rack2']), + 'host5': set(['rack3']), + 'host6': set(['rack3']) + } + expected_x_cost_mat = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0]] + x_cost_mat = fake_cost.get_extended_cost_matrix(fake_hosts, + fake_filter_properties) + self.assertEqual(expected_x_cost_mat, x_cost_mat) + + def test_get_extended_cost_matrix_incomplete_rack_config(self, racks_mock): + fake_hosts = self._get_fake_hosts() + fake_cost = self.cost_classes[0]() + fake_filter_properties = { + 'context': self.context, + 'project_id': self.context.project_id, + 'num_instances': 3 + } + racks_mock.return_value = { + 'host1': set(['rack1']), + 'host2': set(['rack1']), + 'host4': set(['rack2']), + 'host6': set(['rack3']) + } + expected_x_cost_mat = [ + [0, 0, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + [1, 1, 1, 1], + [1, 1, 1, 1], + [1, 1, 1, 1]] + x_cost_mat = fake_cost.get_extended_cost_matrix(fake_hosts, + fake_filter_properties) + self.assertEqual(expected_x_cost_mat, x_cost_mat)