873 lines
25 KiB
Go
873 lines
25 KiB
Go
package libguestd
|
|
|
|
import (
|
|
"encoding/base64"
|
|
"encoding/json"
|
|
"fmt"
|
|
"github.com/digitalocean/go-libvirt"
|
|
"time"
|
|
)
|
|
|
|
// GuestPing returns error when agent didn't respond or something bad happened
|
|
// Returns nil on success.
|
|
func GuestPing(l *libvirt.Libvirt, domain libvirt.Domain) error {
|
|
|
|
_, err := l.QEMUDomainAgentCommand(domain, "{\"execute\":\"guest-ping\"}", 100, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
type GuestAgentCommandInfo struct {
|
|
Name string `json:"name"`
|
|
Enabled bool `json:"enabled"`
|
|
SuccessResponse bool `json:"success-response"`
|
|
}
|
|
type GuestAgentInfo struct {
|
|
Version string `json:"version"`
|
|
SupportedCommands []GuestAgentCommandInfo `json:"supported_commands"`
|
|
}
|
|
|
|
func (ga *GuestAgentInfo) Dump() {
|
|
fmt.Printf("Guest Agent Info:\n")
|
|
fmt.Printf("\tVersion: %s\n", ga.Version)
|
|
fmt.Printf("\tSupportedCommands:\n")
|
|
for _, command := range ga.SupportedCommands {
|
|
if command.Enabled {
|
|
fmt.Printf("\t\t%s\n", command.Name)
|
|
}
|
|
}
|
|
}
|
|
|
|
type GuestAgentInfoWrapper struct {
|
|
Result GuestAgentInfo `json:"return"`
|
|
}
|
|
|
|
func GuestInfo(l *libvirt.Libvirt, domain libvirt.Domain) (GuestAgentInfo, error) {
|
|
r, err := l.QEMUDomainAgentCommand(domain, "{\"execute\":\"guest-info\"}", 100, 0)
|
|
if err != nil {
|
|
return GuestAgentInfo{}, err
|
|
}
|
|
if len(r) > 0 {
|
|
var ret GuestAgentInfoWrapper
|
|
err := json.Unmarshal([]byte(r[0]), &ret)
|
|
if err != nil {
|
|
return GuestAgentInfo{}, err
|
|
}
|
|
return ret.Result, nil
|
|
}
|
|
return GuestAgentInfo{}, nil
|
|
}
|
|
|
|
type GuestOsInfo struct {
|
|
KernelRelease string `json:"kernel-release"`
|
|
KernelVersion string `json:"kernel-version"`
|
|
Machine string `json:"machine"`
|
|
ID string `json:"id"`
|
|
Name string `json:"name"`
|
|
PrettyName string `json:"pretty-name"`
|
|
Version string `json:"version"`
|
|
VersionId string `json:"version-id"`
|
|
Variant string `json:"variant"`
|
|
VariantId string `json:"variant-id"`
|
|
}
|
|
|
|
func (goi *GuestOsInfo) Dump() {
|
|
fmt.Printf("Guest OS Info:\n")
|
|
fmt.Printf("\tKernelRelease: %s\n", goi.KernelRelease)
|
|
fmt.Printf("\tKernelVersion: %s\n", goi.KernelVersion)
|
|
fmt.Printf("\tMachine: %s\n", goi.Machine)
|
|
fmt.Printf("\tID: %s\n", goi.ID)
|
|
fmt.Printf("\tName: %s\n", goi.Name)
|
|
fmt.Printf("\tPrettyName: %s\n", goi.PrettyName)
|
|
fmt.Printf("\tVersion: %s\n", goi.Version)
|
|
fmt.Printf("\tVersionId: %s\n", goi.VersionId)
|
|
fmt.Printf("\tVariant: %s\n", goi.Variant)
|
|
fmt.Printf("\tVariantId: %s\n", goi.VariantId)
|
|
}
|
|
|
|
type GuestOsInfoWrapper struct {
|
|
Result GuestOsInfo `json:"return"`
|
|
}
|
|
|
|
func checkSupportedFeature(AgentInfo GuestAgentInfo, feature string) bool {
|
|
for _, v := range AgentInfo.SupportedCommands {
|
|
if v.Name == feature {
|
|
return v.Enabled
|
|
}
|
|
}
|
|
return false
|
|
}
|
|
|
|
func GuestGetOsInfo(l *libvirt.Libvirt, domain libvirt.Domain, AgentInfo GuestAgentInfo) (GuestOsInfo, error) {
|
|
if !checkSupportedFeature(AgentInfo, "guest-get-osinfo") {
|
|
return GuestOsInfo{}, fmt.Errorf("guest-get-osinfo not supported by agent")
|
|
}
|
|
|
|
r, err := l.QEMUDomainAgentCommand(domain, "{\"execute\":\"guest-get-osinfo\"}", 100, 0)
|
|
if err != nil {
|
|
return GuestOsInfo{}, err
|
|
}
|
|
if len(r) > 0 {
|
|
var ret GuestOsInfoWrapper
|
|
err := json.Unmarshal([]byte(r[0]), &ret)
|
|
if err != nil {
|
|
return GuestOsInfo{}, err
|
|
}
|
|
return ret.Result, nil
|
|
}
|
|
return GuestOsInfo{}, fmt.Errorf("empty response from guest-get-osinfo")
|
|
}
|
|
|
|
type GuestNetworkInterfaceStat struct {
|
|
RxBytes uint64 `json:"rx-bytes"`
|
|
TxBytes uint64 `json:"tx-bytes"`
|
|
RxErrors uint64 `json:"rx-errs"`
|
|
TxErrors uint64 `json:"tx-errs"`
|
|
RxDropped uint64 `json:"rx-dropped"`
|
|
TxDropped uint64 `json:"tx-dropped"`
|
|
RxPackets uint64 `json:"rx-packets"`
|
|
TxPackets uint64 `json:"tx-packets"`
|
|
}
|
|
|
|
func (gnis *GuestNetworkInterfaceStat) String() string {
|
|
return fmt.Sprintf("RX[Bytes: %d, Packets: %d, Dropped: %d, Errors: %d] TX[Bytes: %d, Packets: %d, Dropped: %d, Errors: %d]", gnis.RxBytes, gnis.RxPackets, gnis.RxDropped, gnis.RxErrors, gnis.TxBytes, gnis.TxPackets, gnis.TxDropped, gnis.TxErrors)
|
|
}
|
|
|
|
type GuestIpAddress struct {
|
|
IPAddress string `json:"ip-address"`
|
|
IPAddressType string `json:"ip-address-type"`
|
|
Prefix uint32 `json:"prefix"`
|
|
}
|
|
type GuestNetworkInterface struct {
|
|
Name string `json:"name"`
|
|
HardwareAddress string `json:"hardware-address"`
|
|
IpAddresses []GuestIpAddress `json:"ip-addresses"`
|
|
Statistics GuestNetworkInterfaceStat `json:"statistics"`
|
|
}
|
|
|
|
func (gni *GuestNetworkInterface) Dump() {
|
|
fmt.Printf("Guest Network Interface:\n")
|
|
fmt.Printf("\tName: %s\n", gni.Name)
|
|
fmt.Printf("\tHardware Address: %s\n", gni.HardwareAddress)
|
|
fmt.Printf("\tStatistics: %s\n", gni.Statistics.String())
|
|
for _, ip := range gni.IpAddresses {
|
|
fmt.Printf("\t\t%s/%d (%s)\n", ip.IPAddress, ip.Prefix, ip.IPAddressType)
|
|
}
|
|
|
|
}
|
|
|
|
type GuestNetworkInterfaceWrapper struct {
|
|
Result []GuestNetworkInterface `json:"return"`
|
|
}
|
|
|
|
func GuestGetNetworkInterfaces(l *libvirt.Libvirt, domain libvirt.Domain, AgentInfo GuestAgentInfo) ([]GuestNetworkInterface, error) {
|
|
if !checkSupportedFeature(AgentInfo, "guest-network-get-interfaces") {
|
|
return nil, fmt.Errorf("guest-network-get-interfaces not supported by agent")
|
|
}
|
|
|
|
r, err := l.QEMUDomainAgentCommand(domain, "{\"execute\":\"guest-network-get-interfaces\"}", 100, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(r) > 0 {
|
|
var ret GuestNetworkInterfaceWrapper
|
|
err := json.Unmarshal([]byte(r[0]), &ret)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ret.Result, nil
|
|
}
|
|
return nil, fmt.Errorf("empty response from guest-network-get-interfaces")
|
|
}
|
|
|
|
type GuestPCIAddress struct {
|
|
Domain int `json:"domain"`
|
|
Bus int `json:"bus"`
|
|
Slot int `json:"slot"`
|
|
Function int `json:"function"`
|
|
}
|
|
|
|
func (gpi *GuestPCIAddress) String() string {
|
|
return fmt.Sprintf("pci%d:%d:%d:%d", gpi.Domain, gpi.Bus, gpi.Slot, gpi.Function)
|
|
}
|
|
|
|
type GuestDiskAddress struct {
|
|
PciController GuestPCIAddress `json:"pci-controller"`
|
|
BusType string `json:"bus-type"`
|
|
Bus int `json:"bus"`
|
|
Target int `json:"target"`
|
|
Unit int `json:"unit"`
|
|
Serial string `json:"serial"`
|
|
Dev string `json:"dev"`
|
|
}
|
|
|
|
func (gdi *GuestDiskAddress) String() string {
|
|
return fmt.Sprintf("%s (%s%d/%d/%d at %s)", gdi.Dev, gdi.BusType, gdi.Bus, gdi.Target, gdi.Unit, gdi.PciController.String())
|
|
}
|
|
|
|
type GuestFilesystemInfo struct {
|
|
Name string `json:"name"`
|
|
Type string `json:"type"`
|
|
Mountpoint string `json:"mountpoint"`
|
|
UsedBytes uint64 `json:"used-bytes"`
|
|
TotalBytes uint64 `json:"total-bytes"`
|
|
TotalBytesPrivileged uint64 `json:"total-bytes-privileged"`
|
|
Disk []GuestDiskAddress `json:"disk"`
|
|
}
|
|
|
|
func (gfi *GuestFilesystemInfo) Dump() {
|
|
fmt.Printf("Guest Filesystem Info: %s\n", gfi.Mountpoint)
|
|
fmt.Printf("\tName: %s\n", gfi.Name)
|
|
fmt.Printf("\tType: %s\n", gfi.Type)
|
|
fmt.Printf("\tMountpoint: %s\n", gfi.Mountpoint)
|
|
fmt.Printf("\tUsedBytes: %d MiB\n", gfi.UsedBytes/1024/1024)
|
|
fmt.Printf("\tTotalBytes: %d MiB\n", gfi.TotalBytes/1024/1024)
|
|
fmt.Printf("\tTotalBytesPrivileged: %d MiB\n", gfi.TotalBytesPrivileged/1024/1024)
|
|
|
|
fmt.Printf("\t[")
|
|
Space := gfi.UsedBytes * 40 / gfi.TotalBytes
|
|
var i uint64
|
|
for i = 0; i < Space; i++ {
|
|
fmt.Printf("=")
|
|
}
|
|
for i = Space; i < 40; i++ {
|
|
fmt.Printf(" ")
|
|
}
|
|
fmt.Printf("] %d%%\n", gfi.UsedBytes*100/gfi.TotalBytes)
|
|
|
|
fmt.Printf("\tDisk: \n")
|
|
for _, d := range gfi.Disk {
|
|
fmt.Printf("\t\t%s\n", d.String())
|
|
}
|
|
|
|
}
|
|
|
|
type GuestFilesystemInfoWrapper struct {
|
|
Result []GuestFilesystemInfo `json:"return"`
|
|
}
|
|
|
|
func GuestGetFilesystemInfo(l *libvirt.Libvirt, domain libvirt.Domain, AgentInfo GuestAgentInfo) ([]GuestFilesystemInfo, error) {
|
|
if !checkSupportedFeature(AgentInfo, "guest-get-fsinfo") {
|
|
return nil, fmt.Errorf("guest-get-fsinfo not supported by agent")
|
|
}
|
|
r, err := l.QEMUDomainAgentCommand(domain, "{\"execute\":\"guest-get-fsinfo\"}", 100, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(r) > 0 {
|
|
var ret GuestFilesystemInfoWrapper
|
|
err := json.Unmarshal([]byte(r[0]), &ret)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ret.Result, nil
|
|
}
|
|
return nil, fmt.Errorf("empty response from guest-get-fsinfo")
|
|
}
|
|
|
|
func GuestChangeUserPassword(l *libvirt.Libvirt, domain libvirt.Domain, AgentInfo GuestAgentInfo, Username string, Password string) error {
|
|
if !checkSupportedFeature(AgentInfo, "guest-set-user-password") {
|
|
return fmt.Errorf("guest-set-user-password not supported by agent")
|
|
}
|
|
|
|
type Args struct {
|
|
Username string `json:"username"`
|
|
Password string `json:"password"`
|
|
Crypted bool `json:"crypted"`
|
|
}
|
|
|
|
type req struct {
|
|
Execute string `json:"execute"`
|
|
Args Args `json:"arguments"`
|
|
}
|
|
re := req{
|
|
Execute: "guest-set-user-password",
|
|
Args: Args{
|
|
Username: Username,
|
|
Password: base64.StdEncoding.EncodeToString([]byte(Password)),
|
|
Crypted: false,
|
|
},
|
|
}
|
|
reS, err := json.Marshal(&re)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
_, err = l.QEMUDomainAgentCommand(domain, string(reS), 100, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func GuestAddSshKeys(l *libvirt.Libvirt, domain libvirt.Domain, AgentInfo GuestAgentInfo, Username string, Keys []string, reset bool) error {
|
|
if !checkSupportedFeature(AgentInfo, "guest-ssh-add-authorized-keys") {
|
|
return fmt.Errorf("guest-ssh-add-authorized-keys not supported by agent")
|
|
}
|
|
type Args struct {
|
|
Username string `json:"username"`
|
|
Keys []string `json:"keys"`
|
|
Reset bool `json:"reset"`
|
|
}
|
|
type req struct {
|
|
Execute string `json:"execute"`
|
|
Args Args `json:"arguments"`
|
|
}
|
|
re := req{
|
|
Execute: "guest-ssh-add-authorized-keys",
|
|
Args: Args{
|
|
Username: Username,
|
|
Keys: Keys,
|
|
Reset: reset,
|
|
},
|
|
}
|
|
reS, err := json.Marshal(&re)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = l.QEMUDomainAgentCommand(domain, string(reS), 100, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
func GuestRemoveSshKeys(l *libvirt.Libvirt, domain libvirt.Domain, AgentInfo GuestAgentInfo, Username string, Keys []string) error {
|
|
if !checkSupportedFeature(AgentInfo, "guest-ssh-remove-authorized-keys") {
|
|
return fmt.Errorf("guest-ssh-remove-authorized-keys not supported by agent")
|
|
}
|
|
type Args struct {
|
|
Username string `json:"username"`
|
|
Keys []string `json:"keys"`
|
|
}
|
|
type req struct {
|
|
Execute string `json:"execute"`
|
|
Args Args `json:"arguments"`
|
|
}
|
|
re := req{
|
|
Execute: "guest-ssh-remove-authorized-keys",
|
|
Args: Args{
|
|
Username: Username,
|
|
Keys: Keys,
|
|
},
|
|
}
|
|
reS, err := json.Marshal(&re)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = l.QEMUDomainAgentCommand(domain, string(reS), 100, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
func GuestGetSshKeys(l *libvirt.Libvirt, domain libvirt.Domain, AgentInfo GuestAgentInfo, Username string) ([]string, error) {
|
|
if !checkSupportedFeature(AgentInfo, "guest-ssh-get-authorized-keys") {
|
|
return nil, fmt.Errorf("guest-ssh-get-authorized-keys not supported by agent")
|
|
}
|
|
type Args struct {
|
|
Username string `json:"username"`
|
|
}
|
|
type req struct {
|
|
Execute string `json:"execute"`
|
|
Args Args `json:"arguments"`
|
|
}
|
|
re := req{
|
|
Execute: "guest-ssh-get-authorized-keys",
|
|
Args: Args{
|
|
Username: Username,
|
|
},
|
|
}
|
|
reS, err := json.Marshal(&re)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r, err := l.QEMUDomainAgentCommand(domain, string(reS), 100, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var rt struct {
|
|
Result struct {
|
|
Keys []string `json:"keys"`
|
|
} `json:"return"`
|
|
}
|
|
if len(r) > 0 {
|
|
err = json.Unmarshal([]byte(r[0]), &rt)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return rt.Result.Keys, nil
|
|
}
|
|
return nil, fmt.Errorf("empty response")
|
|
}
|
|
|
|
func GuestFileWrite(l *libvirt.Libvirt, domain libvirt.Domain, AgentInfo GuestAgentInfo, filename string, content []byte) error {
|
|
if !checkSupportedFeature(AgentInfo, "guest-file-open") {
|
|
return fmt.Errorf("guest-file-open not supported by agent")
|
|
}
|
|
if !checkSupportedFeature(AgentInfo, "guest-file-close") {
|
|
return fmt.Errorf("guest-file-close not supported by agent")
|
|
}
|
|
if !checkSupportedFeature(AgentInfo, "guest-file-write") {
|
|
return fmt.Errorf("guest-file-write not supported by agent")
|
|
}
|
|
|
|
var fop struct {
|
|
Execute string `json:"execute"`
|
|
Args struct {
|
|
Path string `json:"path"`
|
|
Mode string `json:"mode"`
|
|
} `json:"arguments"`
|
|
}
|
|
fop.Execute = "guest-file-open"
|
|
fop.Args.Path = filename
|
|
fop.Args.Mode = "w"
|
|
|
|
fopS, err := json.Marshal(fop)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
r, err := l.QEMUDomainAgentCommand(domain, string(fopS), 100, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if len(r) > 0 {
|
|
var hdl struct {
|
|
Handle int `json:"return"`
|
|
}
|
|
err = json.Unmarshal([]byte(r[0]), &hdl)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
var wrq struct {
|
|
Execute string `json:"execute"`
|
|
Args struct {
|
|
Handle int `json:"handle"`
|
|
BufB64 string `json:"buf-b64"`
|
|
} `json:"arguments"`
|
|
}
|
|
wrq.Execute = "guest-file-write"
|
|
wrq.Args.Handle = hdl.Handle
|
|
|
|
chunkSize := 4096
|
|
for i := 0; i < len(content); i += chunkSize {
|
|
end := i + chunkSize
|
|
if end > len(content) {
|
|
end = len(content)
|
|
}
|
|
chunk := content[i:end]
|
|
wrq.Args.BufB64 = base64.StdEncoding.EncodeToString(chunk)
|
|
wrqS, err := json.Marshal(wrq)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = l.QEMUDomainAgentCommand(domain, string(wrqS), 100, 0)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
var crq struct {
|
|
Execute string `json:"execute"`
|
|
Args struct {
|
|
Handle int `json:"handle"`
|
|
} `json:"arguments"`
|
|
}
|
|
crq.Execute = "guest-file-close"
|
|
crq.Args.Handle = hdl.Handle
|
|
crqS, err := json.Marshal(crq)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
_, err = l.QEMUDomainAgentCommand(domain, string(crqS), 100, 0)
|
|
|
|
return err
|
|
}
|
|
return fmt.Errorf("empty response")
|
|
}
|
|
func GuestFileRead(l *libvirt.Libvirt, domain libvirt.Domain, AgentInfo GuestAgentInfo, filename string) ([]byte, error) {
|
|
if !checkSupportedFeature(AgentInfo, "guest-file-open") {
|
|
return nil, fmt.Errorf("guest-file-open not supported by agent")
|
|
}
|
|
if !checkSupportedFeature(AgentInfo, "guest-file-close") {
|
|
return nil, fmt.Errorf("guest-file-close not supported by agent")
|
|
}
|
|
if !checkSupportedFeature(AgentInfo, "guest-file-read") {
|
|
return nil, fmt.Errorf("guest-file-read not supported by agent")
|
|
}
|
|
|
|
var fop struct {
|
|
Execute string `json:"execute"`
|
|
Args struct {
|
|
Path string `json:"path"`
|
|
Mode string `json:"mode"`
|
|
} `json:"arguments"`
|
|
}
|
|
fop.Execute = "guest-file-open"
|
|
fop.Args.Path = filename
|
|
fop.Args.Mode = "r"
|
|
|
|
fopS, err := json.Marshal(fop)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
r, err := l.QEMUDomainAgentCommand(domain, string(fopS), 100, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(r) > 0 {
|
|
var hdl struct {
|
|
Handle int `json:"return"`
|
|
}
|
|
err = json.Unmarshal([]byte(r[0]), &hdl)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var rrq struct {
|
|
Execute string `json:"execute"`
|
|
Args struct {
|
|
Handle int `json:"handle"`
|
|
} `json:"arguments"`
|
|
}
|
|
rrq.Execute = "guest-file-read"
|
|
rrq.Args.Handle = hdl.Handle
|
|
rrqS, err := json.Marshal(rrq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
var gfr struct {
|
|
Return struct {
|
|
Count int `json:"count"`
|
|
BufB64 string `json:"buf-b64"`
|
|
Eof bool `json:"eof"`
|
|
} `json:"return"`
|
|
}
|
|
var ret []byte
|
|
for {
|
|
r, err = l.QEMUDomainAgentCommand(domain, string(rrqS), 100, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(r) > 0 {
|
|
err = json.Unmarshal([]byte(r[0]), &gfr)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if gfr.Return.Count != 0 {
|
|
dec, err := base64.StdEncoding.DecodeString(gfr.Return.BufB64)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
ret = append(ret, dec...)
|
|
}
|
|
if gfr.Return.Eof {
|
|
break
|
|
}
|
|
} else {
|
|
break
|
|
}
|
|
}
|
|
|
|
var crq struct {
|
|
Execute string `json:"execute"`
|
|
Args struct {
|
|
Handle int `json:"handle"`
|
|
} `json:"arguments"`
|
|
}
|
|
crq.Execute = "guest-file-close"
|
|
crq.Args.Handle = hdl.Handle
|
|
crqS, err := json.Marshal(crq)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
_, err = l.QEMUDomainAgentCommand(domain, string(crqS), 100, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return ret, nil
|
|
}
|
|
return nil, fmt.Errorf("empty response")
|
|
}
|
|
|
|
// Starts command on guest using guest-exec
|
|
// Returns PID of started process
|
|
// User MUST call GuestCommandStatus after command finished running to get output
|
|
// and reap process.
|
|
func GuestExecStart(l *libvirt.Libvirt, domain libvirt.Domain, AgentInfo GuestAgentInfo, path string, args []string, env []string, stdin []byte) (int, error) {
|
|
if !checkSupportedFeature(AgentInfo, "guest-exec") {
|
|
return 0, fmt.Errorf("guest-exec not supported by agent")
|
|
}
|
|
if !checkSupportedFeature(AgentInfo, "guest-exec-status") {
|
|
return 0, fmt.Errorf("guest-exec-status not supported by agent")
|
|
}
|
|
|
|
var exec struct {
|
|
Execute string `json:"execute"`
|
|
Arguments struct {
|
|
Path string `json:"path"`
|
|
Arg []string `json:"arg"`
|
|
Env []string `json:"env"`
|
|
InputData string `json:"input-data,omitempty"`
|
|
CaptureOutput bool `json:"capture-output"`
|
|
} `json:"arguments"`
|
|
}
|
|
exec.Execute = "guest-exec"
|
|
exec.Arguments.Path = path
|
|
exec.Arguments.Arg = args
|
|
exec.Arguments.Env = env
|
|
exec.Arguments.InputData = base64.StdEncoding.EncodeToString(stdin)
|
|
exec.Arguments.CaptureOutput = true
|
|
|
|
execS, err := json.Marshal(exec)
|
|
if err != nil {
|
|
|
|
return 0, err
|
|
}
|
|
r, err := l.QEMUDomainAgentCommand(domain, string(execS), 100, 0)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if len(r) > 0 {
|
|
var hdl struct {
|
|
Return struct {
|
|
Pid int `json:"pid"`
|
|
}
|
|
}
|
|
err = json.Unmarshal([]byte(r[0]), &hdl)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return hdl.Return.Pid, nil
|
|
}
|
|
return 0, fmt.Errorf("empty response for %s", execS)
|
|
|
|
}
|
|
|
|
type GuestExecStatus struct {
|
|
Exited bool `json:"exited"`
|
|
ExitCode int `json:"exitcode"`
|
|
Signal int `json:"signal"`
|
|
OutDataB64 string `json:"out-data"` //only populated after process exits
|
|
ErrDataB64 string `json:"err-data"` //only populated after process exits
|
|
OutTruncated bool `json:"out-truncated"`
|
|
ErrTruncated bool `json:"err-truncated"`
|
|
}
|
|
type GuestExecStatusWrapper struct {
|
|
Return GuestExecStatus `json:"return"`
|
|
}
|
|
|
|
// Checks status of executed command.
|
|
func GuestCommandStatus(l *libvirt.Libvirt, domain libvirt.Domain, AgentInfo GuestAgentInfo, pid int) (GuestExecStatus, error) {
|
|
if !checkSupportedFeature(AgentInfo, "guest-exec-status") {
|
|
return GuestExecStatus{}, fmt.Errorf("guest-exec-status not supported by agent")
|
|
}
|
|
var ges struct {
|
|
Execute string `json:"execute"`
|
|
Args struct {
|
|
Pid int `json:"pid"`
|
|
} `json:"arguments"`
|
|
}
|
|
ges.Execute = "guest-exec-status"
|
|
ges.Args.Pid = pid
|
|
gesS, err := json.Marshal(ges)
|
|
if err != nil {
|
|
return GuestExecStatus{}, err
|
|
}
|
|
r, err := l.QEMUDomainAgentCommand(domain, string(gesS), 100, 0)
|
|
if err != nil {
|
|
return GuestExecStatus{}, err
|
|
}
|
|
if len(r) > 0 {
|
|
var gesw GuestExecStatusWrapper
|
|
err = json.Unmarshal([]byte(r[0]), &gesw)
|
|
if err != nil {
|
|
return GuestExecStatus{}, err
|
|
}
|
|
|
|
return gesw.Return, nil
|
|
|
|
}
|
|
return GuestExecStatus{}, fmt.Errorf("empty response for %s", gesS)
|
|
}
|
|
|
|
// Periodically calls GuestCommandStatus untill command finishes running.
|
|
// Polls for status immediately, then every 50 milliseconds with exponential backoff.
|
|
func GuestWaitForCommand(l *libvirt.Libvirt, domain libvirt.Domain, AgentInfo GuestAgentInfo, pid int) (GuestExecStatus, error) {
|
|
|
|
delay := 50 * time.Millisecond
|
|
for {
|
|
ret, err := GuestCommandStatus(l, domain, AgentInfo, pid)
|
|
if err != nil {
|
|
return GuestExecStatus{}, err
|
|
}
|
|
if ret.Exited {
|
|
return ret, nil
|
|
}
|
|
time.Sleep(delay)
|
|
delay *= 2
|
|
}
|
|
|
|
}
|
|
|
|
func GuestGetHostName(l *libvirt.Libvirt, domain libvirt.Domain, AgentInfo GuestAgentInfo) (string, error) {
|
|
if !checkSupportedFeature(AgentInfo, "guest-get-host-name") {
|
|
return "", fmt.Errorf("guest-get-host-name not supported by agent")
|
|
}
|
|
r, err := l.QEMUDomainAgentCommand(domain, "{\"execute\":\"guest-get-host-name\"}", 100, 0)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(r) > 0 {
|
|
var ret struct {
|
|
Return struct {
|
|
Hostname string `json:"host-name"`
|
|
} `json:"return"`
|
|
}
|
|
err := json.Unmarshal([]byte(r[0]), &ret)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return ret.Return.Hostname, nil
|
|
}
|
|
return "", fmt.Errorf("empty response from guest-get-host-name")
|
|
}
|
|
func GuestGetTime(l *libvirt.Libvirt, domain libvirt.Domain, AgentInfo GuestAgentInfo) (int64, error) {
|
|
if !checkSupportedFeature(AgentInfo, "guest-get-time") {
|
|
return 0, fmt.Errorf("guest-get-time not supported by agent")
|
|
}
|
|
r, err := l.QEMUDomainAgentCommand(domain, "{\"execute\":\"guest-get-time\"}", 100, 0)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
if len(r) > 0 {
|
|
var ret struct {
|
|
Return int64 `json:"return"`
|
|
}
|
|
err := json.Unmarshal([]byte(r[0]), &ret)
|
|
if err != nil {
|
|
return 0, err
|
|
}
|
|
return ret.Return, nil
|
|
}
|
|
return 0, fmt.Errorf("empty response from guest-get-time")
|
|
}
|
|
|
|
type GuestDiskInfo struct {
|
|
Name string `json:"name"`
|
|
Partition bool `json:"partition"`
|
|
Dependencies []string `json:"dependencies"`
|
|
Alias string `json:"alias"`
|
|
Address GuestDiskAddress `json:"address"`
|
|
}
|
|
|
|
func (gdi *GuestDiskInfo) Dump() {
|
|
fmt.Printf("Disk:\n")
|
|
fmt.Printf("\tName: %s\n", gdi.Name)
|
|
fmt.Printf("\tPartition: %v\n", gdi.Partition)
|
|
fmt.Printf("\tAlias: %s\n", gdi.Alias)
|
|
fmt.Printf("\tAddress: %s\n", gdi.Address.String())
|
|
for _, d := range gdi.Dependencies {
|
|
fmt.Printf("\t\tDependency: %s\n", d)
|
|
}
|
|
}
|
|
|
|
type GuestDiskInfoWrapper struct {
|
|
Return []GuestDiskInfo `json:"return"`
|
|
}
|
|
|
|
func GuestGetDisks(l *libvirt.Libvirt, domain libvirt.Domain, AgentInfo GuestAgentInfo) ([]GuestDiskInfo, error) {
|
|
if !checkSupportedFeature(AgentInfo, "guest-get-disks") {
|
|
return nil, fmt.Errorf("guest-get-disks not supported by agent")
|
|
}
|
|
r, err := l.QEMUDomainAgentCommand(domain, "{\"execute\":\"guest-get-disks\"}", 100, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(r) > 0 {
|
|
var gdiw GuestDiskInfoWrapper
|
|
err = json.Unmarshal([]byte(r[0]), &gdiw)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return gdiw.Return, nil
|
|
}
|
|
return nil, fmt.Errorf("empty response from guest-get-disks")
|
|
|
|
}
|
|
|
|
type GuestDiskStats struct {
|
|
ReadSectors int `json:"read-sectors"`
|
|
ReadIos int `json:"read-ios"`
|
|
ReadMerges int `json:"read-merges"`
|
|
WriteSectors int `json:"write-sectors"`
|
|
WriteIos int `json:"write-ios"`
|
|
WriteMerges int `json:"write-merges"`
|
|
DiscardSectors int `json:"discard-sectors"`
|
|
DiscardIos int `json:"discard-ios"`
|
|
DiscardMerges int `json:"discard-merges"`
|
|
FlushIos int `json:"flush-ios"`
|
|
ReadTicks int `json:"read-ticks"`
|
|
WriteTicks int `json:"write-ticks"`
|
|
DiscardTicks int `json:"discard-ticks"`
|
|
FlushTicks int `json:"flush-ticks"`
|
|
IosPgr int `json:"ios-pgr"`
|
|
TotalTicks int `json:"total-ticks"`
|
|
WeightTicks int `json:"weight-ticks"`
|
|
}
|
|
type GuestDiskStatInfo struct {
|
|
Name string `json:"name"`
|
|
Major int `json:"major"`
|
|
Minor int `json:"minor"`
|
|
Stats GuestDiskStats `json:"stats"`
|
|
}
|
|
|
|
func (gdsi *GuestDiskStatInfo) Dump() {
|
|
fmt.Printf("Disk I/O stats: %s (%d:%d)\n", gdsi.Name, gdsi.Major, gdsi.Minor)
|
|
fmt.Printf("\tRead sectors: %d\n", gdsi.Stats.ReadSectors)
|
|
fmt.Printf("\tRead Ios: %d\n", gdsi.Stats.ReadIos)
|
|
fmt.Printf("\tRead Merges: %d\n", gdsi.Stats.ReadMerges)
|
|
fmt.Printf("\tWrite sectors: %d\n", gdsi.Stats.WriteSectors)
|
|
fmt.Printf("\tWrite Ios: %d\n", gdsi.Stats.WriteIos)
|
|
fmt.Printf("\tWrite Merges: %d\n", gdsi.Stats.WriteMerges)
|
|
fmt.Printf("\tDiscard sectors: %d\n", gdsi.Stats.DiscardSectors)
|
|
fmt.Printf("\tDiscard Ios: %d\n", gdsi.Stats.DiscardIos)
|
|
fmt.Printf("\tDiscard Merges: %d\n", gdsi.Stats.DiscardMerges)
|
|
fmt.Printf("\tFlush Ios: %d\n", gdsi.Stats.FlushIos)
|
|
fmt.Printf("\tRead Ticks: %d\n", gdsi.Stats.ReadTicks)
|
|
fmt.Printf("\tWrite Ticks: %d\n", gdsi.Stats.WriteTicks)
|
|
fmt.Printf("\tDiscard Ticks: %d\n", gdsi.Stats.DiscardTicks)
|
|
fmt.Printf("\tFlush Ticks: %d\n", gdsi.Stats.FlushTicks)
|
|
fmt.Printf("\tIosPgr: %d\n", gdsi.Stats.IosPgr)
|
|
fmt.Printf("\tTotal Ticks: %d\n", gdsi.Stats.TotalTicks)
|
|
fmt.Printf("\tWeight Ticks: %d\n", gdsi.Stats.WeightTicks)
|
|
}
|
|
|
|
type GuestDiskStatInfoWrapper struct {
|
|
Return []GuestDiskStatInfo `json:"return"`
|
|
}
|
|
|
|
func GuestGetDiskstats(l *libvirt.Libvirt, domain libvirt.Domain, AgentInfo GuestAgentInfo) ([]GuestDiskStatInfo, error) {
|
|
if !checkSupportedFeature(AgentInfo, "guest-get-diskstats") {
|
|
return nil, fmt.Errorf("guest-get-diskstats not supported by agent")
|
|
}
|
|
r, err := l.QEMUDomainAgentCommand(domain, "{\"execute\":\"guest-get-diskstats\"}", 100, 0)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(r) > 0 {
|
|
var gdisw GuestDiskStatInfoWrapper
|
|
err = json.Unmarshal([]byte(r[0]), &gdisw)
|
|
if err != nil {
|
|
return nil, err
|
|
|
|
}
|
|
return gdisw.Return, nil
|
|
}
|
|
return nil, fmt.Errorf("empty response from guest-get-diskstats")
|
|
|
|
}
|