Skip to content

Commit c6ac876

Browse files
committed
Do initial inspection using virtual-media boot instead of PXE/HTTP
Virtual-media is slightly more robust that normal PXE/HTTP boot for the initial inspection, because at that time we don't have the full picture of the network cabling and so we can't make the exact final settings for the BIOS and for Ironic.
1 parent c0cc230 commit c6ac876

8 files changed

Lines changed: 301 additions & 588 deletions

File tree

Lines changed: 45 additions & 90 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from typing import ClassVar
22

3-
from ironic.common import exception
43
from ironic.drivers.modules.inspector.hooks import base
54
from oslo_log import log as logging
65

@@ -14,13 +13,35 @@
1413
class PortBiosNameHook(base.InspectionHook):
1514
"""Set bios_name, pxe_enabled, local_link_connection and physical_network.
1615
17-
Populates extra.bios_name and port name from inspection inventory, then
18-
determines PXE-enabled ports from node.extra["enrolled_pxe_ports"]
19-
(populated during enrolment). If that data is unavailable, all
20-
NIC.Integrated.* and NIC.Slot.* ports are treated as PXE-enabled.
16+
Runs after the "ports" hook has created a baremetal port for each NIC in the
17+
box.
2118
22-
PXE ports get pxe_enabled=True plus placeholder physical_network and
23-
local_link_connection values that neutron requires.
19+
We set the `name` and `extra.bios_name` for each port using the BIOS names
20+
in the inventory data that was collected by redfish inspection.
21+
22+
If this node has no PXE ports at all, then we assume that this box has just
23+
been enrolled and has not yet undergone a successful agent inspection.
24+
Agent inspection will be the next step, and therefore we need to set up the
25+
bare minimum that is required by Ironic/Neutron to prepare to boot the IPA
26+
image.
27+
28+
Even though PXE is not in use, the provisioning network is still required,
29+
because that is how the agent communicates with Ironic. Neutron wants to
30+
make a port in the provisioning network, and it will error out unless it can
31+
find a suitable baremetal port.
32+
33+
We choose one arbitrary baremetal port and we populate its attributes with
34+
dummy data to enable Ironic/Neutron to do an IPA boot:
35+
36+
- pxe_enabled=True
37+
- physical_network="enrol" (placeholder value)
38+
- local_link_connection set to dummy data as placeholder
39+
40+
Note that this only works because neutron is not completely controlling the
41+
DHCP server, so it doesn't matter if we choose the wrong port. If this
42+
situation changes then we would need to configure all possible NICs with
43+
placeholder data, which would result in a configuration for every single
44+
NIC.
2445
"""
2546

2647
dependencies: ClassVar[list[str]] = ["ports"]
@@ -36,8 +57,9 @@ def __call__(self, task, inventory, plugin_data):
3657
}
3758

3859
ports = list(ironic_ports_for_node(task.context, task.node.id))
39-
initial_enroll = _is_initial_enroll(ports)
40-
pxe_nics = _enrolled_pxe_nics(task) if initial_enroll else []
60+
if not ports:
61+
LOG.error("No baremetal ports in Ironic for node %s", task.node.uuid)
62+
return
4163

4264
for baremetal_port in ports:
4365
mac = baremetal_port.address.upper()
@@ -46,68 +68,23 @@ def __call__(self, task, inventory, plugin_data):
4668
_set_port_extra(baremetal_port, mac, bios_name)
4769
_set_port_name(baremetal_port, mac, bios_name, task.node.name)
4870

49-
if initial_enroll:
50-
is_pxe = bios_name is not None and any(
51-
pxe_nic.startswith(bios_name) or bios_name.startswith(pxe_nic)
52-
for pxe_nic in pxe_nics
53-
)
54-
_set_port_pxe_enabled(baremetal_port, mac, bios_name, is_pxe)
55-
else:
56-
is_pxe = baremetal_port.pxe_enabled
57-
58-
if initial_enroll and is_pxe:
59-
_set_port_physical_network(baremetal_port, mac)
60-
_set_port_local_link_connection(baremetal_port, mac)
61-
62-
_assert_has_pxe_port(task, ports)
71+
if not any(port.pxe_enabled for port in ports):
72+
_set_port_pxe_placeholder(ports[0])
6373

6474

65-
def _enrolled_pxe_nics(task) -> list[str]:
66-
"""Read enrolled PXE NIC names from node.extra, or use broad prefixes."""
67-
enrolled_pxe_nics = task.node.extra.get("enrolled_pxe_ports")
68-
if enrolled_pxe_nics:
69-
LOG.info(
70-
"Set node %s pxe flag on interfaces from extra.enrolled_pxe_ports %s",
71-
task.node.uuid,
72-
enrolled_pxe_nics,
73-
)
74-
return enrolled_pxe_nics
75-
else:
76-
LOG.warning(
77-
"Node %s extra.enrolled_pxe_ports is missing, "
78-
"setting pxe flag on all interfaces starting %s.",
79-
task.node.uuid,
80-
PXE_BIOS_NAME_PREFIXES,
81-
)
82-
return PXE_BIOS_NAME_PREFIXES
83-
84-
85-
def _is_initial_enroll(ports) -> bool:
86-
return all(
87-
not port.physical_network or "enrol" in port.physical_network for port in ports
75+
def _set_port_pxe_placeholder(baremetal_port):
76+
LOG.info(
77+
"Populating port %s with placeholder PXE data to support enrol.",
78+
baremetal_port.address,
8879
)
89-
90-
91-
def _set_port_pxe_enabled(baremetal_port, mac, bios_name, is_pxe):
92-
if baremetal_port.pxe_enabled != is_pxe:
93-
LOG.info(
94-
"Port %s (%s) pxe_enabled %s -> %s",
95-
mac,
96-
bios_name,
97-
baremetal_port.pxe_enabled,
98-
is_pxe,
99-
)
100-
baremetal_port.pxe_enabled = is_pxe
101-
baremetal_port.save()
102-
103-
104-
def _assert_has_pxe_port(task, ports):
105-
if any(port.pxe_enabled for port in ports):
106-
return
107-
108-
msg = f"No PXE-enabled ports found for node {task.node.uuid}"
109-
LOG.error(msg)
110-
raise exception.InvalidNodeInventory(node=task.node.uuid, reason=msg)
80+
baremetal_port.pxe_enabled = True
81+
baremetal_port.physical_network = "enrol"
82+
baremetal_port.local_link_connection = {
83+
"port_id": "None",
84+
"switch_id": "00:00:00:00:00:00",
85+
"switch_info": "None",
86+
}
87+
baremetal_port.save()
11188

11289

11390
def _set_port_extra(baremetal_port, mac, required_bios_name):
@@ -140,25 +117,3 @@ def _set_port_name(baremetal_port, mac, required_bios_name, node_name):
140117
)
141118
baremetal_port.name = required_port_name
142119
baremetal_port.save()
143-
144-
145-
def _set_port_physical_network(baremetal_port, mac):
146-
if not baremetal_port.physical_network:
147-
LOG.info("Port %s changing physical_network from None to 'enrol'", mac)
148-
baremetal_port.physical_network = "enrol"
149-
baremetal_port.save()
150-
151-
152-
def _set_port_local_link_connection(baremetal_port, mac):
153-
if not baremetal_port.local_link_connection:
154-
baremetal_port.local_link_connection = {
155-
"port_id": "None",
156-
"switch_id": "00:00:00:00:00:00",
157-
"switch_info": "None",
158-
}
159-
LOG.info(
160-
"Port %s changing local_link_connection from None to %s",
161-
mac,
162-
baremetal_port.local_link_connection,
163-
)
164-
baremetal_port.save()

python/understack-workflows/tests/test_bmc_bios.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,11 @@ def test_update_dell_bios_settings_skips_patch_when_desired_values_are_pending(m
1717
"IPMILan.1.Enable": "Enabled",
1818
}
1919
},
20-
{"Attributes": bmc_bios.required_bios_settings(["NIC.Embedded.1-1-1"])},
20+
{"Attributes": bmc_bios.required_bios_settings("NIC.Embedded.1-1-1")},
2121
]
2222
patch_bios_settings = mocker.patch.object(bmc_bios, "patch_bios_settings")
2323

24-
result = bmc_bios.update_dell_bios_settings(bmc, ["NIC.Embedded.1-1-1"])
24+
result = bmc_bios.update_dell_bios_settings(bmc, "NIC.Embedded.1-1-1")
2525

2626
assert result == {}
2727
patch_bios_settings.assert_not_called()
@@ -54,7 +54,7 @@ def test_update_dell_bios_settings_only_patches_settings_not_already_pending(moc
5454
]
5555
patch_bios_settings = mocker.patch.object(bmc_bios, "patch_bios_settings")
5656

57-
result = bmc_bios.update_dell_bios_settings(bmc, ["NIC.Embedded.1-1-1"])
57+
result = bmc_bios.update_dell_bios_settings(bmc, "NIC.Embedded.1-1-1")
5858

5959
assert result == {
6060
"HttpDev1EnDis": "Enabled",

0 commit comments

Comments
 (0)