Skip to content

Commit

Permalink
Merge pull request Telmate#1139 from Tinyblargon/Telmate#1027
Browse files Browse the repository at this point in the history
Reimplement qemu network interfaces
  • Loading branch information
Tinyblargon authored Oct 28, 2024
2 parents 333a557 + be447b0 commit 8e4bad1
Show file tree
Hide file tree
Showing 14 changed files with 334 additions and 148 deletions.
6 changes: 4 additions & 2 deletions docs/resources/vm_qemu.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@ resource "proxmox_vm_qemu" "pxe-minimal-example" {
pxe = true
target_node = "test"
network {
id = 0
bridge = "vmbr0"
firewall = false
link_down = false
Expand Down Expand Up @@ -165,8 +166,9 @@ second will be `net1` etc...
See the [docs about network devices](https://pve.proxmox.com/pve-docs/chapter-qm.html#qm_network_device) for more
details.

| Argument | Type | Default Value | Description |
| ----------- | ------ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| Argument | Type | Default Value | Description |
| ----------- | ------ | ------------- | ----------- |
| `id` | `int` | | **Required** The ID of the network device `0`-`31`. |
| `model` | `str` | | **Required** Network Card Model. The virtio model provides the best performance with very low CPU overhead. If your guest does not support this driver, it is usually best to use e1000. Options: `e1000`, `e1000-82540em`, `e1000-82544gc`, `e1000-82545em`, `i82551`, `i82557b`, `i82559er`, `ne2k_isa`, `ne2k_pci`, `pcnet`, `rtl8139`, `virtio`, `vmxnet3`. |
| `macaddr` | `str` | | Override the randomly generated MAC Address for the VM. Requires the MAC Address be Unicast. |
| `bridge` | `str` | `"nat"` | Bridge to which the network device should be attached. The Proxmox VE standard bridge is called `vmbr0`. |
Expand Down
1 change: 1 addition & 0 deletions examples/cloudinit_example.tf
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ resource "proxmox_vm_qemu" "cloudinit-test" {

# Setup the network interface and assign a vlan tag: 256
network {
id = 0
model = "virtio"
bridge = "vmbr0"
tag = 256
Expand Down
1 change: 1 addition & 0 deletions examples/pxe_example.tf
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ resource "proxmox_vm_qemu" "pxe-example" {
}

network {
id = 0
bridge = "vmbr0"
firewall = false
link_down = false
Expand Down
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ go 1.21
toolchain go1.21.0

require (
github.com/Telmate/proxmox-api-go v0.0.0-20241023165359-9a3f359f942c
github.com/Telmate/proxmox-api-go v0.0.0-20241028065640-b38644dd6993
github.com/google/uuid v1.6.0
github.com/hashicorp/go-cty v1.4.1-0.20200414143053-d3edf31b6320
github.com/hashicorp/terraform-plugin-sdk/v2 v2.34.0
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,8 @@ github.com/Microsoft/go-winio v0.6.1 h1:9/kr64B9VUZrLm5YYwbGtUJnMgqWVOdUAXu6Migc
github.com/Microsoft/go-winio v0.6.1/go.mod h1:LRdKpFKfdobln8UmuiYcKPot9D2v6svN5+sAH+4kjUM=
github.com/ProtonMail/go-crypto v1.1.0-alpha.2-proton h1:HKz85FwoXx86kVtTvFke7rgHvq/HoloSUvW5semjFWs=
github.com/ProtonMail/go-crypto v1.1.0-alpha.2-proton/go.mod h1:rA3QumHc/FZ8pAHreoekgiAbzpNsfQAosU5td4SnOrE=
github.com/Telmate/proxmox-api-go v0.0.0-20241023165359-9a3f359f942c h1:JJMXCLMlqiHH4tFr6sUjTXP63faXtJq9mRBk0v7XVNU=
github.com/Telmate/proxmox-api-go v0.0.0-20241023165359-9a3f359f942c/go.mod h1:Gu6n6vEn1hlyFUkjrvU+X1fdgaSXLoM9HKYYJqy1fsY=
github.com/Telmate/proxmox-api-go v0.0.0-20241028065640-b38644dd6993 h1:HOkpCRbJXYt/dFecrbp6rs7hVaJjADHMXDVA07Kpewg=
github.com/Telmate/proxmox-api-go v0.0.0-20241028065640-b38644dd6993/go.mod h1:Gu6n6vEn1hlyFUkjrvU+X1fdgaSXLoM9HKYYJqy1fsY=
github.com/agext/levenshtein v1.2.3 h1:YB2fHEn0UJagG8T1rrWknE3ZQzWM06O8AMAatNn7lmo=
github.com/agext/levenshtein v1.2.3/go.mod h1:JEDfjyjHDjOF/1e4FlBE/PkbqA9OfWu2ki2W0IB5558=
github.com/apparentlymart/go-textseg/v12 v12.0.0/go.mod h1:S/4uRK2UtaQttw1GenVJEynmyUenKwP++x/+DdGV/Ec=
Expand Down
141 changes: 141 additions & 0 deletions proxmox/Internal/resource/guest/qemu/network/schema.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package network

import (
"net"

pxapi "github.com/Telmate/proxmox-api-go/proxmox"

"github.com/hashicorp/go-cty/cty"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

const (
Root string = "network"

maximumNetworkInterfaces int = int(pxapi.QemuNetworkInterfacesAmount)

schemaID string = "id"

schemaBridge string = "bridge"
schemaFirewall string = "firewall"
schemaLinkDown string = "link_down"
schemaMAC string = "macaddr"
schemaMTU string = "mtu"
schemaModel string = "model"
schemaNativeVlan string = "tag"
schemaQueues string = "queues"
schemaRate string = "rate"
)

func Schema() *schema.Schema {
return &schema.Schema{
Type: schema.TypeList,
Optional: true,
MaxItems: maximumNetworkInterfaces,
Elem: &schema.Resource{
Schema: map[string]*schema.Schema{
schemaID: {
Type: schema.TypeInt,
Required: true,
ValidateDiagFunc: func(i interface{}, k cty.Path) diag.Diagnostics {
v := i.(int)
if v < 0 {
return diag.Errorf("%s must be in the range 0 - %d, got: %d", schemaID, pxapi.QemuNetworkInterfaceIDMaximum, v)
}
err := pxapi.QemuNetworkInterfaceID(v).Validate()
if err != nil {
return diag.Errorf("%s must be in the range 0 - %d, got: %d", schemaID, pxapi.QemuNetworkInterfaceIDMaximum, v)
}
return nil
}},
schemaBridge: {
Type: schema.TypeString,
Optional: true,
Default: "nat"},
schemaFirewall: {
Type: schema.TypeBool,
Optional: true,
Default: false},
schemaLinkDown: {
Type: schema.TypeBool,
Optional: true,
Default: false},
schemaMAC: {
Type: schema.TypeString,
Optional: true,
Computed: true,
DiffSuppressFunc: func(k, old, new string, d *schema.ResourceData) bool {
oldMAC, _ := net.ParseMAC(old)
newMAC, _ := net.ParseMAC(new)
return oldMAC.String() == newMAC.String()
},
ValidateDiagFunc: func(i interface{}, p cty.Path) diag.Diagnostics {
v := i.(string)
if _, err := net.ParseMAC(v); err != nil {
return diag.Errorf("invalid %s: %s", schemaMAC, v)
}
return nil
}},
schemaMTU: {
Type: schema.TypeInt,
Optional: true,
ValidateDiagFunc: func(i interface{}, k cty.Path) diag.Diagnostics {
v := i.(int)
if v == 1 {
return nil
}
if v < 0 {
return diag.Errorf("%s must be equal or greater than 0, got: %d", schemaMTU, v)
}
if err := pxapi.MTU(v).Validate(); err != nil {
return diag.Errorf("%s must be in the range 576 - 65520, or 1 got: %d", schemaMTU, v)
}
return nil
}},
schemaModel: {
Type: schema.TypeString,
Required: true,
ForceNew: false,
ValidateDiagFunc: func(i interface{}, k cty.Path) diag.Diagnostics {
v := i.(string)
if err := pxapi.QemuNetworkModel(v).Validate(); err != nil {
return diag.Errorf("invalid network %s: %s", schemaModel, v)
}
return nil
}},
schemaNativeVlan: {
Type: schema.TypeInt,
Optional: true,
Description: "VLAN tag.",
ValidateDiagFunc: func(i interface{}, k cty.Path) diag.Diagnostics {
v := i.(int)
if v < 0 {
return diag.Errorf("%s must be equal or greater than 0, got: %d", schemaNativeVlan, v)
}
return nil
}},
schemaQueues: {
Type: schema.TypeInt,
Optional: true,
ValidateDiagFunc: func(i interface{}, k cty.Path) diag.Diagnostics {
v := i.(int)
if v < 0 {
return diag.Errorf("%s must be equal or greater than 0, got: %d", schemaQueues, v)
}
if err := pxapi.QemuNetworkQueue(v).Validate(); err != nil {
return diag.Errorf("%s must be in the range 0 - %d, got: %d", schemaQueues, pxapi.QemuNetworkQueueMaximum, v)
}
return nil
}},
schemaRate: {
Type: schema.TypeInt,
Optional: true,
ValidateDiagFunc: func(i interface{}, k cty.Path) diag.Diagnostics {
v := i.(int)
if v < 0 {
return diag.Errorf("%s must be equal or greater than 0, got: %d", schemaRate, v)
}
return nil
}}}}}
}
61 changes: 61 additions & 0 deletions proxmox/Internal/resource/guest/qemu/network/sdk.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package network

import (
"fmt"
"net"
"strconv"
"strings"

pxapi "github.com/Telmate/proxmox-api-go/proxmox"
"github.com/Telmate/terraform-provider-proxmox/v2/proxmox/Internal/util"
"github.com/hashicorp/terraform-plugin-sdk/v2/diag"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

// Converts the Terraform configuration to the SDK configuration
func SDK(d *schema.ResourceData) (pxapi.QemuNetworkInterfaces, diag.Diagnostics) {
networks := make(pxapi.QemuNetworkInterfaces, maximumNetworkInterfaces)
for i := 0; i < maximumNetworkInterfaces; i++ {
networks[pxapi.QemuNetworkInterfaceID(i)] = pxapi.QemuNetworkInterface{Delete: true}
}
var diags diag.Diagnostics
for _, e := range d.Get(Root).([]interface{}) {
networkMap := e.(map[string]interface{})
id := pxapi.QemuNetworkInterfaceID(networkMap[schemaID].(int))
if v, duplicate := networks[id]; duplicate {
if !v.Delete {
diags = append(diags, diag.Errorf("Duplicate network interface %s %d", schemaID, id)...)
}
}
tmpMAC, _ := net.ParseMAC(networkMap[schemaMAC].(string))
mtu := networkMap[schemaMTU].(int)
var tmpMTU pxapi.QemuMTU
model := pxapi.QemuNetworkModel(networkMap[schemaModel].(string))
if mtu != 0 {
if string(pxapi.QemuNetworkModelVirtIO) == model.String() {
if mtu == 1 {
tmpMTU = pxapi.QemuMTU{Inherit: false}
} else {
tmpMTU = pxapi.QemuMTU{Value: pxapi.MTU(mtu)}
}
} else {
diags = append(diags, diag.Diagnostic{
Severity: diag.Warning,
Summary: fmt.Sprintf("%s is only supported when model is %s", schemaMTU, pxapi.QemuNetworkModelVirtIO)})
}
}
rateString, _, _ := strings.Cut(strconv.Itoa(networkMap[schemaRate].(int)), ".")
rate, _ := strconv.ParseInt(rateString, 10, 64)
networks[id] = pxapi.QemuNetworkInterface{
Bridge: util.Pointer(networkMap[schemaBridge].(string)),
Connected: util.Pointer(!networkMap[schemaLinkDown].(bool)),
Delete: false,
Firewall: util.Pointer(networkMap[schemaFirewall].(bool)),
MAC: &tmpMAC,
MTU: &tmpMTU,
Model: util.Pointer(model),
MultiQueue: util.Pointer(pxapi.QemuNetworkQueue(networkMap[schemaQueues].(int))),
RateLimitKBps: util.Pointer(pxapi.QemuNetworkRate(rate))}
}
return networks, diags
}
52 changes: 52 additions & 0 deletions proxmox/Internal/resource/guest/qemu/network/terraform.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package network

import (
pxapi "github.com/Telmate/proxmox-api-go/proxmox"
"github.com/hashicorp/terraform-plugin-sdk/v2/helper/schema"
)

// Converts the SDK configuration to the Terraform configuration
func Terraform(config pxapi.QemuNetworkInterfaces, d *schema.ResourceData) {
paramMap := make([]interface{}, 0, len(config))
for i := 0; i < maximumNetworkInterfaces; i++ {
v, ok := config[pxapi.QemuNetworkInterfaceID(i)]
if !ok {
continue
}
params := map[string]interface{}{
schemaID: int(i)}
if v.Bridge != nil {
params[schemaBridge] = string(*v.Bridge)
}
if v.Connected != nil {
params[schemaLinkDown] = !*v.Connected
}
if v.Firewall != nil {
params[schemaFirewall] = *v.Firewall
}
if v.MAC != nil {
params[schemaMAC] = v.MAC.String()
}
if v.MTU != nil {
if v.MTU.Inherit {
params[schemaMTU] = 1
} else {
params[schemaMTU] = int(v.MTU.Value)
}
}
if v.Model != nil {
params[schemaModel] = string(*v.Model)
}
if v.MultiQueue != nil {
params[schemaQueues] = int(*v.MultiQueue)
}
if v.RateLimitKBps != nil {
params[schemaRate] = int(*v.RateLimitKBps * 1000)
}
if v.NativeVlan != nil {
params[schemaNativeVlan] = int(*v.NativeVlan)
}
paramMap = append(paramMap, params)
}
d.Set(Root, paramMap)
}
6 changes: 6 additions & 0 deletions proxmox/Internal/util/util.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package util

// Gets inlined by the compiler, so it's not a performance hit
func Pointer[T any](v T) *T {
return &v
}
Loading

0 comments on commit 8e4bad1

Please sign in to comment.