Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Overhaul: Qemu Network interfaces. #365

Merged
merged 13 commits into from
Oct 28, 2024
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,6 @@ proxmox-api-go
.vagrant/
.vscode
.env

coverage.html
coverage.out
264 changes: 56 additions & 208 deletions proxmox/config_qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,6 @@ import (
"errors"
"fmt"
"log"
"math/rand"
"net"
"regexp"
"strconv"
"strings"
Expand All @@ -30,50 +28,50 @@ type (

// ConfigQemu - Proxmox API QEMU options
type ConfigQemu struct {
Agent *QemuGuestAgent `json:"agent,omitempty"`
Args string `json:"args,omitempty"`
Bios string `json:"bios,omitempty"`
Boot string `json:"boot,omitempty"` // TODO should be an array of custom enums
BootDisk string `json:"bootdisk,omitempty"` // TODO discuss deprecation? Only returned as it's deprecated in the proxmox api
CPU *QemuCPU `json:"cpu,omitempty"`
CloudInit *CloudInit `json:"cloudinit,omitempty"`
Description *string `json:"description,omitempty"`
Disks *QemuStorages `json:"disks,omitempty"`
EFIDisk QemuDevice `json:"efidisk,omitempty"` // TODO should be a struct
FullClone *int `json:"fullclone,omitempty"` // TODO should probably be a bool
HaGroup string `json:"hagroup,omitempty"`
HaState string `json:"hastate,omitempty"` // TODO should be custom type with enum
Hookscript string `json:"hookscript,omitempty"`
Hotplug string `json:"hotplug,omitempty"` // TODO should be a struct
Iso *IsoFile `json:"iso,omitempty"` // Same as Disks.Ide.Disk_2.CdRom.Iso
LinkedVmId uint `json:"linked_id,omitempty"` // Only returned setting it has no effect
Machine string `json:"machine,omitempty"` // TODO should be custom type with enum
Memory *QemuMemory `json:"memory,omitempty"`
Name string `json:"name,omitempty"` // TODO should be custom type as there are character and length limitations
Node string `json:"node,omitempty"` // Only returned setting it has no effect, set node in the VmRef instead
Onboot *bool `json:"onboot,omitempty"`
Pool *PoolName `json:"pool,omitempty"`
Protection *bool `json:"protection,omitempty"`
QemuDisks QemuDevices `json:"disk,omitempty"` // DEPRECATED use Disks *QemuStorages instead
QemuIso string `json:"qemuiso,omitempty"` // DEPRECATED use Iso *IsoFile instead
QemuKVM *bool `json:"kvm,omitempty"`
QemuNetworks QemuDevices `json:"network,omitempty"` // TODO should be a struct
QemuOs string `json:"ostype,omitempty"`
QemuPCIDevices QemuDevices `json:"hostpci,omitempty"` // TODO should be a struct
QemuPxe bool `json:"pxe,omitempty"`
QemuUnusedDisks QemuDevices `json:"unused,omitempty"` // TODO should be a struct
QemuUsbs QemuDevices `json:"usb,omitempty"` // TODO should be a struct
QemuVga QemuDevice `json:"vga,omitempty"` // TODO should be a struct
RNGDrive QemuDevice `json:"rng0,omitempty"` // TODO should be a struct
Scsihw string `json:"scsihw,omitempty"` // TODO should be custom type with enum
Serials SerialInterfaces `json:"serials,omitempty"`
Smbios1 string `json:"smbios1,omitempty"` // TODO should be custom type with enum?
Startup string `json:"startup,omitempty"` // TODO should be a struct?
Storage string `json:"storage,omitempty"` // this value is only used when doing a full clone and is never returned
TPM *TpmState `json:"tpm,omitempty"`
Tablet *bool `json:"tablet,omitempty"`
Tags *[]Tag `json:"tags,omitempty"`
VmID int `json:"vmid,omitempty"` // TODO should be a custom type as there are limitations
Agent *QemuGuestAgent `json:"agent,omitempty"`
Args string `json:"args,omitempty"`
Bios string `json:"bios,omitempty"`
Boot string `json:"boot,omitempty"` // TODO should be an array of custom enums
BootDisk string `json:"bootdisk,omitempty"` // TODO discuss deprecation? Only returned as it's deprecated in the proxmox api
CPU *QemuCPU `json:"cpu,omitempty"`
CloudInit *CloudInit `json:"cloudinit,omitempty"`
Description *string `json:"description,omitempty"`
Disks *QemuStorages `json:"disks,omitempty"`
EFIDisk QemuDevice `json:"efidisk,omitempty"` // TODO should be a struct
FullClone *int `json:"fullclone,omitempty"` // TODO should probably be a bool
HaGroup string `json:"hagroup,omitempty"`
HaState string `json:"hastate,omitempty"` // TODO should be custom type with enum
Hookscript string `json:"hookscript,omitempty"`
Hotplug string `json:"hotplug,omitempty"` // TODO should be a struct
Iso *IsoFile `json:"iso,omitempty"` // Same as Disks.Ide.Disk_2.CdRom.Iso
LinkedVmId uint `json:"linked_id,omitempty"` // Only returned setting it has no effect
Machine string `json:"machine,omitempty"` // TODO should be custom type with enum
Memory *QemuMemory `json:"memory,omitempty"`
Name string `json:"name,omitempty"` // TODO should be custom type as there are character and length limitations
Networks QemuNetworkInterfaces `json:"networks,omitempty"`
Node string `json:"node,omitempty"` // Only returned setting it has no effect, set node in the VmRef instead
Onboot *bool `json:"onboot,omitempty"`
Pool *PoolName `json:"pool,omitempty"`
Protection *bool `json:"protection,omitempty"`
QemuDisks QemuDevices `json:"disk,omitempty"` // DEPRECATED use Disks *QemuStorages instead
QemuIso string `json:"qemuiso,omitempty"` // DEPRECATED use Iso *IsoFile instead
QemuKVM *bool `json:"kvm,omitempty"`
QemuOs string `json:"ostype,omitempty"`
QemuPCIDevices QemuDevices `json:"hostpci,omitempty"` // TODO should be a struct
QemuPxe bool `json:"pxe,omitempty"`
QemuUnusedDisks QemuDevices `json:"unused,omitempty"` // TODO should be a struct
QemuUsbs QemuDevices `json:"usb,omitempty"` // TODO should be a struct
QemuVga QemuDevice `json:"vga,omitempty"` // TODO should be a struct
RNGDrive QemuDevice `json:"rng0,omitempty"` // TODO should be a struct
Scsihw string `json:"scsihw,omitempty"` // TODO should be custom type with enum
Serials SerialInterfaces `json:"serials,omitempty"`
Smbios1 string `json:"smbios1,omitempty"` // TODO should be custom type with enum?
Startup string `json:"startup,omitempty"` // TODO should be a struct?
Storage string `json:"storage,omitempty"` // this value is only used when doing a full clone and is never returned
TPM *TpmState `json:"tpm,omitempty"`
Tablet *bool `json:"tablet,omitempty"`
Tags *[]Tag `json:"tags,omitempty"`
VmID int `json:"vmid,omitempty"` // TODO should be a custom type as there are limitations
}

const (
Expand Down Expand Up @@ -119,9 +117,6 @@ func (config *ConfigQemu) defaults() {
if config.QemuKVM == nil {
config.QemuKVM = util.Pointer(true)
}
if config.QemuNetworks == nil {
config.QemuNetworks = QemuDevices{}
}
if config.QemuOs == "" {
config.QemuOs = "other"
}
Expand Down Expand Up @@ -264,7 +259,7 @@ func (config ConfigQemu) mapToAPI(currentConfig ConfigQemu, version Version) (re
config.CreateQemuRngParams(params)

// Create networks config.
config.CreateQemuNetworksParams(params)
itemsToDelete += config.Networks.mapToAPI(currentConfig.Networks, params)

// Create vga config.
vgaParam := QemuDeviceParam{}
Expand Down Expand Up @@ -433,54 +428,7 @@ func (ConfigQemu) mapToStruct(vmr *VmRef, params map[string]interface{}) (*Confi
}
}

// Add networks.
nicNames := []string{}

for k := range params {
if nicName := rxNicName.FindStringSubmatch(k); len(nicName) > 0 {
nicNames = append(nicNames, nicName[0])
}
}

if len(nicNames) > 0 {
config.QemuNetworks = QemuDevices{}
for _, nicName := range nicNames {
nicConfStr := params[nicName]
nicConfList := strings.Split(nicConfStr.(string), ",")

id := rxDeviceID.FindStringSubmatch(nicName)
nicID, _ := strconv.Atoi(id[0])
model, macaddr := ParseSubConf(nicConfList[0], "=")

// Add model and MAC address.
nicConfMap := QemuDevice{
"id": nicID,
"model": model,
"macaddr": macaddr,
}

// Add rest of device config.
nicConfMap.readDeviceConfig(nicConfList[1:])
switch nicConfMap["firewall"] {
case 1:
nicConfMap["firewall"] = true
case 0:
nicConfMap["firewall"] = false
}
switch nicConfMap["link_down"] {
case 1:
nicConfMap["link_down"] = true
case 0:
nicConfMap["link_down"] = false
}

// And device config to networks.
if len(nicConfMap) > 0 {
config.QemuNetworks[nicID] = nicConfMap
}
}
}

config.Networks = QemuNetworkInterfaces{}.mapToSDK(params)
config.Serials = SerialInterfaces{}.mapToSDK(params)

// Add usbs
Expand Down Expand Up @@ -756,6 +704,11 @@ func (config ConfigQemu) Validate(current *ConfigQemu, version Version) (err err
return
}
}
if config.Networks != nil {
if err = config.Networks.Validate(nil); err != nil {
return
}
}
if config.TPM != nil {
if err = config.TPM.Validate(nil); err != nil {
return
Expand All @@ -772,6 +725,11 @@ func (config ConfigQemu) Validate(current *ConfigQemu, version Version) (err err
return
}
}
if config.Networks != nil {
if err = config.Networks.Validate(current.Networks); err != nil {
return
}
}
if config.TPM != nil {
if err = config.TPM.Validate(current.TPM); err != nil {
return
Expand Down Expand Up @@ -1123,70 +1081,6 @@ func FormatUsbParam(usb QemuDevice) string {
return strings.Join(usbConfParam, ",")
}

// Create parameters for each Nic device.
func (c ConfigQemu) CreateQemuNetworksParams(params map[string]interface{}) {
// For new style with multi net device.
for nicID, nicConfMap := range c.QemuNetworks {

nicConfParam := QemuDeviceParam{}

// Set Nic name.
qemuNicName := "net" + strconv.Itoa(nicID)

// Set Mac address.
var macAddr string
switch nicConfMap["macaddr"] {
case nil, "":
// Generate random Mac based on time
macaddr := make(net.HardwareAddr, 6)
r := rand.New(rand.NewSource(time.Now().UnixNano()))
r.Read(macaddr)
macaddr[0] = (macaddr[0] | 2) & 0xfe // fix from github issue #18
macAddr = strings.ToUpper(fmt.Sprintf("%v", macaddr))

// Add Mac to source map so it will be returned. (useful for some use case like Terraform)
nicConfMap["macaddr"] = macAddr
case "repeatable":
// Generate deterministic Mac based on VmID and NicID
// Assume that rare VM has more than 32 nics
macaddr := make(net.HardwareAddr, 6)
pairing := c.VmID<<5 | nicID
// Linux MAC vendor - 00:18:59
macaddr[0] = 0x00
macaddr[1] = 0x18
macaddr[2] = 0x59
macaddr[3] = byte((pairing >> 16) & 0xff)
macaddr[4] = byte((pairing >> 8) & 0xff)
macaddr[5] = byte(pairing & 0xff)
// Convert to string
macAddr = strings.ToUpper(fmt.Sprintf("%v", macaddr))

// Add Mac to source map so it will be returned. (useful for some use case like Terraform)
nicConfMap["macaddr"] = macAddr
default:
macAddr = nicConfMap["macaddr"].(string)
}

// use model=mac format for older proxmox compatibility as the parameters which will be sent to Proxmox API.
nicConfParam = append(nicConfParam, fmt.Sprintf("%v=%v", nicConfMap["model"], macAddr))

// Set bridge if not nat.
if nicConfMap["bridge"].(string) != "nat" {
bridge := fmt.Sprintf("bridge=%v", nicConfMap["bridge"])
nicConfParam = append(nicConfParam, bridge)
}

// Keys that are not used as real/direct conf.
ignoredKeys := []string{"id", "bridge", "macaddr", "model"}

// Rest of config.
nicConfParam = nicConfParam.createDeviceParam(nicConfMap, ignoredKeys)

// Add nic to Qemu prams.
params[qemuNicName] = strings.Join(nicConfParam, ",")
}
}

// Create RNG parameter.
func (c ConfigQemu) CreateQemuRngParams(params map[string]interface{}) {
rngParam := QemuDeviceParam{}
Expand Down Expand Up @@ -1330,49 +1224,3 @@ func (c ConfigQemu) String() string {
jsConf, _ := json.Marshal(c)
return string(jsConf)
}

type QemuNetworkInterfaceID uint8

const (
QemuNetworkInterfaceID_Error_Invalid string = "network interface ID must be in the range 0-31"

QemuNetworkInterfaceID0 QemuNetworkInterfaceID = 0
QemuNetworkInterfaceID1 QemuNetworkInterfaceID = 1
QemuNetworkInterfaceID2 QemuNetworkInterfaceID = 2
QemuNetworkInterfaceID3 QemuNetworkInterfaceID = 3
QemuNetworkInterfaceID4 QemuNetworkInterfaceID = 4
QemuNetworkInterfaceID5 QemuNetworkInterfaceID = 5
QemuNetworkInterfaceID6 QemuNetworkInterfaceID = 6
QemuNetworkInterfaceID7 QemuNetworkInterfaceID = 7
QemuNetworkInterfaceID8 QemuNetworkInterfaceID = 8
QemuNetworkInterfaceID9 QemuNetworkInterfaceID = 9
QemuNetworkInterfaceID10 QemuNetworkInterfaceID = 10
QemuNetworkInterfaceID11 QemuNetworkInterfaceID = 11
QemuNetworkInterfaceID12 QemuNetworkInterfaceID = 12
QemuNetworkInterfaceID13 QemuNetworkInterfaceID = 13
QemuNetworkInterfaceID14 QemuNetworkInterfaceID = 14
QemuNetworkInterfaceID15 QemuNetworkInterfaceID = 15
QemuNetworkInterfaceID16 QemuNetworkInterfaceID = 16
QemuNetworkInterfaceID17 QemuNetworkInterfaceID = 17
QemuNetworkInterfaceID18 QemuNetworkInterfaceID = 18
QemuNetworkInterfaceID19 QemuNetworkInterfaceID = 19
QemuNetworkInterfaceID20 QemuNetworkInterfaceID = 20
QemuNetworkInterfaceID21 QemuNetworkInterfaceID = 21
QemuNetworkInterfaceID22 QemuNetworkInterfaceID = 22
QemuNetworkInterfaceID23 QemuNetworkInterfaceID = 23
QemuNetworkInterfaceID24 QemuNetworkInterfaceID = 24
QemuNetworkInterfaceID25 QemuNetworkInterfaceID = 25
QemuNetworkInterfaceID26 QemuNetworkInterfaceID = 26
QemuNetworkInterfaceID27 QemuNetworkInterfaceID = 27
QemuNetworkInterfaceID28 QemuNetworkInterfaceID = 28
QemuNetworkInterfaceID29 QemuNetworkInterfaceID = 29
QemuNetworkInterfaceID30 QemuNetworkInterfaceID = 30
QemuNetworkInterfaceID31 QemuNetworkInterfaceID = 31
)

func (id QemuNetworkInterfaceID) Validate() error {
if id > 31 {
return errors.New(QemuNetworkInterfaceID_Error_Invalid)
}
return nil
}
22 changes: 11 additions & 11 deletions proxmox/config_qemu_cloudinit.go
Original file line number Diff line number Diff line change
Expand Up @@ -258,19 +258,19 @@ func (CloudInitCustom) mapToSDK(raw string) *CloudInitCustom {
var config CloudInitCustom
params := splitStringOfSettings(raw)
if v, isSet := params["meta"]; isSet {
config.Meta = CloudInitSnippet{}.mapToSDK(v.(string))
config.Meta = CloudInitSnippet{}.mapToSDK(v)
set = true
}
if v, isSet := params["network"]; isSet {
config.Network = CloudInitSnippet{}.mapToSDK(v.(string))
config.Network = CloudInitSnippet{}.mapToSDK(v)
set = true
}
if v, isSet := params["user"]; isSet {
config.User = CloudInitSnippet{}.mapToSDK(v.(string))
config.User = CloudInitSnippet{}.mapToSDK(v)
set = true
}
if v, isSet := params["vendor"]; isSet {
config.Vendor = CloudInitSnippet{}.mapToSDK(v.(string))
config.Vendor = CloudInitSnippet{}.mapToSDK(v)
set = true
}
if set {
Expand Down Expand Up @@ -502,32 +502,32 @@ func (CloudInitNetworkConfig) mapToSDK(param string) (config CloudInitNetworkCon
var ipv6 CloudInitIPv6Config
if v, isSet := params["ip"]; isSet {
ipv4Set = true
if v.(string) == "dhcp" {
if v == "dhcp" {
ipv4.DHCP = true
} else {
tmp := IPv4CIDR(v.(string))
tmp := IPv4CIDR(v)
ipv4.Address = &tmp
}
}
if v, isSet := params["gw"]; isSet {
ipv4Set = true
tmp := IPv4Address(v.(string))
tmp := IPv4Address(v)
ipv4.Gateway = &tmp
}
if v, isSet := params["ip6"]; isSet {
ipv6Set = true
if v.(string) == "dhcp" {
if v == "dhcp" {
ipv6.DHCP = true
} else if v.(string) == "auto" {
} else if v == "auto" {
ipv6.SLAAC = true
} else {
tmp := IPv6CIDR(v.(string))
tmp := IPv6CIDR(v)
ipv6.Address = &tmp
}
}
if v, isSet := params["gw6"]; isSet {
ipv6Set = true
tmp := IPv6Address(v.(string))
tmp := IPv6Address(v)
ipv6.Gateway = &tmp
}
if ipv4Set {
Expand Down
Loading
Loading