Skip to content

Commit 82c1b4e

Browse files
committed
Preserve PXE flags after enrolment
1 parent 3abab46 commit 82c1b4e

2 files changed

Lines changed: 100 additions & 27 deletions

File tree

python/ironic-understack/ironic_understack/port_bios_name_hook.py

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

3+
from ironic.common import exception
34
from ironic.drivers.modules.inspector.hooks import base
45
from oslo_log import log as logging
56

@@ -34,35 +35,32 @@ def __call__(self, task, inventory, plugin_data):
3435
i["mac_address"].upper(): i["name"] for i in inspected_interfaces
3536
}
3637

37-
pxe_nics = _enrolled_pxe_nics(task)
38+
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 []
3841

39-
for baremetal_port in ironic_ports_for_node(task.context, task.node.id):
42+
for baremetal_port in ports:
4043
mac = baremetal_port.address.upper()
4144
bios_name = interface_names.get(mac)
4245

4346
_set_port_extra(baremetal_port, mac, bios_name)
4447
_set_port_name(baremetal_port, mac, bios_name, task.node.name)
4548

46-
is_pxe = bios_name is not None and any(
47-
pxe_nic.startswith(bios_name) or bios_name.startswith(pxe_nic)
48-
for pxe_nic in pxe_nics
49-
)
50-
51-
if baremetal_port.pxe_enabled != is_pxe:
52-
LOG.info(
53-
"Port %s (%s) pxe_enabled %s -> %s",
54-
mac,
55-
bios_name,
56-
baremetal_port.pxe_enabled,
57-
is_pxe,
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
5853
)
59-
baremetal_port.pxe_enabled = is_pxe
60-
baremetal_port.save()
54+
_set_port_pxe_enabled(baremetal_port, mac, bios_name, is_pxe)
55+
else:
56+
is_pxe = baremetal_port.pxe_enabled
6157

62-
if is_pxe:
58+
if initial_enroll and is_pxe:
6359
_set_port_physical_network(baremetal_port, mac)
6460
_set_port_local_link_connection(baremetal_port, mac)
6561

62+
_assert_has_pxe_port(task, ports)
63+
6664

6765
def _enrolled_pxe_nics(task) -> list[str]:
6866
"""Read enrolled PXE NIC names from node.extra, or use broad prefixes."""
@@ -84,6 +82,34 @@ def _enrolled_pxe_nics(task) -> list[str]:
8482
return PXE_BIOS_NAME_PREFIXES
8583

8684

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
88+
)
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)
111+
112+
87113
def _set_port_extra(baremetal_port, mac, required_bios_name):
88114
extra = baremetal_port.extra
89115
current_bios_name = extra.get("bios_name")

python/ironic-understack/ironic_understack/tests/test_port_bios_name_hook.py

Lines changed: 57 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
import logging
22

3+
import pytest
4+
from ironic.common import exception
35
from oslo_utils import uuidutils
46

57
from ironic_understack.port_bios_name_hook import PortBiosNameHook
@@ -137,6 +139,7 @@ def test_retaining_physical_network(mocker, caplog):
137139
port = _make_port(
138140
mocker,
139141
"11:11:11:11:11:11",
142+
pxe_enabled=True,
140143
physical_network="previous_value",
141144
local_link_connection={
142145
"port_id": "Ethernet1/19",
@@ -156,13 +159,23 @@ def test_retaining_physical_network(mocker, caplog):
156159
assert port.local_link_connection["port_id"] == "Ethernet1/19"
157160

158161

159-
def test_clears_pxe_on_previously_enabled_port(mocker, caplog):
160-
"""Port that was pxe_enabled but no longer matches gets cleared."""
162+
def test_preserves_pxe_on_post_enroll_ports(mocker, caplog):
163+
"""Post-enrol inspection keeps the existing PXE decision."""
161164
caplog.set_level(logging.DEBUG)
162165
task = _make_task(mocker, enrolled_pxe_ports=["NIC.Integrated.1-2-1"])
163166

164-
port1 = _make_port(mocker, "11:11:11:11:11:11", pxe_enabled=True)
165-
port2 = _make_port(mocker, "22:22:22:22:22:22")
167+
port1 = _make_port(
168+
mocker,
169+
"11:11:11:11:11:11",
170+
pxe_enabled=True,
171+
physical_network="f20-1-network",
172+
)
173+
port2 = _make_port(
174+
mocker,
175+
"22:22:22:22:22:22",
176+
pxe_enabled=False,
177+
physical_network="f20-1-network",
178+
)
166179

167180
mocker.patch(
168181
"ironic_understack.port_bios_name_hook.ironic_ports_for_node",
@@ -171,28 +184,62 @@ def test_clears_pxe_on_previously_enabled_port(mocker, caplog):
171184

172185
PortBiosNameHook().__call__(task, _INVENTORY, {})
173186

174-
assert port1.pxe_enabled is False
175-
assert port2.pxe_enabled is True
187+
assert port1.pxe_enabled is True
188+
assert port2.pxe_enabled is False
189+
190+
191+
def test_errors_if_post_enroll_ports_have_no_pxe_enabled_port(mocker, caplog):
192+
"""Post-enrol inspection fails if no PXE-enabled port exists."""
193+
caplog.set_level(logging.DEBUG)
194+
task = _make_task(mocker, enrolled_pxe_ports=["NIC.Integrated.1-1-1"])
195+
196+
port1 = _make_port(
197+
mocker,
198+
"11:11:11:11:11:11",
199+
pxe_enabled=False,
200+
physical_network="f20-1-network",
201+
)
202+
port2 = _make_port(
203+
mocker,
204+
"22:22:22:22:22:22",
205+
pxe_enabled=False,
206+
physical_network="f20-1-network",
207+
)
208+
209+
mocker.patch(
210+
"ironic_understack.port_bios_name_hook.ironic_ports_for_node",
211+
return_value=[port1, port2],
212+
)
213+
214+
with pytest.raises(exception.InvalidNodeInventory, match="No PXE-enabled ports"):
215+
PortBiosNameHook().__call__(task, _INVENTORY, {})
176216

177217

178218
def test_removing_bios_name(mocker, caplog):
179219
"""Port with unknown MAC gets bios_name removed."""
180220
caplog.set_level(logging.DEBUG)
181221
task = _make_task(mocker, enrolled_pxe_ports=["NIC.Integrated.1-1-1"])
182222

183-
port = _make_port(
223+
unknown_port = _make_port(
184224
mocker,
185225
"33:33:33:33:33:33",
186226
extra={"bios_name": "old_name_no_longer_valid"},
187227
name="original-name",
228+
physical_network="f20-1-network",
229+
)
230+
pxe_port = _make_port(
231+
mocker,
232+
"11:11:11:11:11:11",
233+
pxe_enabled=True,
234+
physical_network="f20-1-network",
188235
)
189236

190237
mocker.patch(
191238
"ironic_understack.port_bios_name_hook.ironic_ports_for_node",
192-
return_value=[port],
239+
return_value=[unknown_port, pxe_port],
193240
)
194241

195242
PortBiosNameHook().__call__(task, _INVENTORY, {})
196243

197-
assert port.name == "original-name"
198-
assert "bios_name" not in port.extra
244+
assert unknown_port.name == "original-name"
245+
assert "bios_name" not in unknown_port.extra

0 commit comments

Comments
 (0)