Skip to content

Commit c3d87fa

Browse files
authored
Merge pull request #2434 from rackerlabs/enable_storage_nic
fix(ironic): enable Ironic’s storage-port discovery/classification
2 parents 87dbf50 + 345fda9 commit c3d87fa

3 files changed

Lines changed: 69 additions & 5 deletions

File tree

‎components/ironic/cm-trait-based-networking.yaml‎

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,9 +4,9 @@ metadata:
44
name: ironic-trait-based-networking
55
data:
66
trait_based_networking.yaml: |
7-
# Trait Based Networking (TBN) config for grouping category=network ports
8-
# into a dynamic portgroup, and only ever auto-attaching category=network
9-
# ports via vif_attach (category=storage ports are left alone).
7+
# Trait Based Networking (TBN) config. category=network ports auto-attach via
8+
# the CUSTOM_NETWORK_PORTGROUP / default handlers. category=storage ports
9+
# attach only when a VIF requests the CUSTOM_STORAGE_ATTACH trait.
1010
#
1111
# Applied to a node only when CUSTOM_NETWORK_PORTGROUP appears in the
1212
# node's instance_info.traits (Nova copies matching flavor/image traits
@@ -31,6 +31,16 @@ data:
3131
filter: port.category == 'network'
3232
max_count: 1
3333
34+
# Attaches a single category=storage port for a storage VIF. Single-port
35+
# only for now (A/B grouping is deferred). Require it on a flavor with:
36+
# openstack flavor set --property trait:CUSTOM_STORAGE_ATTACH=required <flavor>
37+
CUSTOM_STORAGE_ATTACH:
38+
order: 1
39+
actions:
40+
- action: attach_port
41+
filter: port.category == 'storage'
42+
max_count: 1
43+
3444
# Overrides Ironic's built-in fallback trait (CUSTOM_DEFAULT_TBN_TRAIT),
3545
# which normally attaches the first available port or portgroup regardless
3646
# of category. Redefining it here keeps that fallback scoped to

‎python/ironic-understack/ironic_understack/hooks/inspect_hook_update_baremetal_ports.py‎

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,10 @@
1414

1515
LOG = logging.getLogger(__name__)
1616

17+
# Physnet suffixes realized through ML2 + Undersync. Other derived physnets
18+
# (e.g. "-storage-appliance", "enrol") are nulled so they don't reach binding.
19+
REALIZED_PHYSNET_SUFFIXES = ("-network", "-storage")
20+
1721

1822
class InspectHookUpdateBaremetalPorts(base.InspectionHook):
1923
"""Hook to update ports according to LLDP data."""
@@ -161,7 +165,9 @@ def _set_port_attributes(
161165
)
162166
port.local_link_connection = inspected_port.local_link_connection
163167

164-
if physical_network and not physical_network.endswith("-network"):
168+
if physical_network and not physical_network.endswith(
169+
REALIZED_PHYSNET_SUFFIXES
170+
):
165171
physical_network = None
166172

167173
if port.physical_network != physical_network:

‎python/ironic-understack/ironic_understack/tests/test_inspect_hook_update_baremetal_ports.py‎

Lines changed: 49 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,12 +145,60 @@ def test_with_valid_storage_port(mocker, caplog):
145145
"switch_id": "88:5a:92:ec:54:59",
146146
"switch_info": "f20-3-1f.iad3.rackspace.net",
147147
}
148-
assert mock_port.physical_network is None
148+
# Storage-client physnets are preserved (previously nulled).
149+
assert mock_port.physical_network == "f20-3-storage"
149150
assert mock_port.category == "storage"
150151
assert mock_port.pxe_enabled is False
151152
mock_port.save.assert_called()
152153

153154

155+
def test_non_realized_physnet_is_nulled(mocker, caplog):
156+
"""A "-storage-appliance" physnet is still nulled; only its category survives."""
157+
caplog.set_level(logging.DEBUG)
158+
159+
node_uuid = uuidutils.generate_uuid()
160+
mock_traits = mocker.Mock()
161+
mock_context = mocker.Mock()
162+
mock_node = mocker.Mock(id=1234, traits=mock_traits)
163+
mock_task = mocker.Mock(node=mock_node, context=mock_context)
164+
mock_port = mocker.Mock(
165+
uuid=uuidutils.generate_uuid(),
166+
node_id=node_uuid,
167+
address="33:33:33:33:33:33",
168+
local_link_connection={},
169+
physical_network="original_value",
170+
category=None,
171+
pxe_enabled=True,
172+
)
173+
174+
# Derive "f20-3-storage-appliance" from the connected suffixes.
175+
appliance_mapping = {
176+
"1": "network",
177+
"2": "network",
178+
"1f": "storage-appliance",
179+
"2f": "storage-appliance",
180+
}
181+
182+
mocker.patch(
183+
"ironic_understack.hooks.inspect_hook_update_baremetal_ports.ironic_ports_for_node",
184+
return_value=[mock_port],
185+
)
186+
mocker.patch(
187+
"ironic_understack.hooks.inspect_hook_update_baremetal_ports.CONF.ironic_understack.switch_name_vlan_group_mapping",
188+
appliance_mapping,
189+
)
190+
mocker.patch(
191+
"ironic_understack.hooks.inspect_hook_update_baremetal_ports.objects.TraitList.create"
192+
)
193+
mock_traits.get_trait_names.return_value = ["CUSTOM_BMC_SWITCH", "bar"]
194+
195+
InspectHookUpdateBaremetalPorts().__call__(mock_task, _INVENTORY, _PLUGIN_DATA)
196+
197+
assert mock_port.physical_network is None
198+
assert mock_port.category == "appliance"
199+
mock_port.save.assert_called()
200+
201+
154202
def test_secondary_network_port_has_pxe_disabled(mocker, caplog):
155203
caplog.set_level(logging.DEBUG)
156204

0 commit comments

Comments
 (0)