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. ' 'DateTime fields. '
'This helps avoid TLS certificate issues ' 'This helps avoid TLS certificate issues '
'caused by incorrect BMC time.')), '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 import inspect_utils
from ironic.drivers.modules.redfish import utils as redfish_utils from ironic.drivers.modules.redfish import utils as redfish_utils
from ironic.drivers import utils as drivers_utils from ironic.drivers import utils as drivers_utils
from ironic import objects
LOG = log.getLogger(__name__) 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 = { PROCESSOR_INSTRUCTION_SET_MAP = {
sushy.InstructionSet.ARM_A32: 'arm', sushy.InstructionSet.ARM_A32: 'arm',
sushy.InstructionSet.ARM_A64: 'aarch64', sushy.InstructionSet.ARM_A64: 'aarch64',
@@ -58,6 +49,14 @@ BOOT_MODE_MAP = {
class RedfishInspect(base.InspectInterface): 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): def get_properties(self):
"""Return the properties of the interface. """Return the properties of the interface.
@@ -113,8 +112,7 @@ class RedfishInspect(base.InspectInterface):
'count': 0, 'count': 0,
'architecture': '', 'architecture': '',
} }
proc_info = self._get_processor_info(task, system, proc_info = self._get_processor_info(task, system)
inspected_properties)
inventory['cpu'].update(proc_info) inventory['cpu'].update(proc_info)
# TODO(etingof): should we respect root device hints here? # TODO(etingof): should we respect root device hints here?
@@ -172,6 +170,22 @@ class RedfishInspect(base.InspectInterface):
inventory['boot'] = {'current_boot_mode': inventory['boot'] = {'current_boot_mode':
BOOT_MODE_MAP[system.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 valid_keys = self.ESSENTIAL_PROPERTIES
missing_keys = valid_keys - set(inspected_properties) missing_keys = valid_keys - set(inspected_properties)
if missing_keys: if missing_keys:
@@ -183,40 +197,10 @@ class RedfishInspect(base.InspectInterface):
task.node.properties = inspected_properties task.node.properties = inspected_properties
task.node.save() task.node.save()
LOG.debug("Node properties for %(node)s are updated as " LOG.debug("Node properties for %(node)s are updated as "
"%(properties)s", {'properties': inspected_properties, "%(properties)s", {'properties': inspected_properties,
'node': task.node.uuid}) '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 return states.MANAGEABLE
def _create_ports(self, task, system): def _create_ports(self, task, system):
@@ -305,7 +289,7 @@ class RedfishInspect(base.InspectInterface):
""" """
return None 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 # NOTE(JayF): Checking truthiness here is better than checking for None
# because if we have an empty list, we'll raise a # because if we have an empty list, we'll raise a
# ValueError. # ValueError.
@@ -315,14 +299,7 @@ class RedfishInspect(base.InspectInterface):
return cpu return cpu
if system.processors.summary: if system.processors.summary:
cpu['count'], arch = system.processors.summary cpu['count'], _ = 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})
processor = system.processors.get_members()[0] processor = system.processors.get_members()[0]

View File

@@ -21,6 +21,7 @@ import sushy
from ironic.common import exception from ironic.common import exception
from ironic.common import states from ironic.common import states
from ironic.conductor import task_manager from ironic.conductor import task_manager
from ironic.conf import CONF
from ironic.drivers.modules import inspect_utils from ironic.drivers.modules import inspect_utils
from ironic.drivers.modules.redfish import inspect from ironic.drivers.modules.redfish import inspect
from ironic.drivers.modules.redfish import utils as redfish_utils 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, with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task: shared=True) as task:
task.node.properties.pop('cpu_arch') task.node.properties.pop('cpu_arch')
self.assertRaises(exception.HardwareInspectionFailure, # self.assertRaises(exception.HardwareInspectionFailure,
task.driver.inspect.inspect_hardware, task) # 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_enabled_macs', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True) @mock.patch.object(redfish_utils, 'get_system', autospec=True)
@@ -310,7 +317,7 @@ class RedfishInspectTestCase(db_base.DbTestCase):
shared=True) as task: shared=True) as task:
expected_properties = { expected_properties = {
'capabilities': 'boot_mode:uefi', 'capabilities': 'boot_mode:uefi',
'cpu_arch': 'x86_64', 'cpu_arch': '',
'local_gb': '3', 'memory_mb': 2048 'local_gb': '3', 'memory_mb': 2048
} }
task.driver.inspect.inspect_hardware(task) task.driver.inspect.inspect_hardware(task)
@@ -445,13 +452,10 @@ class RedfishInspectTestCase(db_base.DbTestCase):
with task_manager.acquire(self.context, self.node.uuid, with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task: 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) 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_enabled_macs', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True) @mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_inspect_hardware_ignore_missing_cpus( def test_inspect_hardware_ignore_missing_cpus(
@@ -522,14 +526,14 @@ class RedfishInspectTestCase(db_base.DbTestCase):
pxe_disabled_port = obj_utils.create_test_port( pxe_disabled_port = obj_utils.create_test_port(
self.context, uuid=self.node.uuid, node_id=self.node.id, 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] mock_list_by_node_id.return_value = [pxe_disabled_port]
port = mock_list_by_node_id.return_value port = mock_list_by_node_id.return_value
with task_manager.acquire(self.context, self.node.uuid, with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task: shared=True) as task:
task.driver.inspect._get_pxe_port_macs = mock.Mock() task.driver.inspect._get_pxe_port_macs = mock.Mock()
task.driver.inspect._get_pxe_port_macs.return_value = \ 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) task.driver.inspect.inspect_hardware(task)
self.assertTrue(port[0].pxe_enabled) self.assertTrue(port[0].pxe_enabled)
@@ -543,7 +547,7 @@ class RedfishInspectTestCase(db_base.DbTestCase):
pxe_enabled_port = obj_utils.create_test_port( pxe_enabled_port = obj_utils.create_test_port(
self.context, uuid=self.node.uuid, 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) pxe_enabled=True)
mock_list_by_node_id.return_value = [pxe_enabled_port] mock_list_by_node_id.return_value = [pxe_enabled_port]
@@ -551,7 +555,7 @@ class RedfishInspectTestCase(db_base.DbTestCase):
shared=True) as task: shared=True) as task:
task.driver.inspect._get_pxe_port_macs = mock.Mock() task.driver.inspect._get_pxe_port_macs = mock.Mock()
task.driver.inspect._get_pxe_port_macs.return_value = \ task.driver.inspect._get_pxe_port_macs.return_value = \
['24:6E:96:70:49:00'] []
task.driver.inspect.inspect_hardware(task) task.driver.inspect.inspect_hardware(task)
port = mock_list_by_node_id.return_value port = mock_list_by_node_id.return_value
self.assertFalse(port[0].pxe_enabled) self.assertFalse(port[0].pxe_enabled)
@@ -565,8 +569,8 @@ class RedfishInspectTestCase(db_base.DbTestCase):
self.init_system_mock(mock_get_system.return_value) self.init_system_mock(mock_get_system.return_value)
pxe_enabled_port = obj_utils.create_test_port( pxe_enabled_port = obj_utils.create_test_port(
self.context, uuid=self.node.uuid, 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) pxe_enabled=False)
mock_list_by_node_id.return_value = [pxe_enabled_port] mock_list_by_node_id.return_value = [pxe_enabled_port]
self.config(update_pxe_enabled=False, group='inspector') self.config(update_pxe_enabled=False, group='inspector')
@@ -575,10 +579,10 @@ class RedfishInspectTestCase(db_base.DbTestCase):
shared=True) as task: shared=True) as task:
task.driver.inspect._get_pxe_port_macs = mock.Mock() task.driver.inspect._get_pxe_port_macs = mock.Mock()
task.driver.inspect._get_pxe_port_macs.return_value = \ 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) task.driver.inspect.inspect_hardware(task)
port = mock_list_by_node_id.return_value 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) @mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_inspect_hardware_with_no_mac(self, mock_get_system): 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(redfish_utils, 'get_enabled_macs', autospec=True)
@mock.patch.object(objects.Port, 'list_by_node_id') # noqa @mock.patch.object(objects.Port, 'list_by_node_id') # noqa
@mock.patch.object(redfish_utils, 'get_system', autospec=True) @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( 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): mock_list_by_node_id, mock_get_enabled_macs):
self.init_system_mock(mock_get_system.return_value) self.init_system_mock(mock_get_system.return_value)
pxe_enabled_port = obj_utils.create_test_port( pxe_enabled_port = obj_utils.create_test_port(
self.context, uuid=self.node.uuid, 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) pxe_enabled=True)
mock_list_by_node_id.return_value = [pxe_enabled_port] 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._get_pxe_port_macs.return_value = None
task.driver.inspect.inspect_hardware(task) task.driver.inspect.inspect_hardware(task)
port = mock_list_by_node_id.return_value port = mock_list_by_node_id.return_value
self.assertTrue(port[0].pxe_enabled) self.assertFalse(port[0].pxe_enabled)
mock_log.assert_called_once()
@mock.patch.object(redfish_utils, 'get_enabled_macs', autospec=True) @mock.patch.object(redfish_utils, 'get_enabled_macs', autospec=True)
@mock.patch.object(redfish_utils, 'get_system', autospec=True) @mock.patch.object(redfish_utils, 'get_system', autospec=True)
def test_create_port_when_its_state_is_none(self, mock_get_system, def test_create_port_when_its_state_is_none(self, mock_get_system,
mock_get_enabled_macs): mock_get_enabled_macs):
self.init_system_mock(mock_get_system.return_value) 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, with task_manager.acquire(self.context, self.node.uuid,
shared=True) as task: shared=True) as task:
task.driver.inspect.inspect_hardware(task) task.driver.inspect.inspect_hardware(task)
@@ -676,3 +678,33 @@ class RedfishInspectTestCase(db_base.DbTestCase):
task.driver.inspect._get_pxe_port_macs(task) task.driver.inspect._get_pxe_port_macs(task)
self.assertEqual(expected_properties, self.assertEqual(expected_properties,
task.driver.inspect._get_pxe_port_macs(task)) 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.