Pick ironic nodes without VCPU set

Starting with the Pike release, reporting VCPU/memory/disk is no longer required.
However, we used VCPU to check if a node is available, so nodes without VCPU in
their properties were always ignored. This patch changes the logic to use the existing
_node_resources_unavailable call.

This change also fixes another related issue: when disk or memory are missing from
properties, the virt driver tries to report zero max_unit for them, which is not
allowed by placement.

Change-Id: I1bbfc152189252c5c45e6153695a802d17b76690
Closes-Bug: #1723423
This commit is contained in:
Dmitry Tantsur 2017-10-13 15:18:16 +02:00
parent d9212edb8f
commit b25928dc04
3 changed files with 80 additions and 50 deletions

View File

@ -731,9 +731,11 @@ class IronicDriverTestCase(test.NoDBTestCase):
expected_uuids = [n['uuid'] for n in node_dicts if n['expected']]
self.assertEqual(sorted(expected_uuids), sorted(available_nodes))
@mock.patch.object(ironic_driver.IronicDriver,
'_node_resources_unavailable', return_value=False)
@mock.patch.object(ironic_driver.IronicDriver, '_node_resource')
@mock.patch.object(ironic_driver.IronicDriver, '_node_from_cache')
def test_get_inventory_no_rc(self, mock_nfc, mock_nr):
def test_get_inventory_no_rc(self, mock_nfc, mock_nr, mock_res_unavail):
"""Ensure that when node.resource_class is missing, that we return the
legacy VCPU, MEMORY_MB and DISK_GB resources for inventory.
"""
@ -777,11 +779,14 @@ class IronicDriverTestCase(test.NoDBTestCase):
}
mock_nfc.assert_called_once_with(mock.sentinel.nodename)
mock_nr.assert_called_once_with(mock_nfc.return_value)
mock_res_unavail.assert_called_once_with(mock_nfc.return_value)
self.assertEqual(expected, result)
@mock.patch.object(ironic_driver.IronicDriver,
'_node_resources_unavailable', return_value=False)
@mock.patch.object(ironic_driver.IronicDriver, '_node_resource')
@mock.patch.object(ironic_driver.IronicDriver, '_node_from_cache')
def test_get_inventory_with_rc(self, mock_nfc, mock_nr):
def test_get_inventory_with_rc(self, mock_nfc, mock_nr, mock_res_unavail):
"""Ensure that when node.resource_class is present, that we return the
legacy VCPU, MEMORY_MB and DISK_GB resources for inventory in addition
to the custom resource class inventory record.
@ -834,11 +839,51 @@ class IronicDriverTestCase(test.NoDBTestCase):
}
mock_nfc.assert_called_once_with(mock.sentinel.nodename)
mock_nr.assert_called_once_with(mock_nfc.return_value)
mock_res_unavail.assert_called_once_with(mock_nfc.return_value)
self.assertEqual(expected, result)
@mock.patch.object(ironic_driver.IronicDriver,
'_node_resources_unavailable', return_value=False)
@mock.patch.object(ironic_driver.IronicDriver, '_node_resource')
@mock.patch.object(ironic_driver.IronicDriver, '_node_from_cache')
def test_get_inventory_with_rc_occupied(self, mock_nfc, mock_nr):
def test_get_inventory_only_rc(self, mock_nfc, mock_nr, mock_res_unavail):
"""Ensure that when node.resource_class is present, that we return the
legacy VCPU, MEMORY_MB and DISK_GB resources for inventory in addition
to the custom resource class inventory record.
"""
mock_nr.return_value = {
'vcpus': 0,
'vcpus_used': 0,
'memory_mb': 0,
'memory_mb_used': 0,
'local_gb': 0,
'local_gb_used': 0,
'resource_class': 'iron-nfv',
}
result = self.driver.get_inventory(mock.sentinel.nodename)
expected = {
'CUSTOM_IRON_NFV': {
'total': 1,
'reserved': 0,
'min_unit': 1,
'max_unit': 1,
'step_size': 1,
'allocation_ratio': 1.0,
},
}
mock_nfc.assert_called_once_with(mock.sentinel.nodename)
mock_nr.assert_called_once_with(mock_nfc.return_value)
mock_res_unavail.assert_called_once_with(mock_nfc.return_value)
self.assertEqual(expected, result)
@mock.patch.object(ironic_driver.IronicDriver,
'_node_resources_unavailable', return_value=False)
@mock.patch.object(ironic_driver.IronicDriver, '_node_resource')
@mock.patch.object(ironic_driver.IronicDriver, '_node_from_cache')
def test_get_inventory_with_rc_occupied(self, mock_nfc, mock_nr,
mock_res_unavail):
"""Ensure that when a node is used, we report the inventory matching
the consumed resources.
"""
@ -890,27 +935,19 @@ class IronicDriverTestCase(test.NoDBTestCase):
}
mock_nfc.assert_called_once_with(mock.sentinel.nodename)
mock_nr.assert_called_once_with(mock_nfc.return_value)
mock_res_unavail.assert_called_once_with(mock_nfc.return_value)
self.assertEqual(expected, result)
@mock.patch.object(ironic_driver.IronicDriver, '_node_resource')
@mock.patch.object(ironic_driver.IronicDriver,
'_node_resources_unavailable', return_value=True)
@mock.patch.object(ironic_driver.IronicDriver, '_node_from_cache')
def test_get_inventory_disabled_node(self, mock_nfc, mock_nr):
"""Ensure that when vcpus == 0 (which happens when a node is disabled),
that get_inventory() returns an empty dict.
def test_get_inventory_disabled_node(self, mock_nfc, mock_res_unavail):
"""Ensure that when a node is disabled, that get_inventory() returns
an empty dict.
"""
mock_nr.return_value = {
'vcpus': 0,
'vcpus_used': 0,
'memory_mb': 0,
'memory_mb_used': 0,
'local_gb': 0,
'local_gb_used': 0,
'resource_class': None,
}
result = self.driver.get_inventory(mock.sentinel.nodename)
mock_nfc.assert_called_once_with(mock.sentinel.nodename)
mock_nr.assert_called_once_with(mock_nfc.return_value)
mock_res_unavail.assert_called_once_with(mock_nfc.return_value)
self.assertEqual({}, result)
@mock.patch.object(FAKE_CLIENT.node, 'get')

View File

@ -754,16 +754,13 @@ class IronicDriver(virt_driver.ComputeDriver):
"""
# nodename is the ironic node's UUID.
node = self._node_from_cache(nodename)
info = self._node_resource(node)
# TODO(jaypipes): Completely remove the reporting of VCPU, MEMORY_MB,
# and DISK_GB resource classes in early Queens when Ironic nodes will
# *always* return the custom resource class that represents the
# baremetal node class in an atomic, singular unit.
if info['vcpus'] == 0:
# NOTE(jaypipes): The driver can return 0-valued vcpus when the
# node is "disabled". In the future, we should detach inventory
# accounting from the concept of a node being disabled or not. The
# two things don't really have anything to do with each other.
if self._node_resources_unavailable(node):
# TODO(dtantsur): report resources as reserved instead of reporting
# an empty inventory
LOG.debug('Node %(node)s is not ready for a deployment, '
'reporting an empty inventory for it. Node\'s '
'provision state is %(prov)s, power state is '
@ -772,32 +769,23 @@ class IronicDriver(virt_driver.ComputeDriver):
'power': node.power_state, 'maint': node.maintenance})
return {}
result = {
obj_fields.ResourceClass.VCPU: {
'total': info['vcpus'],
'reserved': 0,
'min_unit': 1,
'max_unit': info['vcpus'],
'step_size': 1,
'allocation_ratio': 1.0,
},
obj_fields.ResourceClass.MEMORY_MB: {
'total': info['memory_mb'],
'reserved': 0,
'min_unit': 1,
'max_unit': info['memory_mb'],
'step_size': 1,
'allocation_ratio': 1.0,
},
obj_fields.ResourceClass.DISK_GB: {
'total': info['local_gb'],
'reserved': 0,
'min_unit': 1,
'max_unit': info['local_gb'],
'step_size': 1,
'allocation_ratio': 1.0,
},
}
info = self._node_resource(node)
result = {}
for rc, field in [(obj_fields.ResourceClass.VCPU, 'vcpus'),
(obj_fields.ResourceClass.MEMORY_MB, 'memory_mb'),
(obj_fields.ResourceClass.DISK_GB, 'local_gb')]:
# NOTE(dtantsur): any of these fields can be zero starting with
# the Pike release.
if info[field]:
result[rc] = {
'total': info[field],
'reserved': 0,
'min_unit': 1,
'max_unit': info[field],
'step_size': 1,
'allocation_ratio': 1.0,
}
rc_name = info.get('resource_class')
if rc_name is not None:
# TODO(jaypipes): Raise an exception in Queens if Ironic doesn't

View File

@ -0,0 +1,5 @@
---
fixes:
- |
Fixes a bug preventing ironic nodes without VCPUs, memory or disk in their
properties from being picked by nova.