Add same/different rack constraints to solver scheduler

This adds two constraint classes, SameRackConstraint and DifferentRackConstraint,
to support rack-based affinity placement functionality, which are similar as the
same/different host constraints but in rack's level.

Change-Id: I4e06ab74808b565da6e93a5817ff095ae97b2c9c
This commit is contained in:
Xinyuan Huang 2015-06-10 01:22:07 +08:00
parent b23f88bae4
commit 0ba1e05467
4 changed files with 703 additions and 0 deletions

View File

@ -0,0 +1,130 @@
# 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 six
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
LOG = logging.getLogger(__name__)
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')
constraint_matrix = [[True for j in xrange(num_instances)]
for i in xrange(num_hosts)]
scheduler_hints = filter_properties.get('scheduler_hints') or {}
affinity_uuids = scheduler_hints.get('same_rack', [])
if not affinity_uuids:
return constraint_matrix
if isinstance(affinity_uuids, six.string_types):
affinity_uuids = [affinity_uuids]
host_racks_map = solver_utils.get_host_racks_map(hosts)
affinity_racks = set([])
affinity_hosts = set([])
for i in xrange(num_hosts):
host_name = hosts[i].host
host_racks = host_racks_map.get(host_name, set([]))
if solver_utils.instance_uuids_overlap(hosts[i], affinity_uuids):
affinity_racks = affinity_racks.union(host_racks)
affinity_hosts.add(host_name)
for i in xrange(num_hosts):
host_name = hosts[i].host
host_racks = host_racks_map.get(host_name, set([]))
if host_name in affinity_hosts:
LOG.debug(_("%(host)s passed same-rack check."),
{'host': host_name})
continue
elif (len(host_racks) == 0) or any([rack not in affinity_racks
for rack in host_racks]):
constraint_matrix[i] = [False for j in xrange(num_instances)]
else:
LOG.debug(_("%(host)s passed same-rack check."),
{'host': host_name})
return constraint_matrix
class DifferentRackConstraint(constraints.BaseLinearConstraint):
"""Place instances in different racks as those of specified instances.
If the specified instances are in hosts without rack config, then place
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')
constraint_matrix = [[True for j in xrange(num_instances)]
for i in xrange(num_hosts)]
scheduler_hints = filter_properties.get('scheduler_hints') or {}
affinity_uuids = scheduler_hints.get('different_rack', [])
if not affinity_uuids:
return constraint_matrix
if isinstance(affinity_uuids, six.string_types):
affinity_uuids = [affinity_uuids]
host_racks_map = solver_utils.get_host_racks_map(hosts)
affinity_racks = set([])
affinity_hosts = set([])
for i in xrange(num_hosts):
host_name = hosts[i].host
host_racks = host_racks_map.get(host_name, set([]))
if solver_utils.instance_uuids_overlap(hosts[i], affinity_uuids):
affinity_racks = affinity_racks.union(host_racks)
affinity_hosts.add(host_name)
for i in xrange(num_hosts):
host_name = hosts[i].host
host_racks = host_racks_map.get(host_name, set([]))
if any([rack in affinity_racks for rack in host_racks]) or (
host_name in affinity_hosts):
constraint_matrix[i] = [False for j in xrange(num_instances)]
LOG.debug(_("%(host)s didnot pass different-rack check."),
{'host': host_name})
return constraint_matrix

View File

@ -86,3 +86,25 @@ def get_host_racks_config():
str(e))
return host_racks_map
def get_host_racks_map(hosts):
"""Return a dict where keys are host names and values are names of racks
belonging to each host. Hosts without rack config will not show up in the
result. By default this checks host aggregate for a metadata key 'rack',
if no such metadata key is found, it will check an external config file.
"""
host_racks_map = {}
for host_state in hosts:
host_name = host_state.host
host_racks = aggregate_values_from_key(host_state, 'rack')
if host_racks:
host_racks_map.setdefault(host_name, set())
host_racks_map[host_name] = host_racks_map[host_name].union(
host_racks)
if not host_racks_map:
host_racks_map = get_host_racks_config()
return host_racks_map

View File

@ -0,0 +1,465 @@
# 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 objects
from nova import test
from nova_solverscheduler.scheduler.solvers.constraints \
import rack_affinity_constraint
from nova_solverscheduler.tests.scheduler import solver_scheduler_fakes \
as fakes
@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()
self.constraint_cls = rack_affinity_constraint.SameRackConstraint
self.context = context.RequestContext('fake', 'fake')
self.fake_hosts = [fakes.FakeSolverSchedulerHostState(
'host%s' % i, 'node1', {}) for i in xrange(1, 7)]
def test_same_rack_one_inst(self, racks_mock):
instance = objects.Instance(uuid='inst1')
instance_uuid = instance.uuid
# let this instance be in host2
self.fake_hosts[1].instances = {instance_uuid: instance}
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2,
'scheduler_hints': {'same_rack': instance_uuid}
}
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],
[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_same_rack_multi_inst(self, racks_mock):
instance1 = objects.Instance(uuid='inst1')
instance2 = objects.Instance(uuid='inst2')
instance1_uuid = instance1.uuid
instance2_uuid = instance2.uuid
# let these instances be in host1 and host3
self.fake_hosts[0].instances = {instance1_uuid: instance1}
self.fake_hosts[2].instances = {instance2_uuid: instance2}
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2,
'scheduler_hints': {'same_rack': [instance1_uuid, instance2_uuid]}
}
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_same_rack_with_cross_rack_host(self, racks_mock):
instance = objects.Instance(uuid='inst1')
instance_uuid = instance.uuid
# let this instance be in host1
self.fake_hosts[0].instances = {instance_uuid: instance}
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2,
'scheduler_hints': {'same_rack': instance_uuid}
}
racks_mock.return_value = {
'host1': set(['rack1', 'rack2']),
'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_same_rack_incomplete_rack_config(self, racks_mock):
instance = objects.Instance(uuid='inst1')
instance_uuid = instance.uuid
# let this instance be in host2
self.fake_hosts[1].instances = {instance_uuid: instance}
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2,
'scheduler_hints': {'same_rack': instance_uuid}
}
racks_mock.return_value = {
'host1': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host6': set(['rack3'])
}
expected_cons_mat = [
[False, False],
[True, True],
[False, False],
[False, False],
[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_same_rack_incomplete_rack_config2(self, racks_mock):
instance = objects.Instance(uuid='inst1')
instance_uuid = instance.uuid
# let this instance be in host3
self.fake_hosts[2].instances = {instance_uuid: instance}
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2,
'scheduler_hints': {'same_rack': instance_uuid}
}
racks_mock.return_value = {
'host1': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host6': set(['rack3'])
}
expected_cons_mat = [
[False, False],
[False, False],
[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_same_rack_no_rack_config(self, racks_mock):
instance = objects.Instance(uuid='inst1')
instance_uuid = instance.uuid
# let this instance be in host2
self.fake_hosts[1].instances = {instance_uuid: instance}
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2,
'scheduler_hints': {'same_rack': instance_uuid}
}
racks_mock.return_value = {}
expected_cons_mat = [
[False, False],
[True, True],
[False, False],
[False, False],
[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)
@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()
self.constraint_cls = rack_affinity_constraint.DifferentRackConstraint
self.context = context.RequestContext('fake', 'fake')
self.fake_hosts = [fakes.FakeSolverSchedulerHostState(
'host%s' % i, 'node1', {}) for i in xrange(1, 7)]
def test_different_rack_one_inst(self, racks_mock):
instance = objects.Instance(uuid='inst1')
instance_uuid = instance.uuid
# let this instance be in host2
self.fake_hosts[1].instances = {instance_uuid: instance}
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2,
'scheduler_hints': {'different_rack': instance_uuid}
}
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 = [
[False, False],
[False, False],
[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)
def test_different_rack_multi_inst(self, racks_mock):
instance1 = objects.Instance(uuid='inst1')
instance2 = objects.Instance(uuid='inst2')
instance1_uuid = instance1.uuid
instance2_uuid = instance2.uuid
# let these instances be in host1 and host3
self.fake_hosts[0].instances = {instance1_uuid: instance1}
self.fake_hosts[2].instances = {instance2_uuid: instance2}
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2,
'scheduler_hints': {'different_rack':
[instance1_uuid, instance2_uuid]}
}
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 = [
[False, False],
[False, False],
[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_different_rack_with_cross_rack_host(self, racks_mock):
instance = objects.Instance(uuid='inst1')
instance_uuid = instance.uuid
# let this instance be in host1
self.fake_hosts[0].instances = {instance_uuid: instance}
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2,
'scheduler_hints': {'different_rack': instance_uuid}
}
racks_mock.return_value = {
'host1': set(['rack1', 'rack2']),
'host2': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host5': set(['rack3']),
'host6': set(['rack3'])
}
expected_cons_mat = [
[False, False],
[False, False],
[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_different_rack_incomplete_rack_config(self, racks_mock):
instance = objects.Instance(uuid='inst1')
instance_uuid = instance.uuid
# let this instance be in host2
self.fake_hosts[1].instances = {instance_uuid: instance}
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2,
'scheduler_hints': {'different_rack': instance_uuid}
}
racks_mock.return_value = {
'host1': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'host6': set(['rack3'])
}
expected_cons_mat = [
[True, True],
[False, False],
[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)
def test_different_rack_incomplete_rack_config2(self, racks_mock):
instance = objects.Instance(uuid='inst1')
instance_uuid = instance.uuid
# let this instance be in host3
self.fake_hosts[2].instances = {instance_uuid: instance}
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2,
'scheduler_hints': {'different_rack': instance_uuid}
}
racks_mock.return_value = {
'host1': set(['rack1']),
'host3': set(['rack2']),
'host4': set(['rack2']),
'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_different_rack_no_rack_config(self, racks_mock):
instance = objects.Instance(uuid='inst1')
instance_uuid = instance.uuid
# let this instance be in host2
self.fake_hosts[1].instances = {instance_uuid: instance}
fake_filter_properties = {
'context': self.context,
'project_id': 'fake',
'num_instances': 2,
'scheduler_hints': {'different_rack': instance_uuid}
}
racks_mock.return_value = {}
expected_cons_mat = [
[True, True],
[False, False],
[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

@ -13,16 +13,41 @@
# License for the specific language governing permissions and limitations
# under the License.
import mock
import os.path
import tempfile
from oslo_config import cfg
from nova import objects
from nova import test
from nova_solverscheduler.scheduler.solvers import utils
from nova_solverscheduler.tests.scheduler import solver_scheduler_fakes \
as fakes
CONF = cfg.CONF
_AGGREGATE_FIXTURES = [
objects.Aggregate(
id=1,
name='aggr1',
hosts=['fake-host'],
metadata={'k1': '1', 'k2': '2'},
),
objects.Aggregate(
id=2,
name='bar',
hosts=['fake-host'],
metadata={'k1': '3', 'k2': '4'},
),
objects.Aggregate(
id=3,
name='bar',
hosts=['fake-host'],
metadata={'k1': '6,7', 'k2': '8, 9'},
),
]
class TestRackConfigLoader(test.NoDBTestCase):
"""Test case for rack config file loading."""
@ -67,3 +92,64 @@ k=bla
def tearDown(self):
self.config.close()
super(TestRackConfigLoader, self).tearDown()
class TestGetHostRacksMap(test.NoDBTestCase):
def setUp(self):
super(TestGetHostRacksMap, self).setUp()
self.fake_aggregates = [
objects.Aggregate(
id=1,
name='aggr1',
hosts=['host1', 'host2'],
metadata={'rack': 'rack1', 'foo': 'bar'},
),
objects.Aggregate(
id=2,
name='aggr2',
hosts=['host2'],
metadata={'rack': 'rack2'},
),
objects.Aggregate(
id=3,
name='aggr3',
hosts=['host3'],
metadata={'foo': 'bar'},
),
]
def test_get_host_racks_map_from_aggregate(self):
host1 = fakes.FakeSolverSchedulerHostState('host1', 'node1',
{'aggregates': self.fake_aggregates[0:1]})
host2 = fakes.FakeSolverSchedulerHostState('host2', 'node2',
{'aggregates': self.fake_aggregates[0:2]})
host3 = fakes.FakeSolverSchedulerHostState('host3', 'node3',
{'aggregates': self.fake_aggregates[2:3]})
host4 = fakes.FakeSolverSchedulerHostState('host4', 'node4',
{'aggregates': []})
hosts = [host1, host2, host3, host4]
result = utils.get_host_racks_map(hosts)
expected_result = {
'host1': set(['rack1']),
'host2': set(['rack1', 'rack2'])
}
self.assertEqual(expected_result, result)
@mock.patch('nova_solverscheduler.scheduler.solvers.utils.'
'get_host_racks_config')
def test_get_host_racks_map_no_aggregate_key(self, getconfig_mock):
host1 = fakes.FakeSolverSchedulerHostState('host1', 'node1', {})
host2 = fakes.FakeSolverSchedulerHostState('host2', 'node2', {})
host3 = fakes.FakeSolverSchedulerHostState('host3', 'node3',
{'aggregates': self.fake_aggregates[2:3]})
host4 = fakes.FakeSolverSchedulerHostState('host4', 'node4', {})
hosts = [host1, host2, host3, host4]
expected_result = {'host1': set('rack1'), 'host2': set('rack1')}
getconfig_mock.return_value = expected_result
result = utils.get_host_racks_map(hosts)
self.assertEqual(expected_result, result)