Skip to content

🌱 Add VLAN interface configuration support#1424

Open
shutingm-kubernetes wants to merge 5 commits intovmware-tanzu:mainfrom
shutingm-kubernetes:shutingm/supports-vlans-in-cloudinit
Open

🌱 Add VLAN interface configuration support#1424
shutingm-kubernetes wants to merge 5 commits intovmware-tanzu:mainfrom
shutingm-kubernetes:shutingm/supports-vlans-in-cloudinit

Conversation

@shutingm-kubernetes
Copy link

@shutingm-kubernetes shutingm-kubernetes commented Jan 21, 2026

What does this PR do, and why is it needed?

Add VLANs field to VirtualMachineNetworkSpec allowing users to configure 802.1Q VLAN interfaces on top of physical network interfaces. Supported with CloudInit bootstrap provider.

Which issue(s) is/are addressed by this PR? (optional, in fixes #<issue number>(, fixes #<issue_number>, ...) format, will close the issue(s) when PR gets merged):

Fixes #

Are there any special notes for your reviewer:

Test Cases Summary

1. API Validation (WebHook)

Webhook Validation Tests

# Test Case Input Expected Result Status
1 Allow valid VLANs - CloudInit
- eth0, eth1
- eth1.vlans: v100 (ID=100), v200 (ID=200)
✅ Admission allowed PASS
2 Disallow VLANs without CloudInit bootstrap - LinuxPrep
- eth0, eth1
- eth1.vlans: v100 (ID=100)
spec.network.vlans: Forbidden: vlans is available only with the following bootstrap providers: CloudInit PASS
3 Disallow VLANs without any bootstrap - No bootstrap
- eth0, eth1
- eth1.vlans: v100 (ID=100)
spec.network.vlans: Forbidden: vlans is available only with the following bootstrap providers: CloudInit PASS
4 Disallow duplicate VLAN IDs on the same interface - CloudInit
- eth0, eth1
- eth1.vlans: vl100a (ID=100), vl100b (ID=100)
spec.network.vlans[1].id: Invalid value: 100: VLAN ID 100 is already used by VLAN "vl100a" on the same parent link "eth1" PASS
5 Disallow duplicate VLAN names - CloudInit
- eth0, eth1, eth2
- eth1.vlans: vl100 (ID=100),
- eth2.vlans: v100 (ID=100)
The VirtualMachine "photon-vlans" is invalid: spec.network.vlans[1]: Duplicate value: map[string]interface {}{"name":"vl100"} PASS
6 Disallow VLAN names conflict with Interface Names - CloudInit
- eth0, eth1
- eth1.vlans: eth1 (ID=100)
spec.network.vlans[0].name: Invalid value: "eth1": vlan name must not conflict with an interface name PASS
7 Disallow VLAN names conflict with GuestDeviceName names - CloudInit
- eth0, eth1(GuestDeviceName: ens100)
- eth1.vlans: ens100 (ID=100)
spec.network.vlans[0].name: Invalid value: "ens100": vlan name must not conflict with an interface guestDeviceName PASS

2. CloudInit Netplan Config Generation

Test Case 1 — Single VLAN

VM CR spec:

spec:
  network:
    interfaces:
      - name: eth0
        network:
          apiVersion: crd.nsx.vmware.com/v1alpha1
          kind: SubnetSet
          name: wc-wzr99
      - gateway4: None
        gateway6: None
        name: eth1
        network:
          apiVersion: crd.nsx.vmware.com/v1alpha1
          kind: Subnet
          name: vlan-ext
    vlans:
      - name: vlan100
        id: 100
        link: eth1

Expected Netplan config generated (injected via Cloud-Init):

network:
  ethernets:
    eth0:
      accept-ra: false
      addresses:
      - 172.26.0.38/27
      dhcp4: false
      dhcp6: false
      gateway4: 172.26.0.33
      match:
        macaddress: 04:50:56:00:ec:00
      nameservers:
        addresses:
        - 192.19.189.10
      set-name: eth0
    eth1:
      accept-ra: false
      addresses:
      - 172.26.0.4/27
      dhcp4: false
      dhcp6: false
      gateway4: 172.26.0.1
      match:
        macaddress: 04:50:56:00:fc:00
      nameservers:
        addresses:
        - 192.19.189.10
      set-name: eth1
  vlans:
    vlan100:
      accept-ra: false
      dhcp4: false
      dhcp6: false
      id: 100
      link: eth1

OS result: Inside the guest, ip link shows eth1 (physical NIC) and vlan100 (802.1Q sub-interface tagged with VLAN ID 100, parent eth1).

[ ~ ]# ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 04:50:56:00:ec:00 brd ff:ff:ff:ff:ff:ff
    altname eno1
    altname enp2s1
    altname ens33
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 04:50:56:00:fc:00 brd ff:ff:ff:ff:ff:ff
    altname eno2
    altname enp2s2
    altname ens34
4: vlan100@eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 04:50:56:00:fc:00 brd ff:ff:ff:ff:ff:ff

Test Case 2 — Multiple VLANs

VM CR spec:

  network:
    interfaces:
      - name: eth0
        network:
          apiVersion: crd.nsx.vmware.com/v1alpha1
          kind: SubnetSet
          name: wc-wzr99
        guestDeviceName: eth0
      - gateway4: None
        gateway6: None
        name: eth1
        guestDeviceName: eth1
        network:
          apiVersion: crd.nsx.vmware.com/v1alpha1
          kind: Subnet
          name: vlan-ext
      - gateway4: None
        gateway6: None
        name: eth2
        guestDeviceName: eth2
        network:
          apiVersion: crd.nsx.vmware.com/v1alpha1
          kind: Subnet
          name: vlan-ext
    vlans:
      - name: vl100a
        id: 100
        link: eth1
      - name: vl200a
        id: 200
        link: eth1
      - name: vl100b
        id: 100
        link: eth2
      - name: vl200b
        id: 200
        link: eth2

Expected Netplan config generated:

network:
  ethernets:
    eth0:
      accept-ra: false
      addresses:
      - 172.26.0.37/27
      dhcp4: false
      dhcp6: false
      gateway4: 172.26.0.33
      match:
        macaddress: "04:50:56:00:34:01"
      nameservers:
        addresses:
        - 192.19.189.10
      set-name: eth0
    eth1:
      accept-ra: false
      addresses:
      - 172.26.0.6/27
      dhcp4: false
      dhcp6: false
      match:
        macaddress: "04:50:56:00:18:00"
      nameservers:
        addresses:
        - 192.19.189.10
      set-name: eth1
    eth2:
      accept-ra: false
      addresses:
      - 172.26.0.7/27
      dhcp4: false
      dhcp6: false
      match:
        macaddress: 04:50:56:00:e4:01
      nameservers:
        addresses:
        - 192.19.189.10
      set-name: eth2
  vlans:
    vl100a:
      accept-ra: false
      dhcp4: false
      dhcp6: false
      id: 100
      link: eth1
    vl100b:
      accept-ra: false
      dhcp4: false
      dhcp6: false
      id: 100
      link: eth2
    vl200a:
      accept-ra: false
      dhcp4: false
      dhcp6: false
      id: 200
      link: eth1
    vl200b:
      accept-ra: false
      dhcp4: false
      dhcp6: false
      id: 200
      link: eth2

OS result: Inside the guest, ip link shows eth1, eth2 (physical NICs) and 4 VLAN sub-interfaces — vl100a (VLAN 100 on eth1), vl200a (VLAN 200 on eth1), vl100b (VLAN 100 on eth2), vl200b (VLAN 200 on eth2). Same VLAN ID is allowed on different parent links.

1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 04:50:56:00:34:01 brd ff:ff:ff:ff:ff:ff
    altname eno1
    altname enp2s1
    altname ens33
3: eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 04:50:56:00:18:00 brd ff:ff:ff:ff:ff:ff
    altname eno2
    altname enp2s2
    altname ens34
4: eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 04:50:56:00:e4:01 brd ff:ff:ff:ff:ff:ff
    altname eno3
    altname enp2s3
    altname ens35
5: vl200a@eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 04:50:56:00:18:00 brd ff:ff:ff:ff:ff:ff
6: vl100a@eth1: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 04:50:56:00:18:00 brd ff:ff:ff:ff:ff:ff
7: vl200b@eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 04:50:56:00:e4:01 brd ff:ff:ff:ff:ff:ff
8: vl100b@eth2: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc noqueue state UP mode DEFAULT group default qlen 1000
    link/ether 04:50:56:00:e4:01 brd ff:ff:ff:ff:ff:ff

Test Case 3 — No VLANs

VM CR spec:

spec:
  network:
    interfaces:
    - name: eth0
      network:
        apiVersion: crd.nsx.vmware.com/v1alpha1
        kind: SubnetSet
        name: wc-2qgnc

Expected Netplan config generated:

network:
  ethernets:
    eth0:
      accept-ra: false
      addresses:
      - 172.26.0.36/27
      dhcp4: false
      dhcp6: false
      gateway4: 172.26.0.33
      match:
        macaddress: 04:50:56:00:e4:00
      nameservers:
        addresses:
        - 192.19.189.10
      set-name: eth0
  # no vlans section

OS result: Inside the guest, only eth0 (physical NIC) is present. No VLAN sub-interfaces are created.

[ ~ ]# ip link show
1: lo: <LOOPBACK,UP,LOWER_UP> mtu 65536 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc mq state UP mode DEFAULT group default qlen 1000
    link/ether 04:50:56:00:e4:00 brd ff:ff:ff:ff:ff:ff
    altname eno1
    altname enp2s1
    altname ens33
3: tunl0@NONE: <NOARP,UP,LOWER_UP> mtu 1480 qdisc noqueue state UNKNOWN mode DEFAULT group default qlen 1000
    link/ipip 0.0.0.0 brd 0.0.0.0

Please add a release note if necessary:

NONE

@github-actions github-actions bot added the size/XL Denotes a PR that changes 500-999 lines. label Jan 21, 2026
@shutingm-kubernetes shutingm-kubernetes force-pushed the shutingm/supports-vlans-in-cloudinit branch from 652b776 to 7288101 Compare March 5, 2026 02:22
@github-actions github-actions bot added size/XXL Denotes a PR that changes 1000+ lines. and removed size/XL Denotes a PR that changes 500-999 lines. labels Mar 5, 2026
@shutingm-kubernetes shutingm-kubernetes changed the title WIP:🌱 Add VLAN interface configuration support 🌱 Add VLAN interface configuration support Mar 5, 2026
@shutingm-kubernetes shutingm-kubernetes force-pushed the shutingm/supports-vlans-in-cloudinit branch 3 times, most recently from 15d7f2c to b6f4985 Compare March 9, 2026 04:01
@aruneshpa
Copy link
Collaborator

I have a basic question. Was there a design discussion (specifically for the API design)? I think you are referring to guest VLAN tagging (802.1Q) where one vNIC can be tagged with multiple VLAN IDs, but even then, I would also want to consider the following model:

spec:
  network:
    interfaces:
      - name: eth0
         vlans:
            - id: 100
            - id: 101

@ethanlynn-bc
Copy link

Hi Arunesh,
To ensure future compatibility with bond interfaces—which might not be organized under "interfaces"—we are defining "vlans" alongside "interfaces" instead of underneath it. This approach also aligns with the cloud-init schema: https://docs.cloud-init.io/en/latest/reference/network-config-format-v2.html#examples
Could we discuss if you have any concerns about VLANs for bond interfaces?

I have a basic question. Was there a design discussion (specifically for the API design)? I think you are referring to guest VLAN tagging (802.1Q) where one vNIC can be tagged with multiple VLAN IDs, but even then, I would also want to consider the following model:

spec:
  network:
    interfaces:
      - name: eth0
         vlans:
            - id: 100
            - id: 101

@shutingm-kubernetes shutingm-kubernetes force-pushed the shutingm/supports-vlans-in-cloudinit branch from b6f4985 to fc14b02 Compare March 12, 2026 09:02
@shutingm-kubernetes
Copy link
Author

I have a basic question. Was there a design discussion (specifically for the API design)? I think you are referring to guest VLAN tagging (802.1Q) where one vNIC can be tagged with multiple VLAN IDs, but even then, I would also want to consider the following model:

spec:
  network:
    interfaces:
      - name: eth0
         vlans:
            - id: 100
            - id: 101

Hi Arunesh,

Following up on your and Andrew's suggestions, I have updated the schema to nest the VLAN configuration directly under the interface.

The updated model now looks like this:

spec:
  network:
    interfaces:
      - name: eth0
        network:
          apiVersion: crd.nsx.vmware.com/v1alpha1
          kind: SubnetSet
          name: primary
      - name: eth1
        network:
          apiVersion: netoperator.vmware.com/v1alpha1
          kind: Subnet
          name: vlan-ext
        vlans:
          - name: vl100a
            id: 100
          - name: vl200a
            id: 200

@shutingm-kubernetes shutingm-kubernetes force-pushed the shutingm/supports-vlans-in-cloudinit branch 5 times, most recently from be29504 to 58e6fa6 Compare March 18, 2026 06:41
@shutingm-kubernetes shutingm-kubernetes force-pushed the shutingm/supports-vlans-in-cloudinit branch from 58e6fa6 to c83d919 Compare March 20, 2026 06:58
@github-actions github-actions bot added size/XL Denotes a PR that changes 500-999 lines. and removed size/XXL Denotes a PR that changes 1000+ lines. labels Mar 20, 2026
@shutingm-kubernetes shutingm-kubernetes force-pushed the shutingm/supports-vlans-in-cloudinit branch from 1569eb5 to 5745983 Compare March 20, 2026 07:53
Add VLANs field to VirtualMachineNetworkSpec allowing users to
configure 802.1Q VLAN interfaces on top of physical network
interfaces. Supported with CloudInit bootstrap provider.

# Conflicts:
#	api/test/v1alpha5/virtualmachine_conversion_test.go

# Conflicts:
#	api/v1alpha2/virtualmachine_conversion.go
#	api/v1alpha3/virtualmachine_conversion.go
#	api/v1alpha4/virtualmachine_conversion.go
@shutingm-kubernetes shutingm-kubernetes force-pushed the shutingm/supports-vlans-in-cloudinit branch from d7c4028 to fcf979b Compare March 23, 2026 02:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/XL Denotes a PR that changes 500-999 lines.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants