Move out TenantRackConstraint and minor cleanups

We need to move TenantRackConstraint to a stand alone module, it should not be
a part of rack affinity constraint. No user impact. This patch also cleans up
and fixes some minor codes in the rack_affinity_constraint and
tenant_rack_constraint modules.

Change-Id: I3dacb3979d5a134817f2502a4f419597e3413180
This commit is contained in:
Xinyuan Huang 2015-08-26 01:18:37 +08:00
parent 39fb7d996a
commit 0c0443b410
4 changed files with 590 additions and 556 deletions

View File

@ -15,94 +15,21 @@
import six
from oslo_config import cfg
from oslo_log import log as logging
from nova.compute import api as compute
from nova.i18n import _
from nova_solverscheduler.scheduler.solvers import constraints
from nova_solverscheduler.scheduler.solvers import utils as solver_utils
rack_affinity_opts = [
cfg.IntOpt('max_racks_per_tenant',
default=1,
help='Maximum number of racks each tenant can have.'),
]
CONF = cfg.CONF
CONF.register_opts(rack_affinity_opts)
LOG = logging.getLogger(__name__)
def _get_sorted_racks(racks, hosts, host_racks_map, filter_properties):
"""sort racks by total acceptable instance nums, then avg costs."""
def mixed_order(item):
return (-item[1], item[2])
num_hosts = len(hosts)
num_instances = filter_properties.get('num_instances')
solver_cache = filter_properties.get('solver_cache') or {}
constraint_matrix = solver_cache.get('constraint_matrix', None)
cost_matrix = solver_cache.get('cost_matrix', None)
if not constraint_matrix:
return list(racks)
if not cost_matrix:
cost_matrix = [[0 for j in xrange(num_instances)]
for i in xrange(num_hosts)]
rack_avail_insts = {}
rack_avg_costs = {}
rack_num_hosts = {}
rack_set = set([])
for i in xrange(len(hosts)):
host_name = hosts[i].host
host_racks = host_racks_map.get(host_name, set())
for rack in host_racks:
if rack in racks:
rack_set.add(rack)
# get maximum available instances number for each rack
cons = constraint_matrix[i]
host_max_avail_insts = 0
for j in xrange(len(cons)):
if cons[j] is True:
host_max_avail_insts = j + 1
rack_avail_insts.setdefault(rack, 0)
rack_avail_insts[rack] += host_max_avail_insts
rack_num_hosts.setdefault(rack, 0)
rack_num_hosts[rack] += 1
rack_avg_costs.setdefault(rack, 0)
n = rack_num_hosts[rack]
rack_avg_costs[rack] = (rack_avg_costs[rack] * (n - 1) +
cost_matrix[i][0]) / n
rack_score_tuples = [
(rack, rack_avail_insts[rack], rack_avg_costs[rack]) for
rack in rack_set]
sorted_rack_tuples = sorted(rack_score_tuples, key=mixed_order)
sorted_racks = [rack for (rack, inst, cost) in sorted_rack_tuples]
return sorted_racks
class SameRackConstraint(constraints.BaseLinearConstraint):
"""Place instances in the same racks as those of specified instances.
If the specified instances are in hosts without rack config, then place
instances in the same hosts as those of specified instances.
"""
def __init__(self):
super(SameRackConstraint, self).__init__()
self.compute_api = compute.API()
def get_constraint_matrix(self, hosts, filter_properties):
num_hosts = len(hosts)
num_instances = filter_properties.get('num_instances')
@ -154,10 +81,6 @@ class DifferentRackConstraint(constraints.BaseLinearConstraint):
instances in different hosts as those of specified instances.
"""
def __init__(self):
super(DifferentRackConstraint, self).__init__()
self.compute_api = compute.API()
def get_constraint_matrix(self, hosts, filter_properties):
num_hosts = len(hosts)
num_instances = filter_properties.get('num_instances')
@ -196,67 +119,3 @@ class DifferentRackConstraint(constraints.BaseLinearConstraint):
{'host': host_name})
return constraint_matrix
class TenantRackConstraint(constraints.BaseLinearConstraint):
"""Limit the maximum number of racks that instances of each tenant can
spread across.
If a host doesnot have rack config, it won't be filtered out by this
constraint and will always be regarded as compatible with rack limit.
"""
precedence = 1
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)]
max_racks = CONF.max_racks_per_tenant
project_id = filter_properties['project_id']
host_racks_map = solver_utils.get_host_racks_map(hosts)
project_hosts = set([])
project_racks = set([])
other_racks = set([])
for i in xrange(num_hosts):
host_name = hosts[i].host
host_racks = host_racks_map.get(host_name, set([]))
if project_id in hosts[i].projects:
project_racks = project_racks.union(host_racks)
project_hosts.add(host_name)
else:
other_racks = other_racks.union(host_racks)
other_racks = other_racks.difference(project_racks)
additional_racks = []
if len(project_racks) < max_racks:
additional_rack_num = max_racks - len(project_racks)
if additional_rack_num >= len(other_racks):
additional_racks = list(other_racks)
else:
sorted_other_racks = _get_sorted_racks(
other_racks, hosts, host_racks_map, filter_properties)
additional_racks = sorted_other_racks[0:additional_rack_num]
acceptable_racks = project_racks.union(additional_racks)
for i in xrange(num_hosts):
host_name = hosts[i].host
host_racks = host_racks_map.get(host_name, set([]))
if (any([rack not in acceptable_racks for rack in host_racks])
and (host_name not in project_hosts)):
constraint_matrix[i] = [False for j in xrange(num_instances)]
LOG.debug(_("%(host)s cannot accept requested instances "
"according to TenantRackAffinityConstraint."),
{'host': host_name})
else:
LOG.debug(_("%(host)s can accept requested instances "
"according to TenantRackAffinityConstraint."),
{'host': host_name})
return constraint_matrix

View File

@ -0,0 +1,154 @@
# 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 constraints
from nova_solverscheduler.scheduler.solvers import utils as solver_utils
tenant_rack_opts = [
cfg.IntOpt('max_racks_per_tenant',
default=1,
help='Maximum number of racks each tenant can have.'),
]
CONF = cfg.CONF
CONF.register_opts(tenant_rack_opts)
LOG = logging.getLogger(__name__)
def _get_sorted_racks(racks, hosts, host_racks_map, filter_properties):
"""sort racks by total acceptable instance nums, then avg costs."""
def mixed_order(item):
return (-item[1], item[2])
num_hosts = len(hosts)
num_instances = filter_properties.get('num_instances')
solver_cache = filter_properties.get('solver_cache') or {}
constraint_matrix = solver_cache.get('constraint_matrix', None)
cost_matrix = solver_cache.get('cost_matrix', None)
if not constraint_matrix:
return list(racks)
if not cost_matrix:
cost_matrix = [[0 for j in xrange(num_instances)]
for i in xrange(num_hosts)]
rack_avail_insts = {}
rack_avg_costs = {}
rack_num_hosts = {}
rack_set = set([])
for i in xrange(len(hosts)):
host_name = hosts[i].host
host_racks = host_racks_map.get(host_name, set())
for rack in host_racks:
if rack in racks:
rack_set.add(rack)
# get maximum available instances number for each rack
cons = constraint_matrix[i]
host_max_avail_insts = 0
for j in xrange(len(cons)):
if cons[j] is True:
host_max_avail_insts = j + 1
rack_avail_insts.setdefault(rack, 0)
rack_avail_insts[rack] += host_max_avail_insts
rack_num_hosts.setdefault(rack, 0)
rack_num_hosts[rack] += 1
rack_avg_costs.setdefault(rack, 0)
n = rack_num_hosts[rack]
rack_avg_costs[rack] = (rack_avg_costs[rack] * (n - 1) +
cost_matrix[i][0]) / n
rack_score_tuples = [
(rack, rack_avail_insts[rack], rack_avg_costs[rack]) for
rack in rack_set]
sorted_rack_tuples = sorted(rack_score_tuples, key=mixed_order)
sorted_racks = [rack for (rack, inst, cost) in sorted_rack_tuples]
return sorted_racks
class TenantRackConstraint(constraints.BaseLinearConstraint):
"""Limit the maximum number of racks that instances of each tenant can
spread across.
If a host doesnot have rack config, it won't be filtered out by this
constraint and will always be regarded as compatible with rack limit.
"""
precedence = 1
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)]
max_racks = CONF.max_racks_per_tenant
project_id = filter_properties['project_id']
host_racks_map = solver_utils.get_host_racks_map(hosts)
project_hosts = set([])
project_racks = set([])
other_racks = set([])
for i in xrange(num_hosts):
host_name = hosts[i].host
host_racks = host_racks_map.get(host_name, set([]))
if project_id in hosts[i].projects:
project_racks = project_racks.union(host_racks)
project_hosts.add(host_name)
else:
other_racks = other_racks.union(host_racks)
other_racks = other_racks.difference(project_racks)
additional_racks = []
if len(project_racks) < max_racks:
additional_rack_num = max_racks - len(project_racks)
if additional_rack_num >= len(other_racks):
additional_racks = list(other_racks)
else:
sorted_other_racks = _get_sorted_racks(
other_racks, hosts, host_racks_map, filter_properties)
additional_racks = sorted_other_racks[0:additional_rack_num]
acceptable_racks = project_racks.union(additional_racks)
for i in xrange(num_hosts):
host_name = hosts[i].host
host_racks = host_racks_map.get(host_name, set([]))
if (any([rack not in acceptable_racks for rack in host_racks])
and (host_name not in project_hosts)):
constraint_matrix[i] = [False for j in xrange(num_instances)]
LOG.debug(_("%(host)s cannot accept requested instances "
"according to TenantRackConstraint."),
{'host': host_name})
else:
LOG.debug(_("%(host)s can accept requested instances "
"according to TenantRackConstraint."),
{'host': host_name})
return constraint_matrix

View File

@ -24,200 +24,8 @@ from nova_solverscheduler.tests.scheduler import solver_scheduler_fakes \
as fakes
class TestRackSorting(test.NoDBTestCase):
def setUp(self):
super(TestRackSorting, self).setUp()
self.fake_hosts = [fakes.FakeSolverSchedulerHostState(
'host%s' % i, 'node1', {}) for i in xrange(1, 7)]
def test_get_sorted_racks_normal(self):
"""Let number of available instances: rack1 = rack3 < rack2, and
present host costs: rack3 < rack2 < rack1. The sorted racks should
be: rack2, rack3, rack1.
"""
fake_racks_list = ['rack1', 'rack2', 'rack3']
fake_cost_matrix = [
[5, 5, 5],
[4, 4, 4],
[3, 3, 3],
[2, 2, 2],
[1, 1, 1],
[0, 0, 0]
]
fake_constraint_matrix = [
[True, True, False],
[True, False, False],
[True, True, True],
[True, True, True],
[False, False, False],
[True, True, True]
]
fake_filter_properties = {
'num_instances': 3,
'solver_cache': {'cost_matrix': fake_cost_matrix,
'constraint_matrix': fake_constraint_matrix}
}
fake_host_racks_map = {
'host1': set(['rack1']),
'host2': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host5': set(['rack3']),
'host6': set(['rack3'])
}
expected_result = ['rack2', 'rack3', 'rack1']
result = rack_affinity_constraint._get_sorted_racks(fake_racks_list,
self.fake_hosts, fake_host_racks_map, fake_filter_properties)
self.assertEqual(expected_result, result)
def test_get_sorted_racks_normal_shorter_input_racks_list(self):
fake_racks_list = ['rack1', 'rack2']
fake_cost_matrix = [
[5, 5, 5],
[4, 4, 4],
[3, 3, 3],
[2, 2, 2],
[1, 1, 1],
[0, 0, 0]
]
fake_constraint_matrix = [
[True, True, False],
[True, False, False],
[True, True, True],
[True, True, True],
[False, False, False],
[True, True, True]
]
fake_filter_properties = {
'num_instances': 3,
'solver_cache': {'cost_matrix': fake_cost_matrix,
'constraint_matrix': fake_constraint_matrix}
}
fake_host_racks_map = {
'host1': set(['rack1']),
'host2': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host5': set(['rack3']),
'host6': set(['rack3'])
}
expected_result = ['rack2', 'rack1']
result = rack_affinity_constraint._get_sorted_racks(fake_racks_list,
self.fake_hosts, fake_host_racks_map, fake_filter_properties)
self.assertEqual(expected_result, result)
def test_get_sorted_racks_no_cost_matrix(self):
fake_racks_list = ['rack1', 'rack2', 'rack3']
fake_cost_matrix = []
fake_constraint_matrix = [
[True, True, False],
[True, False, False],
[True, True, True],
[True, True, True],
[False, False, False],
[True, True, True]
]
fake_filter_properties = {
'num_instances': 3,
'solver_cache': {'cost_matrix': fake_cost_matrix,
'constraint_matrix': fake_constraint_matrix}
}
fake_host_racks_map = {
'host1': set(['rack1']),
'host2': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host5': set(['rack3']),
'host6': set(['rack3'])
}
expected_result_idx0 = 'rack2'
result = rack_affinity_constraint._get_sorted_racks(fake_racks_list,
self.fake_hosts, fake_host_racks_map, fake_filter_properties)
self.assertEqual(expected_result_idx0, result[0])
def test_get_sorted_racks_no_constraint_matrix(self):
fake_racks_list = ['rack1', 'rack2', 'rack3']
fake_cost_matrix = [
[5, 5, 5],
[4, 4, 4],
[3, 3, 3],
[2, 2, 2],
[1, 1, 1],
[0, 0, 0]
]
fake_constraint_matrix = []
fake_filter_properties = {
'num_instances': 3,
'solver_cache': {'cost_matrix': fake_cost_matrix,
'constraint_matrix': fake_constraint_matrix}
}
fake_host_racks_map = {
'host1': set(['rack1']),
'host2': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host5': set(['rack3']),
'host6': set(['rack3'])
}
expected_result = ['rack1', 'rack2', 'rack3']
result = rack_affinity_constraint._get_sorted_racks(fake_racks_list,
self.fake_hosts, fake_host_racks_map, fake_filter_properties)
self.assertEqual(expected_result, result)
def test_get_sorted_racks_no_host_racks_map(self):
fake_racks_list = ['rack1', 'rack2', 'rack3']
fake_cost_matrix = [
[5, 5, 5],
[4, 4, 4],
[3, 3, 3],
[2, 2, 2],
[1, 1, 1],
[0, 0, 0]
]
fake_constraint_matrix = [
[True, True, False],
[True, False, False],
[True, True, True],
[True, True, True],
[False, False, False],
[True, True, True]
]
fake_filter_properties = {
'num_instances': 3,
'solver_cache': {'cost_matrix': fake_cost_matrix,
'constraint_matrix': fake_constraint_matrix}
}
fake_host_racks_map = {}
expected_result = []
result = rack_affinity_constraint._get_sorted_racks(fake_racks_list,
self.fake_hosts, fake_host_racks_map, fake_filter_properties)
self.assertEqual(expected_result, result)
@mock.patch('nova_solverscheduler.scheduler.solvers.utils.get_host_racks_map')
class TestSameRackConstraint(test.NoDBTestCase):
USES_DB = True
def setUp(self):
super(TestSameRackConstraint, self).setUp()
@ -437,7 +245,6 @@ class TestSameRackConstraint(test.NoDBTestCase):
@mock.patch('nova_solverscheduler.scheduler.solvers.utils.get_host_racks_map')
class TestDifferentRackConstraint(test.NoDBTestCase):
USES_DB = True
def setUp(self):
super(TestDifferentRackConstraint, self).setUp()
@ -654,225 +461,3 @@ class TestDifferentRackConstraint(test.NoDBTestCase):
self.fake_hosts, fake_filter_properties)
self.assertEqual(expected_cons_mat, cons_mat)
@mock.patch('nova_solverscheduler.scheduler.solvers.utils.get_host_racks_map')
class TestTenantRackConstraint(test.NoDBTestCase):
def setUp(self):
super(TestTenantRackConstraint, self).setUp()
self.constraint_cls = rack_affinity_constraint.\
TenantRackConstraint
self.context = context.RequestContext('fake', 'fake')
self.fake_hosts = [fakes.FakeSolverSchedulerHostState(
'host%s' % i, 'node1', {'projects': []})
for i in xrange(1, 7)]
def test_tenant_rack_max_racks_reached(self, racks_mock):
self.flags(max_racks_per_tenant=2)
# let the tenant vm's be in host1, host3
self.fake_hosts[0].projects = ['fake']
self.fake_hosts[2].projects = ['fake']
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2
}
racks_mock.return_value = {
'host1': set(['rack1']),
'host2': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host5': set(['rack3']),
'host6': set(['rack3'])
}
expected_cons_mat = [
[True, True],
[True, True],
[True, True],
[True, True],
[False, False],
[False, False]
]
cons_mat = self.constraint_cls().get_constraint_matrix(
self.fake_hosts, fake_filter_properties)
self.assertEqual(expected_cons_mat, cons_mat)
def test_tenant_rack_choose_additional_racks1(self, racks_mock):
"""sort additional racks by available instances number"""
self.flags(max_racks_per_tenant=2)
# let the tenant vm's be in host1
self.fake_hosts[0].projects = ['fake']
fake_cost_matrix = [
[0, 1],
[1, 2],
[2, 3],
[3, 4],
[4, 5],
[5, 6]
]
fake_constraint_matrix = [
[True, True],
[True, True],
[True, False],
[False, False],
[True, True],
[False, False]
]
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2,
'solver_cache': {'cost_matrix': fake_cost_matrix,
'constraint_matrix': fake_constraint_matrix}
}
racks_mock.return_value = {
'host1': set(['rack1']),
'host2': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host5': set(['rack3']),
'host6': set(['rack3'])
}
expected_cons_mat = [
[True, True],
[True, True],
[False, False],
[False, False],
[True, True],
[True, True]
]
cons_mat = self.constraint_cls().get_constraint_matrix(
self.fake_hosts, fake_filter_properties)
self.assertEqual(expected_cons_mat, cons_mat)
def test_tenant_rack_choose_additional_racks2(self, racks_mock):
"""sort additional racks by costs when available num_hosts are same."""
self.flags(max_racks_per_tenant=2)
# let the tenant vm's be in host1
self.fake_hosts[0].projects = ['fake']
fake_cost_matrix = [
[0, 1],
[1, 2],
[2, 3],
[3, 4],
[4, 5],
[5, 6]
]
fake_constraint_matrix = [
[True, True],
[True, True],
[True, True],
[False, False],
[True, True],
[False, False]
]
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2,
'solver_cache': {'cost_matrix': fake_cost_matrix,
'constraint_matrix': fake_constraint_matrix}
}
racks_mock.return_value = {
'host1': set(['rack1']),
'host2': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host5': set(['rack3']),
'host6': set(['rack3'])
}
expected_cons_mat = [
[True, True],
[True, True],
[True, True],
[True, True],
[False, False],
[False, False]
]
cons_mat = self.constraint_cls().get_constraint_matrix(
self.fake_hosts, fake_filter_properties)
self.assertEqual(expected_cons_mat, cons_mat)
def test_tenant_rack_incomplete_rack_config(self, racks_mock):
self.flags(max_racks_per_tenant=1)
# let the tenant vm's be in host2, host3
self.fake_hosts[1].projects = ['fake']
self.fake_hosts[2].projects = ['fake']
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2
}
racks_mock.return_value = {
'host1': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host6': set(['rack3'])
}
expected_cons_mat = [
[False, False],
[True, True],
[True, True],
[True, True],
[True, True],
[False, False]
]
cons_mat = self.constraint_cls().get_constraint_matrix(
self.fake_hosts, fake_filter_properties)
self.assertEqual(expected_cons_mat, cons_mat)
def test_tenant_rack_no_rack_config(self, racks_mock):
self.flags(max_racks_per_tenant=1)
# let the tenant vm's be in host2, host3
self.fake_hosts[1].projects = ['fake']
self.fake_hosts[2].projects = ['fake']
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2
}
racks_mock.return_value = {}
expected_cons_mat = [
[True, True],
[True, True],
[True, True],
[True, True],
[True, True],
[True, True]
]
cons_mat = self.constraint_cls().get_constraint_matrix(
self.fake_hosts, fake_filter_properties)
self.assertEqual(expected_cons_mat, cons_mat)

View File

@ -0,0 +1,436 @@
# 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 tenant_rack_constraint
from nova_solverscheduler.tests.scheduler import solver_scheduler_fakes \
as fakes
class TestRackSorting(test.NoDBTestCase):
def setUp(self):
super(TestRackSorting, self).setUp()
self.fake_hosts = [fakes.FakeSolverSchedulerHostState(
'host%s' % i, 'node1', {}) for i in xrange(1, 7)]
def test_get_sorted_racks_normal(self):
"""Let number of available instances: rack1 = rack3 < rack2, and
present host costs: rack3 < rack2 < rack1. The sorted racks should
be: rack2, rack3, rack1.
"""
fake_racks_list = ['rack1', 'rack2', 'rack3']
fake_cost_matrix = [
[5, 5, 5],
[4, 4, 4],
[3, 3, 3],
[2, 2, 2],
[1, 1, 1],
[0, 0, 0]
]
fake_constraint_matrix = [
[True, True, False],
[True, False, False],
[True, True, True],
[True, True, True],
[False, False, False],
[True, True, True]
]
fake_filter_properties = {
'num_instances': 3,
'solver_cache': {'cost_matrix': fake_cost_matrix,
'constraint_matrix': fake_constraint_matrix}
}
fake_host_racks_map = {
'host1': set(['rack1']),
'host2': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host5': set(['rack3']),
'host6': set(['rack3'])
}
expected_result = ['rack2', 'rack3', 'rack1']
result = tenant_rack_constraint._get_sorted_racks(fake_racks_list,
self.fake_hosts, fake_host_racks_map, fake_filter_properties)
self.assertEqual(expected_result, result)
def test_get_sorted_racks_normal_shorter_input_racks_list(self):
fake_racks_list = ['rack1', 'rack2']
fake_cost_matrix = [
[5, 5, 5],
[4, 4, 4],
[3, 3, 3],
[2, 2, 2],
[1, 1, 1],
[0, 0, 0]
]
fake_constraint_matrix = [
[True, True, False],
[True, False, False],
[True, True, True],
[True, True, True],
[False, False, False],
[True, True, True]
]
fake_filter_properties = {
'num_instances': 3,
'solver_cache': {'cost_matrix': fake_cost_matrix,
'constraint_matrix': fake_constraint_matrix}
}
fake_host_racks_map = {
'host1': set(['rack1']),
'host2': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host5': set(['rack3']),
'host6': set(['rack3'])
}
expected_result = ['rack2', 'rack1']
result = tenant_rack_constraint._get_sorted_racks(fake_racks_list,
self.fake_hosts, fake_host_racks_map, fake_filter_properties)
self.assertEqual(expected_result, result)
def test_get_sorted_racks_no_cost_matrix(self):
fake_racks_list = ['rack1', 'rack2', 'rack3']
fake_cost_matrix = []
fake_constraint_matrix = [
[True, True, False],
[True, False, False],
[True, True, True],
[True, True, True],
[False, False, False],
[True, True, True]
]
fake_filter_properties = {
'num_instances': 3,
'solver_cache': {'cost_matrix': fake_cost_matrix,
'constraint_matrix': fake_constraint_matrix}
}
fake_host_racks_map = {
'host1': set(['rack1']),
'host2': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host5': set(['rack3']),
'host6': set(['rack3'])
}
expected_result_idx0 = 'rack2'
result = tenant_rack_constraint._get_sorted_racks(fake_racks_list,
self.fake_hosts, fake_host_racks_map, fake_filter_properties)
self.assertEqual(expected_result_idx0, result[0])
def test_get_sorted_racks_no_constraint_matrix(self):
fake_racks_list = ['rack1', 'rack2', 'rack3']
fake_cost_matrix = [
[5, 5, 5],
[4, 4, 4],
[3, 3, 3],
[2, 2, 2],
[1, 1, 1],
[0, 0, 0]
]
fake_constraint_matrix = []
fake_filter_properties = {
'num_instances': 3,
'solver_cache': {'cost_matrix': fake_cost_matrix,
'constraint_matrix': fake_constraint_matrix}
}
fake_host_racks_map = {
'host1': set(['rack1']),
'host2': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host5': set(['rack3']),
'host6': set(['rack3'])
}
expected_result = ['rack1', 'rack2', 'rack3']
result = tenant_rack_constraint._get_sorted_racks(fake_racks_list,
self.fake_hosts, fake_host_racks_map, fake_filter_properties)
self.assertEqual(expected_result, result)
def test_get_sorted_racks_no_host_racks_map(self):
fake_racks_list = ['rack1', 'rack2', 'rack3']
fake_cost_matrix = [
[5, 5, 5],
[4, 4, 4],
[3, 3, 3],
[2, 2, 2],
[1, 1, 1],
[0, 0, 0]
]
fake_constraint_matrix = [
[True, True, False],
[True, False, False],
[True, True, True],
[True, True, True],
[False, False, False],
[True, True, True]
]
fake_filter_properties = {
'num_instances': 3,
'solver_cache': {'cost_matrix': fake_cost_matrix,
'constraint_matrix': fake_constraint_matrix}
}
fake_host_racks_map = {}
expected_result = []
result = tenant_rack_constraint._get_sorted_racks(fake_racks_list,
self.fake_hosts, fake_host_racks_map, fake_filter_properties)
self.assertEqual(expected_result, result)
@mock.patch('nova_solverscheduler.scheduler.solvers.utils.get_host_racks_map')
class TestTenantRackConstraint(test.NoDBTestCase):
def setUp(self):
super(TestTenantRackConstraint, self).setUp()
self.constraint_cls = tenant_rack_constraint.\
TenantRackConstraint
self.context = context.RequestContext('fake', 'fake')
self.fake_hosts = [fakes.FakeSolverSchedulerHostState(
'host%s' % i, 'node1', {'projects': []})
for i in xrange(1, 7)]
def test_tenant_rack_max_racks_reached(self, racks_mock):
self.flags(max_racks_per_tenant=2)
# let the tenant vm's be in host1, host3
self.fake_hosts[0].projects = ['fake']
self.fake_hosts[2].projects = ['fake']
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2
}
racks_mock.return_value = {
'host1': set(['rack1']),
'host2': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host5': set(['rack3']),
'host6': set(['rack3'])
}
expected_cons_mat = [
[True, True],
[True, True],
[True, True],
[True, True],
[False, False],
[False, False]
]
cons_mat = self.constraint_cls().get_constraint_matrix(
self.fake_hosts, fake_filter_properties)
self.assertEqual(expected_cons_mat, cons_mat)
def test_tenant_rack_choose_additional_racks1(self, racks_mock):
"""sort additional racks by available instances number"""
self.flags(max_racks_per_tenant=2)
# let the tenant vm's be in host1
self.fake_hosts[0].projects = ['fake']
fake_cost_matrix = [
[0, 1],
[1, 2],
[2, 3],
[3, 4],
[4, 5],
[5, 6]
]
fake_constraint_matrix = [
[True, True],
[True, True],
[True, False],
[False, False],
[True, True],
[False, False]
]
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2,
'solver_cache': {'cost_matrix': fake_cost_matrix,
'constraint_matrix': fake_constraint_matrix}
}
racks_mock.return_value = {
'host1': set(['rack1']),
'host2': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host5': set(['rack3']),
'host6': set(['rack3'])
}
expected_cons_mat = [
[True, True],
[True, True],
[False, False],
[False, False],
[True, True],
[True, True]
]
cons_mat = self.constraint_cls().get_constraint_matrix(
self.fake_hosts, fake_filter_properties)
self.assertEqual(expected_cons_mat, cons_mat)
def test_tenant_rack_choose_additional_racks2(self, racks_mock):
"""sort additional racks by costs when available num_hosts are same."""
self.flags(max_racks_per_tenant=2)
# let the tenant vm's be in host1
self.fake_hosts[0].projects = ['fake']
fake_cost_matrix = [
[0, 1],
[1, 2],
[2, 3],
[3, 4],
[4, 5],
[5, 6]
]
fake_constraint_matrix = [
[True, True],
[True, True],
[True, True],
[False, False],
[True, True],
[False, False]
]
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2,
'solver_cache': {'cost_matrix': fake_cost_matrix,
'constraint_matrix': fake_constraint_matrix}
}
racks_mock.return_value = {
'host1': set(['rack1']),
'host2': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host5': set(['rack3']),
'host6': set(['rack3'])
}
expected_cons_mat = [
[True, True],
[True, True],
[True, True],
[True, True],
[False, False],
[False, False]
]
cons_mat = self.constraint_cls().get_constraint_matrix(
self.fake_hosts, fake_filter_properties)
self.assertEqual(expected_cons_mat, cons_mat)
def test_tenant_rack_incomplete_rack_config(self, racks_mock):
self.flags(max_racks_per_tenant=1)
# let the tenant vm's be in host2, host3
self.fake_hosts[1].projects = ['fake']
self.fake_hosts[2].projects = ['fake']
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2
}
racks_mock.return_value = {
'host1': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host6': set(['rack3'])
}
expected_cons_mat = [
[False, False],
[True, True],
[True, True],
[True, True],
[True, True],
[False, False]
]
cons_mat = self.constraint_cls().get_constraint_matrix(
self.fake_hosts, fake_filter_properties)
self.assertEqual(expected_cons_mat, cons_mat)
def test_tenant_rack_no_rack_config(self, racks_mock):
self.flags(max_racks_per_tenant=1)
# let the tenant vm's be in host2, host3
self.fake_hosts[1].projects = ['fake']
self.fake_hosts[2].projects = ['fake']
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2
}
racks_mock.return_value = {}
expected_cons_mat = [
[True, True],
[True, True],
[True, True],
[True, True],
[True, True],
[True, True]
]
cons_mat = self.constraint_cls().get_constraint_matrix(
self.fake_hosts, fake_filter_properties)
self.assertEqual(expected_cons_mat, cons_mat)