clustvirt/lib/host/lib.go

414 lines
13 KiB
Go
Raw Normal View History

// 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 (
"sync"
"git.staur.ca/stobbsm/clustvirt/lib/guest"
"git.staur.ca/stobbsm/clustvirt/lib/log"
"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
2024-03-20 17:36:53 +00:00
HostInfo *libvirtxml.CapsHost
// NodeMemory provides basic memory information about the Host
2024-03-20 17:36:53 +00:00
NodeMemory *NodeMemoryInfo
// VMList is the list of virtual machines available to the host
2024-03-20 17:36:53 +00:00
VMList []*libvirtxml.Domain
// NetIfList is the list of network interfaces on the host
2024-03-20 17:36:53 +00:00
NetIfFList []*libvirtxml.Interface
// NetworkList is the list of defined networks on the host
2024-03-20 17:36:53 +00:00
NetworkList []*libvirtxml.Network
// DeviceList is the list of devices on the host
2024-03-20 17:36:53 +00:00
DeviceList []*libvirtxml.NodeDevice
// SecretList provides a list of secrets available to the host
2024-03-20 17:36:53 +00:00
SecretList []*libvirtxml.Secret
// StoragePoolList provides the list of stoarge ppols available to the host
2024-03-20 17:36:53 +00:00
StoragePoolList []*libvirtxml.StoragePool
// VolumeList is the list of volumes available on the host
2024-03-20 17:36:53 +00:00
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.Info("Host.Close").Str("hostname", h.HostName).Msg("closing connection")
close(h.close)
return <-h.closeErr
}
// private methods that load the different informational parts
func (h *Host) getInfo() {
wg := new(sync.WaitGroup)
log.Info("host.getInfo").Str("hostname", h.HostName).Msg("collecting host information")
infoFuncs := []func(){
2024-03-20 17:36:53 +00:00
h.hostInfo,
h.memoryInfo,
h.getStoragePools,
h.getNetsInfo,
2024-03-20 17:36:53 +00:00
h.getIfaceInfo,
h.getDevicesInfo,
h.getDomainInfo,
h.getSecretsInfo,
}
for _, f := range infoFuncs {
wg.Add(1)
go func(wg *sync.WaitGroup) {
defer wg.Done()
f()
}(wg)
}
wg.Wait()
}
2024-03-20 17:36:53 +00:00
func (h *Host) hostInfo() {
log.Info("host.hostInfo").Str("hostname", h.HostName).Msg("collecting hostinfo")
var err error
2024-03-20 17:36:53 +00:00
rawxml, err := h.conn.GetCapabilities()
if err != nil {
log.Error("Host.hostInfo").Str("hostname", h.HostName).Err(ErrGetXML).Err(err).Send()
2024-03-20 17:36:53 +00:00
}
xmldoc := &libvirtxml.Caps{}
if err = xmldoc.Unmarshal(rawxml); err != nil {
log.Error("Host.hostInfo").Str("hostname", h.HostName).Err(ErrParseXML).Err(err).Send()
}
2024-03-20 17:36:53 +00:00
h.HostInfo = &xmldoc.Host
h.SystemHostName, err = h.conn.GetHostname()
if err != nil {
log.Error("Host.hostInfo").Str("hostname", h.HostName).Err(err).Msg("unable to set SystemHostName")
}
if h.SystemHostName == "" {
h.SystemHostName = h.HostName
}
log.Info("Host.hostInfo").Str("hostname", h.HostName).Str("system hostname", h.SystemHostName).Msg("set system hostname")
h.LibVersion, err = h.conn.GetLibVersion()
if err != nil {
log.Error("Host.hostInfo").Str("hostname", h.HostName).Err(err).Msg("unable to get libversion")
return
}
log.Info("Host.hostInfo").Str("hostname", h.HostName).Uint32("libversion", h.LibVersion).Send()
2024-03-20 17:36:53 +00:00
}
2024-03-20 17:36:53 +00:00
func (h *Host) memoryInfo() {
mi, err := h.conn.GetMemoryStats(libvirt.NODE_MEMORY_STATS_ALL_CELLS, 0)
if err != nil {
log.Error("Host.memoryInfo").Str("hostname", h.HostName).Err(err).Send()
}
2024-03-20 17:36:53 +00:00
h.NodeMemory = &NodeMemoryInfo{
2024-03-20 21:45:25 +00:00
Total: mi.Total,
Free: mi.Free,
2024-03-20 17:36:53 +00:00
Buffers: mi.Buffers,
2024-03-20 21:45:25 +00:00
Cached: mi.Cached,
}
h.SysInfo, err = h.conn.GetSysinfo(0)
if err != nil {
log.Error("Host.memoryInfo").Str("hostname", h.HostName).Err(err).Msg("failed to GetSysInfo")
}
h.Alive, err = h.conn.IsAlive()
if err != nil {
log.Error("Host.memoryInfo").Str("hostname", h.HostName).Err(err).Msg("failed check to IsAlive")
}
h.Encrypted, err = h.conn.IsEncrypted()
if err != nil {
log.Error("Host.memoryInfo").Str("hostname", h.HostName).Err(err).Msg("failed to check IsEncrypted")
}
h.Secure, err = h.conn.IsSecure()
if err != nil {
log.Error("Host.memoryInfo").Str("hostname", h.HostName).Err(err).Msg("failed to check IsSecure")
}
}
2024-03-20 17:36:53 +00:00
func (h *Host) getStoragePools() {
log.Info("host.getStoragePools").Str("hostname", h.HostName).Msg("collection storage pool information")
2024-03-20 17:36:53 +00:00
// Get list of all storage pools on the host
spools, err := h.conn.ListAllStoragePools(0)
if err != nil {
log.Error("Host.getStoragePools").Str("hostname", h.HostName).Err(err).Msg("failed to ListAllStoragePools")
2024-03-20 17:36:53 +00:00
}
if len(spools) > 0 {
h.StoragePoolList = make([]*libvirtxml.StoragePool, len(spools))
for i, s := range spools {
// run in a function to allow defer s.Free()
func() {
defer s.Free()
// Get the XML represenation of each storage pool, parse it with libvirtxml
rawxml, err := s.GetXMLDesc(0)
if err != nil {
log.Error("Host.getStoragePools").Str("hostname", h.HostName).Err(ErrGetXML).Err(err).Send()
2024-03-20 17:36:53 +00:00
return
}
xmldoc := &libvirtxml.StoragePool{}
err = xmldoc.Unmarshal(rawxml)
if err != nil {
log.Error("Host.getStoragePools").Str("hostname", h.HostName).Err(ErrParseXML).Err(err).Send()
2024-03-20 17:36:53 +00:00
return
}
h.StoragePoolList[i] = xmldoc
// Get list of all storage volumes in the current storage pool
svols, err := s.ListAllStorageVolumes(0)
if err != nil {
log.Error("Host.getStoragePools").Str("hostname", h.HostName).Str("storagepool", h.StoragePoolList[i].Name).Err(err).Msg("failed to ListAllStorageVolumes")
2024-03-20 17:36:53 +00:00
}
if len(svols) > 0 {
// define temporary variable to hold slice of StorageVolume, that can
// either be appended to the existing slice, or used in place of the newly
// defined slice
tvl := make([]*libvirtxml.StorageVolume, len(svols))
for j, sv := range svols {
// run in a function so I can defer the sv.Free() call
func() {
defer sv.Free()
rawxml, err = sv.GetXMLDesc(0)
if err != nil {
log.Error("Host.getStoragePools").Str("hostname", h.HostName).Err(ErrGetXML).Err(err).Send()
2024-03-20 17:36:53 +00:00
return
}
xmldoc := &libvirtxml.StorageVolume{}
err = xmldoc.Unmarshal(rawxml)
if err != nil {
log.Error("Host.getStoragePools").Str("hostname", h.HostName).Err(ErrParseXML).Err(err).Send()
2024-03-20 17:36:53 +00:00
return
}
tvl[j] = xmldoc
log.Info("Host.getStoragePools").Str("hostname", h.HostName).Str("added volume", tvl[j].Name).Send()
2024-03-20 17:36:53 +00:00
}()
}
// Append the contents of tvl to h.VolumeList
if h.VolumeList == nil {
log.Info("Host.getStoragePools").Str("hostname", h.HostName).Str("storagepool", h.StoragePoolList[i].Name).Msg("initializing VolumeList")
2024-03-20 17:36:53 +00:00
h.VolumeList = []*libvirtxml.StorageVolume{}
}
// Only append if the temporary storage volume isn't nil
log.Info("Host.getStoragePools").Str("hostname", h.HostName).Str("storagepool", h.StoragePoolList[i].Name).Int("VolumeList count", len(h.VolumeList)).Msg("before filter")
2024-03-20 17:36:53 +00:00
for _, tsv := range tvl {
if tsv != nil {
h.VolumeList = append(h.VolumeList, tsv)
}
}
log.Info("Host.getStoragePools").Str("hostname", h.HostName).Str("storagepool", h.StoragePoolList[i].Name).Int("VolumeList count", len(h.VolumeList)).Msg("after filter")
2024-03-20 17:36:53 +00:00
}
}()
}
}
}
func (h *Host) getSecretsInfo() {
log.Info("host.getSecretsInfo").Str("hostname", h.HostName).Msg("collecting secret information")
2024-03-20 17:36:53 +00:00
nsecrets, err := h.conn.ListAllSecrets(0)
if err != nil {
log.Error("Host.getSecretsInfo").Str("hostname", h.HostName).Err(err).Send()
2024-03-20 17:36:53 +00:00
}
if len(nsecrets) > 0 {
h.SecretList = make([]*libvirtxml.Secret, len(nsecrets))
for i, s := range nsecrets {
func() {
defer s.Free()
rawxml, err := s.GetXMLDesc(0)
if err != nil {
log.Error("Host.getSecretsInfo").Str("hostname", h.HostName).Err(ErrGetXML).Err(err).Send()
2024-03-20 17:36:53 +00:00
}
xmldoc := &libvirtxml.Secret{}
if err = xmldoc.Unmarshal(rawxml); err != nil {
log.Error("Host.getSecretsInfo").Str("hostname", h.HostName).Err(ErrParseXML).Err(err).Send()
2024-03-20 17:36:53 +00:00
}
h.SecretList[i] = xmldoc
}()
}
}
}
func (h *Host) getDomainInfo() {
log.Info("host.getDomainInfo").Str("hostname", h.HostName).Msg("collecting domain (vm) information")
// getDomainInfo
doms, err := h.conn.ListAllDomains(0)
if err != nil {
log.Error("Host.getDomainInfo").Str("hostname", h.HostName).Err(err).Send()
return
}
if len(doms) > 0 {
2024-03-20 21:45:25 +00:00
h.VMList = make([]*libvirtxml.Domain, len(doms))
for i, d := range doms {
2024-03-20 21:45:25 +00:00
func() {
defer d.Free()
rawxml, err := d.GetXMLDesc(0)
if err != nil {
log.Error("Host.getDomainInfo").Str("hostname", h.HostName).Err(ErrGetXML).Err(err).Send()
return
2024-03-20 21:45:25 +00:00
}
h.VMList[i] = &libvirtxml.Domain{}
if err = h.VMList[i].Unmarshal(rawxml); err != nil {
log.Error("Host.getDomainInfo").Str("hostname", h.HostName).Err(ErrParseXML).Err(err).Send()
return
2024-03-20 21:45:25 +00:00
}
}()
}
}
}
func (h *Host) getIfaceInfo() {
// getIfaceInfo
2024-03-20 21:45:25 +00:00
ifaces, err := h.conn.ListAllInterfaces(0)
if err != nil {
log.Error("Host.getIfaceInfo").Str("hostname", h.HostName).Err(err).Send()
}
if len(ifaces) > 0 {
2024-03-20 21:45:25 +00:00
h.NetIfFList = make([]*libvirtxml.Interface, len(ifaces))
for i, ni := range ifaces {
2024-03-20 21:45:25 +00:00
func() {
defer ni.Free()
rawxml, err := ni.GetXMLDesc(0)
if err != nil {
log.Error("Host.getIfaceInfo").Str("hostname", h.HostName).Err(ErrGetXML).Err(err).Send()
2024-03-20 21:45:25 +00:00
return
}
h.NetIfFList[i] = &libvirtxml.Interface{}
if err = h.NetIfFList[i].Unmarshal(rawxml); err != nil {
log.Error("Host.getIfaceInfo").Str("hostname", h.HostName).Err(ErrParseXML).Err(err).Send()
2024-03-20 21:45:25 +00:00
return
}
}()
}
}
}
func (h *Host) getNetsInfo() {
// getNetsInfo
nets, err := h.conn.ListAllNetworks(0)
if err != nil {
log.Error("Host.getNetsInfo").Str("hostname", h.HostName).Err(err).Send()
}
if len(nets) > 0 {
h.NetworkList = make([]*libvirtxml.Network, len(nets))
for i, net := range nets {
func() {
defer net.Free()
rawxml, err := net.GetXMLDesc(0)
if err != nil {
log.Error("Host.getNetsInfo").Str("hostname", h.HostName).Err(ErrGetXML).Err(err).Send()
return
}
h.NetworkList[i] = &libvirtxml.Network{}
if err = h.NetworkList[i].Unmarshal(rawxml); err != nil {
log.Error("Host.getNetsInfo").Str("hostname", h.HostName).Err(ErrParseXML).Err(err).Send()
return
}
}()
}
}
}
func (h *Host) getDevicesInfo() {
ndevs, err := h.conn.ListAllNodeDevices(0)
if err != nil {
log.Error("Host.getDevicesInfo").Str("hostname", h.HostName).Err(err).Send()
}
if len(ndevs) > 0 {
h.DeviceList = make([]*libvirtxml.NodeDevice, len(ndevs))
for i, dev := range ndevs {
func() {
defer dev.Free()
rawxml, err := dev.GetXMLDesc(0)
if err != nil {
log.Error("Host.getDevicesInfo").Str("hostname", h.HostName).Err(ErrGetXML).Err(err).Send()
return
}
h.DeviceList[i] = &libvirtxml.NodeDevice{}
if err = h.DeviceList[i].Unmarshal(rawxml); err != nil {
log.Error("Host.getDevicesInfo").Str("hostname", h.HostName).Err(ErrParseXML).Err(err).Send()
return
}
}()
}
}
}