libguestd/pkg/libguestd/libguestd.go
2024-07-07 18:51:49 +02:00

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")
}