Skip to content

Expose vlan trunking in metadata/configdrive#620

Open
leust wants to merge 1 commit into
stable/2023.2-m3from
expose-vlan-trunking
Open

Expose vlan trunking in metadata/configdrive#620
leust wants to merge 1 commit into
stable/2023.2-m3from
expose-vlan-trunking

Conversation

@leust
Copy link
Copy Markdown

@leust leust commented Apr 22, 2026

Add neutron trunk subports to network_data.json so cloud-init can bring up the VLAN subinterfaces in the guest. Subport VIFs hang off their parent in a new trunk_vifs field and get flattened into vlan links (vlan_link, vlan_id, vlan_mac_address) when the metadata is rendered.

Based on upstream change [1] (blueprint expose-vlan-trunking [2], spec [3]) with these fixes:

  • Set profile.tag (segmentation_id) and profile.parent_name (parent port id) on the subport VIF in _populate_trunk_info, otherwise _get_eth_link has no way to read them from neutron.
  • Fix the VIF.hydrate() call on nested trunk_vifs: it takes a dict, not kwargs, so the upstream splat would break info_cache deserialization.
  • Skip subports whose parent is not in network_info instead of raising.
  • Fix the trunk subport test fixture so the id from show_port matches the sub_ports[].port_id.

[1] https://review.opendev.org/c/openstack/nova/+/941227
[2] https://blueprints.launchpad.net/nova/+spec/expose-vlan-trunking
[3] https://review.opendev.org/c/openstack/nova-specs/+/471815

Change-Id: Iad2d56f85a0a8b2524bc047d64d8e9a7514e4aa4

@leust leust requested review from fwiesel and joker-at-work April 22, 2026 14:43
Copy link
Copy Markdown

@joker-at-work joker-at-work left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we maybe extend nova/tests/unit/test_metadata.py's similar to test_network_data_response() to ensure these subports properly end up in a VM-visible part?

Comment thread nova/network/model.py Outdated
Comment on lines +514 to +515
for _vif in self['trunk_vifs']:
if vif['id'] == _vif['id']:
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional equivalent:

if any(vif['id'] == _vif['id'] for _vif in self['trunk_vifs']):
    return

Comment thread nova/network/neutron.py Outdated
Comment on lines +3592 to +3593
self._populate_trunk_info(
vif, current_neutron_port, context, client)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we call it explicitly and not as part of _build_vif_model()? Feels like we're preparing future errors where we build a vif model but forget to add this call.

Comment thread nova/network/neutron.py Outdated
instance=instance
)

def _populate_trunk_info(self, vif, current_neutron_port, context, client):
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Having context and client not as the first arguments feels inconsistent with the other methods of this class. Can we make them the first/second argument after self?

Comment thread nova/network/neutron.py Outdated
Comment on lines +3431 to +3434
port = self._show_port(context, port_id,
neutron_client=client)
subport_network = client.show_network(
port['network_id'])['network']
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems quite costly. Could we maybe using a "list port" endpoint filtering for the port ids and network ids in some kind of pre-fetch before we're trying to build the vif models?

We're doing something similar here in hammer for router ports.

Comment thread nova/network/neutron.py
subport_vif = self._build_vif_model(
context, client, port, [subport_network],
[port_id])
subport_profile = dict(subport_vif['profile'] or {})
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to convert to a dict() here? What could subport_vif['profile'] be that we'll need this, a list of tuples?

Comment thread nova/virt/netutils.py Outdated
continue

parent_vif = None
if vif['type'] == 'trunk-subport':
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we should use model.VIF_TYPE_TRUNK_SUBPORT here instead of the hard-coded string.

Comment thread nova/virt/netutils.py Outdated
Comment on lines +206 to +208
vif_profile = vif.get('profile')
if not vif_profile:
continue
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this could be well-expressed with the walrus-operator:

if not (vif_profile := vif.get('profile')):
    continue

same below

Comment thread nova/virt/netutils.py Outdated
Comment on lines +212 to +213
parent_vif = get_vif_from_network_info(parent_vif_id,
network_info)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could™ instead of building a list of trunk_vifs build a list of tuples (vif, parent_vif) in the loop in line 200, having (vif, None) for parent vifs. Then we wouldn't have to iterate over network_info for every single subport again. parent_vif would be the vif we're a subport of, which we have in the first for. We wouldn't do the whole vif.get('profile') and `vif_profile.get('parent_name') necessarily though. Do you feel like it's needed?

Comment thread nova/virt/netutils.py Outdated

if nic_type == 'vlan':
link.update({
'vif_id': vif['id'],
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

vif_id is already part of link and has the same value.

Comment thread nova/virt/netutils.py Outdated
Comment on lines +296 to +297
if nic_type == 'vlan':
link.update({
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

optional: We could put and additional_link_data or something into the if above, so we don't have 2 if nic_type == 'vlan' 12 lines apart.

Add neutron trunk subports to network_data.json so cloud-init can
bring up the VLAN subinterfaces in the guest. Subport VIFs hang off
their parent in a new trunk_vifs field and get flattened into vlan
links (vlan_link, vlan_id, vlan_mac_address) when the metadata is
rendered.

Based on upstream change [1] (blueprint expose-vlan-trunking [2],
spec [3]), with a few cosmetic fixes and a performance improvement
replacing the per-subport show_port / show_network calls with single
bulk list_ports / list_networks calls.

[1] https://review.opendev.org/c/openstack/nova/+/941227
[2] https://blueprints.launchpad.net/nova/+spec/expose-vlan-trunking
[3] https://review.opendev.org/c/openstack/nova-specs/+/471815

Change-Id: I2b7c017a659adc0fd3c7ac363b29e2f67f145bea
@leust leust force-pushed the expose-vlan-trunking branch from 8b6306d to 8b0b977 Compare May 5, 2026 10:36
@leust leust requested a review from joker-at-work May 6, 2026 05:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants