Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 12 additions & 12 deletions components/images-openstack.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -34,18 +34,18 @@ images:
ironic_retrive_swift_config: "ghcr.io/rackerlabs/understack/openstack-client:2025.2"

# neutron
neutron_db_sync: "ghcr.io/rackerlabs/understack/neutron:2026.1"
neutron_dhcp: "ghcr.io/rackerlabs/understack/neutron:2026.1"
neutron_l3: "ghcr.io/rackerlabs/understack/neutron:2026.1"
neutron_l2gw: "ghcr.io/rackerlabs/understack/neutron:2026.1"
neutron_linuxbridge_agent: "ghcr.io/rackerlabs/understack/neutron:2026.1"
neutron_metadata: "ghcr.io/rackerlabs/understack/neutron:2026.1"
neutron_ovn_metadata: "ghcr.io/rackerlabs/understack/neutron:2026.1"
neutron_openvswitch_agent: "ghcr.io/rackerlabs/understack/neutron:2026.1"
neutron_server: "ghcr.io/rackerlabs/understack/neutron:2026.1"
neutron_rpc_server: "ghcr.io/rackerlabs/understack/neutron:2026.1"
neutron_bagpipe_bgp: "ghcr.io/rackerlabs/understack/neutron:2026.1"
neutron_netns_cleanup_cron: "ghcr.io/rackerlabs/understack/neutron:2026.1"
neutron_db_sync: "ghcr.io/rackerlabs/understack/neutron:pr-2145"
neutron_dhcp: "ghcr.io/rackerlabs/understack/neutron:pr-2145"
neutron_l3: "ghcr.io/rackerlabs/understack/neutron:pr-2145"
neutron_l2gw: "ghcr.io/rackerlabs/understack/neutron:pr-2145"
neutron_linuxbridge_agent: "ghcr.io/rackerlabs/understack/neutron:pr-2145"
neutron_metadata: "ghcr.io/rackerlabs/understack/neutron:pr-2145"
neutron_ovn_metadata: "ghcr.io/rackerlabs/understack/neutron:pr-2145"
neutron_openvswitch_agent: "ghcr.io/rackerlabs/understack/neutron:pr-2145"
neutron_server: "ghcr.io/rackerlabs/understack/neutron:pr-2145"
neutron_rpc_server: "ghcr.io/rackerlabs/understack/neutron:pr-2145"
neutron_bagpipe_bgp: "ghcr.io/rackerlabs/understack/neutron:pr-2145"
neutron_netns_cleanup_cron: "ghcr.io/rackerlabs/understack/neutron:pr-2145"

# nova
nova_api: "ghcr.io/rackerlabs/understack/nova:2026.1"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -266,13 +266,6 @@ def _update_port_baremetal(self, context: PortContext) -> None:
port = context.current

vlan_group_name = port[portbindings.PROFILE].get("physical_network")
if vlan_group_name is None:
local_link_info = utils.local_link_from_binding_profile(
port[portbindings.PROFILE]
)
vlan_group_name = self.ironic_client.baremetal_port_physical_network(
local_link_info
)

if current_vif_unbound and original_vif_other:
self._tenant_network_port_cleanup(context)
Expand Down Expand Up @@ -329,13 +322,6 @@ def _delete_port_baremetal(self, context: PortContext) -> None:
port = context.current

vlan_group_name = port[portbindings.PROFILE].get("physical_network")
if vlan_group_name is None:
local_link_info = utils.local_link_from_binding_profile(
port[portbindings.PROFILE]
)
vlan_group_name = self.ironic_client.baremetal_port_physical_network(
local_link_info
)

if vlan_group_name and is_provisioning_network(port["network_id"]):
# Signals end of the provisioning / cleaning cycle, so we
Expand Down Expand Up @@ -387,19 +373,19 @@ def _bind_port_segment(self, context: PortContext, segment):
port = context.current

vlan_group_name = port[portbindings.PROFILE].get("physical_network")
if vlan_group_name is None:
local_link_info = utils.local_link_from_binding_profile(
port[portbindings.PROFILE]
)
vlan_group_name = self.ironic_client.baremetal_port_physical_network(
local_link_info
)

if not vlan_group_name:
LOG.error(
"bind_port_segment: no physical_network found for baremetal "
"port with mac address: %(mac)s",
{"mac": mac_address},
"bind_port_segment: physical_network is required in binding_profile "
"for baremetal port binding, but was not found. "
"port_id=%(port_id)s mac_address=%(mac)s network_id=%(network_id)s. "
"Please ensure the port's binding_profile contains physical_network "
"in local_link_information.",
{
"port_id": port["id"],
"mac": mac_address,
"network_id": network_id,
},
)
return

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -199,13 +199,14 @@ def binding_profile(request, port_id) -> str:
req = getattr(request, "param", {})
return json.dumps(
{
"physical_network": req.get("physical_network", "physnet"),
"local_link_information": [
{
"port_id": req.get("port_id", str(port_id)),
"switch_id": "11:22:33:44:55:66",
"switch_info": "a1-1-1.iad3.rackspace.net",
}
]
],
}
)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -250,18 +250,24 @@ def test_refuses_unsupported_vnic_type(

port_context.continue_binding.assert_not_called()

@pytest.mark.usefixtures("_ironic_baremetal_port_physical_network")
def test_does_not_bind_when_physical_network_not_found(
def test_fails_when_physical_network_missing(
self, mocker, port_context, understack_driver
):
understack_driver.ironic_client.baremetal_port_physical_network.return_value = (
None
)
"""Port binding fails when physical_network is absent from binding_profile.

This enforces that physical_network must be present for binding to succeed.
"""
# Remove physical_network from binding_profile to test hard requirement
binding_profile_dict = port_context.current[portbindings.PROFILE].copy()
binding_profile_dict.pop("physical_network", None)
port_context.current[portbindings.PROFILE] = binding_profile_dict

mocker.patch.object(port_context, "continue_binding")
port_context._prepare_to_bind(port_context.network.network_segments)

understack_driver.bind_port(port_context)

# Binding should not proceed without physical_network
port_context.continue_binding.assert_not_called()

@pytest.mark.parametrize("port_dict", [{"trunk": True}], indirect=True)
Expand Down
44 changes: 32 additions & 12 deletions python/neutron-understack/neutron_understack/trunk.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,16 @@ def _add_subports_networks_to_parent_port_switchport(
binding_host = parent_port.bindings[0].host

vlan_group_name = binding_profile.get("physical_network")
if vlan_group_name is None:
local_link_info = utils.local_link_from_binding_profile(binding_profile)
vlan_group_name = self.ironic_client.baremetal_port_physical_network(
local_link_info
if not vlan_group_name:
raise exc.BadRequest(
resource="port",
msg=(
"physical_network is required in binding_profile for baremetal "
f"port trunk configuration. Port {parent_port.id} does not have "
"physical_network in its binding_profile. "
"Please ensure the port's binding_profile contains "
"physical_network in local_link_information."
),
)

self._handle_segment_allocation(subports, vlan_group_name, binding_host)
Expand All @@ -211,10 +217,17 @@ def _clean_parent_port_switchport_config(
binding_host = parent_port_obj.bindings[0].host

vlan_group_name = binding_profile.get("physical_network")
if vlan_group_name is None:
local_link_info = utils.local_link_from_binding_profile(binding_profile)
vlan_group_name = self.ironic_client.baremetal_port_physical_network(
local_link_info
if not vlan_group_name:
port_id = parent_port_obj.id
raise exc.BadRequest(
resource="port",
msg=(
"physical_network is required in binding_profile for baremetal "
f"port trunk configuration. Port {port_id} does not have "
"physical_network in its binding_profile. "
"Please ensure the port's binding_profile contains "
"physical_network in local_link_information."
),
)
self._handle_subports_removal(
binding_profile=binding_profile,
Expand Down Expand Up @@ -265,10 +278,17 @@ def subports_added_post(self, resource, event, trunk_plugin, payload):
if utils.parent_port_is_bound(parent_port):
binding_profile = parent_port.bindings[0].profile
vlan_group_name = binding_profile.get("physical_network")
if vlan_group_name is None:
local_link_info = utils.local_link_from_binding_profile(binding_profile)
vlan_group_name = self.ironic_client.baremetal_port_physical_network(
local_link_info
if not vlan_group_name:
port_id = parent_port.id
raise exc.BadRequest(
resource="port",
msg=(
"physical_network is required in binding_profile for "
f"baremetal port trunk configuration. Port {port_id} does not "
"have physical_network in its binding_profile. "
"Please ensure the port's binding_profile contains "
"physical_network in local_link_information."
),
)
LOG.debug("subports_added_post found vlan_group_name=%s", vlan_group_name)
self.undersync.sync(vlan_group_name)
Expand Down
Loading
Loading