Allocation candidates prefer matching name

This change finds a node with the same name as the allocation and
moves it to the beginning of the shuffled candidate list so that node
is the first allocation attempt.

It is common for node naming scheme to match the node's role (such as
compute-1, compute-2). Also this often matches the hostname
(allocation name) scheme. Without this change, this scenario will
generally result in swapped names (node compute-1 having hostname
compute-2, etc).

By preferring matching names this situation can be avoided in the
majority of cases, while not otherwise affecting the candidiate
allocation approach.

Change-Id: Ie990bfc209959d58852b9080778602eab5aa30af
This commit is contained in:
Steve Baker 2022-06-15 10:50:36 +12:00
parent 0659485d63
commit 2b55444f37
6 changed files with 60 additions and 3 deletions

View File

@ -48,7 +48,11 @@ parameters must be missing or match the provided node.
Added support for backfilling allocations. Added support for backfilling allocations.
.. versionadded:: 1.60 .. versionadded:: 1.60
Introduced the ``owner`` field. Introduced the ``owner`` field.
.. versionadded:: 1.79
A node with the same name as the allocation ``name`` is moved to the
start of the derived candidiate list.
Normal response codes: 201 Normal response codes: 201

View File

@ -116,6 +116,7 @@ BASE_VERSION = 1
# v1.76: Add support for changing boot_mode and secure_boot state # v1.76: Add support for changing boot_mode and secure_boot state
# v1.77: Add fields selector to drivers list and driver detail. # v1.77: Add fields selector to drivers list and driver detail.
# v1.78: Add node history endpoint # v1.78: Add node history endpoint
# v1.79: Change allocation behaviour to prefer node name match
MINOR_0_JUNO = 0 MINOR_0_JUNO = 0
MINOR_1_INITIAL_VERSION = 1 MINOR_1_INITIAL_VERSION = 1
@ -196,6 +197,7 @@ MINOR_75_NODE_BOOT_MODE = 75
MINOR_76_NODE_CHANGE_BOOT_MODE = 76 MINOR_76_NODE_CHANGE_BOOT_MODE = 76
MINOR_77_DRIVER_FIELDS_SELECTOR = 77 MINOR_77_DRIVER_FIELDS_SELECTOR = 77
MINOR_78_NODE_HISTORY = 78 MINOR_78_NODE_HISTORY = 78
MINOR_79_ALLOCATION_NODE_NAME = 79
# When adding another version, update: # When adding another version, update:
# - MINOR_MAX_VERSION # - MINOR_MAX_VERSION
@ -203,7 +205,7 @@ MINOR_78_NODE_HISTORY = 78
# explanation of what changed in the new version # explanation of what changed in the new version
# - common/release_mappings.py, RELEASE_MAPPING['master']['api'] # - common/release_mappings.py, RELEASE_MAPPING['master']['api']
MINOR_MAX_VERSION = MINOR_78_NODE_HISTORY MINOR_MAX_VERSION = MINOR_79_ALLOCATION_NODE_NAME
# String representations of the minor and maximum versions # String representations of the minor and maximum versions
_MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION) _MIN_VERSION_STRING = '{}.{}'.format(BASE_VERSION, MINOR_1_INITIAL_VERSION)

View File

@ -471,7 +471,7 @@ RELEASE_MAPPING = {
} }
}, },
'master': { 'master': {
'api': '1.78', 'api': '1.79',
'rpc': '1.55', 'rpc': '1.55',
'objects': { 'objects': {
'Allocation': ['1.1'], 'Allocation': ['1.1'],

View File

@ -141,6 +141,15 @@ def _candidate_nodes(context, allocation):
# in the same order. # in the same order.
random.shuffle(nodes) random.shuffle(nodes)
# NOTE(sbaker): if the allocation name matches a node name, attempt that
# node first. This will reduce confusion when nodes have the same naming
# scheme as allocations.
if allocation.name:
for i, node in enumerate(nodes):
if node.name == allocation.name:
nodes.insert(0, nodes.pop(i))
break
LOG.debug('%(count)d nodes are candidates for allocation %(uuid)s', LOG.debug('%(count)d nodes are candidates for allocation %(uuid)s',
{'count': len(nodes), 'uuid': allocation.uuid}) {'count': len(nodes), 'uuid': allocation.uuid})
return nodes return nodes

View File

@ -476,6 +476,41 @@ class DoAllocateTestCase(db_base.DbTestCase):
# All nodes are filtered out on the database level. # All nodes are filtered out on the database level.
self.assertFalse(mock_acquire.called) self.assertFalse(mock_acquire.called)
def test_name_match_first(self):
obj_utils.create_test_node(self.context,
uuid=uuidutils.generate_uuid(),
name='node-1',
power_state='power on',
resource_class='x-large',
provision_state='available')
node = obj_utils.create_test_node(self.context,
uuid=uuidutils.generate_uuid(),
name='node-2',
power_state='power on',
resource_class='x-large',
provision_state='available')
obj_utils.create_test_node(self.context,
uuid=uuidutils.generate_uuid(),
name='node-3',
power_state='power on',
resource_class='x-large',
provision_state='available')
allocation = obj_utils.create_test_allocation(self.context,
name='node-2',
resource_class='x-large')
allocations.do_allocate(self.context, allocation)
allocation = objects.Allocation.get_by_uuid(self.context,
allocation['uuid'])
self.assertIsNone(allocation['last_error'])
self.assertEqual('active', allocation['state'])
node = objects.Node.get_by_uuid(self.context, node['uuid'])
self.assertEqual(allocation['uuid'], node['instance_uuid'])
self.assertEqual(allocation['id'], node['allocation_id'])
self.assertEqual('node-2', node['name'])
class BackfillAllocationTestCase(db_base.DbTestCase): class BackfillAllocationTestCase(db_base.DbTestCase):
def test_with_associated_node(self): def test_with_associated_node(self):

View File

@ -0,0 +1,7 @@
---
features:
- |
When an allocation is being processed, the randomized candidate list is now
modified so that a node with a matching name to the allocation is moved
to the beginning of the list. This greatly increases the chance of node name
and allocation name matching in environments where the naming schemes align.