From f96a911722d7999a84a9a0f2a7992993afb83b8a Mon Sep 17 00:00:00 2001 From: Matthew Stobbs Date: Tue, 12 Mar 2024 22:17:42 -0600 Subject: [PATCH] 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 --- go.mod | 2 + go.sum | 2 + lib/guest/lib.go | 49 +++++ lib/host/lib.go | 447 ++++++++++++++++++++++++++++++++++++---------- lib/secret/lib.go | 13 ++ main.go | 5 +- 6 files changed, 424 insertions(+), 94 deletions(-) diff --git a/go.mod b/go.mod index a9969c1..0cfea1d 100644 --- a/go.mod +++ b/go.mod @@ -3,3 +3,5 @@ module git.staur.ca/stobbsm/clustvirt go 1.22.1 require libvirt.org/go/libvirt v1.10001.0 + +require gopkg.in/ffmt.v1 v1.5.6 // indirect diff --git a/go.sum b/go.sum index 5306274..239d34a 100644 --- a/go.sum +++ b/go.sum @@ -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/go.mod h1:1WiFE8EjZfq+FCVog+rvr1yatKbKZ9FaFMZgEqxEJqQ= diff --git a/lib/guest/lib.go b/lib/guest/lib.go index b5e6db0..dc52ec5 100644 --- a/lib/guest/lib.go +++ b/lib/guest/lib.go @@ -15,6 +15,55 @@ import ( // 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 diff --git a/lib/host/lib.go b/lib/host/lib.go index 459b174..cf140bb 100644 --- a/lib/host/lib.go +++ b/lib/host/lib.go @@ -8,8 +8,10 @@ package host import ( "log" + "sync" "git.staur.ca/stobbsm/clustvirt/lib/guest" + "git.staur.ca/stobbsm/clustvirt/lib/secret" "git.staur.ca/stobbsm/clustvirt/util" "libvirt.org/go/libvirt" ) @@ -18,40 +20,44 @@ import ( // If a connection is closed prematurely, will re-open the connection and // try the attempted method again type Host struct { - HostName string - SystemHostName string - FreeMemory uint64 - LibVersion uint32 - HostInfo NodeInfo - HostSEVInfo SEVInfo - AvailableCPUTypes []string - NodeMemory NodeMemoryInfo + // HostName used to make the connection + HostName string + // SystemHostName is the hostname as reported by the system itself + SystemHostName string + // FreeMemory is the available free memory + FreeMemory uint64 + // LibVersion is the version of Libvirt on the host + LibVersion uint32 + // HostInfo provides basic HW information about a host + HostInfo NodeInfo + // HostSEVInfo provides informatoin about AMD SEV extentions available on the host + HostSEVInfo SEVInfo + // AvailableCPUTypes are the available types of CPUs that can be used for VM creation + AvailableCPUTypes []string + // NodeMemory provides basic memory information about the Host + NodeMemory NodeMemoryInfo + // StorageCapabilities is the XML representation of the hosts storage capabilities StorageCapabilities string - SysInfo string - Alive bool - Encrypted bool - Secure bool - // alldomains - VMList []string - // allinterfaces - IFList []string - // allnetworks - NetList []string - // alldevices - DeviceList []string - // allsecrets - SecretList []string - // allstoragepools - StoragePoolList []string - // defineddomains - // definedinterfaces - // definednetworks - // definedstoragepools - // listdomains []int id - // listinterfaces []string interfacename - // listnetworks []string networkname - // listserets []string secretname - // liststoragepools []string storagepoolname + // SysInfo is the XML representation of the host system information + SysInfo string + // Alive indicates if the connection is alive + Alive bool + // Encrypted indicates if the connection is encrypted + Encrypted bool + // Secure indicates if the connection is secure + Secure bool + // VMList is the list of virtual machines available to the host + VMList []VMInfo + // NetIfList is the list of network interfaces on the host + NetIfFList []NetIfInfo + // NetworkList is the list of defined networks on the host + NetworkList []NetworkInfo + // DeviceList is the list of devices on the host + DeviceList []DeviceInfo + // SecretList provides a list of secrets available to the host + SecretList []SecretInfo + // StoragePoolList provides the list of stoarge ppols available to the host + StoragePoolList []StoragePoolInfo uri *URI conn *libvirt.Connect @@ -59,6 +65,71 @@ type Host struct { 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 type NodeInfo struct { // livirt.NodeInfo section @@ -107,16 +178,133 @@ func ConnectHost(uri *URI, host string) (*Host, error) { h.close = make(chan struct{}) 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 h.AvailableCPUTypes, err = h.conn.GetCPUModelNames("x86_64", 0) if err != nil { log.Println("Error getting cpu model names", err) } - // Extract libvirt.NodeInfo and libvirt.NodeSEVParameters ni, err := h.conn.GetNodeInfo() if err != nil { - return nil, err + log.Println(err) } h.HostInfo.Model = ni.Model h.HostInfo.Memory = ni.Memory @@ -126,33 +314,6 @@ func ConnectHost(uri *URI, host string) (*Host, error) { h.HostInfo.Sockets = ni.Sockets h.HostInfo.Cores = ni.Cores 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() if err != nil { log.Println(err) @@ -200,40 +361,142 @@ func ConnectHost(uri *URI, host string) (*Host, error) { if err != nil { 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) 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 +func (h *Host) getSEVInfo(wg *sync.WaitGroup) { + defer wg.Done() + // getSEVInfo + h.HostSEVInfo.SEVEnabled = true + ns, err := h.conn.GetSEVInfo(0) + if err != nil { + log.Println(err) + lverr, ok := err.(libvirt.Error) + if ok { + switch lverr.Code { + case 84: + log.Println("SEV functions not supported") + h.HostSEVInfo.SEVEnabled = false + default: + log.Println("Error encountered", lverr.Error()) + } + } } - 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) + 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) Close() error { - log.Println("Closing Host", h.HostName) - close(h.close) - return <-h.closeErr +func (h *Host) getDomainInfo(wg *sync.WaitGroup) { + defer wg.Done() + // getDomainInfo + doms, err := h.conn.ListAllDomains(0) + 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() + } + } } diff --git a/lib/secret/lib.go b/lib/secret/lib.go index a48db44..a99aec8 100644 --- a/lib/secret/lib.go +++ b/lib/secret/lib.go @@ -1 +1,14 @@ 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", +} diff --git a/main.go b/main.go index f2a2772..67eec9c 100644 --- a/main.go +++ b/main.go @@ -4,6 +4,7 @@ import ( "log" "git.staur.ca/stobbsm/clustvirt/lib/host" + ffmt "gopkg.in/ffmt.v1" ) func main() { @@ -14,11 +15,11 @@ func main() { log.Fatal(err) } defer venus.Close() - log.Println(venus) + ffmt.P(venus) lm, err := venus.GetGuestByName("logan-minecraft") if err != nil { log.Fatal(err) } defer lm.Close() - log.Println(lm) + //ffmt.P(lm) }