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

965 lines
25 KiB
Go
Raw Normal View History

package proxmox
// inspired by https://github.com/Telmate/vagrant-proxmox/blob/master/lib/vagrant-proxmox/proxmox/connection.rb
import (
2019-10-27 08:10:35 -04:00
"bytes"
"crypto/tls"
"encoding/json"
"errors"
"fmt"
"io"
"io/ioutil"
2019-10-27 08:10:35 -04:00
"mime/multipart"
"net"
"net/http"
2020-07-15 17:20:09 -04:00
"os"
"regexp"
"strconv"
"strings"
"time"
)
// TaskTimeout - default async task call timeout in seconds
const TaskTimeout = 300
// TaskStatusCheckInterval - time between async checks in seconds
const TaskStatusCheckInterval = 2
const exitStatusSuccess = "OK"
// Client - URL, user and password to specifc Proxmox node
type Client struct {
session *Session
ApiUrl string
Username string
Password string
2019-09-07 15:07:09 -04:00
Otp string
}
// 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 {
2020-01-17 14:26:44 -05:00
vmId int
node string
pool string
vmType string
haState string
}
func (vmr *VmRef) SetNode(node string) {
vmr.node = node
return
}
2019-09-07 15:07:09 -04:00
func (vmr *VmRef) SetPool(pool string) {
vmr.pool = pool
}
func (vmr *VmRef) SetVmType(vmType string) {
vmr.vmType = vmType
return
}
2019-10-27 08:10:35 -04:00
func (vmr *VmRef) GetVmType() string {
2019-09-07 15:07:09 -04:00
return vmr.vmType
}
func (vmr *VmRef) VmId() int {
return vmr.vmId
}
func (vmr *VmRef) Node() string {
return vmr.node
}
2019-09-07 15:07:09 -04:00
func (vmr *VmRef) Pool() string {
return vmr.pool
}
2020-01-17 14:26:44 -05:00
func (vmr *VmRef) HaState() string {
return vmr.haState
}
func NewVmRef(vmId int) (vmr *VmRef) {
vmr = &VmRef{vmId: vmId, node: "", vmType: ""}
return
}
func NewClient(apiUrl string, hclient *http.Client, tls *tls.Config) (client *Client, err error) {
var sess *Session
sess, err = NewSession(apiUrl, hclient, tls)
if err == nil {
client = &Client{session: sess, ApiUrl: apiUrl}
}
return client, err
}
2019-09-07 15:07:09 -04:00
func (c *Client) Login(username string, password string, otp string) (err error) {
c.Username = username
c.Password = password
2019-10-27 08:10:35 -04:00
c.Otp = otp
2019-09-07 15:07:09 -04:00
return c.session.Login(username, password, otp)
}
func (c *Client) GetJsonRetryable(url string, data *map[string]interface{}, tries int) error {
var statErr error
for ii := 0; ii < tries; ii++ {
_, statErr = c.session.GetJSON(url, nil, nil, data)
if statErr == nil {
return nil
}
// if statErr != io.ErrUnexpectedEOF { // don't give up on ErrUnexpectedEOF
// return statErr
// }
time.Sleep(5 * time.Second)
}
return statErr
}
func (c *Client) GetNodeList() (list map[string]interface{}, err error) {
err = c.GetJsonRetryable("/nodes", &list, 3)
return
}
func (c *Client) GetVmList() (list map[string]interface{}, err error) {
err = c.GetJsonRetryable("/cluster/resources?type=vm", &list, 3)
return
}
func (c *Client) CheckVmRef(vmr *VmRef) (err error) {
if vmr.node == "" || vmr.vmType == "" {
_, err = c.GetVmInfo(vmr)
}
return
}
func (c *Client) GetVmInfo(vmr *VmRef) (vmInfo map[string]interface{}, err error) {
resp, err := c.GetVmList()
vms := resp["data"].([]interface{})
for vmii := range vms {
vm := vms[vmii].(map[string]interface{})
if int(vm["vmid"].(float64)) == vmr.vmId {
vmInfo = vm
vmr.node = vmInfo["node"].(string)
vmr.vmType = vmInfo["type"].(string)
2019-10-27 08:10:35 -04:00
vmr.pool = ""
if vmInfo["pool"] != nil {
vmr.pool = vmInfo["pool"].(string)
}
2020-01-17 14:26:44 -05:00
if vmInfo["hastate"] != nil {
vmr.haState = vmInfo["hastate"].(string)
}
return
}
}
return nil, errors.New(fmt.Sprintf("Vm '%d' not found", vmr.vmId))
}
func (c *Client) GetVmRefByName(vmName string) (vmr *VmRef, err error) {
resp, err := c.GetVmList()
vms := resp["data"].([]interface{})
for vmii := range vms {
vm := vms[vmii].(map[string]interface{})
if vm["name"] != nil && vm["name"].(string) == vmName {
vmr = NewVmRef(int(vm["vmid"].(float64)))
vmr.node = vm["node"].(string)
vmr.vmType = vm["type"].(string)
2019-10-27 08:10:35 -04:00
vmr.pool = ""
if vm["pool"] != nil {
vmr.pool = vm["pool"].(string)
}
2020-01-17 14:26:44 -05:00
if vm["hastate"] != nil {
vmr.haState = vm["hastate"].(string)
}
return
}
}
return nil, errors.New(fmt.Sprintf("Vm '%s' not found", vmName))
}
func (c *Client) GetVmState(vmr *VmRef) (vmState map[string]interface{}, err error) {
err = c.CheckVmRef(vmr)
if err != nil {
return nil, err
}
var data map[string]interface{}
url := fmt.Sprintf("/nodes/%s/%s/%d/status/current", vmr.node, vmr.vmType, vmr.vmId)
err = c.GetJsonRetryable(url, &data, 3)
if err != nil {
return nil, err
}
if data["data"] == nil {
return nil, errors.New("Vm STATE not readable")
}
vmState = data["data"].(map[string]interface{})
return
}
func (c *Client) GetVmConfig(vmr *VmRef) (vmConfig map[string]interface{}, err error) {
err = c.CheckVmRef(vmr)
if err != nil {
return nil, err
}
var data map[string]interface{}
url := fmt.Sprintf("/nodes/%s/%s/%d/config", vmr.node, vmr.vmType, vmr.vmId)
err = c.GetJsonRetryable(url, &data, 3)
if err != nil {
return nil, err
}
if data["data"] == nil {
return nil, errors.New("Vm CONFIG not readable")
}
vmConfig = data["data"].(map[string]interface{})
return
}
func (c *Client) GetVmSpiceProxy(vmr *VmRef) (vmSpiceProxy map[string]interface{}, err error) {
err = c.CheckVmRef(vmr)
if err != nil {
return nil, err
}
var data map[string]interface{}
url := fmt.Sprintf("/nodes/%s/%s/%d/spiceproxy", vmr.node, vmr.vmType, vmr.vmId)
_, err = c.session.PostJSON(url, nil, nil, nil, &data)
if err != nil {
return nil, err
}
if data["data"] == nil {
return nil, errors.New("Vm SpiceProxy not readable")
}
vmSpiceProxy = data["data"].(map[string]interface{})
return
}
type AgentNetworkInterface struct {
MACAddress string
IPAddresses []net.IP
Name string
Statistics map[string]int64
}
func (a *AgentNetworkInterface) UnmarshalJSON(b []byte) error {
var intermediate struct {
HardwareAddress string `json:"hardware-address"`
IPAddresses []struct {
IPAddress string `json:"ip-address"`
IPAddressType string `json:"ip-address-type"`
Prefix int `json:"prefix"`
} `json:"ip-addresses"`
Name string `json:"name"`
Statistics map[string]int64 `json:"statistics"`
}
err := json.Unmarshal(b, &intermediate)
if err != nil {
return err
}
a.IPAddresses = make([]net.IP, len(intermediate.IPAddresses))
for idx, ip := range intermediate.IPAddresses {
a.IPAddresses[idx] = net.ParseIP(ip.IPAddress)
if a.IPAddresses[idx] == nil {
return fmt.Errorf("Could not parse %s as IP", ip.IPAddress)
}
}
a.MACAddress = intermediate.HardwareAddress
a.Name = intermediate.Name
a.Statistics = intermediate.Statistics
return nil
}
func (c *Client) GetVmAgentNetworkInterfaces(vmr *VmRef) ([]AgentNetworkInterface, error) {
var ifs []AgentNetworkInterface
err := c.doAgentGet(vmr, "network-get-interfaces", &ifs)
return ifs, err
}
func (c *Client) doAgentGet(vmr *VmRef, command string, output interface{}) error {
err := c.CheckVmRef(vmr)
if err != nil {
return err
}
url := fmt.Sprintf("/nodes/%s/%s/%d/agent/%s", vmr.node, vmr.vmType, vmr.vmId, command)
resp, err := c.session.Get(url, nil, nil)
if err != nil {
return err
}
return TypedResponse(resp, output)
}
func (c *Client) CreateTemplate(vmr *VmRef) error {
err := c.CheckVmRef(vmr)
if err != nil {
return err
}
url := fmt.Sprintf("/nodes/%s/%s/%d/template", vmr.node, vmr.vmType, vmr.vmId)
_, err = c.session.Post(url, nil, nil, nil)
if err != nil {
return err
}
return nil
}
func (c *Client) MonitorCmd(vmr *VmRef, command string) (monitorRes map[string]interface{}, err error) {
err = c.CheckVmRef(vmr)
if err != nil {
return nil, err
}
reqbody := ParamsToBody(map[string]interface{}{"command": command})
url := fmt.Sprintf("/nodes/%s/%s/%d/monitor", vmr.node, vmr.vmType, vmr.vmId)
resp, err := c.session.Post(url, nil, nil, &reqbody)
2019-09-07 15:07:09 -04:00
if err != nil {
return nil, err
}
monitorRes, err = ResponseJSON(resp)
return
}
2019-09-07 15:07:09 -04:00
func (c *Client) Sendkey(vmr *VmRef, qmKey string) error {
err := c.CheckVmRef(vmr)
if err != nil {
return err
}
reqbody := ParamsToBody(map[string]interface{}{"key": qmKey})
url := fmt.Sprintf("/nodes/%s/%s/%d/sendkey", vmr.node, vmr.vmType, vmr.vmId)
// No return, even for errors: https://bugzilla.proxmox.com/show_bug.cgi?id=2275
_, err = c.session.Put(url, nil, nil, &reqbody)
return err
}
// WaitForCompletion - poll the API for task completion
func (c *Client) WaitForCompletion(taskResponse map[string]interface{}) (waitExitStatus string, err error) {
if taskResponse["errors"] != nil {
errJSON, _ := json.MarshalIndent(taskResponse["errors"], "", " ")
return string(errJSON), errors.New("Error reponse")
}
if taskResponse["data"] == nil {
return "", nil
}
waited := 0
taskUpid := taskResponse["data"].(string)
for waited < TaskTimeout {
exitStatus, statErr := c.GetTaskExitstatus(taskUpid)
if statErr != nil {
if statErr != io.ErrUnexpectedEOF { // don't give up on ErrUnexpectedEOF
return "", statErr
}
}
if exitStatus != nil {
waitExitStatus = exitStatus.(string)
return
}
time.Sleep(TaskStatusCheckInterval * time.Second)
waited = waited + TaskStatusCheckInterval
}
return "", errors.New("Wait timeout for:" + taskUpid)
}
var rxTaskNode = regexp.MustCompile("UPID:(.*?):")
func (c *Client) GetTaskExitstatus(taskUpid string) (exitStatus interface{}, err error) {
node := rxTaskNode.FindStringSubmatch(taskUpid)[1]
url := fmt.Sprintf("/nodes/%s/tasks/%s/status", node, taskUpid)
var data map[string]interface{}
_, err = c.session.GetJSON(url, nil, nil, &data)
if err == nil {
exitStatus = data["data"].(map[string]interface{})["exitstatus"]
}
if exitStatus != nil && exitStatus != exitStatusSuccess {
err = errors.New(exitStatus.(string))
}
return
}
func (c *Client) StatusChangeVm(vmr *VmRef, setStatus string) (exitStatus string, err error) {
err = c.CheckVmRef(vmr)
if err != nil {
return "", err
}
url := fmt.Sprintf("/nodes/%s/%s/%d/status/%s", vmr.node, vmr.vmType, vmr.vmId, setStatus)
var taskResponse map[string]interface{}
for i := 0; i < 3; i++ {
_, err = c.session.PostJSON(url, nil, nil, nil, &taskResponse)
exitStatus, err = c.WaitForCompletion(taskResponse)
if exitStatus == "" {
time.Sleep(TaskStatusCheckInterval * time.Second)
} else {
return
}
}
return
}
func (c *Client) StartVm(vmr *VmRef) (exitStatus string, err error) {
return c.StatusChangeVm(vmr, "start")
}
func (c *Client) StopVm(vmr *VmRef) (exitStatus string, err error) {
return c.StatusChangeVm(vmr, "stop")
}
func (c *Client) ShutdownVm(vmr *VmRef) (exitStatus string, err error) {
return c.StatusChangeVm(vmr, "shutdown")
}
func (c *Client) ResetVm(vmr *VmRef) (exitStatus string, err error) {
return c.StatusChangeVm(vmr, "reset")
}
func (c *Client) SuspendVm(vmr *VmRef) (exitStatus string, err error) {
return c.StatusChangeVm(vmr, "suspend")
}
func (c *Client) ResumeVm(vmr *VmRef) (exitStatus string, err error) {
return c.StatusChangeVm(vmr, "resume")
}
func (c *Client) DeleteVm(vmr *VmRef) (exitStatus string, err error) {
err = c.CheckVmRef(vmr)
if err != nil {
return "", err
}
2020-01-17 14:26:44 -05:00
//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)
exitStatus, err = c.WaitForCompletion(taskResponse)
return
}
func (c *Client) CreateQemuVm(node string, vmParams map[string]interface{}) (exitStatus string, err error) {
// Create VM disks first to ensure disks names.
createdDisks, createdDisksErr := c.createVMDisks(node, vmParams)
if createdDisksErr != nil {
return "", createdDisksErr
}
// Then create the VM itself.
reqbody := ParamsToBody(vmParams)
url := fmt.Sprintf("/nodes/%s/qemu", node)
var resp *http.Response
resp, err = c.session.Post(url, nil, nil, &reqbody)
defer resp.Body.Close()
if err != nil {
2019-04-12 06:27:32 -04:00
// This might not work if we never got a body. We'll ignore errors in trying to read,
// but extract the body if possible to give any error information back in the exitStatus
b, _ := ioutil.ReadAll(resp.Body)
exitStatus = string(b)
2019-04-12 06:27:32 -04:00
return exitStatus, err
}
taskResponse, err := ResponseJSON(resp)
if err != nil {
2019-04-12 06:27:32 -04:00
return "", err
}
exitStatus, err = c.WaitForCompletion(taskResponse)
// Delete VM disks if the VM didn't create.
if exitStatus != "OK" {
deleteDisksErr := c.DeleteVMDisks(node, createdDisks)
if deleteDisksErr != nil {
return "", deleteDisksErr
}
}
return
}
2019-09-07 15:07:09 -04:00
func (c *Client) CreateLxcContainer(node string, vmParams map[string]interface{}) (exitStatus string, err error) {
reqbody := ParamsToBody(vmParams)
url := fmt.Sprintf("/nodes/%s/lxc", node)
var resp *http.Response
resp, err = c.session.Post(url, nil, nil, &reqbody)
defer resp.Body.Close()
if err != nil {
// This might not work if we never got a body. We'll ignore errors in trying to read,
// but extract the body if possible to give any error information back in the exitStatus
b, _ := ioutil.ReadAll(resp.Body)
exitStatus = string(b)
return exitStatus, err
}
taskResponse, err := ResponseJSON(resp)
if err != nil {
return "", err
}
exitStatus, err = c.WaitForCompletion(taskResponse)
return
}
func (c *Client) CloneQemuVm(vmr *VmRef, vmParams map[string]interface{}) (exitStatus string, err error) {
reqbody := ParamsToBody(vmParams)
url := fmt.Sprintf("/nodes/%s/qemu/%d/clone", vmr.node, vmr.vmId)
resp, err := c.session.Post(url, nil, nil, &reqbody)
if err == nil {
taskResponse, err := ResponseJSON(resp)
if err != nil {
return "", err
}
exitStatus, err = c.WaitForCompletion(taskResponse)
}
return
}
func (c *Client) RollbackQemuVm(vmr *VmRef, snapshot string) (exitStatus string, err error) {
err = c.CheckVmRef(vmr)
if err != nil {
return "", err
}
url := fmt.Sprintf("/nodes/%s/%s/%d/snapshot/%s/rollback", vmr.node, vmr.vmType, vmr.vmId, snapshot)
var taskResponse map[string]interface{}
_, err = c.session.PostJSON(url, nil, nil, nil, &taskResponse)
exitStatus, err = c.WaitForCompletion(taskResponse)
return
}
// SetVmConfig - send config options
func (c *Client) SetVmConfig(vmr *VmRef, vmParams map[string]interface{}) (exitStatus interface{}, err error) {
reqbody := ParamsToBody(vmParams)
url := fmt.Sprintf("/nodes/%s/%s/%d/config", 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
}
2019-09-07 15:07:09 -04:00
// SetLxcConfig - send config options
func (c *Client) SetLxcConfig(vmr *VmRef, vmParams map[string]interface{}) (exitStatus interface{}, err error) {
reqbody := ParamsToBody(vmParams)
url := fmt.Sprintf("/nodes/%s/%s/%d/config", vmr.node, vmr.vmType, 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
}
2020-01-17 14:26:44 -05:00
// 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
//size:+2G
if disk == "" {
disk = "virtio0"
}
size := fmt.Sprintf("+%dG", moreSizeGB)
reqbody := ParamsToBody(map[string]interface{}{"disk": disk, "size": size})
url := fmt.Sprintf("/nodes/%s/%s/%d/resize", vmr.node, vmr.vmType, 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
}
2019-09-07 15:07:09 -04:00
func (c *Client) MoveQemuDisk(vmr *VmRef, disk string, storage string) (exitStatus interface{}, err error) {
if disk == "" {
disk = "virtio0"
}
reqbody := ParamsToBody(map[string]interface{}{"disk": disk, "storage": storage, "delete": true})
url := fmt.Sprintf("/nodes/%s/%s/%d/move_disk", 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
}
// GetNextID - Get next free VMID
func (c *Client) GetNextID(currentID int) (nextID int, err error) {
var data map[string]interface{}
var url string
if currentID >= 100 {
url = fmt.Sprintf("/cluster/nextid?vmid=%d", currentID)
} else {
url = "/cluster/nextid"
}
_, err = c.session.GetJSON(url, nil, nil, &data)
if err == nil {
if data["errors"] != nil {
if currentID >= 100 {
return c.GetNextID(currentID + 1)
} else {
return -1, errors.New("error using /cluster/nextid")
}
}
nextID, err = strconv.Atoi(data["data"].(string))
} else if strings.HasPrefix(err.Error(), "400 ") {
return c.GetNextID(currentID + 1)
}
return
}
// CreateVMDisk - Create single disk for VM on host node.
func (c *Client) CreateVMDisk(
nodeName string,
storageName string,
fullDiskName string,
diskParams map[string]interface{},
) error {
reqbody := ParamsToBody(diskParams)
url := fmt.Sprintf("/nodes/%s/storage/%s/content", nodeName, storageName)
resp, err := c.session.Post(url, nil, nil, &reqbody)
if err == nil {
taskResponse, err := ResponseJSON(resp)
if err != nil {
return err
}
if diskName, containsData := taskResponse["data"]; !containsData || diskName != fullDiskName {
return errors.New(fmt.Sprintf("Cannot create VM disk %s", fullDiskName))
}
} else {
return err
}
return nil
}
// createVMDisks - Make disks parameters and create all VM disks on host node.
func (c *Client) createVMDisks(
node string,
vmParams map[string]interface{},
) (disks []string, err error) {
var createdDisks []string
vmID := vmParams["vmid"].(int)
for deviceName, deviceConf := range vmParams {
rxStorageModels := `(ide|sata|scsi|virtio)\d+`
if matched, _ := regexp.MatchString(rxStorageModels, deviceName); matched {
deviceConfMap := ParseConf(deviceConf.(string), ",", "=")
// This if condition to differentiate between `disk` and `cdrom`.
if media, containsFile := deviceConfMap["media"]; containsFile && media == "disk" {
fullDiskName := deviceConfMap["file"].(string)
storageName, volumeName := getStorageAndVolumeName(fullDiskName, ":")
diskParams := map[string]interface{}{
"vmid": vmID,
"filename": volumeName,
"size": deviceConfMap["size"],
}
err := c.CreateVMDisk(node, storageName, fullDiskName, diskParams)
if err != nil {
return createdDisks, err
} else {
createdDisks = append(createdDisks, fullDiskName)
}
}
}
}
return createdDisks, nil
}
// DeleteVMDisks - Delete VM disks from host node.
// By default the VM disks are deteled when the VM is deleted,
// so mainly this is used to delete the disks in case VM creation didn't complete.
func (c *Client) DeleteVMDisks(
node string,
disks []string,
) error {
for _, fullDiskName := range disks {
storageName, volumeName := getStorageAndVolumeName(fullDiskName, ":")
url := fmt.Sprintf("/nodes/%s/storage/%s/content/%s", node, storageName, volumeName)
_, err := c.session.Post(url, nil, nil, nil)
if err != nil {
return err
}
}
return nil
}
2019-10-27 08:10:35 -04:00
func (c *Client) Upload(node string, storage string, contentType string, filename string, file io.Reader) error {
2020-07-15 17:20:09 -04:00
var doStreamingIO bool
var fileSize int64
var contentLength int64
if f, ok := file.(*os.File); ok {
doStreamingIO = true
fileInfo, err := f.Stat()
if err != nil {
return err
}
fileSize = fileInfo.Size()
}
var body io.Reader
var mimetype string
var err error
if doStreamingIO {
body, mimetype, contentLength, err = createStreamedUploadBody(contentType, filename, fileSize, file)
} else {
body, mimetype, err = createUploadBody(contentType, filename, file)
}
2019-10-27 08:10:35 -04:00
if err != nil {
return err
}
url := fmt.Sprintf("%s/nodes/%s/storage/%s/upload", c.session.ApiUrl, node, storage)
req, err := c.session.NewRequest(http.MethodPost, url, nil, body)
if err != nil {
return err
}
req.Header.Add("Content-Type", mimetype)
req.Header.Add("Accept", "application/json")
2020-07-15 17:20:09 -04:00
if doStreamingIO {
req.ContentLength = contentLength
}
2020-01-17 14:26:44 -05:00
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
2019-10-27 08:10:35 -04:00
}
func createUploadBody(contentType string, filename string, r io.Reader) (io.Reader, string, error) {
var buf bytes.Buffer
w := multipart.NewWriter(&buf)
err := w.WriteField("content", contentType)
if err != nil {
return nil, "", err
}
fw, err := w.CreateFormFile("filename", filename)
if err != nil {
return nil, "", err
}
_, err = io.Copy(fw, r)
if err != nil {
return nil, "", err
}
err = w.Close()
if err != nil {
return nil, "", err
}
return &buf, w.FormDataContentType(), nil
}
2020-07-15 17:20:09 -04:00
// createStreamedUploadBody - Use MultiReader to create the multipart body from the file reader,
// avoiding allocation of large files in memory before upload (useful e.g. for Windows ISOs).
func createStreamedUploadBody(contentType string, filename string, fileSize int64, r io.Reader) (io.Reader, string, int64, error) {
var buf bytes.Buffer
w := multipart.NewWriter(&buf)
err := w.WriteField("content", contentType)
if err != nil {
return nil, "", 0, err
}
_, err = w.CreateFormFile("filename", filename)
if err != nil {
return nil, "", 0, err
}
headerSize := buf.Len()
err = w.Close()
if err != nil {
return nil, "", 0, err
}
mr := io.MultiReader(bytes.NewReader(buf.Bytes()[:headerSize]),
r,
bytes.NewReader(buf.Bytes()[headerSize:]))
contentLength := int64(buf.Len()) + fileSize
return mr, w.FormDataContentType(), contentLength, nil
}
// getStorageAndVolumeName - Extract disk storage and disk volume, since disk name is saved
// in Proxmox with its storage.
func getStorageAndVolumeName(
fullDiskName string,
separator string,
) (storageName string, diskName string) {
storageAndVolumeName := strings.Split(fullDiskName, separator)
storageName, volumeName := storageAndVolumeName[0], storageAndVolumeName[1]
// when disk type is dir, volumeName is `file=local:100/vm-100-disk-0.raw`
re := regexp.MustCompile(`\d+/(?P<filename>\S+.\S+)`)
match := re.FindStringSubmatch(volumeName)
if len(match) == 2 {
volumeName = match[1]
}
return storageName, volumeName
}
2019-10-27 08:10:35 -04:00
func (c *Client) UpdateVMPool(vmr *VmRef, pool string) (exitStatus interface{}, err error) {
// Same pool
if vmr.pool == pool {
return
}
// Remove from old pool
if vmr.pool != "" {
paramMap := map[string]interface{}{
"vms": vmr.vmId,
"delete": 1,
}
reqbody := ParamsToBody(paramMap)
url := fmt.Sprintf("/pools/%s", vmr.pool)
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)
if err != nil {
return nil, err
}
}
}
// Add to the new pool
if pool != "" {
paramMap := map[string]interface{}{
"vms": vmr.vmId,
}
reqbody := ParamsToBody(paramMap)
url := fmt.Sprintf("/pools/%s", pool)
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)
} else {
return nil, err
}
}
return
}
2020-01-17 14:26:44 -05:00
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
}