diff --git a/go.mod b/go.mod index 8a7289ff5..bc90c868f 100644 --- a/go.mod +++ b/go.mod @@ -14,7 +14,7 @@ require ( github.com/NaverCloudPlatform/ncloud-sdk-go v0.0.0-20180110055012-c2e73f942591 github.com/PuerkitoBio/goquery v1.5.0 // indirect github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d // indirect - github.com/Telmate/proxmox-api-go v0.0.0-20191015171801-b0c2796b9fcf + github.com/Telmate/proxmox-api-go v0.0.0-20200116224409-320525bf3340 github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af // indirect github.com/aliyun/alibaba-cloud-sdk-go v0.0.0-20190418113227-25233c783f4e github.com/aliyun/aliyun-oss-go-sdk v0.0.0-20170113022742-e6dbea820a9f diff --git a/go.sum b/go.sum index ba8e78e84..70f5e41d0 100644 --- a/go.sum +++ b/go.sum @@ -43,6 +43,8 @@ github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d h1:G0m3OIz70MZUW github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/Telmate/proxmox-api-go v0.0.0-20191015171801-b0c2796b9fcf h1:rVT2xsBm03Jp0r0yfGm5AMlqp0mZmxTTiNnSrc9S+Hs= github.com/Telmate/proxmox-api-go v0.0.0-20191015171801-b0c2796b9fcf/go.mod h1:OGWyIMJ87/k/GCz8CGiWB2HOXsOVDM6Lpe/nFPkC4IQ= +github.com/Telmate/proxmox-api-go v0.0.0-20200116224409-320525bf3340 h1:bOjy6c07dpipWm11dL92FbtmXGnDywOm2uKzG4CePuY= +github.com/Telmate/proxmox-api-go v0.0.0-20200116224409-320525bf3340/go.mod h1:OGWyIMJ87/k/GCz8CGiWB2HOXsOVDM6Lpe/nFPkC4IQ= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af h1:DBNMBMuMiWYu0b+8KMJuWmfCkcxl09JwdlqwDZZ6U14= github.com/abdullin/seq v0.0.0-20160510034733-d5467c17e7af/go.mod h1:5Jv4cbFiHJMsVxt52+i0Ha45fjshj6wxYr1r19tB9bw= github.com/agext/levenshtein v1.2.1 h1:QmvMAjj2aEICytGiWzmxoE0x2KZvE0fvmqMOfy2tjT8= @@ -693,4 +695,5 @@ honnef.co/go/tools v0.0.0-20190418001031-e561f6794a2a/go.mod h1:rf3lG4BRIbNafJWh honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.1-2019.2.3 h1:3JgtbtFHMiCmsznwGVTUWbgGov+pVqnlf1dEJTNAXeM= honnef.co/go/tools v0.0.1-2019.2.3/go.mod h1:a3bituU0lyd329TUQxRnasdCoJDkEUEAqEt0JzvZhAg= +howett.net/plist v0.0.0-20181124034731-591f970eefbb/go.mod h1:vMygbs4qMhSZSc4lCUl2OEE+rDiIIJAIdR4m7MiMcm0= rsc.io/binaryregexp v0.2.0/go.mod h1:qTv7/COck+e2FymRvadv62gMdZztPaShugOCi3I+8D8= diff --git a/vendor/github.com/Telmate/proxmox-api-go/proxmox/client.go b/vendor/github.com/Telmate/proxmox-api-go/proxmox/client.go index 6afc03942..787e7497f 100644 --- a/vendor/github.com/Telmate/proxmox-api-go/proxmox/client.go +++ b/vendor/github.com/Telmate/proxmox-api-go/proxmox/client.go @@ -39,10 +39,11 @@ type Client struct { // VmRef - virtual machine ref parts // map[type:qemu node:proxmox1-xx id:qemu/132 diskread:5.57424738e+08 disk:0 netin:5.9297450593e+10 mem:3.3235968e+09 uptime:1.4567097e+07 vmid:132 template:0 maxcpu:2 netout:6.053310416e+09 maxdisk:3.4359738368e+10 maxmem:8.592031744e+09 diskwrite:1.49663619584e+12 status:running cpu:0.00386980694947209 name:appt-app1-dev.xxx.xx] type VmRef struct { - vmId int - node string - pool string - vmType string + vmId int + node string + pool string + vmType string + haState string } func (vmr *VmRef) SetNode(node string) { @@ -75,6 +76,10 @@ func (vmr *VmRef) Pool() string { return vmr.pool } +func (vmr *VmRef) HaState() string { + return vmr.haState +} + func NewVmRef(vmId int) (vmr *VmRef) { vmr = &VmRef{vmId: vmId, node: "", vmType: ""} return @@ -141,6 +146,9 @@ func (c *Client) GetVmInfo(vmr *VmRef) (vmInfo map[string]interface{}, err error if vmInfo["pool"] != nil { vmr.pool = vmInfo["pool"].(string) } + if vmInfo["hastate"] != nil { + vmr.haState = vmInfo["hastate"].(string) + } return } } @@ -160,6 +168,9 @@ func (c *Client) GetVmRefByName(vmName string) (vmr *VmRef, err error) { if vm["pool"] != nil { vmr.pool = vm["pool"].(string) } + if vm["hastate"] != nil { + vmr.haState = vm["hastate"].(string) + } return } } @@ -413,6 +424,23 @@ func (c *Client) DeleteVm(vmr *VmRef) (exitStatus string, err error) { if err != nil { return "", err } + + //Remove HA if required + if vmr.haState != "" { + url := fmt.Sprintf("/cluster/ha/resources/%d", vmr.vmId) + resp, err := c.session.Delete(url, nil, nil) + if err == nil { + taskResponse, err := ResponseJSON(resp) + if err != nil { + return "", err + } + exitStatus, err = c.WaitForCompletion(taskResponse) + if err != nil { + return "", err + } + } + } + url := fmt.Sprintf("/nodes/%s/%s/%d", vmr.node, vmr.vmType, vmr.vmId) var taskResponse map[string]interface{} _, err = c.session.RequestJSON("DELETE", url, nil, nil, nil, &taskResponse) @@ -536,6 +564,22 @@ func (c *Client) SetLxcConfig(vmr *VmRef, vmParams map[string]interface{}) (exit return } +// MigrateNode - Migrate a VM +func (c *Client) MigrateNode(vmr *VmRef, newTargetNode string, online bool) (exitStatus interface{}, err error) { + reqbody := ParamsToBody(map[string]interface{}{"target": newTargetNode, "online": online}) + url := fmt.Sprintf("/nodes/%s/%s/%d/migrate", vmr.node, vmr.vmType, vmr.vmId) + resp, err := c.session.Post(url, nil, nil, &reqbody) + if err == nil { + taskResponse, err := ResponseJSON(resp) + if err != nil { + return nil, err + } + exitStatus, err = c.WaitForCompletion(taskResponse) + return exitStatus, err + } + return nil, err +} + func (c *Client) ResizeQemuDisk(vmr *VmRef, disk string, moreSizeGB int) (exitStatus interface{}, err error) { // PUT //disk:virtio0 @@ -691,8 +735,23 @@ func (c *Client) Upload(node string, storage string, contentType string, filenam req.Header.Add("Content-Type", mimetype) req.Header.Add("Accept", "application/json") - _, err = c.session.Do(req) - return err + resp, err := c.session.Do(req) + if err != nil { + return err + } + + taskResponse, err := ResponseJSON(resp) + if err != nil { + return err + } + exitStatus, err := c.WaitForCompletion(taskResponse) + if err != nil { + return err + } + if exitStatus != exitStatusSuccess { + return fmt.Errorf("Moving file to destination failed: %v", exitStatus) + } + return nil } func createUploadBody(contentType string, filename string, r io.Reader) (io.Reader, string, error) { @@ -787,3 +846,61 @@ func (c *Client) UpdateVMPool(vmr *VmRef, pool string) (exitStatus interface{}, } return } + +func (c *Client) UpdateVMHA(vmr *VmRef, haState string) (exitStatus interface{}, err error) { + // Same hastate + if vmr.haState == haState { + return + } + + //Remove HA + if haState == "" { + url := fmt.Sprintf("/cluster/ha/resources/%d", vmr.vmId) + resp, err := c.session.Delete(url, nil, nil) + if err == nil { + taskResponse, err := ResponseJSON(resp) + if err != nil { + return nil, err + } + exitStatus, err = c.WaitForCompletion(taskResponse) + } + return nil, err + } + + //Activate HA + if vmr.haState == "" { + paramMap := map[string]interface{}{ + "sid": vmr.vmId, + } + reqbody := ParamsToBody(paramMap) + resp, err := c.session.Post("/cluster/ha/resources", nil, nil, &reqbody) + if err == nil { + taskResponse, err := ResponseJSON(resp) + if err != nil { + return nil, err + } + exitStatus, err = c.WaitForCompletion(taskResponse) + + if err != nil { + return nil, err + } + } + } + + //Set wanted state + paramMap := map[string]interface{}{ + "state": haState, + } + reqbody := ParamsToBody(paramMap) + url := fmt.Sprintf("/cluster/ha/resources/%d", vmr.vmId) + resp, err := c.session.Put(url, nil, nil, &reqbody) + if err == nil { + taskResponse, err := ResponseJSON(resp) + if err != nil { + return nil, err + } + exitStatus, err = c.WaitForCompletion(taskResponse) + } + + return +} diff --git a/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_qemu.go b/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_qemu.go index db7b40ccc..10f23641d 100644 --- a/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_qemu.go +++ b/vendor/github.com/Telmate/proxmox-api-go/proxmox/config_qemu.go @@ -23,15 +23,19 @@ type ( // 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"` Hotplug string `json:"hotplug"` @@ -41,8 +45,10 @@ type ConfigQemu struct { 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"` @@ -93,6 +99,19 @@ func (config ConfigQemu) CreateVm(vmr *VmRef, client *Client) (err error) { "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 } @@ -108,6 +127,13 @@ func (config ConfigQemu) CreateVm(vmr *VmRef, client *Client) (err error) { // 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) @@ -118,6 +144,9 @@ func (config ConfigQemu) CreateVm(vmr *VmRef, client *Client) (err error) { if err != nil { return fmt.Errorf("Error creating VM: %v, error status: %s (params: %v)", err, exitStatus, params) } + + client.UpdateVMHA(vmr, config.HaState) + return } @@ -175,6 +204,7 @@ func (config ConfigQemu) CloneVm(sourceVmr *VmRef, vmr *VmRef, client *Client) ( if err != nil { return } + return config.UpdateConfig(vmr, client) } @@ -193,6 +223,25 @@ func (config ConfigQemu) UpdateConfig(vmr *VmRef, client *Client) (err error) { "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 } @@ -202,11 +251,31 @@ func (config ConfigQemu) UpdateConfig(vmr *VmRef, client *Client) (err error) { } // Create disks config. - config.CreateQemuDisksParams(vmr.vmId, configParams, true) + 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) @@ -242,12 +311,19 @@ func (config ConfigQemu) UpdateConfig(vmr *VmRef, client *Client) (err error) { 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.Fatal(err) + log.Print(err) return err } + client.UpdateVMHA(vmr, config.HaState) + _, err = client.UpdateVMPool(vmr, config.Pool) return err @@ -312,6 +388,10 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e 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))) @@ -335,10 +415,18 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e 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) @@ -369,9 +457,15 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e 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, @@ -385,10 +479,19 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e 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)) @@ -459,6 +562,16 @@ func NewConfigQemuFromApi(vmr *VmRef, client *Client) (config *ConfigQemu, err e } } + //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{} @@ -776,9 +889,9 @@ func (c ConfigQemu) CreateQemuDisksParams( // Disk name. var diskFile string - // Currently ZFS local, LVM, Ceph RBD, and Directory are considered. + // 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)` + 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) @@ -865,3 +978,30 @@ func (c ConfigQemu) CreateQemuSerialsParams( 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 +} diff --git a/vendor/modules.txt b/vendor/modules.txt index 8167e4b34..7d4a24d16 100644 --- a/vendor/modules.txt +++ b/vendor/modules.txt @@ -63,7 +63,7 @@ github.com/NaverCloudPlatform/ncloud-sdk-go/sdk github.com/PuerkitoBio/goquery # github.com/StackExchange/wmi v0.0.0-20190523213315-cbe66965904d github.com/StackExchange/wmi -# github.com/Telmate/proxmox-api-go v0.0.0-20191015171801-b0c2796b9fcf +# github.com/Telmate/proxmox-api-go v0.0.0-20200116224409-320525bf3340 github.com/Telmate/proxmox-api-go/proxmox # github.com/agext/levenshtein v1.2.1 github.com/agext/levenshtein