host will now load VM data

- All host node info is done using goroutines to speed up the process as much as possible
- The vast majority of data is stored at this point
  - Need to work out refreshes, using either polling or events
This commit is contained in:
Matthew Stobbs 2024-03-12 22:17:42 -06:00
parent 899a80f2e5
commit f96a911722
6 changed files with 424 additions and 94 deletions

2
go.mod
View File

@ -3,3 +3,5 @@ module git.staur.ca/stobbsm/clustvirt
go 1.22.1 go 1.22.1
require libvirt.org/go/libvirt v1.10001.0 require libvirt.org/go/libvirt v1.10001.0
require gopkg.in/ffmt.v1 v1.5.6 // indirect

2
go.sum
View File

@ -1,2 +1,4 @@
gopkg.in/ffmt.v1 v1.5.6 h1:4Bu3riZp5sAIXW2T/18JM9BkwJLodurXFR0f7PXp+cw=
gopkg.in/ffmt.v1 v1.5.6/go.mod h1:LssvGOZFiBGoBcobkTqnyh+uN1VzIRoibW+c0JI/Ha4=
libvirt.org/go/libvirt v1.10001.0 h1:lEVDNE7xfzmZXiDEGIS8NvJSuaz11OjRXw+ufbQEtPY= libvirt.org/go/libvirt v1.10001.0 h1:lEVDNE7xfzmZXiDEGIS8NvJSuaz11OjRXw+ufbQEtPY=
libvirt.org/go/libvirt v1.10001.0/go.mod h1:1WiFE8EjZfq+FCVog+rvr1yatKbKZ9FaFMZgEqxEJqQ= libvirt.org/go/libvirt v1.10001.0/go.mod h1:1WiFE8EjZfq+FCVog+rvr1yatKbKZ9FaFMZgEqxEJqQ=

View File

@ -15,6 +15,55 @@ import (
// ErrVMNotFound error when virtual machine is not found // ErrVMNotFound error when virtual machine is not found
var ErrVMNotFound = errors.New("virtual machine 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 // VM holds a handle is used to communicate to Domains
type VM struct { type VM struct {
Name string Name string

View File

@ -8,8 +8,10 @@ package host
import ( import (
"log" "log"
"sync"
"git.staur.ca/stobbsm/clustvirt/lib/guest" "git.staur.ca/stobbsm/clustvirt/lib/guest"
"git.staur.ca/stobbsm/clustvirt/lib/secret"
"git.staur.ca/stobbsm/clustvirt/util" "git.staur.ca/stobbsm/clustvirt/util"
"libvirt.org/go/libvirt" "libvirt.org/go/libvirt"
) )
@ -18,40 +20,44 @@ import (
// If a connection is closed prematurely, will re-open the connection and // If a connection is closed prematurely, will re-open the connection and
// try the attempted method again // try the attempted method again
type Host struct { type Host struct {
// HostName used to make the connection
HostName string HostName string
// SystemHostName is the hostname as reported by the system itself
SystemHostName string SystemHostName string
// FreeMemory is the available free memory
FreeMemory uint64 FreeMemory uint64
// LibVersion is the version of Libvirt on the host
LibVersion uint32 LibVersion uint32
// HostInfo provides basic HW information about a host
HostInfo NodeInfo HostInfo NodeInfo
// HostSEVInfo provides informatoin about AMD SEV extentions available on the host
HostSEVInfo SEVInfo HostSEVInfo SEVInfo
// AvailableCPUTypes are the available types of CPUs that can be used for VM creation
AvailableCPUTypes []string AvailableCPUTypes []string
// NodeMemory provides basic memory information about the Host
NodeMemory NodeMemoryInfo NodeMemory NodeMemoryInfo
// StorageCapabilities is the XML representation of the hosts storage capabilities
StorageCapabilities string StorageCapabilities string
// SysInfo is the XML representation of the host system information
SysInfo string SysInfo string
// Alive indicates if the connection is alive
Alive bool Alive bool
// Encrypted indicates if the connection is encrypted
Encrypted bool Encrypted bool
// Secure indicates if the connection is secure
Secure bool Secure bool
// alldomains // VMList is the list of virtual machines available to the host
VMList []string VMList []VMInfo
// allinterfaces // NetIfList is the list of network interfaces on the host
IFList []string NetIfFList []NetIfInfo
// allnetworks // NetworkList is the list of defined networks on the host
NetList []string NetworkList []NetworkInfo
// alldevices // DeviceList is the list of devices on the host
DeviceList []string DeviceList []DeviceInfo
// allsecrets // SecretList provides a list of secrets available to the host
SecretList []string SecretList []SecretInfo
// allstoragepools // StoragePoolList provides the list of stoarge ppols available to the host
StoragePoolList []string StoragePoolList []StoragePoolInfo
// defineddomains
// definedinterfaces
// definednetworks
// definedstoragepools
// listdomains []int id
// listinterfaces []string interfacename
// listnetworks []string networkname
// listserets []string secretname
// liststoragepools []string storagepoolname
uri *URI uri *URI
conn *libvirt.Connect conn *libvirt.Connect
@ -59,6 +65,71 @@ type Host struct {
closeErr chan error closeErr chan error
} }
// DeviceInfo holds basic information for host devices
type DeviceInfo struct {
Name string
Capabilities []string
XML string
}
// VMInfo holds basic VM information, like the name and ID
type VMInfo struct {
Name string
ID uint
UUID []byte
XML string
// States are the current states active on the host
States []guest.VMState
}
// SecretInfo doesn't let you see the contents of secrets, but does let you see what secrets have
// been defined in a simple format.
type SecretInfo struct {
UUID string
XML string
Type string
}
// StoragePoolInfo holds basic information on storage pools
type StoragePoolInfo struct {
Name string
UUID []byte
Type string
XML string
Active bool
Persistent bool
// HAEnabled indicates if the storage pool has High Availability
HAEnabled bool
// Volumes defined in the storage pool
Volumes []VolumeInfo
}
// VolumeInfo holds basic information about Volumes available in storage pools
type VolumeInfo struct {
Name string
// StoragePool this volume is part of
StoragePool StoragePoolInfo
Type string
Size uint
XML string
}
// NetIfInfo holds basic information about available network interfaces (not their connections, the devices themselves)
type NetIfInfo struct {
Name string
MacAddr string
XML string
}
// NetworkInfo holds basic information about network connections
type NetworkInfo struct {
Name string
UUID []byte
XML string
// NetIf is the network interface this connection is applied to
NetIf NetIfInfo
}
// NodeInfo represents the basic HW info for a host node // NodeInfo represents the basic HW info for a host node
type NodeInfo struct { type NodeInfo struct {
// livirt.NodeInfo section // livirt.NodeInfo section
@ -107,16 +178,133 @@ func ConnectHost(uri *URI, host string) (*Host, error) {
h.close = make(chan struct{}) h.close = make(chan struct{})
h.closeErr = make(chan error) h.closeErr = make(chan error)
h.getInfo()
go func() {
defer close(h.closeErr)
<-h.close
_, err := h.conn.Close()
h.closeErr <- err
}()
return h, nil
}
// connect creates a host connection
func (h *Host) connect() error {
var err error
h.conn, err = libvirt.NewConnect(h.uri.ConnectionString(h.HostName))
return err
}
// GetGuestByName returns a GuestVM instance that exists on the given host
func (h *Host) GetGuestByName(name string) (*guest.VM, error) {
g, err := guest.GetGuest(name, h.conn)
if err == nil {
return g, nil
}
lverr, ok := err.(libvirt.Error)
if ok && lverr.Code == libvirt.ERR_INVALID_CONN {
// try again after creating a new connection
return guest.GetGuest(name, h.conn)
}
return nil, err
}
// Close triggers closing the host connection
func (h *Host) Close() error {
log.Println("Closing Host", h.HostName)
close(h.close)
return <-h.closeErr
}
// private methods that load the different informational parts
func (h *Host) getInfo() {
var wg sync.WaitGroup
infoFuncs := []func(*sync.WaitGroup){
h.getDevicesInfo,
h.getDomainInfo,
h.getIfaceInfo,
h.getNetsInfo,
h.getNodeInfo,
h.getSEVInfo,
h.getSecretsInfo,
h.getStoragePools,
}
for _, f := range infoFuncs {
wg.Add(1)
f(&wg)
}
wg.Wait()
}
func (h *Host) getStoragePools(wg *sync.WaitGroup) {
defer wg.Done()
spools, err := h.conn.ListAllStoragePools(0)
if err != nil {
log.Println(err)
}
if len(spools) > 0 {
h.StoragePoolList = make([]StoragePoolInfo, len(spools))
for i, s := range spools {
if h.StoragePoolList[i].XML, err = s.GetXMLDesc(0); err != nil {
log.Println(err)
}
if h.StoragePoolList[i].Name, err = s.GetName(); err != nil {
log.Println(err)
}
if h.StoragePoolList[i].UUID, err = s.GetUUID(); err != nil {
log.Println(err)
}
if h.StoragePoolList[i].Active, err = s.IsActive(); err != nil {
log.Println(err)
}
if h.StoragePoolList[i].Persistent, err = s.IsPersistent(); err != nil {
log.Println(err)
}
s.Free()
}
}
}
func (h *Host) getSecretsInfo(wg *sync.WaitGroup) {
defer wg.Done()
nsecrets, err := h.conn.ListAllSecrets(0)
if err != nil {
log.Println(err)
}
if len(nsecrets) > 0 {
h.SecretList = make([]SecretInfo, len(nsecrets))
for i, s := range nsecrets {
if h.SecretList[i].XML, err = s.GetXMLDesc(0); err != nil {
log.Println(err)
}
stype, err := s.GetUsageType()
if err != nil {
log.Println(err)
}
h.SecretList[i].Type = secret.SecretUsageTypeMap[stype]
s.Free()
}
}
}
func (h *Host) getNodeInfo(wg *sync.WaitGroup) {
defer wg.Done()
var err error var err error
h.AvailableCPUTypes, err = h.conn.GetCPUModelNames("x86_64", 0) h.AvailableCPUTypes, err = h.conn.GetCPUModelNames("x86_64", 0)
if err != nil { if err != nil {
log.Println("Error getting cpu model names", err) log.Println("Error getting cpu model names", err)
} }
// Extract libvirt.NodeInfo and libvirt.NodeSEVParameters
ni, err := h.conn.GetNodeInfo() ni, err := h.conn.GetNodeInfo()
if err != nil { if err != nil {
return nil, err log.Println(err)
} }
h.HostInfo.Model = ni.Model h.HostInfo.Model = ni.Model
h.HostInfo.Memory = ni.Memory h.HostInfo.Memory = ni.Memory
@ -126,33 +314,6 @@ func ConnectHost(uri *URI, host string) (*Host, error) {
h.HostInfo.Sockets = ni.Sockets h.HostInfo.Sockets = ni.Sockets
h.HostInfo.Cores = ni.Cores h.HostInfo.Cores = ni.Cores
h.HostInfo.Threads = ni.Threads h.HostInfo.Threads = ni.Threads
// Assume SEV is enabled, until we know otherwise
h.HostSEVInfo.SEVEnabled = true
ns, err := h.conn.GetSEVInfo(0)
if err != nil {
lverr, ok := err.(libvirt.Error)
if !ok {
return nil, err
}
switch lverr.Code {
case 84:
log.Println("SEV functions not supported")
h.HostSEVInfo.SEVEnabled = false
default:
log.Println("Error encountered", lverr.Error())
}
}
if h.HostSEVInfo.SEVEnabled {
h.HostSEVInfo.PDH = util.SetNotSet(ns.PDH, ns.PDHSet)
h.HostSEVInfo.CertChain = util.SetNotSet(ns.CertChain, ns.CertChainSet)
h.HostSEVInfo.CBitPos = ns.CBitPos
h.HostSEVInfo.ReducedPhysBits = ns.ReducedPhysBits
h.HostSEVInfo.MaxGuests = ns.MaxGuests
h.HostSEVInfo.MaxEsGuests = ns.MaxEsGuests
h.HostSEVInfo.CPU0ID = util.SetNotSet(ns.CPU0ID, ns.CPU0IDSet)
}
h.SystemHostName, err = h.conn.GetHostname() h.SystemHostName, err = h.conn.GetHostname()
if err != nil { if err != nil {
log.Println(err) log.Println(err)
@ -200,40 +361,142 @@ func ConnectHost(uri *URI, host string) (*Host, error) {
if err != nil { if err != nil {
log.Println(err) log.Println(err)
} }
go func() {
defer close(h.closeErr)
<-h.close
_, err := h.conn.Close()
h.closeErr <- err
}()
return h, nil
} }
// connect creates a host connection func (h *Host) getSEVInfo(wg *sync.WaitGroup) {
func (h *Host) connect() error { defer wg.Done()
var err error // getSEVInfo
h.conn, err = libvirt.NewConnect(h.uri.ConnectionString(h.HostName)) h.HostSEVInfo.SEVEnabled = true
return err ns, err := h.conn.GetSEVInfo(0)
} if err != nil {
log.Println(err)
// GetGuestByName returns a GuestVM instance that exists on the given host
func (h *Host) GetGuestByName(name string) (*guest.VM, error) {
g, err := guest.GetGuest(name, h.conn)
if err == nil {
return g, nil
}
lverr, ok := err.(libvirt.Error) lverr, ok := err.(libvirt.Error)
if ok && lverr.Code == libvirt.ERR_INVALID_CONN { if ok {
// try again after creating a new connection switch lverr.Code {
return guest.GetGuest(name, h.conn) case 84:
log.Println("SEV functions not supported")
h.HostSEVInfo.SEVEnabled = false
default:
log.Println("Error encountered", lverr.Error())
}
}
}
if h.HostSEVInfo.SEVEnabled {
h.HostSEVInfo.PDH = util.SetNotSet(ns.PDH, ns.PDHSet)
h.HostSEVInfo.CertChain = util.SetNotSet(ns.CertChain, ns.CertChainSet)
h.HostSEVInfo.CBitPos = ns.CBitPos
h.HostSEVInfo.ReducedPhysBits = ns.ReducedPhysBits
h.HostSEVInfo.MaxGuests = ns.MaxGuests
h.HostSEVInfo.MaxEsGuests = ns.MaxEsGuests
h.HostSEVInfo.CPU0ID = util.SetNotSet(ns.CPU0ID, ns.CPU0IDSet)
} }
return nil, err
} }
// Close triggers closing the host connection func (h *Host) getDomainInfo(wg *sync.WaitGroup) {
func (h *Host) Close() error { defer wg.Done()
log.Println("Closing Host", h.HostName) // getDomainInfo
close(h.close) doms, err := h.conn.ListAllDomains(0)
return <-h.closeErr if err != nil {
log.Println(err)
}
if len(doms) > 0 {
h.VMList = make([]VMInfo, len(doms))
for i, d := range doms {
// Just going to log errors here, and free the dom after getting what we can
if h.VMList[i].Name, err = d.GetName(); err != nil {
log.Println(err)
}
if h.VMList[i].UUID, err = d.GetUUID(); err != nil {
log.Println(err)
}
if h.VMList[i].ID, err = d.GetID(); err != nil {
log.Println(err)
}
if h.VMList[i].XML, err = d.GetXMLDesc(0); err != nil {
log.Println(err)
}
d.Free()
}
}
}
func (h *Host) getIfaceInfo(wg *sync.WaitGroup) {
defer wg.Done()
// getIfaceInfo
ifaces, err := h.conn.ListInterfaces()
if err != nil {
log.Println(err)
}
if len(ifaces) > 0 {
h.NetIfFList = make([]NetIfInfo, len(ifaces))
for i, ni := range ifaces {
h.NetIfFList[i].Name = ni
iface, err := h.conn.LookupInterfaceByName(ni)
if err != nil {
log.Println(err)
}
if h.NetIfFList[i].MacAddr, err = iface.GetMACString(); err != nil {
log.Println(err)
}
if h.NetIfFList[i].XML, err = iface.GetXMLDesc(0); err != nil {
log.Println(err)
}
iface.Free()
}
}
}
func (h *Host) getNetsInfo(wg *sync.WaitGroup) {
defer wg.Done()
// getNetsInfo
nets, err := h.conn.ListNetworks()
if err != nil {
log.Println(err)
}
if len(nets) > 0 {
h.NetworkList = make([]NetworkInfo, len(nets))
for i, netName := range nets {
net, err := h.conn.LookupNetworkByName(netName)
if err != nil {
log.Println(err)
}
if h.NetworkList[i].Name, err = net.GetName(); err != nil {
log.Println(err)
}
if h.NetworkList[i].UUID, err = net.GetUUID(); err != nil {
log.Println(err)
}
if h.NetworkList[i].XML, err = net.GetXMLDesc(0); err != nil {
log.Println(err)
}
net.Free()
}
}
}
func (h *Host) getDevicesInfo(wg *sync.WaitGroup) {
defer wg.Done()
ndevs, err := h.conn.ListAllNodeDevices(0)
if err != nil {
log.Println(err)
}
if len(ndevs) > 0 {
h.DeviceList = make([]DeviceInfo, len(ndevs))
for i, dev := range ndevs {
if h.DeviceList[i].Name, err = dev.GetName(); err != nil {
log.Println(err)
}
if h.DeviceList[i].Capabilities, err = dev.ListCaps(); err != nil {
log.Println(err)
}
if h.DeviceList[i].XML, err = dev.GetXMLDesc(0); err != nil {
log.Println(err)
}
dev.Free()
}
}
} }

View File

@ -1 +1,14 @@
package secret package secret
import "libvirt.org/go/libvirt"
// SecretUsageTypeMap provides string representation to secret types used by
// libvirt
var SecretUsageTypeMap map[libvirt.SecretUsageType]string = map[libvirt.SecretUsageType]string{
libvirt.SECRET_USAGE_TYPE_NONE: "none",
libvirt.SECRET_USAGE_TYPE_VOLUME: "volume",
libvirt.SECRET_USAGE_TYPE_ISCSI: "iscsi",
libvirt.SECRET_USAGE_TYPE_VTPM: "vtpm",
libvirt.SECRET_USAGE_TYPE_TLS: "tls",
libvirt.SECRET_USAGE_TYPE_CEPH: "ceph",
}

View File

@ -4,6 +4,7 @@ import (
"log" "log"
"git.staur.ca/stobbsm/clustvirt/lib/host" "git.staur.ca/stobbsm/clustvirt/lib/host"
ffmt "gopkg.in/ffmt.v1"
) )
func main() { func main() {
@ -14,11 +15,11 @@ func main() {
log.Fatal(err) log.Fatal(err)
} }
defer venus.Close() defer venus.Close()
log.Println(venus) ffmt.P(venus)
lm, err := venus.GetGuestByName("logan-minecraft") lm, err := venus.GetGuestByName("logan-minecraft")
if err != nil { if err != nil {
log.Fatal(err) log.Fatal(err)
} }
defer lm.Close() defer lm.Close()
log.Println(lm) //ffmt.P(lm)
} }