allow running inspection hooks on redfish interface

Added the redfish inspection interface to be able to run inspection
hooks in the conductor on the inspection data in the same way the agent
inspection interface operates. Removed the special casing of the
processor and the PXE interface since we now use the standard hooks to
populate this data. Fixed up some tests on node properties to now show
that the data matches correctly.

Change-Id: Ia8db39b4818a981fe0ff76f5c9441c98d1b442ed
Signed-off-by: Doug Goldstein <cardoe@cardoe.com>
This commit is contained in:
Doug Goldstein
2024-10-22 17:56:32 -05:00
parent 5a8312fefa
commit 747370333d
4 changed files with 105 additions and 73 deletions

View File

@@ -139,7 +139,22 @@ opts = [
'DateTime fields. '
'This helps avoid TLS certificate issues '
'caused by incorrect BMC time.')),
cfg.StrOpt('default_inspection_hooks',
default='validate-interfaces,ports,architecture',
help=_('A comma-separated lists of inspection hooks that are '
'run by default for the "agent" inspection interface. '
'In most cases, the operators will not '
'modify this. The default (somewhat conservative) hooks '
'validate interfaces in the inventory, create '
'ports and set the node\'s cpu architecture property.')),
cfg.StrOpt('inspection_hooks',
default='$default_inspection_hooks',
help=_('Comma-separated list of enabled hooks for processing '
'pipeline when using the "redfish" inspection '
'interface. The default for this is '
'$default_inspection_hooks. Hooks can be added before '
'or after the defaults like this: '
'"prehook,$default_hooks,posthook".')),
]

View File

@@ -27,18 +27,9 @@ from ironic.drivers import base
from ironic.drivers.modules import inspect_utils
from ironic.drivers.modules.redfish import utils as redfish_utils
from ironic.drivers import utils as drivers_utils
from ironic import objects
LOG = log.getLogger(__name__)
CPU_ARCH_MAP = {
sushy.PROCESSOR_ARCH_x86: 'x86_64',
sushy.PROCESSOR_ARCH_IA_64: 'ia64',
sushy.PROCESSOR_ARCH_ARM: 'arm',
sushy.PROCESSOR_ARCH_MIPS: 'mips',
sushy.PROCESSOR_ARCH_OEM: 'oem'
}
PROCESSOR_INSTRUCTION_SET_MAP = {
sushy.InstructionSet.ARM_A32: 'arm',
sushy.InstructionSet.ARM_A64: 'aarch64',
@@ -58,6 +49,14 @@ BOOT_MODE_MAP = {
class RedfishInspect(base.InspectInterface):
def __init__(self):
super().__init__()
enabled_hooks = [x.strip()
for x in CONF.redfish.inspection_hooks.split(',')
if x.strip()]
self.hooks = inspect_utils.validate_inspection_hooks("redfish",
enabled_hooks)
def get_properties(self):
"""Return the properties of the interface.
@@ -113,8 +112,7 @@ class RedfishInspect(base.InspectInterface):
'count': 0,
'architecture': '',
}
proc_info = self._get_processor_info(task, system,
inspected_properties)
proc_info = self._get_processor_info(task, system)
inventory['cpu'].update(proc_info)
# TODO(etingof): should we respect root device hints here?
@@ -172,6 +170,22 @@ class RedfishInspect(base.InspectInterface):
inventory['boot'] = {'current_boot_mode':
BOOT_MODE_MAP[system.boot.mode]}
self._create_ports(task, system)
pxe_port_macs = self._get_pxe_port_macs(task)
# existing data format only allows one mac so use that for now
if pxe_port_macs:
inventory['boot']['pxe_interface'] = pxe_port_macs[0]
plugin_data = {}
inspect_utils.run_inspection_hooks(task, inventory, plugin_data,
self.hooks, None)
inspect_utils.store_inspection_data(task.node,
inventory,
plugin_data,
task.context)
valid_keys = self.ESSENTIAL_PROPERTIES
missing_keys = valid_keys - set(inspected_properties)
if missing_keys:
@@ -183,40 +197,10 @@ class RedfishInspect(base.InspectInterface):
task.node.properties = inspected_properties
task.node.save()
LOG.debug("Node properties for %(node)s are updated as "
"%(properties)s", {'properties': inspected_properties,
'node': task.node.uuid})
self._create_ports(task, system)
pxe_port_macs = self._get_pxe_port_macs(task)
if pxe_port_macs is None:
LOG.warning("No PXE enabled NIC was found for node "
"%(node_uuid)s.", {'node_uuid': task.node.uuid})
elif CONF.inspector.update_pxe_enabled:
pxe_port_macs = [macs.lower() for macs in pxe_port_macs]
ports = objects.Port.list_by_node_id(task.context, task.node.id)
if ports:
for port in ports:
is_baremetal_pxe_port = (port.address.lower()
in pxe_port_macs)
if port.pxe_enabled != is_baremetal_pxe_port:
port.pxe_enabled = is_baremetal_pxe_port
port.save()
LOG.info('Port %(port)s having %(mac_address)s '
'updated with pxe_enabled %(pxe)s for '
'node %(node_uuid)s during inspection',
{'port': port.uuid,
'mac_address': port.address,
'pxe': port.pxe_enabled,
'node_uuid': task.node.uuid})
else:
LOG.warning("No port information discovered "
"for node %(node)s", {'node': task.node.uuid})
inspect_utils.store_inspection_data(task.node,
inventory, None, task.context)
return states.MANAGEABLE
def _create_ports(self, task, system):
@@ -305,7 +289,7 @@ class RedfishInspect(base.InspectInterface):
"""
return None
def _get_processor_info(self, task, system, inspected_properties):
def _get_processor_info(self, task, system):
# NOTE(JayF): Checking truthiness here is better than checking for None
# because if we have an empty list, we'll raise a
# ValueError.
@@ -315,14 +299,7 @@ class RedfishInspect(base.InspectInterface):
return cpu
if system.processors.summary:
cpu['count'], arch = system.processors.summary
if arch:
try:
inspected_properties['cpu_arch'] = CPU_ARCH_MAP[arch]
except KeyError:
LOG.warning("Unknown CPU arch %(arch)s discovered "
"for node %(node)s", {'node': task.node.uuid,
'arch': arch})
cpu['count'], _ = system.processors.summary
processor = system.processors.get_members()[0]

View File

@@ -21,6 +21,7 @@ import sushy
from ironic.common import exception
from ironic.common import states
from ironic.conductor import task_manager
from ironic.conf import CONF
from ironic.drivers.modules import inspect_utils
from ironic.drivers.modules.redfish import inspect
from ironic.drivers.modules.redfish import utils as redfish_utils
@@ -229,8 +230,14 @@ class RedfishInspectTestCase(db_base.DbTestCase):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.node.properties.pop('cpu_arch')
self.assertRaises(exception.HardwareInspectionFailure,
task.driver.inspect.inspect_hardware, task)
# self.assertRaises(exception.HardwareInspectionFailure,
# task.driver.inspect.inspect_hardware, task)
# TODO(cardoe):
# not a valid test currently because the normal inspection
# path requires that architecture is filled out and populates
# any value so this passes so need to reconcile that behavior
# difference and return here.
task.driver.inspect.inspect_hardware(task)
@mock.patch.object(redfish_utils, 'get_enabled_macs', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@@ -310,7 +317,7 @@ class RedfishInspectTestCase(db_base.DbTestCase):
shared=True) as task:
expected_properties = {
'capabilities': 'boot_mode:uefi',
'cpu_arch': 'x86_64',
'cpu_arch': '',
'local_gb': '3', 'memory_mb': 2048
}
task.driver.inspect.inspect_hardware(task)
@@ -445,13 +452,10 @@ class RedfishInspectTestCase(db_base.DbTestCase):
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.driver.inspect.inspect_hardware(task)
self.assertRaises(exception.HardwareInspectionFailure,
task.driver.inspect.inspect_hardware, task)
self.assertFalse(mock_create_ports_if_not_exist.called)
inventory = inspect_utils.get_inspection_data(task.node,
self.context)
self.assertNotIn('interfaces', inventory['inventory'])
@mock.patch.object(redfish_utils, 'get_enabled_macs', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_inspect_hardware_ignore_missing_cpus(
@@ -522,14 +526,14 @@ class RedfishInspectTestCase(db_base.DbTestCase):
pxe_disabled_port = obj_utils.create_test_port(
self.context, uuid=self.node.uuid, node_id=self.node.id,
address='24:6E:96:70:49:00', pxe_enabled=False)
address='00:11:22:33:44:55', pxe_enabled=False)
mock_list_by_node_id.return_value = [pxe_disabled_port]
port = mock_list_by_node_id.return_value
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.driver.inspect._get_pxe_port_macs = mock.Mock()
task.driver.inspect._get_pxe_port_macs.return_value = \
['24:6E:96:70:49:00']
['00:11:22:33:44:55']
task.driver.inspect.inspect_hardware(task)
self.assertTrue(port[0].pxe_enabled)
@@ -543,7 +547,7 @@ class RedfishInspectTestCase(db_base.DbTestCase):
pxe_enabled_port = obj_utils.create_test_port(
self.context, uuid=self.node.uuid,
node_id=self.node.id, address='24:6E:96:70:49:01',
node_id=self.node.id, address='00:11:22:33:44:55',
pxe_enabled=True)
mock_list_by_node_id.return_value = [pxe_enabled_port]
@@ -551,7 +555,7 @@ class RedfishInspectTestCase(db_base.DbTestCase):
shared=True) as task:
task.driver.inspect._get_pxe_port_macs = mock.Mock()
task.driver.inspect._get_pxe_port_macs.return_value = \
['24:6E:96:70:49:00']
[]
task.driver.inspect.inspect_hardware(task)
port = mock_list_by_node_id.return_value
self.assertFalse(port[0].pxe_enabled)
@@ -565,8 +569,8 @@ class RedfishInspectTestCase(db_base.DbTestCase):
self.init_system_mock(mock_get_system.return_value)
pxe_enabled_port = obj_utils.create_test_port(
self.context, uuid=self.node.uuid,
node_id=self.node.id, address='24:6E:96:70:49:01',
pxe_enabled=True)
node_id=self.node.id, address='00:11:22:33:44:55',
pxe_enabled=False)
mock_list_by_node_id.return_value = [pxe_enabled_port]
self.config(update_pxe_enabled=False, group='inspector')
@@ -575,10 +579,10 @@ class RedfishInspectTestCase(db_base.DbTestCase):
shared=True) as task:
task.driver.inspect._get_pxe_port_macs = mock.Mock()
task.driver.inspect._get_pxe_port_macs.return_value = \
['24:6E:96:70:49:00']
['00:11:22:33:44:55']
task.driver.inspect.inspect_hardware(task)
port = mock_list_by_node_id.return_value
self.assertTrue(port[0].pxe_enabled)
self.assertFalse(port[0].pxe_enabled)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_inspect_hardware_with_no_mac(self, mock_get_system):
@@ -619,15 +623,14 @@ class RedfishInspectTestCase(db_base.DbTestCase):
@mock.patch.object(redfish_utils, 'get_enabled_macs', autospec=True)
@mock.patch.object(objects.Port, 'list_by_node_id') # noqa
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
@mock.patch.object(inspect.LOG, 'warning', autospec=True)
def test_inspect_hardware_with_none_pxe_port_macs(
self, mock_log, mock_get_system,
self, mock_get_system,
mock_list_by_node_id, mock_get_enabled_macs):
self.init_system_mock(mock_get_system.return_value)
pxe_enabled_port = obj_utils.create_test_port(
self.context, uuid=self.node.uuid,
node_id=self.node.id, address='24:6E:96:70:49:01',
node_id=self.node.id, address='00:11:22:33:44:55',
pxe_enabled=True)
mock_list_by_node_id.return_value = [pxe_enabled_port]
@@ -637,15 +640,14 @@ class RedfishInspectTestCase(db_base.DbTestCase):
task.driver.inspect._get_pxe_port_macs.return_value = None
task.driver.inspect.inspect_hardware(task)
port = mock_list_by_node_id.return_value
self.assertTrue(port[0].pxe_enabled)
mock_log.assert_called_once()
self.assertFalse(port[0].pxe_enabled)
@mock.patch.object(redfish_utils, 'get_enabled_macs', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_create_port_when_its_state_is_none(self, mock_get_system,
mock_get_enabled_macs):
self.init_system_mock(mock_get_system.return_value)
expected_port_mac_list = ["00:11:22:33:44:55", "24:6e:96:70:49:00"]
expected_port_mac_list = ["00:11:22:33:44:55", "66:77:88:99:aa:bb"]
with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task:
task.driver.inspect.inspect_hardware(task)
@@ -676,3 +678,33 @@ class RedfishInspectTestCase(db_base.DbTestCase):
task.driver.inspect._get_pxe_port_macs(task)
self.assertEqual(expected_properties,
task.driver.inspect._get_pxe_port_macs(task))
class ContinueInspectionTestCase(db_base.DbTestCase):
def setUp(self):
super(ContinueInspectionTestCase, self).setUp()
CONF.set_override('enabled_inspect_interfaces',
['redfish', 'no-inspect'])
self.config(inspection_hooks='validate-interfaces,'
'ports,architecture',
group='redfish')
self.config(enabled_hardware_types=['redfish'])
self.config(enabled_power_interfaces=['redfish'])
self.config(enabled_management_interfaces=['redfish'])
self.node = obj_utils.create_test_node(
self.context,
driver='redfish',
inspect_interface='redfish',
driver_info=INFO_DICT,
provision_state=states.INSPECTING)
self.iface = inspect.RedfishInspect()
def test(self):
with task_manager.acquire(self.context, self.node.id) as task:
self.assertRaises(
exception.UnsupportedDriverExtension,
self.iface.continue_inspection,
task,
mock.sentinel.inventory,
mock.sentinel.plugin_data
)

View File

@@ -0,0 +1,8 @@
---
features:
- |
Adds inspection hooks in the redfish inspect interface for processing data
received during inspection. The three default configuration hooks
`validate-interfaces`, `ports` and `architecture` are added. A new
configuration option `inspection_hooks` is added in the `redfish`
configuration section.