Skip to content

Commit

Permalink
Merge pull request #359 from Tinyblargon/#350
Browse files Browse the repository at this point in the history
feat: `ConfigQemu.Serials` Reimplement
  • Loading branch information
Tinyblargon authored Aug 27, 2024
2 parents b6deb23 + 0430b85 commit 0d787af
Show file tree
Hide file tree
Showing 4 changed files with 421 additions and 96 deletions.
151 changes: 56 additions & 95 deletions proxmox/config_qemu.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,50 +30,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
Storage string `json:"storage,omitempty"` // this value is only used when doing a full clone and is never returned
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"`
QemuSerials QemuDevices `json:"serial,omitempty"` // TODO should be a struct
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
Smbios1 string `json:"smbios1,omitempty"` // TODO should be custom type with enum?
Startup string `json:"startup,omitempty"` // TODO should be a struct?
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
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
}

const (
Expand Down Expand Up @@ -128,9 +128,6 @@ func (config *ConfigQemu) defaults() {
if config.QemuPCIDevices == nil {
config.QemuPCIDevices = QemuDevices{}
}
if config.QemuSerials == nil {
config.QemuSerials = QemuDevices{}
}
if config.QemuUnusedDisks == nil {
config.QemuUnusedDisks = QemuDevices{}
}
Expand Down Expand Up @@ -256,6 +253,9 @@ func (config ConfigQemu) mapToAPI(currentConfig ConfigQemu, version Version) (re
if config.Memory != nil {
itemsToDelete += config.Memory.mapToAPI(currentConfig.Memory, params)
}
if config.Serials != nil {
itemsToDelete += config.Serials.mapToAPI(currentConfig.Serials, params)
}

// Create EFI disk
config.CreateQemuEfiParams(params)
Expand All @@ -272,8 +272,6 @@ func (config ConfigQemu) mapToAPI(currentConfig ConfigQemu, version Version) (re
if len(vgaParam) > 0 {
params["vga"] = strings.Join(vgaParam, ",")
}
// Create serial interfaces
config.CreateQemuSerialsParams(params)

// Create usb interfaces
config.CreateQemuUsbsParams(params)
Expand Down Expand Up @@ -483,32 +481,7 @@ func (ConfigQemu) mapToStruct(vmr *VmRef, params map[string]interface{}) (*Confi
}
}

// Add serials
serialNames := []string{}

for k := range params {
if serialName := rxSerialName.FindStringSubmatch(k); len(serialName) > 0 {
serialNames = append(serialNames, serialName[0])
}
}

if len(serialNames) > 0 {
config.QemuSerials = QemuDevices{}
for _, serialName := range serialNames {
id := rxDeviceID.FindStringSubmatch(serialName)
serialID, _ := strconv.Atoi(id[0])

serialConfMap := QemuDevice{
"id": serialID,
"type": params[serialName],
}

// And device config to serials map.
if len(serialConfMap) > 0 {
config.QemuSerials[serialID] = serialConfMap
}
}
}
config.Serials = SerialInterfaces{}.mapToSDK(params)

// Add usbs
usbNames := []string{}
Expand Down Expand Up @@ -827,6 +800,11 @@ func (config ConfigQemu) Validate(current *ConfigQemu, version Version) (err err
return
}
}
if len(config.Serials) > 0 {
if err = config.Serials.Validate(); err != nil {
return
}
}
if config.Tags != nil {
if err := Tag("").validate(*config.Tags); err != nil {
return err
Expand All @@ -850,16 +828,13 @@ storage:xxx
*/
func (config ConfigQemu) CloneVm(sourceVmr *VmRef, vmr *VmRef, client *Client) (err error) {
vmr.SetVmType("qemu")
storage := config.Storage
var storage string
fullClone := "1"
if config.FullClone != nil {
fullClone = strconv.Itoa(*config.FullClone)
}
// if storage is not set, use the storage of the first disk
if storage == "" && len(config.QemuDisks) > 0 {
if disk0Storage, ok := config.QemuDisks[0]["storage"].(string); ok && len(disk0Storage) > 0 {
storage = disk0Storage
}
if disk0Storage, ok := config.QemuDisks[0]["storage"].(string); ok && len(disk0Storage) > 0 {
storage = disk0Storage
}
params := map[string]interface{}{
"newid": vmr.vmId,
Expand Down Expand Up @@ -893,7 +868,6 @@ var (
rxUnusedDiskName = regexp.MustCompile(`^(unused)\d+`)
rxNicName = regexp.MustCompile(`net\d+`)
rxMpName = regexp.MustCompile(`mp\d+`)
rxSerialName = regexp.MustCompile(`serial\d+`)
rxUsbName = regexp.MustCompile(`usb\d+`)
rxPCIName = regexp.MustCompile(`hostpci\d+`)
)
Expand Down Expand Up @@ -1292,19 +1266,6 @@ func (c ConfigQemu) CreateQemuPCIsParams(params map[string]interface{}) {
}
}

// Create parameters for serial interface
func (c ConfigQemu) CreateQemuSerialsParams(params map[string]interface{}) {
// For new style with multi disk device.
for serialID, serialConfMap := range c.QemuSerials {
// Device name.
deviceType := serialConfMap["type"].(string)
qemuSerialName := "serial" + strconv.Itoa(serialID)

// Add back to Qemu prams.
params[qemuSerialName] = deviceType
}
}

// Create parameters for usb interface
func (c ConfigQemu) CreateQemuUsbsParams(params map[string]interface{}) {
for usbID, usbConfMap := range c.QemuUsbs {
Expand Down
149 changes: 149 additions & 0 deletions proxmox/config_qemu_serial.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
package proxmox

import (
"errors"
"regexp"
"strconv"
)

type SerialID uint8

const (
SerialID0 SerialID = 0
SerialID1 SerialID = 1
SerialID2 SerialID = 2
SerialID3 SerialID = 3
SerialID_Errors_Invalid string = "serial id must be one of 0,1,2,3"
)

func (id SerialID) String() string {
return strconv.Itoa(int(id))
}

func (id SerialID) Validate() error {
if id > 3 {
return errors.New(SerialID_Errors_Invalid)
}
return nil
}

type SerialInterface struct {
Delete bool `json:"delete,omitempty"` // If true, the serial adapter will be removed.
Path SerialPath `json:"path,omitempty"` // Path to the serial device. Mutually exclusive with socket.
Socket bool `json:"socket,omitempty"` // If true, the serial device is a socket. Mutually exclusive with path.
}

const (
SerialInterface_Errors_MutualExclusive string = "path and socket are mutually exclusive"
SerialInterface_Errors_Empty string = "path or socket must be set"
)

var regexSerialPortPath = regexp.MustCompile(`^/dev/.+$`)

func (port SerialInterface) mapToAPI(id SerialID, params map[string]interface{}) {
tmpPath := "socket"
if !port.Socket {
tmpPath = string(port.Path)
}
params["serial"+id.String()] = tmpPath
}

func (SerialInterface) mapToSDK(v string) SerialInterface {
if v == "socket" {
return SerialInterface{
Socket: true}
}
return SerialInterface{
Path: SerialPath(v),
}
}

func (port SerialInterface) Validate() error {
if port.Delete {
return nil
}
if port.Path != "" && port.Socket {
return errors.New(SerialInterface_Errors_MutualExclusive)
}
if !port.Socket {
if port.Path == "" {
return errors.New(SerialInterface_Errors_Empty)
}
if err := port.Path.Validate(); err != nil {
return err
}
}
return nil
}

type SerialInterfaces map[SerialID]SerialInterface

func (config SerialInterfaces) mapToAPI(current SerialInterfaces, params map[string]interface{}) (delete string) {
if len(current) != 0 { // Update
for id, port := range config {
if _, ok := current[id]; ok {
if port.Delete {
delete += ",serial" + id.String()
continue
}
if current[id].Path != port.Path || current[id].Socket != port.Socket {
port.mapToAPI(id, params)
}
} else if !port.Delete {
port.mapToAPI(id, params)
}
}
return
}
// Create
for id, port := range config {
if !port.Delete {
port.mapToAPI(id, params)
}
}
return
}

func (SerialInterfaces) mapToSDK(params map[string]interface{}) SerialInterfaces {
Serials := SerialInterfaces{}
if v, isSet := params["serial0"]; isSet {
Serials[SerialID0] = SerialInterface{}.mapToSDK(v.(string))
}
if v, isSet := params["serial1"]; isSet {
Serials[SerialID1] = SerialInterface{}.mapToSDK(v.(string))
}
if v, isSet := params["serial2"]; isSet {
Serials[SerialID2] = SerialInterface{}.mapToSDK(v.(string))
}
if v, isSet := params["serial3"]; isSet {
Serials[SerialID3] = SerialInterface{}.mapToSDK(v.(string))
}
if len(Serials) > 0 {
return Serials
}
return nil
}

func (s SerialInterfaces) Validate() error {
for id, e := range s {
if err := id.Validate(); err != nil {
return err
}
if err := e.Validate(); err != nil {
return err
}
}
return nil
}

type SerialPath string

const SerialPath_Errors_Invalid string = "path must start with /dev/"

func (path SerialPath) Validate() error {
matches, _ := regexp.MatchString(regexSerialPortPath.String(), string(path))
if !matches {
return errors.New(SerialPath_Errors_Invalid)
}
return nil
}
Loading

0 comments on commit 0d787af

Please sign in to comment.