mirror of
https://github.com/bpg/terraform-provider-proxmox.git
synced 2025-07-05 05:24:01 +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:
parent
3195b3cdf4
commit
cb5fc279cd
@ -414,6 +414,9 @@ output "ubuntu_vm_public_key" {
|
|||||||
- `queues` - (Optional) The number of queues for VirtIO (1..64).
|
- `queues` - (Optional) The number of queues for VirtIO (1..64).
|
||||||
- `rate_limit` - (Optional) The rate limit in megabytes per second.
|
- `rate_limit` - (Optional) The rate limit in megabytes per second.
|
||||||
- `vlan_id` - (Optional) The VLAN identifier.
|
- `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
|
- `node_name` - (Required) The name of the node to assign the virtual machine
|
||||||
to.
|
to.
|
||||||
- `on_boot` - (Optional) Specifies whether a VM will be started during system
|
- `on_boot` - (Optional) Specifies whether a VM will be started during system
|
||||||
|
53
example/resource_virtual_environment_trunks.tf
Normal file
53
example/resource_virtual_environment_trunks.tf
Normal 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"
|
||||||
|
}
|
||||||
|
}
|
@ -78,13 +78,11 @@ func TestAccResourceVM(t *testing.T) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func TestAccResourceVMNetwork(t *testing.T) {
|
func TestAccResourceVMNetwork(t *testing.T) {
|
||||||
t.Skip("This test is hanging up")
|
|
||||||
|
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
step resource.TestStep
|
step resource.TestStep
|
||||||
}{
|
}{
|
||||||
{"network interfaces mac", resource.TestStep{
|
{"network interfaces", resource.TestStep{
|
||||||
Config: `
|
Config: `
|
||||||
resource "proxmox_virtual_environment_file" "cloud_config" {
|
resource "proxmox_virtual_environment_file" "cloud_config" {
|
||||||
content_type = "snippets"
|
content_type = "snippets"
|
||||||
@ -133,6 +131,7 @@ EOF
|
|||||||
}
|
}
|
||||||
network_device {
|
network_device {
|
||||||
bridge = "vmbr0"
|
bridge = "vmbr0"
|
||||||
|
trunks = "10;20;30"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,8 +143,12 @@ EOF
|
|||||||
overwrite_unmanaged = true
|
overwrite_unmanaged = true
|
||||||
}`,
|
}`,
|
||||||
Check: resource.ComposeTestCheckFunc(
|
Check: resource.ComposeTestCheckFunc(
|
||||||
resource.TestCheckResourceAttr("proxmox_virtual_environment_vm.test_vm_network1", "ipv4_addresses.#", "2"),
|
testResourceAttributes("proxmox_virtual_environment_vm.test_vm_network1", map[string]string{
|
||||||
resource.TestCheckResourceAttr("proxmox_virtual_environment_vm.test_vm_network1", "mac_addresses.#", "2"),
|
"ipv4_addresses.#": "2",
|
||||||
|
"mac_addresses.#": "2",
|
||||||
|
"network_device.0.bridge": "vmbr0",
|
||||||
|
"network_device.0.trunks": "10;20;30",
|
||||||
|
}),
|
||||||
),
|
),
|
||||||
}},
|
}},
|
||||||
}
|
}
|
||||||
|
@ -100,6 +100,7 @@ const (
|
|||||||
dvNetworkDeviceQueues = 0
|
dvNetworkDeviceQueues = 0
|
||||||
dvNetworkDeviceRateLimit = 0
|
dvNetworkDeviceRateLimit = 0
|
||||||
dvNetworkDeviceVLANID = 0
|
dvNetworkDeviceVLANID = 0
|
||||||
|
dvNetworkDeviceTrunks = ""
|
||||||
dvNetworkDeviceMTU = 0
|
dvNetworkDeviceMTU = 0
|
||||||
dvOperatingSystemType = "other"
|
dvOperatingSystemType = "other"
|
||||||
dvPoolID = ""
|
dvPoolID = ""
|
||||||
@ -234,6 +235,7 @@ const (
|
|||||||
mkNetworkDeviceQueues = "queues"
|
mkNetworkDeviceQueues = "queues"
|
||||||
mkNetworkDeviceRateLimit = "rate_limit"
|
mkNetworkDeviceRateLimit = "rate_limit"
|
||||||
mkNetworkDeviceVLANID = "vlan_id"
|
mkNetworkDeviceVLANID = "vlan_id"
|
||||||
|
mkNetworkDeviceTrunks = "trunks"
|
||||||
mkNetworkDeviceMTU = "mtu"
|
mkNetworkDeviceMTU = "mtu"
|
||||||
mkNetworkInterfaceNames = "network_interface_names"
|
mkNetworkInterfaceNames = "network_interface_names"
|
||||||
mkNodeName = "node_name"
|
mkNodeName = "node_name"
|
||||||
@ -1129,6 +1131,11 @@ func VM() *schema.Resource {
|
|||||||
Optional: true,
|
Optional: true,
|
||||||
Default: dvNetworkDeviceVLANID,
|
Default: dvNetworkDeviceVLANID,
|
||||||
},
|
},
|
||||||
|
mkNetworkDeviceTrunks: {
|
||||||
|
Type: schema.TypeString,
|
||||||
|
Optional: true,
|
||||||
|
Description: "List of VLAN trunks for the network interface",
|
||||||
|
},
|
||||||
mkNetworkDeviceMTU: {
|
mkNetworkDeviceMTU: {
|
||||||
Type: schema.TypeInt,
|
Type: schema.TypeInt,
|
||||||
Description: "Maximum transmission unit (MTU)",
|
Description: "Maximum transmission unit (MTU)",
|
||||||
@ -2012,7 +2019,10 @@ func vmCreateClone(ctx context.Context, d *schema.ResourceData, m interface{}) d
|
|||||||
}
|
}
|
||||||
|
|
||||||
if len(networkDevice) > 0 {
|
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++ {
|
for i := 0; i < len(updateBody.NetworkDevices); i++ {
|
||||||
if !updateBody.NetworkDevices[i].Enabled {
|
if !updateBody.NetworkDevices[i].Enabled {
|
||||||
@ -2402,7 +2412,10 @@ func vmCreateCustom(ctx context.Context, d *schema.ResourceData, m interface{})
|
|||||||
name := d.Get(mkName).(string)
|
name := d.Get(mkName).(string)
|
||||||
tags := d.Get(mkTags).([]interface{})
|
tags := d.Get(mkTags).([]interface{})
|
||||||
|
|
||||||
networkDeviceObjects := vmGetNetworkDeviceObjects(d)
|
networkDeviceObjects, err := vmGetNetworkDeviceObjects(d)
|
||||||
|
if err != nil {
|
||||||
|
return diag.FromErr(err)
|
||||||
|
}
|
||||||
|
|
||||||
nodeName := d.Get(mkNodeName).(string)
|
nodeName := d.Get(mkNodeName).(string)
|
||||||
|
|
||||||
@ -3025,7 +3038,7 @@ func vmGetHostUSBDeviceObjects(d *schema.ResourceData) vms.CustomUSBDevices {
|
|||||||
return usbDeviceObjects
|
return usbDeviceObjects
|
||||||
}
|
}
|
||||||
|
|
||||||
func vmGetNetworkDeviceObjects(d *schema.ResourceData) vms.CustomNetworkDevices {
|
func vmGetNetworkDeviceObjects(d *schema.ResourceData) (vms.CustomNetworkDevices, error) {
|
||||||
networkDevice := d.Get(mkNetworkDevice).([]interface{})
|
networkDevice := d.Get(mkNetworkDevice).([]interface{})
|
||||||
networkDeviceObjects := make(vms.CustomNetworkDevices, len(networkDevice))
|
networkDeviceObjects := make(vms.CustomNetworkDevices, len(networkDevice))
|
||||||
|
|
||||||
@ -3040,6 +3053,7 @@ func vmGetNetworkDeviceObjects(d *schema.ResourceData) vms.CustomNetworkDevices
|
|||||||
queues := block[mkNetworkDeviceQueues].(int)
|
queues := block[mkNetworkDeviceQueues].(int)
|
||||||
rateLimit := block[mkNetworkDeviceRateLimit].(float64)
|
rateLimit := block[mkNetworkDeviceRateLimit].(float64)
|
||||||
vlanID := block[mkNetworkDeviceVLANID].(int)
|
vlanID := block[mkNetworkDeviceVLANID].(int)
|
||||||
|
trunks := block[mkNetworkDeviceTrunks].(string)
|
||||||
mtu := block[mkNetworkDeviceMTU].(int)
|
mtu := block[mkNetworkDeviceMTU].(int)
|
||||||
|
|
||||||
device := vms.CustomNetworkDevice{
|
device := vms.CustomNetworkDevice{
|
||||||
@ -3068,6 +3082,23 @@ func vmGetNetworkDeviceObjects(d *schema.ResourceData) vms.CustomNetworkDevices
|
|||||||
device.Tag = &vlanID
|
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 {
|
if mtu != 0 {
|
||||||
device.MTU = &mtu
|
device.MTU = &mtu
|
||||||
}
|
}
|
||||||
@ -3075,7 +3106,7 @@ func vmGetNetworkDeviceObjects(d *schema.ResourceData) vms.CustomNetworkDevices
|
|||||||
networkDeviceObjects[i] = device
|
networkDeviceObjects[i] = device
|
||||||
}
|
}
|
||||||
|
|
||||||
return networkDeviceObjects
|
return networkDeviceObjects, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func vmGetSerialDeviceList(d *schema.ResourceData) vms.CustomSerialDevices {
|
func vmGetSerialDeviceList(d *schema.ResourceData) vms.CustomSerialDevices {
|
||||||
@ -4123,6 +4154,13 @@ func vmReadCustom(
|
|||||||
networkDevice[mkNetworkDeviceVLANID] = 0
|
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 {
|
if nd.MTU != nil {
|
||||||
networkDevice[mkNetworkDeviceMTU] = nd.MTU
|
networkDevice[mkNetworkDeviceMTU] = nd.MTU
|
||||||
} else {
|
} else {
|
||||||
@ -5148,7 +5186,7 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
|
|||||||
|
|
||||||
// Prepare the new memory configuration.
|
// Prepare the new memory configuration.
|
||||||
if d.HasChange(mkMemory) {
|
if d.HasChange(mkMemory) {
|
||||||
memoryBlock, err := structure.GetSchemaBlock(
|
memoryBlock, er := structure.GetSchemaBlock(
|
||||||
resource,
|
resource,
|
||||||
d,
|
d,
|
||||||
[]string{mkMemory},
|
[]string{mkMemory},
|
||||||
@ -5156,7 +5194,7 @@ func vmUpdate(ctx context.Context, d *schema.ResourceData, m interface{}) diag.D
|
|||||||
true,
|
true,
|
||||||
)
|
)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return diag.FromErr(err)
|
return diag.FromErr(er)
|
||||||
}
|
}
|
||||||
|
|
||||||
memoryDedicated := memoryBlock[mkMemoryDedicated].(int)
|
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.
|
// Prepare the new network device configuration.
|
||||||
if d.HasChange(mkNetworkDevice) {
|
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++ {
|
for i := 0; i < len(updateBody.NetworkDevices); i++ {
|
||||||
if !updateBody.NetworkDevices[i].Enabled {
|
if !updateBody.NetworkDevices[i].Enabled {
|
||||||
|
Loading…
Reference in New Issue
Block a user