clustvirt/lib/host/lib.go
Matthew Stobbs de93204e3d migrate data gathering to libvirtxml parsing
- instead of getting all the data the hard way, use libvirtxml
  to parse the XML from libvirt
- this makes it more accurate, and more future proof when schema
  changes occur
- add pcidb to query devices better
2024-03-19 15:25:57 -06:00

447 lines
11 KiB
Go

// Package host provides utilities and data structures in relation to a libvirt host.
// This includes getting a list of virtual machines running on a host, launching
// a new virtual machine on a host, triggering a virtual machine migration to another
// host, getting hardware and resource usage from a host, and eventually more.
// Most of this is data at the moment, ensuring data can be gathered efficiently without
// slowing down the host from it's main job of running virtual machines.
package host
import (
"log"
"sync"
"git.staur.ca/stobbsm/clustvirt/lib/guest"
"git.staur.ca/stobbsm/clustvirt/lib/secret"
"git.staur.ca/stobbsm/clustvirt/lib/storagepool"
"git.staur.ca/stobbsm/clustvirt/lib/storagevol"
"libvirt.org/go/libvirt"
"libvirt.org/go/libvirtxml"
)
// Host holds information and acts as a connection handle for a Host
// If a connection is closed prematurely, will re-open the connection and
// try the attempted method again
type Host struct {
// HostName used to make the connection
HostName string
// SystemHostName is the hostname as reported by the system itself
SystemHostName string
// LibVersion is the version of Libvirt on the host
LibVersion uint32
// 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
// HostInfo provides basic HW information about a host
HostInfo libvirtxml.CapsHost
// NodeMemory provides basic memory information about the Host
NodeMemory NodeMemoryInfo
// VMList is the list of virtual machines available to the host
VMList []libvirtxml.Domain
// NetIfList is the list of network interfaces on the host
NetIfFList []libvirtxml.Interface
// NetworkList is the list of defined networks on the host
NetworkList []libvirtxml.Network
// DeviceList is the list of devices on the host
DeviceList []libvirtxml.NodeDevice
// SecretList provides a list of secrets available to the host
SecretList []libvirtxml.Secret
// StoragePoolList provides the list of stoarge ppols available to the host
StoragePoolList []libvirtxml.StoragePool
// VolumeList is the list of volumes available on the host
VolumeList []libvirtxml.StorageVolume
uri *URI
conn *libvirt.Connect
close chan struct{}
closeErr chan error
}
// NodeMemoryInfo provides statistis about node memory usage from libvirt.NodeMemoryStats
type NodeMemoryInfo struct {
Total uint64
Free uint64
Buffers uint64
Cached uint64
}
// ConnectHost creates a host connection wrapper that can be used regularly
func ConnectHost(uri *URI, host string) (*Host, error) {
h := &Host{
HostName: host,
uri: uri,
}
if err := h.connect(); err != nil {
return nil, err
}
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() {
wg := new(sync.WaitGroup)
infoFuncs := []func(){
h.getDevicesInfo,
// h.getDomainInfo,
h.getIfaceInfo,
h.getNetsInfo,
h.getNodeInfo,
// h.getSecretsInfo,
// h.getStoragePools,
}
for _, f := range infoFuncs {
wg.Add(1)
go func(wg *sync.WaitGroup) {
defer wg.Done()
f()
}(wg)
}
wg.Wait()
}
func (h *Host) getStoragePools() {
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)
}
if h.StoragePoolList[i].AutoStart, err = s.GetAutostart(); err != nil {
log.Println(err)
}
spInfo, err := s.GetInfo()
if err != nil {
log.Println(err)
}
h.StoragePoolList[i].State = storagepool.StoragePoolStateMap[spInfo.State]
h.StoragePoolList[i].Capacity = spInfo.Capacity
h.StoragePoolList[i].Allocation = spInfo.Allocation
h.StoragePoolList[i].Available = spInfo.Available
spoolXML := &libvirtxml.StoragePool{}
if err = spoolXML.Unmarshal(h.StoragePoolList[i].XML); err != nil {
log.Println(err)
}
h.StoragePoolList[i].Type = spoolXML.Type
for _, t := range storagepool.NetTypes {
if h.StoragePoolList[i].Type == t {
h.StoragePoolList[i].IsNet = true
h.StoragePoolList[i].HAEnabled = true
continue
}
}
svols, err := s.ListAllStorageVolumes(0)
if err != nil {
log.Println(err)
}
if len(svols) > 0 {
h.StoragePoolList[i].Volumes = make([]VolumeInfo, len(svols))
for j, sv := range svols {
if h.StoragePoolList[i].Volumes[j].Name, err = sv.GetName(); err != nil {
log.Println(err)
}
if h.StoragePoolList[i].Volumes[j].Key, err = sv.GetKey(); err != nil {
log.Println(err)
}
if h.StoragePoolList[i].Volumes[j].Path, err = sv.GetPath(); err != nil {
log.Println(err)
}
svInfo, err := sv.GetInfo() // Type, Capacity, Allocation
if err != nil {
log.Println(err)
}
h.StoragePoolList[i].Volumes[j].Type = storagevol.StorageVolTypeMap[svInfo.Type]
h.StoragePoolList[i].Volumes[j].Capacity = svInfo.Capacity
h.StoragePoolList[i].Volumes[j].Allocation = svInfo.Allocation
if h.StoragePoolList[i].Volumes[j].XML, err = sv.GetXMLDesc(0); err != nil {
log.Println(err)
}
sv.Free()
}
}
s.Free()
}
}
}
func (h *Host) getSecretsInfo() {
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() {
var err error
h.AvailableCPUTypes, err = h.conn.GetCPUModelNames("x86_64", 0)
if err != nil {
log.Println("Error getting cpu model names", err)
}
ni, err := h.conn.GetNodeInfo()
if err != nil {
log.Println(err)
}
h.HostInfo.Model = ni.Model
h.HostInfo.Memory = ni.Memory
h.HostInfo.Cpus = ni.Cpus
h.HostInfo.MHz = ni.MHz
h.HostInfo.Nodes = ni.Nodes
h.HostInfo.Sockets = ni.Sockets
h.HostInfo.Cores = ni.Cores
h.HostInfo.Threads = ni.Threads
h.SystemHostName, err = h.conn.GetHostname()
if err != nil {
log.Println(err)
}
if h.SystemHostName == "" {
h.SystemHostName = h.HostName
}
h.LibVersion, err = h.conn.GetLibVersion()
if err != nil {
log.Println(err)
}
h.FreeMemory, err = h.conn.GetFreeMemory()
if err != nil {
log.Println(err)
}
mi, err := h.conn.GetMemoryStats(libvirt.NODE_MEMORY_STATS_ALL_CELLS, 0)
if err != nil {
log.Println(err)
}
h.NodeMemory.Total = mi.Total
h.NodeMemory.Free = mi.Free
h.NodeMemory.Buffers = mi.Buffers
h.NodeMemory.Cached = mi.Cached
h.StorageCapabilities, err = h.conn.GetStoragePoolCapabilities(0)
if err != nil {
log.Println(err)
}
h.SysInfo, err = h.conn.GetSysinfo(0)
if err != nil {
log.Println(err)
}
h.Alive, err = h.conn.IsAlive()
if err != nil {
log.Println(err)
}
h.Encrypted, err = h.conn.IsEncrypted()
if err != nil {
log.Println(err)
}
h.Secure, err = h.conn.IsSecure()
if err != nil {
log.Println(err)
}
}
func (h *Host) getDomainInfo() {
// 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)
}
vmXML := &libvirtxml.Domain{}
if err = vmXML.Unmarshal(h.VMList[i].XML); err != nil {
log.Println(err)
}
h.VMList[i].VCPUs = vmXML.VCPU.Value
h.VMList[i].Memory = vmXML.CurrentMemory.Value
if h.VMList[i].Active, err = d.IsActive(); err != nil {
log.Println(err)
}
d.Free()
}
}
}
func (h *Host) getIfaceInfo() {
// 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() {
// 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)
}
if h.NetworkList[i].Active, err = net.IsActive(); err != nil {
log.Println(err)
}
net.Free()
}
}
}
func (h *Host) getDevicesInfo() {
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)
}
dx := &libvirtxml.NodeDevice{}
if err != dx.Unmarshal(h.DeviceList[i].XML); err != nil {
log.Println(err)
}
h.DeviceList[i].Driver = dx.Driver.Name
dx.Capability.PCI.Class
dev.Free()
}
}
}