Skip to content

Commit 2050345

Browse files
committed
Implement ironic post-inspection hook to set port bios_name, pxe_enabled
1 parent 4579a65 commit 2050345

File tree

3 files changed

+130
-0
lines changed

3 files changed

+130
-0
lines changed
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
import ironic.objects
2+
3+
4+
def ironic_ports_for_node(context, node_id: str) -> list:
5+
return ironic.objects.Port.list_by_node_id(context, node_id)
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
from ironic.drivers.modules.inspector.hooks import base
2+
from oslo_log import log as logging
3+
4+
from ironic_understack.ironic_wrapper import ironic_ports_for_node
5+
6+
LOG = logging.getLogger(__name__)
7+
8+
9+
class PortBiosNameHook(base.InspectionHook):
10+
"""Set port.extra.bios_name and pxe_enabled fields from redfish data."""
11+
12+
# "ports" creates baremetal ports for each physical NIC, be sure to run this
13+
# first because we will only be updating ports that already exist:
14+
dependencies = ["ports"]
15+
16+
def __call__(self, task, inventory, plugin_data):
17+
"""Populate the baremetal_port.extra.bios_name attribute."""
18+
inspected_interfaces = inventory.get("interfaces")
19+
if not inspected_interfaces:
20+
LOG.error("No interfaces in inventory for node %s", task.node.uuid)
21+
return
22+
23+
interface_names = {
24+
i["mac_address"].upper(): i["name"] for i in inspected_interfaces
25+
}
26+
27+
pxe_interface = _pxe_interface_name(inspected_interfaces)
28+
29+
for baremetal_port in ironic_ports_for_node(task.context, task.node.id):
30+
mac = baremetal_port.address.upper()
31+
required_bios_name = interface_names.get(mac)
32+
extra = baremetal_port.extra
33+
current_bios_name = extra.get("bios_name")
34+
35+
if current_bios_name != required_bios_name:
36+
LOG.info(
37+
"Port %(mac)s updating bios_name from %(old)s to %(new)s",
38+
{"mac": mac, "old": current_bios_name, "new": required_bios_name},
39+
)
40+
41+
if required_bios_name:
42+
extra["bios_name"] = required_bios_name
43+
else:
44+
extra.pop("bios_name", None)
45+
46+
baremetal_port.extra = extra
47+
baremetal_port.save()
48+
49+
required_pxe = required_bios_name == pxe_interface
50+
if baremetal_port.pxe_enabled != required_pxe:
51+
LOG.info("Port %s changed pxe_enabled to %s", mac, required_pxe)
52+
baremetal_port.pxe_enabled = required_pxe
53+
baremetal_port.save()
54+
55+
56+
def _pxe_interface_name(inspected_interfaces: list[dict]) -> str:
57+
"""Use a heuristic to determine our default interface for PXE."""
58+
names = sorted(i["name"] for i in inspected_interfaces)
59+
for prefix in ["NIC.Integrated", "NIC.Slot"]:
60+
for name in names:
61+
if name.startswith(prefix):
62+
return name
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import logging
2+
3+
from oslo_utils import uuidutils
4+
5+
from ironic_understack.port_bios_name_hook import PortBiosNameHook
6+
7+
_INVENTORY = {
8+
"memory": {"physical_mb": 98304},
9+
"interfaces": [
10+
{"mac_address": "11:11:11:11:11:11", "name": "NIC.Integrated.1-1"},
11+
{"mac_address": "22:22:22:22:22:22", "name": "NIC.Integrated.1-2"},
12+
],
13+
}
14+
15+
16+
def test_adding_bios_name(mocker, caplog):
17+
caplog.set_level(logging.DEBUG)
18+
19+
node_uuid = uuidutils.generate_uuid()
20+
mock_context = mocker.Mock()
21+
mock_node = mocker.Mock(id=1234)
22+
mock_task = mocker.Mock(node=mock_node, context=mock_context)
23+
mock_port = mocker.Mock(
24+
uuid=uuidutils.generate_uuid(),
25+
node_id=node_uuid,
26+
address="11:11:11:11:11:11",
27+
extra={},
28+
)
29+
30+
mocker.patch(
31+
"ironic_understack.port_bios_name_hook.ironic_ports_for_node",
32+
return_value=[mock_port],
33+
)
34+
35+
PortBiosNameHook().__call__(mock_task, _INVENTORY, {})
36+
37+
assert mock_port.extra == {"bios_name": "NIC.Integrated.1-1"}
38+
mock_port.save.assert_called()
39+
40+
41+
def test_removing_bios_name(mocker, caplog):
42+
caplog.set_level(logging.DEBUG)
43+
44+
node_uuid = uuidutils.generate_uuid()
45+
mock_context = mocker.Mock()
46+
mock_node = mocker.Mock(id=1234)
47+
mock_task = mocker.Mock(node=mock_node, context=mock_context)
48+
mock_port = mocker.Mock(
49+
uuid=uuidutils.generate_uuid(),
50+
node_id=node_uuid,
51+
address="33:33:33:33:33:33",
52+
extra={"bios_name": "old_name_no_longer_valid"},
53+
)
54+
55+
mocker.patch(
56+
"ironic_understack.port_bios_name_hook.ironic_ports_for_node",
57+
return_value=[mock_port],
58+
)
59+
60+
PortBiosNameHook().__call__(mock_task, _INVENTORY, {})
61+
62+
assert "bios_name" not in mock_port.extra
63+
mock_port.save.assert_called()

0 commit comments

Comments
 (0)