clustvirt/lib/guest/lib.go

269 lines
8.2 KiB
Go

// Package guest implements utilities and structured data for libvirt virtual machines
package guest
import (
"errors"
"time"
"git.staur.ca/stobbsm/clustvirt/util"
log "git.staur.ca/stobbsm/simplelog"
"libvirt.org/go/libvirt"
)
// Errors
// ErrVMNotFound error when virtual machine is not found
var ErrVMNotFound = errors.New("virtual machine not found")
// VMState represents different states a virtual machine can be in
// Some states are mutually exclusive, but some can be applied at the
// same time.
// ie. A machine can be running, and migrating at the same time.
// A machine can be running, but had a failed migration
type VMState uint8
const (
// Steady States
// StateStopped means the VM isn't running
StateStopped VMState = iota
// StateRunning means the VM is currently running
StateRunning
// StateFailed means the VM is in a failed state, which can indicate a few things:
// The VM healthcheck (not yet defined, but will be) has failed
// A Migration has failed
// Adding or removing a device has failed
StateFailed
// StateMigrated means the VM was successfully migrated to a new host
StateMigrated
// StateHealthy means the VM had a successful healthcheck the last time it was done
StateHealthy
// StateUnhealthy means the opposite of healthy, as in the VM has failed a health check
StateUnhealthy
// Transient states
// StateStarted means the VM has been turned on, but is not nesicarily available yet
StateStarted
// StateRestarted means a reboot was triggered, but hasn't be finished yet
StateRestarted
// StatePoweroff means a shutdown was triggered, but isn't finished yet
StatePoweroff
// StateMigrate means a migration was started, but isn't done yet
StateMigrate
// StateCreate means a VM is being created
StateCreate
// StateInstall means a VM is being installed. This usually implies from an ISO and
// not an automated installation
StateInstall
// StateDestroy means a VM is scheduled or is in progress of being destroyed and deleted
StateDestroy
// StateBackup means a VM is in process of being backed up
StateBackup
// StateRestore means a VM is in process of being restored
StateRestore
)
// VM holds a handle is used to communicate to Domains
type VM struct {
Name string
HostName string
ID uint
BackupXML string
Autostart bool
BlockIOParameters BlockIOParametersInfo
Disks []DiskInfo
Users []UserInfo
OS OSInfo
TimeZone TimeZoneInfo
NetIFace []NetIFaceInfo
close chan struct{}
closeErr chan error
dom *libvirt.Domain
}
// BlockIOParametersInfo is the struct describing VM block IO parameters
type BlockIOParametersInfo struct {
Weight uint
DeviceWeight string
DeviceReadIops string
DeviceWriteIops string
DeviceReadBps string
DeviceWriteBps string
}
// DiskInfo is the struct for guest disk information, and is used in VM
type DiskInfo struct {
Name string
Partition bool
Alias string
GuestAlias string
DiskDependency []string
}
// UserInfo is the struct for VM user information, and is used in VM
type UserInfo struct {
Name string
Domain string
LoginTime time.Time
}
// OSInfo is the struct for guest operating system information, and is used in VM
type OSInfo struct {
ID string
Name string
PrettyName string
Version string
VersionID string
KernelRelease string
KernelVersion string
Machine string
Variant string
VariantID string
}
// TimeZoneInfo provides guest timezone information, and is used in VM
type TimeZoneInfo struct {
Name string
Offset int
}
// NetIFaceInfo provides guest Network Interface information, and is used in VM
type NetIFaceInfo struct {
Name string
HWAddr string
IP []IPInfo
}
// IPInfo provides guest IP network information, and is used in NetIFaceInfo
type IPInfo struct {
Type string
Addr string
Prefix uint
}
// GetGuest loads guest information by name and returns it
func GetGuest(name string, conn *libvirt.Connect) (*VM, error) {
g := &VM{
Name: name,
close: make(chan struct{}),
closeErr: make(chan error),
}
var err error
// If the domain can't be found by name, exit early with an error
if g.dom, err = conn.LookupDomainByName(name); err != nil {
return g, err
}
if g.ID, err = g.dom.GetID(); err != nil {
return g, err
}
if g.Autostart, err = g.dom.GetAutostart(); err != nil {
return g, err
}
blkiop, err := g.dom.GetBlkioParameters(0)
if err != nil {
return g, err
}
g.BlockIOParameters.Weight = blkiop.Weight
g.BlockIOParameters.DeviceWeight = util.SetNotSet(blkiop.DeviceWeight, blkiop.DeviceWeightSet)
g.BlockIOParameters.DeviceReadIops = util.SetNotSet(blkiop.DeviceReadIops, blkiop.DeviceReadIopsSet)
g.BlockIOParameters.DeviceWriteIops = util.SetNotSet(blkiop.DeviceWriteIops, blkiop.DeviceWriteIopsSet)
g.BlockIOParameters.DeviceReadBps = util.SetNotSet(blkiop.DeviceReadBps, blkiop.DeviceReadBpsSet)
g.BlockIOParameters.DeviceWriteBps = util.SetNotSet(blkiop.DeviceWriteBps, blkiop.DeviceWriteBpsSet)
// Set as much guest info as possible
info, err := g.dom.GetGuestInfo(
libvirt.DOMAIN_GUEST_INFO_USERS|libvirt.DOMAIN_GUEST_INFO_OS|
libvirt.DOMAIN_GUEST_INFO_DISKS|libvirt.DOMAIN_GUEST_INFO_HOSTNAME|
libvirt.DOMAIN_GUEST_INFO_INTERFACES|libvirt.DOMAIN_GUEST_INFO_TIMEZONE|
libvirt.DOMAIN_GUEST_INFO_FILESYSTEM,
0,
)
if err != nil {
return g, err
}
// Set disk info
if len(info.Disks) > 0 {
g.Disks = make([]DiskInfo, len(info.Disks))
for i, n := range info.Disks {
g.Disks[i].Name = util.SetNotSet(n.Name, n.NameSet)
g.Disks[i].Alias = util.SetNotSet(n.Alias, n.AliasSet)
g.Disks[i].Partition = n.Partition
g.Disks[i].GuestAlias = util.SetNotSet(n.GuestAlias, n.GuestAliasSet)
g.Disks[i].DiskDependency = make([]string, len(n.Dependencies))
for j, k := range n.Dependencies {
g.Disks[i].DiskDependency[j] = util.SetNotSet(k.Name, k.NameSet)
}
}
}
if len(info.Users) > 0 {
g.Users = make([]UserInfo, len(info.Users))
for i, n := range info.Users {
g.Users[i].Name = util.SetNotSet(n.Name, n.NameSet)
g.Users[i].Domain = util.SetNotSet(n.Domain, n.DomainSet)
g.Users[i].LoginTime = time.Unix(int64(n.LoginTime), 0)
}
}
g.OS.ID = util.SetNotSet(info.OS.ID, info.OS.IDSet)
g.OS.Name = util.SetNotSet(info.OS.Name, info.OS.NameSet)
g.OS.Machine = util.SetNotSet(info.OS.Machine, info.OS.MachineSet)
g.OS.Variant = util.SetNotSet(info.OS.Variant, info.OS.VariantSet)
g.OS.Version = util.SetNotSet(info.OS.Version, info.OS.VersionSet)
g.OS.VariantID = util.SetNotSet(info.OS.VariantID, info.OS.VariantIDSet)
g.OS.VersionID = util.SetNotSet(info.OS.VersionID, info.OS.VersionIDSet)
g.OS.PrettyName = util.SetNotSet(info.OS.PrettyName, info.OS.PrettyNameSet)
g.OS.KernelRelease = util.SetNotSet(info.OS.KernelRelease, info.OS.KernelReleaseSet)
g.OS.KernelVersion = util.SetNotSet(info.OS.KernelVersion, info.OS.KernelVersionSet)
g.TimeZone.Name = util.SetNotSet(info.TimeZone.Name, info.TimeZone.NameSet)
g.TimeZone.Offset = info.TimeZone.Offset
// Set the hostname from the guest if it is set, otherwise use the VM name
if info.HostnameSet {
g.HostName = info.Hostname
} else {
g.HostName = g.Name
}
if len(info.Interfaces) > 0 {
g.NetIFace = make([]NetIFaceInfo, len(info.Interfaces))
for i, n := range info.Interfaces {
g.NetIFace[i].HWAddr = util.SetNotSet(n.Hwaddr, n.HwaddrSet)
g.NetIFace[i].Name = util.SetNotSet(n.Name, n.NameSet)
if len(n.Addrs) > 0 {
g.NetIFace[i].IP = make([]IPInfo, len(n.Addrs))
for j, a := range n.Addrs {
g.NetIFace[i].IP[j].Addr = util.SetNotSet(a.Addr, a.AddrSet)
g.NetIFace[i].IP[j].Type = util.SetNotSet(a.Type, a.TypeSet)
g.NetIFace[i].IP[j].Prefix = a.Prefix
}
}
}
}
// Not errors, but still log the warnings when this happens
if g.BackupXML, err = g.dom.BackupGetXMLDesc(0); err != nil {
log.Warn("guest.GetGuest").Str("guest", g.Name).Err(err).Send()
}
go func() {
defer close(g.closeErr)
<-g.close
g.closeErr <- g.dom.Free()
}()
return g, nil
}
// Close closes an open connection
func (g *VM) Close() error {
log.Info("guest.Close").Str("guest", g.Name).Msg("closing vm")
close(g.close)
return <-g.closeErr
}