Skip to content

Commit

Permalink
Merge pull request #365 from Tinyblargon/#341
Browse files Browse the repository at this point in the history
 Overhaul: Qemu Network interfaces.
  • Loading branch information
Tinyblargon authored Oct 28, 2024
2 parents 9a3f359 + 07cc687 commit b38644d
Show file tree
Hide file tree
Showing 18 changed files with 1,733 additions and 303 deletions.
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 @@ -261,19 +261,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 @@ -505,32 +505,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

0 comments on commit b38644d

Please sign in to comment.