packer-cn/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_qemu.go

1012 lines
26 KiB
Go

package proxmox
import (
"encoding/json"
"errors"
"fmt"
"io"
"log"
"math/rand"
"net"
"net/url"
"regexp"
"strconv"
"strings"
"time"
)
type (
QemuDevices map[int]map[string]interface{}
QemuDevice map[string]interface{}
QemuDeviceParam []string
)
// ConfigQemu - Proxmox API QEMU options
type ConfigQemu struct {
VmID int `json:"vmid"`
Name string `json:"name"`
Description string `json:"desc"`
Pool string `json:"pool,omitempty"`
Bios string `json:"bios"`
Onboot bool `json:"onboot"`
Agent int `json:"agent"`
Memory int `json:"memory"`
Balloon int `json:"balloon"`
QemuOs string `json:"os"`
QemuCores int `json:"cores"`
QemuSockets int `json:"sockets"`
QemuVcpus int `json:"vcpus"`
QemuCpu string `json:"cpu"`
QemuNuma bool `json:"numa"`
QemuKVM bool `json:"kvm"`
Hotplug string `json:"hotplug"`
QemuIso string `json:"iso"`
FullClone *int `json:"fullclone"`
Boot string `json:"boot"`
BootDisk string `json:"bootdisk,omitempty"`
Scsihw string `json:"scsihw,omitempty"`
QemuDisks QemuDevices `json:"disk"`
QemuVga QemuDevice `json:"vga,omitempty"`
QemuNetworks QemuDevices `json:"network"`
QemuSerials QemuDevices `json:"serial,omitempty"`
HaState string `json:"hastate,omitempty"`
// Deprecated single disk.
DiskSize float64 `json:"diskGB"`
Storage string `json:"storage"`
StorageType string `json:"storageType"` // virtio|scsi (cloud-init defaults to scsi)
// Deprecated single nic.
QemuNicModel string `json:"nic"`
QemuBrige string `json:"bridge"`
QemuVlanTag int `json:"vlan"`
QemuMacAddr string `json:"mac"`
// cloud-init options
CIuser string `json:"ciuser"`
CIpassword string `json:"cipassword"`
CIcustom string `json:"cicustom"`
Searchdomain string `json:"searchdomain"`
Nameserver string `json:"nameserver"`
Sshkeys string `json:"sshkeys"`
// arrays are hard, support 3 interfaces for now
Ipconfig0 string `json:"ipconfig0"`
Ipconfig1 string `json:"ipconfig1"`
Ipconfig2 string `json:"ipconfig2"`
}
// CreateVm - Tell Proxmox API to make the VM
func (config ConfigQemu) CreateVm(vmr *VmRef, client *Client) (err error) {
if config.HasCloudInit() {
return errors.New("Cloud-init parameters only supported on clones or updates")
}
vmr.SetVmType("qemu")
params := map[string]interface{}{
"vmid": vmr.vmId,
"name": config.Name,
"onboot": config.Onboot,
"agent": config.Agent,
"ide2": config.QemuIso + ",media=cdrom",
"ostype": config.QemuOs,
"sockets": config.QemuSockets,
"cores": config.QemuCores,
"cpu": config.QemuCpu,
"numa": config.QemuNuma,
"kvm": config.QemuKVM,
"hotplug": config.Hotplug,
"memory": config.Memory,
"boot": config.Boot,
"description": config.Description,
}
if config.Bios != "" {
params["bios"] = config.Bios
}
if config.Balloon >= 1 {
params["balloon"] = config.Balloon
}
if config.QemuVcpus >= 1 {
params["vcpus"] = config.QemuVcpus
}
if vmr.pool != "" {
params["pool"] = vmr.pool
}
if config.BootDisk != "" {
params["bootdisk"] = config.BootDisk
}
if config.Scsihw != "" {
params["scsihw"] = config.Scsihw
}
// Create disks config.
config.CreateQemuDisksParams(vmr.vmId, params, false)
// Create vga config.
vgaParam := QemuDeviceParam{}
vgaParam = vgaParam.createDeviceParam(config.QemuVga, nil)
if len(vgaParam) > 0 {
params["vga"] = strings.Join(vgaParam, ",")
}
// Create networks config.
config.CreateQemuNetworksParams(vmr.vmId, params)
// Create serial interfaces
config.CreateQemuSerialsParams(vmr.vmId, params)
exitStatus, err := client.CreateQemuVm(vmr.node, params)
if err != nil {
return fmt.Errorf("Error creating VM: %v, error status: %s (params: %v)", err, exitStatus, params)
}
client.UpdateVMHA(vmr, config.HaState)
return
}
// HasCloudInit - are there cloud-init options?
func (config ConfigQemu) HasCloudInit() bool {
return config.CIuser != "" ||
config.CIpassword != "" ||
config.Searchdomain != "" ||
config.Nameserver != "" ||
config.Sshkeys != "" ||
config.Ipconfig0 != "" ||
config.Ipconfig1 != "" ||
config.CIcustom != ""
}
/*
CloneVm
Example: Request
nodes/proxmox1-xx/qemu/1012/clone
newid:145
name:tf-clone1
target:proxmox1-xx
full:1
storage:xxx
*/
func (config ConfigQemu) CloneVm(sourceVmr *VmRef, vmr *VmRef, client *Client) (err error) {
vmr.SetVmType("qemu")
fullclone := "1"
if config.FullClone != nil {
fullclone = strconv.Itoa(*config.FullClone)
}
storage := config.Storage
if disk0Storage, ok := config.QemuDisks[0]["storage"].(string); ok && len(disk0Storage) > 0 {
storage = disk0Storage
}
params := map[string]interface{}{
"newid": vmr.vmId,
"target": vmr.node,
"name": config.Name,
"full": fullclone,
}
if vmr.pool != "" {
params["pool"] = vmr.pool
}
if fullclone == "1" {
params["storage"] = storage
}
_, err = client.CloneQemuVm(sourceVmr, params)
return err
}
func (config ConfigQemu) UpdateConfig(vmr *VmRef, client *Client) (err error) {
configParams := map[string]interface{}{
"name": config.Name,
"description": config.Description,
"onboot": config.Onboot,
"agent": config.Agent,
"sockets": config.QemuSockets,
"cores": config.QemuCores,
"cpu": config.QemuCpu,
"numa": config.QemuNuma,
"kvm": config.QemuKVM,
"hotplug": config.Hotplug,
"memory": config.Memory,
"boot": config.Boot,
}
//Array to list deleted parameters
deleteParams := []string{}
if config.Bios != "" {
configParams["bios"] = config.Bios
}
if config.Balloon >= 1 {
configParams["balloon"] = config.Balloon
} else {
deleteParams = append(deleteParams, "balloon")
}
if config.QemuVcpus >= 1 {
configParams["vcpus"] = config.QemuVcpus
} else {
deleteParams = append(deleteParams, "vcpus")
}
if config.BootDisk != "" {
configParams["bootdisk"] = config.BootDisk
}
if config.Scsihw != "" {
configParams["scsihw"] = config.Scsihw
}
// Create disks config.
configParamsDisk := map[string]interface{}{
"vmid": vmr.vmId,
}
config.CreateQemuDisksParams(vmr.vmId, configParamsDisk, false)
client.createVMDisks(vmr.node, configParamsDisk)
//Copy the disks to the global configParams
for key, value := range configParamsDisk {
//vmid is only required in createVMDisks
if key != "vmid" {
configParams[key] = value
}
}
// Create networks config.
config.CreateQemuNetworksParams(vmr.vmId, configParams)
// Create vga config.
vgaParam := QemuDeviceParam{}
vgaParam = vgaParam.createDeviceParam(config.QemuVga, nil)
if len(vgaParam) > 0 {
configParams["vga"] = strings.Join(vgaParam, ",")
} else {
deleteParams = append(deleteParams, "vga")
}
// Create serial interfaces
config.CreateQemuSerialsParams(vmr.vmId, configParams)
// cloud-init options
if config.CIuser != "" {
configParams["ciuser"] = config.CIuser
}
if config.CIpassword != "" {
configParams["cipassword"] = config.CIpassword
}
if config.CIcustom != "" {
configParams["cicustom"] = config.CIcustom
}
if config.Searchdomain != "" {
configParams["searchdomain"] = config.Searchdomain
}
if config.Nameserver != "" {
configParams["nameserver"] = config.Nameserver
}
if config.Sshkeys != "" {
sshkeyEnc := url.PathEscape(config.Sshkeys + "\n")
sshkeyEnc = strings.Replace(sshkeyEnc, "+", "%2B", -1)
sshkeyEnc = strings.Replace(sshkeyEnc, "@", "%40", -1)
sshkeyEnc = strings.Replace(sshkeyEnc, "=", "%3D", -1)
configParams["sshkeys"] = sshkeyEnc
}
if config.Ipconfig0 != "" {
configParams["ipconfig0"] = config.Ipconfig0
}
if config.Ipconfig1 != "" {
configParams["ipconfig1"] = config.Ipconfig1
}
if config.Ipconfig2 != "" {
configParams["ipconfig2"] = config.Ipconfig2
}
if len(deleteParams) > 0 {
configParams["delete"] = strings.Join(deleteParams, ", ")
}
_, err = client.SetVmConfig(vmr, configParams)
if err != nil {
log.Print(err)
return err
}
client.UpdateVMHA(vmr, config.HaState)
_, err = client.UpdateVMPool(vmr, config.Pool)
return err
}
func NewConfigQemuFromJson(io io.Reader) (config *ConfigQemu, err error) {
config = &ConfigQemu{QemuVlanTag: -1, QemuKVM: true}
err = json.NewDecoder(io).Decode(config)
if err != nil {
log.Fatal(err)
return nil, err
}
log.Println(config)
return
}
var (
rxIso = regexp.MustCompile(`(.*?),media`)
rxDeviceID = regexp.MustCompile(`\d+`)
rxDiskName = regexp.MustCompile(`(virtio|scsi)\d+`)
rxDiskType = regexp.MustCompile(`\D+`)
rxNicName = regexp.MustCompile(`net\d+`)
rxMpName = regexp.MustCompile(`mp\d+`)
rxSerialName = regexp.MustCompile(`serial\d+`)
)
func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err error) {
var vmConfig map[string]interface{}
for ii := 0; ii < 3; ii++ {
vmConfig, err = client.GetVmConfig(vmr)
if err != nil {
log.Fatal(err)
return nil, err
}
// this can happen:
// {"data":{"lock":"clone","digest":"eb54fb9d9f120ba0c3bdf694f73b10002c375c38","description":" qmclone temporary file\n"}})
if vmConfig["lock"] == nil {
break
} else {
time.Sleep(8 * time.Second)
}
}
if vmConfig["lock"] != nil {
return nil, errors.New("vm locked, could not obtain config")
}
// vmConfig Sample: map[ cpu:host
// net0:virtio=62:DF:XX:XX:XX:XX,bridge=vmbr0
// ide2:local:iso/xxx-xx.iso,media=cdrom memory:2048
// smbios1:uuid=8b3bf833-aad8-4545-xxx-xxxxxxx digest:aa6ce5xxxxx1b9ce33e4aaeff564d4 sockets:1
// name:terraform-ubuntu1404-template bootdisk:virtio0
// virtio0:ProxmoxxxxISCSI:vm-1014-disk-2,size=4G
// description:Base image
// cores:2 ostype:l26
name := ""
if _, isSet := vmConfig["name"]; isSet {
name = vmConfig["name"].(string)
}
description := ""
if _, isSet := vmConfig["description"]; isSet {
description = vmConfig["description"].(string)
}
bios := "seabios"
if _, isSet := vmConfig["bios"]; isSet {
bios = vmConfig["bios"].(string)
}
onboot := true
if _, isSet := vmConfig["onboot"]; isSet {
onboot = Itob(int(vmConfig["onboot"].(float64)))
}
agent := 0
if _, isSet := vmConfig["agent"]; isSet {
switch vmConfig["agent"].(type) {
case float64:
agent = int(vmConfig["agent"].(float64))
case string:
agent, _ = strconv.Atoi(vmConfig["agent"].(string))
}
}
ostype := "other"
if _, isSet := vmConfig["ostype"]; isSet {
ostype = vmConfig["ostype"].(string)
}
memory := 0.0
if _, isSet := vmConfig["memory"]; isSet {
memory = vmConfig["memory"].(float64)
}
balloon := 0.0
if _, isSet := vmConfig["balloon"]; isSet {
balloon = vmConfig["balloon"].(float64)
}
cores := 1.0
if _, isSet := vmConfig["cores"]; isSet {
cores = vmConfig["cores"].(float64)
}
vcpus := 0.0
if _, isSet := vmConfig["vcpus"]; isSet {
vcpus = vmConfig["vcpus"].(float64)
}
sockets := 1.0
if _, isSet := vmConfig["sockets"]; isSet {
sockets = vmConfig["sockets"].(float64)
}
cpu := "host"
if _, isSet := vmConfig["cpu"]; isSet {
cpu = vmConfig["cpu"].(string)
}
numa := false
if _, isSet := vmConfig["numa"]; isSet {
numa = Itob(int(vmConfig["numa"].(float64)))
}
//Can be network,disk,cpu,memory,usb
hotplug := "network,disk,usb"
if _, isSet := vmConfig["hotplug"]; isSet {
hotplug = vmConfig["hotplug"].(string)
}
//boot by default from hard disk (c), CD-ROM (d), network (n).
boot := "cdn"
if _, isSet := vmConfig["boot"]; isSet {
boot = vmConfig["boot"].(string)
}
bootdisk := ""
if _, isSet := vmConfig["bootdisk"]; isSet {
bootdisk = vmConfig["bootdisk"].(string)
}
kvm := true
if _, isSet := vmConfig["kvm"]; isSet {
kvm = Itob(int(vmConfig["kvm"].(float64)))
}
scsihw := "lsi"
if _, isSet := vmConfig["scsihw"]; isSet {
scsihw = vmConfig["scsihw"].(string)
}
hastate := ""
if _, isSet := vmConfig["hastate"]; isSet {
hastate = vmConfig["hastate"].(string)
}
config = &ConfigQemu{
Name: name,
Description: strings.TrimSpace(description),
Bios: bios,
Onboot: onboot,
Agent: agent,
QemuOs: ostype,
Memory: int(memory),
QemuCores: int(cores),
QemuSockets: int(sockets),
QemuCpu: cpu,
QemuNuma: numa,
QemuKVM: kvm,
Hotplug: hotplug,
QemuVlanTag: -1,
Boot: boot,
BootDisk: bootdisk,
Scsihw: scsihw,
HaState: hastate,
QemuDisks: QemuDevices{},
QemuVga: QemuDevice{},
QemuNetworks: QemuDevices{},
QemuSerials: QemuDevices{},
}
if balloon >= 1 {
config.Balloon = int(balloon)
}
if vcpus >= 1 {
config.QemuVcpus = int(vcpus)
}
if vmConfig["ide2"] != nil {
isoMatch := rxIso.FindStringSubmatch(vmConfig["ide2"].(string))
config.QemuIso = isoMatch[1]
}
if _, isSet := vmConfig["ciuser"]; isSet {
config.CIuser = vmConfig["ciuser"].(string)
}
if _, isSet := vmConfig["cipassword"]; isSet {
config.CIpassword = vmConfig["cipassword"].(string)
}
if _, isSet := vmConfig["cicustom"]; isSet {
config.CIcustom = vmConfig["cicustom"].(string)
}
if _, isSet := vmConfig["searchdomain"]; isSet {
config.Searchdomain = vmConfig["searchdomain"].(string)
}
if _, isSet := vmConfig["nameserver"]; isSet {
config.Nameserver = vmConfig["nameserver"].(string)
}
if _, isSet := vmConfig["sshkeys"]; isSet {
config.Sshkeys, _ = url.PathUnescape(vmConfig["sshkeys"].(string))
}
if _, isSet := vmConfig["ipconfig0"]; isSet {
config.Ipconfig0 = vmConfig["ipconfig0"].(string)
}
if _, isSet := vmConfig["ipconfig1"]; isSet {
config.Ipconfig1 = vmConfig["ipconfig1"].(string)
}
if _, isSet := vmConfig["ipconfig2"]; isSet {
config.Ipconfig2 = vmConfig["ipconfig2"].(string)
}
// Add disks.
diskNames := []string{}
for k, _ := range vmConfig {
if diskName := rxDiskName.FindStringSubmatch(k); len(diskName) > 0 {
diskNames = append(diskNames, diskName[0])
}
}
for _, diskName := range diskNames {
diskConfStr := vmConfig[diskName]
diskConfList := strings.Split(diskConfStr.(string), ",")
//
id := rxDeviceID.FindStringSubmatch(diskName)
diskID, _ := strconv.Atoi(id[0])
diskType := rxDiskType.FindStringSubmatch(diskName)[0]
storageName, fileName := ParseSubConf(diskConfList[0], ":")
//
diskConfMap := QemuDevice{
"id": diskID,
"type": diskType,
"storage": storageName,
"file": fileName,
}
// Add rest of device config.
diskConfMap.readDeviceConfig(diskConfList[1:])
// And device config to disks map.
if len(diskConfMap) > 0 {
config.QemuDisks[diskID] = diskConfMap
}
}
//Display
if vga, isSet := vmConfig["vga"]; isSet {
vgaList := strings.Split(vga.(string), ",")
vgaMap := QemuDevice{}
vgaMap.readDeviceConfig(vgaList)
if len(vgaMap) > 0 {
config.QemuVga = vgaMap
}
}
// Add networks.
nicNames := []string{}
for k, _ := range vmConfig {
if nicName := rxNicName.FindStringSubmatch(k); len(nicName) > 0 {
nicNames = append(nicNames, nicName[0])
}
}
for _, nicName := range nicNames {
nicConfStr := vmConfig[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:])
// And device config to networks.
if len(nicConfMap) > 0 {
config.QemuNetworks[nicID] = nicConfMap
}
}
// Add serials
serialNames := []string{}
for k, _ := range vmConfig {
if serialName := rxSerialName.FindStringSubmatch(k); len(serialName) > 0 {
serialNames = append(serialNames, serialName[0])
}
}
for _, serialName := range serialNames {
id := rxDeviceID.FindStringSubmatch(serialName)
serialID, _ := strconv.Atoi(id[0])
serialConfMap := QemuDevice{
"id": serialID,
"type": vmConfig[serialName],
}
// And device config to serials map.
if len(serialConfMap) > 0 {
config.QemuSerials[serialID] = serialConfMap
}
}
return
}
// Useful waiting for ISO install to complete
func WaitForShutdown(vmr *VmRef, client *Client) (err error) {
for ii := 0; ii < 100; ii++ {
vmState, err := client.GetVmState(vmr)
if err != nil {
log.Print("Wait error:")
log.Println(err)
} else if vmState["status"] == "stopped" {
return nil
}
time.Sleep(5 * time.Second)
}
return errors.New("Not shutdown within wait time")
}
// This is because proxmox create/config API won't let us make usernet devices
func SshForwardUsernet(vmr *VmRef, client *Client) (sshPort string, err error) {
vmState, err := client.GetVmState(vmr)
if err != nil {
return "", err
}
if vmState["status"] == "stopped" {
return "", errors.New("VM must be running first")
}
sshPort = strconv.Itoa(vmr.VmId() + 22000)
_, err = client.MonitorCmd(vmr, "netdev_add user,id=net1,hostfwd=tcp::"+sshPort+"-:22")
if err != nil {
return "", err
}
_, err = client.MonitorCmd(vmr, "device_add virtio-net-pci,id=net1,netdev=net1,addr=0x13")
if err != nil {
return "", err
}
return
}
// device_del net1
// netdev_del net1
func RemoveSshForwardUsernet(vmr *VmRef, client *Client) (err error) {
vmState, err := client.GetVmState(vmr)
if err != nil {
return err
}
if vmState["status"] == "stopped" {
return errors.New("VM must be running first")
}
_, err = client.MonitorCmd(vmr, "device_del net1")
if err != nil {
return err
}
_, err = client.MonitorCmd(vmr, "netdev_del net1")
if err != nil {
return err
}
return nil
}
func MaxVmId(client *Client) (max int, err error) {
resp, err := client.GetVmList()
vms := resp["data"].([]interface{})
max = 100
for vmii := range vms {
vm := vms[vmii].(map[string]interface{})
vmid := int(vm["vmid"].(float64))
if vmid > max {
max = vmid
}
}
return
}
func SendKeysString(vmr *VmRef, client *Client, keys string) (err error) {
vmState, err := client.GetVmState(vmr)
if err != nil {
return err
}
if vmState["status"] == "stopped" {
return errors.New("VM must be running first")
}
for _, r := range keys {
c := string(r)
lower := strings.ToLower(c)
if c != lower {
c = "shift-" + lower
} else {
switch c {
case "!":
c = "shift-1"
case "@":
c = "shift-2"
case "#":
c = "shift-3"
case "$":
c = "shift-4"
case "%%":
c = "shift-5"
case "^":
c = "shift-6"
case "&":
c = "shift-7"
case "*":
c = "shift-8"
case "(":
c = "shift-9"
case ")":
c = "shift-0"
case "_":
c = "shift-minus"
case "+":
c = "shift-equal"
case " ":
c = "spc"
case "/":
c = "slash"
case "\\":
c = "backslash"
case ",":
c = "comma"
case "-":
c = "minus"
case "=":
c = "equal"
case ".":
c = "dot"
case "?":
c = "shift-slash"
}
}
_, err = client.MonitorCmd(vmr, "sendkey "+c)
if err != nil {
return err
}
time.Sleep(100)
}
return nil
}
// Create parameters for each Nic device.
func (c ConfigQemu) CreateQemuNetworksParams(vmID int, params map[string]interface{}) error {
// For backward compatibility.
if len(c.QemuNetworks) == 0 && len(c.QemuNicModel) > 0 {
deprecatedStyleMap := QemuDevice{
"id": 0,
"model": c.QemuNicModel,
"bridge": c.QemuBrige,
"macaddr": c.QemuMacAddr,
}
if c.QemuVlanTag > 0 {
deprecatedStyleMap["tag"] = strconv.Itoa(c.QemuVlanTag)
}
c.QemuNetworks[0] = deprecatedStyleMap
}
// 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.
if nicConfMap["macaddr"] == nil || nicConfMap["macaddr"].(string) == "" {
// Generate Mac based on VmID and NicID so it will be the same always.
macaddr := make(net.HardwareAddr, 6)
rand.Seed(time.Now().UnixNano())
rand.Read(macaddr)
macaddr[0] = (macaddr[0] | 2) & 0xfe // fix from github issue #18
macAddrUppr := strings.ToUpper(fmt.Sprintf("%v", macaddr))
// use model=mac format for older proxmox compatability
macAddr := fmt.Sprintf("%v=%v", nicConfMap["model"], macAddrUppr)
// Add Mac to source map so it will be returned. (useful for some use case like Terraform)
nicConfMap["macaddr"] = macAddrUppr
// and also add it to the parameters which will be sent to Proxmox API.
nicConfParam = append(nicConfParam, macAddr)
} else {
macAddr := fmt.Sprintf("%v=%v", nicConfMap["model"], nicConfMap["macaddr"].(string))
nicConfParam = append(nicConfParam, 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, ",")
}
return nil
}
// Create parameters for each disk.
func (c ConfigQemu) CreateQemuDisksParams(
vmID int,
params map[string]interface{},
cloned bool,
) error {
// For backward compatibility.
if len(c.QemuDisks) == 0 && len(c.Storage) > 0 {
dType := c.StorageType
if dType == "" {
if c.HasCloudInit() {
dType = "scsi"
} else {
dType = "virtio"
}
}
deprecatedStyleMap := QemuDevice{
"id": 0,
"type": dType,
"storage": c.Storage,
"size": c.DiskSize,
"storage_type": "lvm", // default old style
"cache": "none", // default old value
}
if c.QemuDisks == nil {
c.QemuDisks = make(QemuDevices)
}
c.QemuDisks[0] = deprecatedStyleMap
}
// For new style with multi disk device.
for diskID, diskConfMap := range c.QemuDisks {
// skip the first disk for clones (may not always be right, but a template probably has at least 1 disk)
if diskID == 0 && cloned {
continue
}
diskConfParam := QemuDeviceParam{
"media=disk",
}
// Device name.
deviceType := diskConfMap["type"].(string)
qemuDiskName := deviceType + strconv.Itoa(diskID)
// Set disk storage.
// Disk size.
diskSizeGB := fmt.Sprintf("size=%v", diskConfMap["size"])
diskConfParam = append(diskConfParam, diskSizeGB)
// Disk name.
var diskFile string
// Currently ZFS local, LVM, Ceph RBD, CephFS and Directory are considered.
// Other formats are not verified, but could be added if they're needed.
rxStorageTypes := `(zfspool|lvm|rbd|cephfs)`
storageType := diskConfMap["storage_type"].(string)
if matched, _ := regexp.MatchString(rxStorageTypes, storageType); matched {
diskFile = fmt.Sprintf("file=%v:vm-%v-disk-%v", diskConfMap["storage"], vmID, diskID)
} else {
diskFile = fmt.Sprintf("file=%v:%v/vm-%v-disk-%v.%v", diskConfMap["storage"], vmID, vmID, diskID, diskConfMap["format"])
}
diskConfParam = append(diskConfParam, diskFile)
// Set cache if not none (default).
if diskConfMap["cache"].(string) != "none" {
diskCache := fmt.Sprintf("cache=%v", diskConfMap["cache"])
diskConfParam = append(diskConfParam, diskCache)
}
// Keys that are not used as real/direct conf.
ignoredKeys := []string{"id", "type", "storage", "storage_type", "size", "cache"}
// Rest of config.
diskConfParam = diskConfParam.createDeviceParam(diskConfMap, ignoredKeys)
// Add back to Qemu prams.
params[qemuDiskName] = strings.Join(diskConfParam, ",")
}
return nil
}
// Create the parameters for each device that will be sent to Proxmox API.
func (p QemuDeviceParam) createDeviceParam(
deviceConfMap QemuDevice,
ignoredKeys []string,
) QemuDeviceParam {
for key, value := range deviceConfMap {
if ignored := inArray(ignoredKeys, key); !ignored {
var confValue interface{}
if bValue, ok := value.(bool); ok && bValue {
confValue = "1"
} else if sValue, ok := value.(string); ok && len(sValue) > 0 {
confValue = sValue
} else if iValue, ok := value.(int); ok && iValue > 0 {
confValue = iValue
}
if confValue != nil {
deviceConf := fmt.Sprintf("%v=%v", key, confValue)
p = append(p, deviceConf)
}
}
}
return p
}
// readDeviceConfig - get standard sub-conf strings where `key=value` and update conf map.
func (confMap QemuDevice) readDeviceConfig(confList []string) error {
// Add device config.
for _, conf := range confList {
key, value := ParseSubConf(conf, "=")
confMap[key] = value
}
return nil
}
func (c ConfigQemu) String() string {
jsConf, _ := json.Marshal(c)
return string(jsConf)
}
// Create parameters for serial interface
func (c ConfigQemu) CreateQemuSerialsParams(
vmID int,
params map[string]interface{},
) error {
// 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
}
return nil
}
// NextId - Get next free VMID
func (c *Client) NextId() (id int, err error) {
var data map[string]interface{}
_, err = c.session.GetJSON("/cluster/nextid", nil, nil, &data)
if err != nil {
return -1, err
}
if data["data"] == nil || data["errors"] != nil {
return -1, fmt.Errorf(data["errors"].(string))
}
i, err := strconv.Atoi(data["data"].(string))
if err != nil {
return -1, err
}
return i, nil
}
// VMIdExists - If you pass an VMID that exists it will raise an error otherwise it will return the vmID
func (c *Client) VMIdExists(vmID int) (id int, err error) {
_, err = c.session.Get(fmt.Sprintf("/cluster/nextid?vmid=%d", vmID), nil, nil)
if err != nil {
return -1, err
}
return vmID, nil
}