Skip to content

Commit

Permalink
feat(vm): add VLAN trunk support (#1086)
Browse files Browse the repository at this point in the history
* feat(vm): add `VLAN` trunk support

Signed-off-by: Jack Hodgkiss <identity@jackhodgkiss.uk>

* update docs
* better error handling
* add trunks to acceptance test

Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>

---------

Signed-off-by: Jack Hodgkiss <identity@jackhodgkiss.uk>
Signed-off-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
Co-authored-by: Pavel Boldyrev <627562+bpg@users.noreply.github.com>
  • Loading branch information
jackhodgkiss and bpg committed Mar 3, 2024
1 parent 3195b3c commit cb5fc27
Show file tree
Hide file tree
Showing 4 changed files with 112 additions and 12 deletions.
3 changes: 3 additions & 0 deletions docs/resources/virtual_environment_vm.md
Original file line number Diff line number Diff line change
Expand Up @@ -414,6 +414,9 @@ output "ubuntu_vm_public_key" {
- `queues` - (Optional) The number of queues for VirtIO (1..64).
- `rate_limit` - (Optional) The rate limit in megabytes per second.
- `vlan_id` - (Optional) The VLAN identifier.
- `trunks` - (Optional) String containing a `;` separated list of VLAN trunks
("10;20;30"). Note that the VLAN-aware feature need to be enabled on the PVE
Linux Bridge to use trunks.
- `node_name` - (Required) The name of the node to assign the virtual machine
to.
- `on_boot` - (Optional) Specifies whether a VM will be started during system
Expand Down
53 changes: 53 additions & 0 deletions example/resource_virtual_environment_trunks.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
resource "proxmox_virtual_environment_vm" "trunks-example" {
name = "trunks-example"
node_name = data.proxmox_virtual_environment_nodes.example.names[0]
description = "Example of a VM using trunks to pass multiple VLANs on a single network interface."

disk {
datastore_id = local.datastore_id
file_id = proxmox_virtual_environment_download_file.latest_debian_12_bookworm_qcow2_img.id
interface = "scsi0"
discard = "on"
cache = "writeback"
ssd = true
}

initialization {
datastore_id = local.datastore_id
interface = "scsi4"

dns {
servers = ["1.1.1.1", "8.8.8.8"]
}

ip_config {
ipv4 {
address = "dhcp"
}
}
user_data_file_id = proxmox_virtual_environment_file.user_config.id
vendor_data_file_id = proxmox_virtual_environment_file.vendor_config.id
meta_data_file_id = proxmox_virtual_environment_file.meta_config.id
}

memory {
dedicated = 1024
}

cpu {
cores = 2
}

agent {
enabled = true
}

boot_order = ["scsi0"]
scsi_hardware = "virtio-scsi-pci"

network_device {
model = "virtio"
bridge = "vmbr0"
trunks = "10;20;30"
}
}
13 changes: 8 additions & 5 deletions fwprovider/tests/resource_vm_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,13 +78,11 @@ func TestAccResourceVM(t *testing.T) {
}

func TestAccResourceVMNetwork(t *testing.T) {
t.Skip("This test is hanging up")

tests := []struct {
name string
step resource.TestStep
}{
{"network interfaces mac", resource.TestStep{
{"network interfaces", resource.TestStep{
Config: `
resource "proxmox_virtual_environment_file" "cloud_config" {
content_type = "snippets"
Expand Down Expand Up @@ -133,6 +131,7 @@ EOF
}
network_device {
bridge = "vmbr0"
trunks = "10;20;30"
}
}
Expand All @@ -144,8 +143,12 @@ EOF
overwrite_unmanaged = true
}`,
Check: resource.ComposeTestCheckFunc(
resource.TestCheckResourceAttr("proxmox_virtual_environment_vm.test_vm_network1", "ipv4_addresses.#", "2"),
resource.TestCheckResourceAttr("proxmox_virtual_environment_vm.test_vm_network1", "mac_addresses.#", "2"),
testResourceAttributes("proxmox_virtual_environment_vm.test_vm_network1", map[string]string{
"ipv4_addresses.#": "2",
"mac_addresses.#": "2",
"network_device.0.bridge": "vmbr0",
"network_device.0.trunks": "10;20;30",
}),
),
}},
}
Expand Down
55 changes: 48 additions & 7 deletions proxmoxtf/resource/vm/vm.go
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,7 @@ const (
dvNetworkDeviceQueues = 0
dvNetworkDeviceRateLimit = 0
dvNetworkDeviceVLANID = 0
dvNetworkDeviceTrunks = ""
dvNetworkDeviceMTU = 0
dvOperatingSystemType = "other"
dvPoolID = ""
Expand Down Expand Up @@ -234,6 +235,7 @@ const (
mkNetworkDeviceQueues = "queues"
mkNetworkDeviceRateLimit = "rate_limit"
mkNetworkDeviceVLANID = "vlan_id"
mkNetworkDeviceTrunks = "trunks"
mkNetworkDeviceMTU = "mtu"
mkNetworkInterfaceNames = "network_interface_names"
mkNodeName = "node_name"
Expand Down Expand Up @@ -1129,6 +1131,11 @@ func VM() *schema.Resource {
Optional: true,
Default: dvNetworkDeviceVLANID,
},
mkNetworkDeviceTrunks: {
Type: schema.TypeString,
Optional: true,
Description: "List of VLAN trunks for the network interface",
},
mkNetworkDeviceMTU: {
Type: schema.TypeInt,
Description: "Maximum transmission unit (MTU)",
Expand Down Expand Up @@ -2012,7 +2019,10 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
}

if len(networkDevice) > 0 {
updateBody.NetworkDevices = vmGetNetworkDeviceObjects(d)
updateBody.NetworkDevices, err = vmGetNetworkDeviceObjects(d)
if err != nil {
return diag.FromErr(err)
}

for i := 0; i < len(updateBody.NetworkDevices); i++ {
if !updateBody.NetworkDevices[i].Enabled {
Expand Down Expand Up @@ -2402,7 +2412,10 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
name := d.Get(mkName).(string)
tags := d.Get(mkTags).([]interface{})

networkDeviceObjects := vmGetNetworkDeviceObjects(d)
networkDeviceObjects, err := vmGetNetworkDeviceObjects(d)
if err != nil {
return diag.FromErr(err)
}

nodeName := d.Get(mkNodeName).(string)

Expand Down Expand Up @@ -3025,7 +3038,7 @@ func vmGetHostUSBDeviceObjects(d *schema.ResourceData) vms.CustomUSBDevices {
return usbDeviceObjects
}

func vmGetNetworkDeviceObjects(d *schema.ResourceData) vms.CustomNetworkDevices {
func vmGetNetworkDeviceObjects(d *schema.ResourceData) (vms.CustomNetworkDevices, error) {
networkDevice := d.Get(mkNetworkDevice).([]interface{})
networkDeviceObjects := make(vms.CustomNetworkDevices, len(networkDevice))

Expand All @@ -3040,6 +3053,7 @@ func vmGetNetworkDeviceObjects(d *schema.ResourceData) vms.CustomNetworkDevices
queues := block[mkNetworkDeviceQueues].(int)
rateLimit := block[mkNetworkDeviceRateLimit].(float64)
vlanID := block[mkNetworkDeviceVLANID].(int)
trunks := block[mkNetworkDeviceTrunks].(string)
mtu := block[mkNetworkDeviceMTU].(int)

device := vms.CustomNetworkDevice{
Expand Down Expand Up @@ -3068,14 +3082,31 @@ func vmGetNetworkDeviceObjects(d *schema.ResourceData) vms.CustomNetworkDevices
device.Tag = &vlanID
}

if trunks != "" {
splitTrunks := strings.Split(trunks, ";")

var trunksAsInt []int

for _, numStr := range splitTrunks {
num, err := strconv.Atoi(numStr)
if err != nil {
return nil, fmt.Errorf("error parsing trunks: %w", err)
}

trunksAsInt = append(trunksAsInt, num)
}

device.Trunks = trunksAsInt
}

if mtu != 0 {
device.MTU = &mtu
}

networkDeviceObjects[i] = device
}

return networkDeviceObjects
return networkDeviceObjects, nil
}

func vmGetSerialDeviceList(d *schema.ResourceData) vms.CustomSerialDevices {
Expand Down Expand Up @@ -4123,6 +4154,13 @@ func vmReadCustom(
networkDevice[mkNetworkDeviceVLANID] = 0
}

if nd.Trunks != nil {
networkDevice[mkNetworkDeviceTrunks] = strings.Trim(
strings.Join(strings.Fields(fmt.Sprint(nd.Trunks)), ";"), "[]")
} else {
networkDevice[mkNetworkDeviceTrunks] = ""
}

if nd.MTU != nil {
networkDevice[mkNetworkDeviceMTU] = nd.MTU
} else {
Expand Down Expand Up @@ -5148,15 +5186,15 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D

// Prepare the new memory configuration.
if d.HasChange(mkMemory) {
memoryBlock, err := structure.GetSchemaBlock(
memoryBlock, er := structure.GetSchemaBlock(
resource,
d,
[]string{mkMemory},
0,
true,
)
if err != nil {
return diag.FromErr(err)
return diag.FromErr(er)
}

memoryDedicated := memoryBlock[mkMemoryDedicated].(int)
Expand All @@ -5180,7 +5218,10 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D

// Prepare the new network device configuration.
if d.HasChange(mkNetworkDevice) {
updateBody.NetworkDevices = vmGetNetworkDeviceObjects(d)
updateBody.NetworkDevices, err = vmGetNetworkDeviceObjects(d)
if err != nil {
return diag.FromErr(err)
}

for i := 0; i < len(updateBody.NetworkDevices); i++ {
if !updateBody.NetworkDevices[i].Enabled {
Expand Down

0 comments on commit cb5fc27

Please sign in to comment.