Compare commits
No commits in common. "f96a911722d7999a84a9a0f2a7992993afb83b8a" and "00758af30b1a80d6510d9716833e289e9079a518" have entirely different histories.
f96a911722
...
00758af30b
4
go.mod
4
go.mod
@ -2,6 +2,4 @@ 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 // indirect
|
||||||
|
|
||||||
require gopkg.in/ffmt.v1 v1.5.6 // indirect
|
|
||||||
|
2
go.sum
2
go.sum
@ -1,4 +1,2 @@
|
|||||||
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=
|
||||||
|
@ -1 +0,0 @@
|
|||||||
package device
|
|
@ -6,7 +6,6 @@ import (
|
|||||||
"log"
|
"log"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"git.staur.ca/stobbsm/clustvirt/util"
|
|
||||||
"libvirt.org/go/libvirt"
|
"libvirt.org/go/libvirt"
|
||||||
)
|
)
|
||||||
|
|
||||||
@ -15,55 +14,6 @@ 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
|
||||||
@ -170,11 +120,11 @@ func GetGuest(name string, conn *libvirt.Connect) (*VM, error) {
|
|||||||
return g, err
|
return g, err
|
||||||
}
|
}
|
||||||
g.BlockIOParameters.Weight = blkiop.Weight
|
g.BlockIOParameters.Weight = blkiop.Weight
|
||||||
g.BlockIOParameters.DeviceWeight = util.SetNotSet(blkiop.DeviceWeight, blkiop.DeviceWeightSet)
|
g.BlockIOParameters.DeviceWeight = setNotSet(blkiop.DeviceWeight, blkiop.DeviceWeightSet)
|
||||||
g.BlockIOParameters.DeviceReadIops = util.SetNotSet(blkiop.DeviceReadIops, blkiop.DeviceReadIopsSet)
|
g.BlockIOParameters.DeviceReadIops = setNotSet(blkiop.DeviceReadIops, blkiop.DeviceReadIopsSet)
|
||||||
g.BlockIOParameters.DeviceWriteIops = util.SetNotSet(blkiop.DeviceWriteIops, blkiop.DeviceWriteIopsSet)
|
g.BlockIOParameters.DeviceWriteIops = setNotSet(blkiop.DeviceWriteIops, blkiop.DeviceWriteIopsSet)
|
||||||
g.BlockIOParameters.DeviceReadBps = util.SetNotSet(blkiop.DeviceReadBps, blkiop.DeviceReadBpsSet)
|
g.BlockIOParameters.DeviceReadBps = setNotSet(blkiop.DeviceReadBps, blkiop.DeviceReadBpsSet)
|
||||||
g.BlockIOParameters.DeviceWriteBps = util.SetNotSet(blkiop.DeviceWriteBps, blkiop.DeviceWriteBpsSet)
|
g.BlockIOParameters.DeviceWriteBps = setNotSet(blkiop.DeviceWriteBps, blkiop.DeviceWriteBpsSet)
|
||||||
|
|
||||||
// Set as much guest info as possible
|
// Set as much guest info as possible
|
||||||
info, err := g.dom.GetGuestInfo(
|
info, err := g.dom.GetGuestInfo(
|
||||||
@ -191,13 +141,13 @@ func GetGuest(name string, conn *libvirt.Connect) (*VM, error) {
|
|||||||
if len(info.Disks) > 0 {
|
if len(info.Disks) > 0 {
|
||||||
g.Disks = make([]DiskInfo, len(info.Disks))
|
g.Disks = make([]DiskInfo, len(info.Disks))
|
||||||
for i, n := range info.Disks {
|
for i, n := range info.Disks {
|
||||||
g.Disks[i].Name = util.SetNotSet(n.Name, n.NameSet)
|
g.Disks[i].Name = setNotSet(n.Name, n.NameSet)
|
||||||
g.Disks[i].Alias = util.SetNotSet(n.Alias, n.AliasSet)
|
g.Disks[i].Alias = setNotSet(n.Alias, n.AliasSet)
|
||||||
g.Disks[i].Partition = n.Partition
|
g.Disks[i].Partition = n.Partition
|
||||||
g.Disks[i].GuestAlias = util.SetNotSet(n.GuestAlias, n.GuestAliasSet)
|
g.Disks[i].GuestAlias = setNotSet(n.GuestAlias, n.GuestAliasSet)
|
||||||
g.Disks[i].DiskDependency = make([]string, len(n.Dependencies))
|
g.Disks[i].DiskDependency = make([]string, len(n.Dependencies))
|
||||||
for j, k := range n.Dependencies {
|
for j, k := range n.Dependencies {
|
||||||
g.Disks[i].DiskDependency[j] = util.SetNotSet(k.Name, k.NameSet)
|
g.Disks[i].DiskDependency[j] = setNotSet(k.Name, k.NameSet)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -205,23 +155,23 @@ func GetGuest(name string, conn *libvirt.Connect) (*VM, error) {
|
|||||||
if len(info.Users) > 0 {
|
if len(info.Users) > 0 {
|
||||||
g.Users = make([]UserInfo, len(info.Users))
|
g.Users = make([]UserInfo, len(info.Users))
|
||||||
for i, n := range info.Users {
|
for i, n := range info.Users {
|
||||||
g.Users[i].Name = util.SetNotSet(n.Name, n.NameSet)
|
g.Users[i].Name = setNotSet(n.Name, n.NameSet)
|
||||||
g.Users[i].Domain = util.SetNotSet(n.Domain, n.DomainSet)
|
g.Users[i].Domain = setNotSet(n.Domain, n.DomainSet)
|
||||||
g.Users[i].LoginTime = time.Unix(int64(n.LoginTime), 0)
|
g.Users[i].LoginTime = time.Unix(int64(n.LoginTime), 0)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
g.OS.ID = util.SetNotSet(info.OS.ID, info.OS.IDSet)
|
g.OS.ID = setNotSet(info.OS.ID, info.OS.IDSet)
|
||||||
g.OS.Name = util.SetNotSet(info.OS.Name, info.OS.NameSet)
|
g.OS.Name = setNotSet(info.OS.Name, info.OS.NameSet)
|
||||||
g.OS.Machine = util.SetNotSet(info.OS.Machine, info.OS.MachineSet)
|
g.OS.Machine = setNotSet(info.OS.Machine, info.OS.MachineSet)
|
||||||
g.OS.Variant = util.SetNotSet(info.OS.Variant, info.OS.VariantSet)
|
g.OS.Variant = setNotSet(info.OS.Variant, info.OS.VariantSet)
|
||||||
g.OS.Version = util.SetNotSet(info.OS.Version, info.OS.VersionSet)
|
g.OS.Version = setNotSet(info.OS.Version, info.OS.VersionSet)
|
||||||
g.OS.VariantID = util.SetNotSet(info.OS.VariantID, info.OS.VariantIDSet)
|
g.OS.VariantID = setNotSet(info.OS.VariantID, info.OS.VariantIDSet)
|
||||||
g.OS.VersionID = util.SetNotSet(info.OS.VersionID, info.OS.VersionIDSet)
|
g.OS.VersionID = setNotSet(info.OS.VersionID, info.OS.VersionIDSet)
|
||||||
g.OS.PrettyName = util.SetNotSet(info.OS.PrettyName, info.OS.PrettyNameSet)
|
g.OS.PrettyName = setNotSet(info.OS.PrettyName, info.OS.PrettyNameSet)
|
||||||
g.OS.KernelRelease = util.SetNotSet(info.OS.KernelRelease, info.OS.KernelReleaseSet)
|
g.OS.KernelRelease = setNotSet(info.OS.KernelRelease, info.OS.KernelReleaseSet)
|
||||||
g.OS.KernelVersion = util.SetNotSet(info.OS.KernelVersion, info.OS.KernelVersionSet)
|
g.OS.KernelVersion = setNotSet(info.OS.KernelVersion, info.OS.KernelVersionSet)
|
||||||
g.TimeZone.Name = util.SetNotSet(info.TimeZone.Name, info.TimeZone.NameSet)
|
g.TimeZone.Name = setNotSet(info.TimeZone.Name, info.TimeZone.NameSet)
|
||||||
g.TimeZone.Offset = info.TimeZone.Offset
|
g.TimeZone.Offset = info.TimeZone.Offset
|
||||||
|
|
||||||
// Set the hostname from the guest if it is set, otherwise use the VM name
|
// Set the hostname from the guest if it is set, otherwise use the VM name
|
||||||
@ -233,13 +183,13 @@ func GetGuest(name string, conn *libvirt.Connect) (*VM, error) {
|
|||||||
if len(info.Interfaces) > 0 {
|
if len(info.Interfaces) > 0 {
|
||||||
g.NetIFace = make([]NetIFaceInfo, len(info.Interfaces))
|
g.NetIFace = make([]NetIFaceInfo, len(info.Interfaces))
|
||||||
for i, n := range info.Interfaces {
|
for i, n := range info.Interfaces {
|
||||||
g.NetIFace[i].HWAddr = util.SetNotSet(n.Hwaddr, n.HwaddrSet)
|
g.NetIFace[i].HWAddr = setNotSet(n.Hwaddr, n.HwaddrSet)
|
||||||
g.NetIFace[i].Name = util.SetNotSet(n.Name, n.NameSet)
|
g.NetIFace[i].Name = setNotSet(n.Name, n.NameSet)
|
||||||
if len(n.Addrs) > 0 {
|
if len(n.Addrs) > 0 {
|
||||||
g.NetIFace[i].IP = make([]IPInfo, len(n.Addrs))
|
g.NetIFace[i].IP = make([]IPInfo, len(n.Addrs))
|
||||||
for j, a := range n.Addrs {
|
for j, a := range n.Addrs {
|
||||||
g.NetIFace[i].IP[j].Addr = util.SetNotSet(a.Addr, a.AddrSet)
|
g.NetIFace[i].IP[j].Addr = setNotSet(a.Addr, a.AddrSet)
|
||||||
g.NetIFace[i].IP[j].Type = util.SetNotSet(a.Type, a.TypeSet)
|
g.NetIFace[i].IP[j].Type = setNotSet(a.Type, a.TypeSet)
|
||||||
g.NetIFace[i].IP[j].Prefix = a.Prefix
|
g.NetIFace[i].IP[j].Prefix = a.Prefix
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -260,6 +210,14 @@ func GetGuest(name string, conn *libvirt.Connect) (*VM, error) {
|
|||||||
return g, nil
|
return g, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// helper function to set a string value to "NotSet" if it wasn't actually set
|
||||||
|
func setNotSet(v string, s bool) string {
|
||||||
|
if s {
|
||||||
|
return v
|
||||||
|
}
|
||||||
|
return "Notset"
|
||||||
|
}
|
||||||
|
|
||||||
// Close closes an open connection
|
// Close closes an open connection
|
||||||
func (g *VM) Close() error {
|
func (g *VM) Close() error {
|
||||||
log.Println("Closing VM", g.Name)
|
log.Println("Closing VM", g.Name)
|
72
lib/host/host.go
Normal file
72
lib/host/host.go
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
package host
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"log"
|
||||||
|
|
||||||
|
"git.staur.ca/stobbsm/clustvirt/lib/guest"
|
||||||
|
"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 string
|
||||||
|
|
||||||
|
conn *libvirt.Connect
|
||||||
|
close chan struct{}
|
||||||
|
closeErr chan error
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConnectHost creates a host connection wrapper that can be used regularly
|
||||||
|
func ConnectHost(host string) (*Host, error) {
|
||||||
|
h := &Host{
|
||||||
|
HostName: host,
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := h.connect(); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
h.close = make(chan struct{})
|
||||||
|
h.closeErr = make(chan error)
|
||||||
|
|
||||||
|
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(
|
||||||
|
fmt.Sprintf("qemu+ssh://%s/system", 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
|
||||||
|
}
|
502
lib/host/lib.go
502
lib/host/lib.go
@ -1,502 +0,0 @@
|
|||||||
// 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/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
|
|
||||||
// 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
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
@ -1,82 +0,0 @@
|
|||||||
package host
|
|
||||||
|
|
||||||
import (
|
|
||||||
"log"
|
|
||||||
"strings"
|
|
||||||
)
|
|
||||||
|
|
||||||
// URI is a string type, accessed via the pre-defined variables, and represent
|
|
||||||
// the URI pattern used to connect to a host.
|
|
||||||
// Example:
|
|
||||||
// Driver[+Transport]://<host or empty for local>[:PORT]/<path>[?Options&in=uri&format]
|
|
||||||
type URI struct {
|
|
||||||
Driver string
|
|
||||||
Transport string
|
|
||||||
Path string
|
|
||||||
Options []string
|
|
||||||
}
|
|
||||||
|
|
||||||
// CustomURI create and return a custom URI method, following RFC2396,
|
|
||||||
// keeping in mind that the hostname will be inserted between the transport and path
|
|
||||||
func CustomURI(driver, transport, path string, options ...string) *URI {
|
|
||||||
return &URI{
|
|
||||||
Driver: driver,
|
|
||||||
Transport: transport,
|
|
||||||
Path: path,
|
|
||||||
Options: options,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// URIs available to build connections to a libvirt host
|
|
||||||
var (
|
|
||||||
// URI for connecting to a remote QEMU system over SSH
|
|
||||||
URI_QEMU_SSH_SYSTEM = &URI{Driver: "qemu", Transport: "ssh", Path: "system"}
|
|
||||||
// URI for connecting to a remote QEMU system over TLS
|
|
||||||
// Builds the URI qemu://<host:port>/system
|
|
||||||
URI_QEMU_TLS_SYSTEM = &URI{Driver: "qemu", Transport: "", Path: "system"}
|
|
||||||
// URI for connecting to a remote QEMU session over SSH
|
|
||||||
URI_QEMU_SSH_SESSION = &URI{Driver: "qemu", Transport: "ssh", Path: "session"}
|
|
||||||
// URI for connecting to a local QEMU system over a UNIX socket
|
|
||||||
URI_QEMU_UNIX_SYSTEM = &URI{Driver: "qemu", Transport: "unix", Path: "system"}
|
|
||||||
// URI for connecting to a remote QEMU system over unsecured TCP
|
|
||||||
URI_QEMU_TCP_SYSTEM = &URI{Driver: "qemu", Transport: "tcp", Path: "system"}
|
|
||||||
|
|
||||||
// URI for connecting to a remote XEN system with SSH
|
|
||||||
URI_XEN_SSH_SYSTEM = &URI{Driver: "xen", Transport: "ssh", Path: "system"}
|
|
||||||
// URI for connecting to a remote XEN system over TLS
|
|
||||||
URI_XEN_TLS_SYSTEM = &URI{Driver: "xen", Transport: "", Path: "system"}
|
|
||||||
)
|
|
||||||
|
|
||||||
// ConnectionString takes a host name to interpolate into a URI and returns the string
|
|
||||||
func (u *URI) ConnectionString(h string) string {
|
|
||||||
var sb strings.Builder
|
|
||||||
optlen := len(u.Options)
|
|
||||||
sb.WriteString(u.Driver)
|
|
||||||
if u.Transport != "" {
|
|
||||||
sb.WriteRune('+')
|
|
||||||
sb.WriteString(u.Transport)
|
|
||||||
}
|
|
||||||
sb.WriteString("://")
|
|
||||||
if h != "" {
|
|
||||||
sb.WriteString(h)
|
|
||||||
}
|
|
||||||
sb.WriteRune('/')
|
|
||||||
sb.WriteString(u.Path)
|
|
||||||
|
|
||||||
if optlen > 0 {
|
|
||||||
sb.WriteRune('?')
|
|
||||||
for i, o := range u.Options {
|
|
||||||
sb.WriteString(o)
|
|
||||||
if optlen != i+1 {
|
|
||||||
sb.WriteRune('&')
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
log.Printf("Connection URI: %s", sb.String())
|
|
||||||
return sb.String()
|
|
||||||
}
|
|
||||||
|
|
||||||
// AddOpt adds more options to the option list
|
|
||||||
func (u *URI) AddOpt(opt string) {
|
|
||||||
u.Options = append(u.Options, opt)
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
package netif
|
|
@ -1 +0,0 @@
|
|||||||
package network
|
|
@ -1,14 +0,0 @@
|
|||||||
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",
|
|
||||||
}
|
|
@ -1 +0,0 @@
|
|||||||
package storagepool
|
|
6
main.go
6
main.go
@ -4,22 +4,20 @@ 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() {
|
||||||
log.Println("Starting clustvirt, the libvirt cluster manager")
|
log.Println("Starting clustvirt, the libvirt cluster manager")
|
||||||
|
|
||||||
venus, err := host.ConnectHost(host.URI_QEMU_SSH_SYSTEM, "venus.staur.ca")
|
venus, err := host.ConnectHost("venus.staur.ca")
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.Fatal(err)
|
log.Fatal(err)
|
||||||
}
|
}
|
||||||
defer venus.Close()
|
defer venus.Close()
|
||||||
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()
|
||||||
//ffmt.P(lm)
|
log.Println(lm)
|
||||||
}
|
}
|
||||||
|
@ -1,9 +0,0 @@
|
|||||||
package util
|
|
||||||
|
|
||||||
// helper function to set a string value to "NotSet" if it wasn't actually set
|
|
||||||
func SetNotSet(v string, s bool) string {
|
|
||||||
if s {
|
|
||||||
return v
|
|
||||||
}
|
|
||||||
return "Notset"
|
|
||||||
}
|
|
Loading…
Reference in New Issue
Block a user