packer-xcp-ng/builder/xenserver/common/client.go

975 lines
21 KiB
Go
Raw Permalink Normal View History

2023-02-14 15:48:24 +01:00
package common
import (
"encoding/xml"
"errors"
"fmt"
"log"
xmlrpc "github.com/amfranz/go-xmlrpc-client"
xenapi "github.com/terra-farm/go-xen-api-client"
)
type XenAPIClient struct {
Session interface{}
Host string
Url string
Username string
Password string
RPC *xmlrpc.Client
}
type APIResult struct {
Status string
Value interface{}
ErrorDescription string
}
type XenAPIObject struct {
Ref string
Client *XenAPIClient
}
type Host XenAPIObject
type VM XenAPIObject
type SR XenAPIObject
type VDI XenAPIObject
type Network XenAPIObject
type VBD XenAPIObject
type VIF XenAPIObject
type PIF XenAPIObject
type Pool XenAPIObject
type Task XenAPIObject
type VDIType int
const (
_ VDIType = iota
Disk
CD
Floppy
)
type TaskStatusType int
const (
_ TaskStatusType = iota
Pending
Success
Failure
Cancelling
Cancelled
)
func (c *XenAPIClient) RPCCall(result interface{}, method string, params []interface{}) (err error) {
fmt.Println(params)
p := xmlrpc.Params{Params: params}
err = c.RPC.Call(method, p, result)
return err
}
func (client *XenAPIClient) Login() (err error) {
//Do loging call
result := xmlrpc.Struct{}
params := make([]interface{}, 2)
params[0] = client.Username
params[1] = client.Password
err = client.RPCCall(&result, "session.login_with_password", params)
client.Session = result["Value"]
return err
}
func (client *XenAPIClient) APICall(result *APIResult, method string, params ...interface{}) (err error) {
if client.Session == nil {
fmt.Println("Error: no session")
return fmt.Errorf("No session. Unable to make call")
}
//Make a params slice which will include the session
p := make([]interface{}, len(params)+1)
p[0] = client.Session
if params != nil {
for idx, element := range params {
p[idx+1] = element
}
}
res := xmlrpc.Struct{}
err = client.RPCCall(&res, method, p)
if err != nil {
return err
}
result.Status = res["Status"].(string)
if result.Status != "Success" {
fmt.Println("Encountered an API error: ", result.Status)
fmt.Println(res["ErrorDescription"])
return fmt.Errorf("API Error: %s", res["ErrorDescription"])
} else {
result.Value = res["Value"]
}
return
}
func (client *XenAPIClient) GetHosts() (hosts []*Host, err error) {
hosts = make([]*Host, 0)
result := APIResult{}
_ = client.APICall(&result, "host.get_all")
for _, elem := range result.Value.([]interface{}) {
host := new(Host)
host.Ref = elem.(string)
host.Client = client
hosts = append(hosts, host)
}
return
}
func (client *XenAPIClient) GetPools() (pools []*Pool, err error) {
pools = make([]*Pool, 0)
result := APIResult{}
err = client.APICall(&result, "pool.get_all")
if err != nil {
return pools, err
}
for _, elem := range result.Value.([]interface{}) {
pool := new(Pool)
pool.Ref = elem.(string)
pool.Client = client
pools = append(pools, pool)
}
return pools, nil
}
func (client *XenAPIClient) GetDefaultSR() (sr *SR, err error) {
pools, err := client.GetPools()
if err != nil {
return nil, err
}
pool_rec, err := pools[0].GetRecord()
if err != nil {
return nil, err
}
if pool_rec["default_SR"] == "" {
return nil, errors.New("No default_SR specified for the pool.")
}
sr = new(SR)
sr.Ref = pool_rec["default_SR"].(string)
sr.Client = client
return sr, nil
}
func (client *XenAPIClient) GetVMByUuid(vm_uuid string) (vm *VM, err error) {
vm = new(VM)
result := APIResult{}
err = client.APICall(&result, "VM.get_by_uuid", vm_uuid)
if err != nil {
return nil, err
}
vm.Ref = result.Value.(string)
vm.Client = client
return
}
func (client *XenAPIClient) GetVMByNameLabel(name_label string) (vms []*VM, err error) {
vms = make([]*VM, 0)
result := APIResult{}
err = client.APICall(&result, "VM.get_by_name_label", name_label)
if err != nil {
return vms, err
}
for _, elem := range result.Value.([]interface{}) {
vm := new(VM)
vm.Ref = elem.(string)
vm.Client = client
vms = append(vms, vm)
}
return vms, nil
}
func (client *XenAPIClient) GetNetworkByUuid(network_uuid string) (network *Network, err error) {
network = new(Network)
result := APIResult{}
err = client.APICall(&result, "network.get_by_uuid", network_uuid)
if err != nil {
return nil, err
}
network.Ref = result.Value.(string)
network.Client = client
return
}
func (client *XenAPIClient) GetNetworkByNameLabel(name_label string) (networks []*Network, err error) {
networks = make([]*Network, 0)
result := APIResult{}
err = client.APICall(&result, "network.get_by_name_label", name_label)
if err != nil {
return networks, err
}
for _, elem := range result.Value.([]interface{}) {
network := new(Network)
network.Ref = elem.(string)
network.Client = client
networks = append(networks, network)
}
return networks, nil
}
func (client *XenAPIClient) GetVdiByNameLabel(name_label string) (vdis []*VDI, err error) {
vdis = make([]*VDI, 0)
result := APIResult{}
err = client.APICall(&result, "VDI.get_by_name_label", name_label)
if err != nil {
return vdis, err
}
for _, elem := range result.Value.([]interface{}) {
vdi := new(VDI)
vdi.Ref = elem.(string)
vdi.Client = client
vdis = append(vdis, vdi)
}
return vdis, nil
}
func (client *XenAPIClient) GetVdiByUuid(vdi_uuid string) (vdi *VDI, err error) {
vdi = new(VDI)
result := APIResult{}
err = client.APICall(&result, "VDI.get_by_uuid", vdi_uuid)
if err != nil {
return nil, err
}
vdi.Ref = result.Value.(string)
vdi.Client = client
return
}
func (client *XenAPIClient) GetPIFs() (pifs []*PIF, err error) {
pifs = make([]*PIF, 0)
result := APIResult{}
err = client.APICall(&result, "PIF.get_all")
if err != nil {
return pifs, err
}
for _, elem := range result.Value.([]interface{}) {
pif := new(PIF)
pif.Ref = elem.(string)
pif.Client = client
pifs = append(pifs, pif)
}
return pifs, nil
}
// Host associated functions
func (self *Host) GetSoftwareVersion() (versions map[string]interface{}, err error) {
versions = make(map[string]interface{})
result := APIResult{}
err = self.Client.APICall(&result, "host.get_software_version", self.Ref)
if err != nil {
return nil, err
}
for k, v := range result.Value.(xmlrpc.Struct) {
versions[k] = v.(string)
}
return
}
func (self *Host) CallPlugin(plugin, function string, args map[string]string) (res string, err error) {
args_rec := make(xmlrpc.Struct)
for key, value := range args {
args_rec[key] = value
}
result := APIResult{}
err = self.Client.APICall(&result, "host.call_plugin", self.Ref, plugin, function, args_rec)
if err != nil {
return "", err
}
// The plugin should return a string value
res = result.Value.(string)
return
}
// VM associated functions
func (self *VM) Clone(label string) (new_instance *VM, err error) {
new_instance = new(VM)
result := APIResult{}
err = self.Client.APICall(&result, "VM.clone", self.Ref, label)
if err != nil {
return nil, err
}
new_instance.Ref = result.Value.(string)
new_instance.Client = self.Client
return
}
func (self *VM) Destroy() (err error) {
result := APIResult{}
err = self.Client.APICall(&result, "VM.destroy", self.Ref)
if err != nil {
return err
}
return
}
func (self *VM) Start(paused, force bool) (err error) {
result := APIResult{}
err = self.Client.APICall(&result, "VM.start", self.Ref, paused, force)
if err != nil {
return err
}
return
}
func (self *VM) CleanShutdown() (err error) {
result := APIResult{}
err = self.Client.APICall(&result, "VM.clean_shutdown", self.Ref)
if err != nil {
return err
}
return
}
func Unpause(c *Connection, vmRef xenapi.VMRef) (err error) {
err = c.client.VM.Unpause(c.session, vmRef)
if err != nil {
return err
}
return
}
func (self *VM) SetHVMBoot(policy, bootOrder string) (err error) {
result := APIResult{}
err = self.Client.APICall(&result, "VM.set_HVM_boot_policy", self.Ref, policy)
if err != nil {
return err
}
result = APIResult{}
params := make(xmlrpc.Struct)
params["order"] = bootOrder
err = self.Client.APICall(&result, "VM.set_HVM_boot_params", self.Ref, params)
if err != nil {
return err
}
return
}
func (self *VM) SetPVBootloader(pv_bootloader, pv_args string) (err error) {
result := APIResult{}
err = self.Client.APICall(&result, "VM.set_PV_bootloader", self.Ref, pv_bootloader)
if err != nil {
return err
}
result = APIResult{}
err = self.Client.APICall(&result, "VM.set_PV_bootloader_args", self.Ref, pv_args)
if err != nil {
return err
}
return
}
func (self *VM) GetDomainId() (domid string, err error) {
result := APIResult{}
err = self.Client.APICall(&result, "VM.get_domid", self.Ref)
if err != nil {
return "", err
}
domid = result.Value.(string)
return domid, nil
}
func (self *VM) GetPowerState() (state string, err error) {
result := APIResult{}
err = self.Client.APICall(&result, "VM.get_power_state", self.Ref)
if err != nil {
return "", err
}
state = result.Value.(string)
return state, nil
}
func (self *VM) GetUuid() (uuid string, err error) {
result := APIResult{}
err = self.Client.APICall(&result, "VM.get_uuid", self.Ref)
if err != nil {
return "", err
}
uuid = result.Value.(string)
return uuid, nil
}
func (self *VM) GetVBDs() (vbds []VBD, err error) {
vbds = make([]VBD, 0)
result := APIResult{}
err = self.Client.APICall(&result, "VM.get_VBDs", self.Ref)
if err != nil {
return vbds, err
}
for _, elem := range result.Value.([]interface{}) {
vbd := VBD{}
vbd.Ref = elem.(string)
vbd.Client = self.Client
vbds = append(vbds, vbd)
}
return vbds, nil
}
func GetDisks(c *Connection, vmRef xenapi.VMRef) (vdis []xenapi.VDIRef, err error) {
// Return just data disks (non-isos)
vdis = make([]xenapi.VDIRef, 0)
vbds, err := c.client.VM.GetVBDs(c.session, vmRef)
if err != nil {
return nil, err
}
for _, vbd := range vbds {
rec, err := c.client.VBD.GetRecord(c.session, vbd)
if err != nil {
return nil, err
}
if rec.Type == "Disk" {
vdi, err := c.client.VBD.GetVDI(c.session, vbd)
if err != nil {
return nil, err
}
vdis = append(vdis, vdi)
}
}
return vdis, nil
}
func (self *VM) GetGuestMetricsRef() (ref string, err error) {
result := APIResult{}
err = self.Client.APICall(&result, "VM.get_guest_metrics", self.Ref)
if err != nil {
return "", nil
}
ref = result.Value.(string)
return ref, err
}
func (self *VM) GetGuestMetrics() (metrics map[string]interface{}, err error) {
metrics_ref, err := self.GetGuestMetricsRef()
if err != nil {
return nil, err
}
if metrics_ref == "OpaqueRef:NULL" {
return nil, nil
}
result := APIResult{}
err = self.Client.APICall(&result, "VM_guest_metrics.get_record", metrics_ref)
if err != nil {
return nil, err
}
return result.Value.(xmlrpc.Struct), nil
}
func (self *VM) SetStaticMemoryRange(min, max uint) (err error) {
result := APIResult{}
strMin := fmt.Sprintf("%d", min)
strMax := fmt.Sprintf("%d", max)
err = self.Client.APICall(&result, "VM.set_memory_limits", self.Ref, strMin, strMax, strMin, strMax)
if err != nil {
return err
}
return
}
func ConnectVdi(c *Connection, vmRef xenapi.VMRef, vdiRef xenapi.VDIRef, vbdType xenapi.VbdType) (err error) {
var mode xenapi.VbdMode
var unpluggable bool
var bootable bool
var t xenapi.VbdType
switch vbdType {
case xenapi.VbdTypeCD:
mode = xenapi.VbdModeRO
bootable = true
unpluggable = false
t = xenapi.VbdTypeCD
case xenapi.VbdTypeDisk:
mode = xenapi.VbdModeRW
bootable = false
unpluggable = false
t = xenapi.VbdTypeDisk
case xenapi.VbdTypeFloppy:
mode = xenapi.VbdModeRW
bootable = false
unpluggable = true
t = xenapi.VbdTypeFloppy
}
vbd_ref, err := c.client.VBD.Create(c.session, xenapi.VBDRecord{
VM: xenapi.VMRef(vmRef),
VDI: xenapi.VDIRef(vdiRef),
Userdevice: "autodetect",
Empty: false,
// OtherConfig: map[string]interface{{}},
QosAlgorithmType: "",
// QosAlgorithmParams: map[string]interface{{}},
Mode: mode,
Unpluggable: unpluggable,
Bootable: bootable,
Type: t,
})
if err != nil {
return err
}
fmt.Println("VBD Ref:", vbd_ref)
uuid, err := c.client.VBD.GetUUID(c.session, vbd_ref)
fmt.Println("VBD UUID: ", uuid)
/*
// 2. Plug VBD (Non need - the VM hasn't booted.
// @todo - check VM state
result = APIResult{}
err = self.Client.APICall(&result, "VBD.plug", vbd_ref)
if err != nil {
return err
}
*/
return
}
func DisconnectVdi(c *Connection, vmRef xenapi.VMRef, vdi xenapi.VDIRef) error {
vbds, err := c.client.VM.GetVBDs(c.session, vmRef)
if err != nil {
return fmt.Errorf("Unable to get VM VBDs: %s", err.Error())
}
for _, vbd := range vbds {
rec, err := c.client.VBD.GetRecord(c.session, vbd)
if err != nil {
return fmt.Errorf("Could not get record for VBD '%s': %s", vbd, err.Error())
}
recVdi := rec.VDI
if recVdi == vdi {
_ = c.client.VBD.Unplug(c.session, vbd)
err = c.client.VBD.Destroy(c.session, vbd)
if err != nil {
return fmt.Errorf("Could not destroy VBD '%s': %s", vbd, err.Error())
}
return nil
} else {
log.Printf("Could not find VDI record in VBD '%s'", vbd)
}
}
return fmt.Errorf("Could not find VBD for VDI '%s'", vdi)
}
func (self *VM) SetPlatform(params map[string]string) (err error) {
result := APIResult{}
platform_rec := make(xmlrpc.Struct)
for key, value := range params {
platform_rec[key] = value
}
err = self.Client.APICall(&result, "VM.set_platform", self.Ref, platform_rec)
if err != nil {
return err
}
return
}
func ConnectNetwork(c *Connection, networkRef xenapi.NetworkRef, vmRef xenapi.VMRef, device string) (*xenapi.VIFRef, error) {
vif, err := c.client.VIF.Create(c.session, xenapi.VIFRecord{
Network: networkRef,
VM: vmRef,
Device: device,
LockingMode: xenapi.VifLockingModeNetworkDefault,
})
if err != nil {
return nil, err
}
log.Printf("Created the following VIF: %s", vif)
return &vif, nil
}
// Setters
func (self *VM) SetIsATemplate(is_a_template bool) (err error) {
result := APIResult{}
err = self.Client.APICall(&result, "VM.set_is_a_template", self.Ref, is_a_template)
if err != nil {
return err
}
return
}
// SR associated functions
func (self *SR) CreateVdi(name_label string, size int64) (vdi *VDI, err error) {
vdi = new(VDI)
vdi_rec := make(xmlrpc.Struct)
vdi_rec["name_label"] = name_label
vdi_rec["SR"] = self.Ref
vdi_rec["virtual_size"] = fmt.Sprintf("%d", size)
vdi_rec["type"] = "user"
vdi_rec["sharable"] = false
vdi_rec["read_only"] = false
oc := make(xmlrpc.Struct)
oc["temp"] = "temp"
vdi_rec["other_config"] = oc
result := APIResult{}
err = self.Client.APICall(&result, "VDI.create", vdi_rec)
if err != nil {
return nil, err
}
vdi.Ref = result.Value.(string)
vdi.Client = self.Client
return
}
// Network associated functions
func (self *Network) GetAssignedIPs() (ip_map map[string]string, err error) {
ip_map = make(map[string]string, 0)
result := APIResult{}
err = self.Client.APICall(&result, "network.get_assigned_ips", self.Ref)
if err != nil {
return ip_map, err
}
for k, v := range result.Value.(xmlrpc.Struct) {
ip_map[k] = v.(string)
}
return ip_map, nil
}
// PIF associated functions
func (self *PIF) GetRecord() (record map[string]interface{}, err error) {
record = make(map[string]interface{})
result := APIResult{}
err = self.Client.APICall(&result, "PIF.get_record", self.Ref)
if err != nil {
return record, err
}
for k, v := range result.Value.(xmlrpc.Struct) {
record[k] = v
}
return record, nil
}
// Pool associated functions
func (self *Pool) GetRecord() (record map[string]interface{}, err error) {
record = make(map[string]interface{})
result := APIResult{}
err = self.Client.APICall(&result, "pool.get_record", self.Ref)
if err != nil {
return record, err
}
for k, v := range result.Value.(xmlrpc.Struct) {
record[k] = v
}
return record, nil
}
// VBD associated functions
func (self *VBD) GetRecord() (record map[string]interface{}, err error) {
record = make(map[string]interface{})
result := APIResult{}
err = self.Client.APICall(&result, "VBD.get_record", self.Ref)
if err != nil {
return record, err
}
for k, v := range result.Value.(xmlrpc.Struct) {
record[k] = v
}
return record, nil
}
func (self *VBD) GetVDI() (vdi *VDI, err error) {
vbd_rec, err := self.GetRecord()
if err != nil {
return nil, err
}
vdi = new(VDI)
vdi.Ref = vbd_rec["VDI"].(string)
vdi.Client = self.Client
return vdi, nil
}
func (self *VBD) Eject() (err error) {
result := APIResult{}
err = self.Client.APICall(&result, "VBD.eject", self.Ref)
if err != nil {
return err
}
return nil
}
func (self *VBD) Unplug() (err error) {
result := APIResult{}
err = self.Client.APICall(&result, "VBD.unplug", self.Ref)
if err != nil {
return err
}
return nil
}
func (self *VBD) Destroy() (err error) {
result := APIResult{}
err = self.Client.APICall(&result, "VBD.destroy", self.Ref)
if err != nil {
return err
}
return nil
}
// VIF associated functions
func (self *VIF) Destroy() (err error) {
result := APIResult{}
err = self.Client.APICall(&result, "VIF.destroy", self.Ref)
if err != nil {
return err
}
return nil
}
// VDI associated functions
func (self *VDI) GetUuid() (vdi_uuid string, err error) {
result := APIResult{}
err = self.Client.APICall(&result, "VDI.get_uuid", self.Ref)
if err != nil {
return "", err
}
vdi_uuid = result.Value.(string)
return vdi_uuid, nil
}
func (self *VDI) GetVBDs() (vbds []VBD, err error) {
vbds = make([]VBD, 0)
result := APIResult{}
err = self.Client.APICall(&result, "VDI.get_VBDs", self.Ref)
if err != nil {
return vbds, err
}
for _, elem := range result.Value.([]interface{}) {
vbd := VBD{}
vbd.Ref = elem.(string)
vbd.Client = self.Client
vbds = append(vbds, vbd)
}
return vbds, nil
}
func (self *VDI) Destroy() (err error) {
result := APIResult{}
err = self.Client.APICall(&result, "VDI.destroy", self.Ref)
if err != nil {
return err
}
return
}
// Expose a VDI using the Transfer VM
// (Legacy VHD export)
type TransferRecord struct {
UrlFull string `xml:"url_full,attr"`
}
func Expose(c *Connection, vdiRef xenapi.VDIRef, format string) (url string, err error) {
hosts, err := c.client.Host.GetAll(c.session)
if err != nil {
err = errors.New(fmt.Sprintf("Could not retrieve hosts in the pool: %s", err.Error()))
return "", err
}
host := hosts[0]
if err != nil {
err = errors.New(fmt.Sprintf("Failed to get VDI uuid for %s: %s", vdiRef, err.Error()))
return "", err
}
args := make(map[string]string)
args["transfer_mode"] = "http"
args["vdi_uuid"] = string(vdiRef)
args["expose_vhd"] = "true"
args["network_uuid"] = "management"
args["timeout_minutes"] = "5"
handle, err := c.client.Host.CallPlugin(c.session, host, "transfer", "expose", args)
if err != nil {
err = errors.New(fmt.Sprintf("Error whilst exposing VDI %s: %s", vdiRef, err.Error()))
return "", err
}
args = make(map[string]string)
args["record_handle"] = handle
record_xml, err := c.client.Host.CallPlugin(c.session, host, "transfer", "get_record", args)
if err != nil {
err = errors.New(fmt.Sprintf("Unable to retrieve transfer record for VDI %s: %s", vdiRef, err.Error()))
return "", err
}
var record TransferRecord
xml.Unmarshal([]byte(record_xml), &record)
if record.UrlFull == "" {
return "", errors.New(fmt.Sprintf("Error: did not parse XML properly: '%s'", record_xml))
}
// Handles either raw or VHD formats
switch format {
case "vhd":
url = fmt.Sprintf("%s.vhd", record.UrlFull)
case "raw":
url = record.UrlFull
}
return
}
func Unexpose(c *Connection, vdiRef xenapi.VDIRef) (err error) {
disk_uuid, err := c.client.VDI.GetUUID(c.session, vdiRef)
if err != nil {
return err
}
hosts, err := c.client.Host.GetAll(c.session)
if err != nil {
err = errors.New(fmt.Sprintf("Could not retrieve hosts in the pool: %s", err.Error()))
return err
}
host := hosts[0]
args := make(map[string]string)
args["vdi_uuid"] = disk_uuid
result, err := c.client.Host.CallPlugin(c.session, host, "transfer", "unexpose", args)
if err != nil {
return err
}
log.Println(fmt.Sprintf("Unexpose result: %s", result))
return nil
}
// Task associated functions
// func (self *Task) GetResult() (object *XenAPIObject, err error) {
// result := APIResult{}
// err = self.Client.APICall(&result, "task.get_result", self.Ref)
// if err != nil {
// return
// }
// switch ref := result.Value.(type) {
// case string:
// // @fixme: xapi currently sends us an xmlrpc-encoded string via xmlrpc.
// // This seems to be a bug in xapi. Remove this workaround when it's fixed
// re := regexp.MustCompile("^<value><array><data><value>([^<]*)</value>.*</data></array></value>$")
// match := re.FindStringSubmatch(ref)
// if match == nil {
// object = nil
// } else {
// object = &XenAPIObject{
// Ref: match[1],
// Client: self.Client,
// }
// }
// case nil:
// object = nil
// default:
// err = fmt.Errorf("task.get_result: unknown value type %T (expected string or nil)", ref)
// }
// return
// }
// Client Initiator
type Connection struct {
client *xenapi.Client
session xenapi.SessionRef
Host string
Username string
Password string
}
func (c Connection) GetSession() string {
return string(c.session)
}
func NewXenAPIClient(host, username, password string) (*Connection, error) {
client, err := xenapi.NewClient("https://"+host, nil)
if err != nil {
return nil, err
}
session, err := client.Session.LoginWithPassword(username, password, "1.0", "packer")
if err != nil {
return nil, err
}
return &Connection{client, session, host, username, password}, nil
}
func (c *Connection) GetClient() *xenapi.Client {
return c.client
}
func (c *Connection) GetSessionRef() xenapi.SessionRef {
return c.session
}