0
0
mirror of https://github.com/bpg/terraform-provider-proxmox.git synced 2025-06-30 02:31:10 +00:00

feat(vm): add VLAN trunk support (#1086)

* 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>
This commit is contained in:
Jack Hodgkiss 2024-03-03 18:57:58 +00:00 committed by GitHub
parent 3195b3cdf4
commit cb5fc279cd
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
4 changed files with 112 additions and 12 deletions

View File

@ -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

View File

@ -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"
}
}

View File

@ -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"
@ -133,6 +131,7 @@ EOF
}
network_device {
bridge = "vmbr0"
trunks = "10;20;30"
}
}
@ -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",
}),
),
}},
}

View File

@ -100,6 +100,7 @@ const (
dvNetworkDeviceQueues = 0
dvNetworkDeviceRateLimit = 0
dvNetworkDeviceVLANID = 0
dvNetworkDeviceTrunks = ""
dvNetworkDeviceMTU = 0
dvOperatingSystemType = "other"
dvPoolID = ""
@ -234,6 +235,7 @@ const (
mkNetworkDeviceQueues = "queues"
mkNetworkDeviceRateLimit = "rate_limit"
mkNetworkDeviceVLANID = "vlan_id"
mkNetworkDeviceTrunks = "trunks"
mkNetworkDeviceMTU = "mtu"
mkNetworkInterfaceNames = "network_interface_names"
mkNodeName = "node_name"
@ -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)",
@ -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 {
@ -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)
@ -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))
@ -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{
@ -3068,6 +3082,23 @@ 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
}
@ -3075,7 +3106,7 @@ func vmGetNetworkDeviceObjects(d *schema.ResourceData) vms.CustomNetworkDevices
networkDeviceObjects[i] = device
}
return networkDeviceObjects
return networkDeviceObjects, nil
}
func vmGetSerialDeviceList(d *schema.ResourceData) vms.CustomSerialDevices {
@ -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 {
@ -5148,7 +5186,7 @@ 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},
@ -5156,7 +5194,7 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
true,
)
if err != nil {
return diag.FromErr(err)
return diag.FromErr(er)
}
memoryDedicated := memoryBlock[mkMemoryDedicated].(int)
@ -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 {