clustvirt/lib/host/lib.go

553 lines
14 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 (
"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"
"git.staur.ca/stobbsm/clustvirt/util"
"libvirt.org/go/libvirt"
)
// 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
// 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 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
close chan 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
AutoStart bool
State string
Capacity uint64
Allocation uint64
Available uint64
// 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
Key string
Path string
Type string
Capacity uint64
Allocation uint64
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
Model string
Memory uint64
Cpus uint
MHz uint
Nodes uint32
Sockets uint32
Cores uint32
Threads uint32
}
// SEVInfo provides information about AMD SEV support
type SEVInfo struct {
// livirt.NodeSEVParameters section
SEVEnabled bool
PDH string
CertChain string
CBitPos uint
ReducedPhysBits uint
MaxGuests uint
MaxEsGuests uint
CPU0ID string
}
// 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() {
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)
}
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
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(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)
}
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) 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())
}
}
}
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)
}
}
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()
}
}
}