Add support for best-effort accommodations

Adds a new accommodation instruction 'best_effort' that allows the
deployment to proceed even if the number of available compute nodes
is less than what was requested.

For example, this will create a single pair of agents on separate
compute nodes if possible, but will fall back to creating them on
the same compute node if only one is available:

accommodation: pair, single_room, best_effort, compute_nodes: 2

Change-Id: I1986f550ac0dc934bfa5db84b40a6419d6608c7e
Closes-Bug: 1807630
This commit is contained in:
Sphicas, Phil (ps3910) 2018-12-09 18:01:31 -08:00
parent 249ad453a4
commit 3d1d37b96a
4 changed files with 133 additions and 7 deletions

View File

@ -89,6 +89,7 @@ that allow control the scheduling precisely:
* ``density: N`` - the multiplier for number of instances per compute node
* ``compute_nodes: N`` - how many compute nodes should be used (by default Shaker use all of them \*see note below)
* ``zones: [Z1, Z2]`` - list of Nova availability zones to use
* ``best_effort`` - proceed even if the number of available compute nodes is less than what was requested
Examples:

View File

@ -68,20 +68,35 @@ def generate_agents(compute_nodes, accommodation, unique):
# sort nodes to interleave hosts from different zones
compute_nodes = prepare_for_cross_az(compute_nodes, zones)
best_effort = accommodation.get('best_effort', False)
compute_nodes_requested = accommodation.get('compute_nodes')
if compute_nodes_requested:
if compute_nodes_requested > len(compute_nodes):
raise DeploymentException(
'Not enough compute nodes %(cn)s for requested '
'instance accommodation %(acc)s' %
dict(cn=compute_nodes, acc=accommodation))
compute_nodes = random.sample(compute_nodes, compute_nodes_requested)
if best_effort:
LOG.warn('Allowing best_effort accommodation: '
'compute nodes requested: %(req)d: '
'available: %(avail)d available' %
dict(req=compute_nodes_requested,
avail=len(compute_nodes)))
else:
raise DeploymentException(
'Not enough compute nodes %(cn)s for requested '
'instance accommodation %(acc)s' %
dict(cn=compute_nodes, acc=accommodation))
else:
compute_nodes = random.sample(compute_nodes,
compute_nodes_requested)
cn_count = len(compute_nodes)
iterations = cn_count * density
if 'single_room' in accommodation and 'pair' in accommodation:
iterations //= 2
# special case to allow pair, single_room on single compute node
if best_effort and iterations == 1:
LOG.warn('Allowing best_effort accommodation: '
'single_room, pair on one compute node')
else:
iterations //= 2
node_formula = lambda x: compute_nodes[x % cn_count]
agents = {}

View File

@ -31,7 +31,7 @@ mapping:
matching: any
sequence:
- type: str
enum: [pair, alone, double_room, single_room, mixed_room, cross_az]
enum: [pair, alone, double_room, single_room, mixed_room, cross_az, best_effort]
- type: map
mapping:
density:

View File

@ -146,6 +146,56 @@ class TestDeploy(testtools.TestCase):
self.assertRaises(deploy.DeploymentException, deploy.generate_agents,
['uno'], accommodation, unique)
def test_generate_agents_pair_single_room_best_effort(self):
unique = 'UU1D'
expected = {
'UU1D_master_0': {
'id': 'UU1D_master_0',
'mode': 'master',
'availability_zone': '%s:uno' % ZONE,
'node': 'uno',
'zone': ZONE,
'slave_id': 'UU1D_slave_0'},
'UU1D_slave_0': {
'id': 'UU1D_slave_0',
'master_id': 'UU1D_master_0',
'mode': 'slave',
'availability_zone': '%s:uno' % ZONE,
'zone': ZONE,
'node': 'uno'},
}
accommodation = deploy.normalize_accommodation(
['pair', 'single_room', 'best_effort'])
actual = deploy.generate_agents(nodes_helper('uno'),
accommodation,
unique)
self.assertEqual(expected, actual)
def test_generate_agents_pair_single_room_best_effort_three_nodes(self):
unique = 'UU1D'
expected = {
'UU1D_master_0': {
'id': 'UU1D_master_0',
'mode': 'master',
'availability_zone': '%s:uno' % ZONE,
'node': 'uno',
'zone': ZONE,
'slave_id': 'UU1D_slave_0'},
'UU1D_slave_0': {
'id': 'UU1D_slave_0',
'master_id': 'UU1D_master_0',
'mode': 'slave',
'availability_zone': '%s:dos' % ZONE,
'zone': ZONE,
'node': 'dos'},
}
accommodation = deploy.normalize_accommodation(
['pair', 'single_room', 'best_effort'])
actual = deploy.generate_agents(nodes_helper('uno', 'dos', 'tre'),
accommodation,
unique)
self.assertEqual(expected, actual)
def test_generate_agents_pair_single_room_compute_nodes_not_enough(self):
unique = 'UU1D'
accommodation = deploy.normalize_accommodation(
@ -153,6 +203,31 @@ class TestDeploy(testtools.TestCase):
self.assertRaises(deploy.DeploymentException, deploy.generate_agents,
['uno'], accommodation, unique)
def test_generate_agents_pair_single_room_compute_nodes_best_effort(self):
unique = 'UU1D'
expected = {
'UU1D_master_0': {
'id': 'UU1D_master_0',
'mode': 'master',
'availability_zone': '%s:uno' % ZONE,
'node': 'uno',
'zone': ZONE,
'slave_id': 'UU1D_slave_0'},
'UU1D_slave_0': {
'id': 'UU1D_slave_0',
'master_id': 'UU1D_master_0',
'mode': 'slave',
'availability_zone': '%s:uno' % ZONE,
'zone': ZONE,
'node': 'uno'},
}
accommodation = deploy.normalize_accommodation(
['pair', 'single_room', 'best_effort', {'compute_nodes': 2}])
actual = deploy.generate_agents(nodes_helper('uno'),
accommodation,
unique)
self.assertEqual(expected, actual)
def test_generate_agents_pair_double_room(self):
unique = 'UU1D'
expected = {
@ -332,6 +407,41 @@ class TestDeploy(testtools.TestCase):
unique)
self.assertEqual(expected, actual)
def test_generate_agents_alone_single_room_compute_nodes_not_enough(self):
unique = 'UU1D'
compute_nodes = nodes_helper('uno', 'duo')
accommodation = deploy.normalize_accommodation(
['single_room', {'compute_nodes': 3}])
self.assertRaises(deploy.DeploymentException, deploy.generate_agents,
compute_nodes, accommodation, unique)
@mock.patch('random.sample')
def test_generate_agents_alone_single_room_compute_nodes_best_effort(self,
mr):
unique = 'UU1D'
expected = {
'UU1D_agent_0': {
'id': 'UU1D_agent_0',
'mode': 'alone',
'availability_zone': '%s:uno' % ZONE,
'zone': ZONE,
'node': 'uno'},
'UU1D_agent_1': {
'id': 'UU1D_agent_1',
'mode': 'alone',
'availability_zone': '%s:duo' % ZONE,
'zone': ZONE,
'node': 'duo'},
}
compute_nodes = nodes_helper('uno', 'duo')
mr.side_effect = lambda x, n: x[:n]
accommodation = deploy.normalize_accommodation(
['single_room', 'best_effort', {'compute_nodes': 3}])
actual = deploy.generate_agents(compute_nodes,
accommodation,
unique)
self.assertEqual(expected, actual)
@mock.patch('random.sample')
def test_generate_agents_alone_single_room_density_compute_nodes(self, mr):
unique = 'UU1D'