getting host data before each vm on host data

- Hostdata is useful
This commit is contained in:
Matthew Stobbs 2024-03-11 23:35:32 -06:00
parent 00758af30b
commit b5ab70fc25
6 changed files with 211 additions and 42 deletions

2
go.mod
View File

@ -2,4 +2,4 @@ module git.staur.ca/stobbsm/clustvirt
go 1.22.1
require libvirt.org/go/libvirt v1.10001.0 // indirect
require libvirt.org/go/libvirt v1.10001.0

View File

@ -6,6 +6,7 @@ import (
"log"
"time"
"git.staur.ca/stobbsm/clustvirt/util"
"libvirt.org/go/libvirt"
)
@ -120,11 +121,11 @@ func GetGuest(name string, conn *libvirt.Connect) (*VM, error) {
return g, err
}
g.BlockIOParameters.Weight = blkiop.Weight
g.BlockIOParameters.DeviceWeight = setNotSet(blkiop.DeviceWeight, blkiop.DeviceWeightSet)
g.BlockIOParameters.DeviceReadIops = setNotSet(blkiop.DeviceReadIops, blkiop.DeviceReadIopsSet)
g.BlockIOParameters.DeviceWriteIops = setNotSet(blkiop.DeviceWriteIops, blkiop.DeviceWriteIopsSet)
g.BlockIOParameters.DeviceReadBps = setNotSet(blkiop.DeviceReadBps, blkiop.DeviceReadBpsSet)
g.BlockIOParameters.DeviceWriteBps = setNotSet(blkiop.DeviceWriteBps, blkiop.DeviceWriteBpsSet)
g.BlockIOParameters.DeviceWeight = util.SetNotSet(blkiop.DeviceWeight, blkiop.DeviceWeightSet)
g.BlockIOParameters.DeviceReadIops = util.SetNotSet(blkiop.DeviceReadIops, blkiop.DeviceReadIopsSet)
g.BlockIOParameters.DeviceWriteIops = util.SetNotSet(blkiop.DeviceWriteIops, blkiop.DeviceWriteIopsSet)
g.BlockIOParameters.DeviceReadBps = util.SetNotSet(blkiop.DeviceReadBps, blkiop.DeviceReadBpsSet)
g.BlockIOParameters.DeviceWriteBps = util.SetNotSet(blkiop.DeviceWriteBps, blkiop.DeviceWriteBpsSet)
// Set as much guest info as possible
info, err := g.dom.GetGuestInfo(
@ -141,13 +142,13 @@ func GetGuest(name string, conn *libvirt.Connect) (*VM, error) {
if len(info.Disks) > 0 {
g.Disks = make([]DiskInfo, len(info.Disks))
for i, n := range info.Disks {
g.Disks[i].Name = setNotSet(n.Name, n.NameSet)
g.Disks[i].Alias = setNotSet(n.Alias, n.AliasSet)
g.Disks[i].Name = util.SetNotSet(n.Name, n.NameSet)
g.Disks[i].Alias = util.SetNotSet(n.Alias, n.AliasSet)
g.Disks[i].Partition = n.Partition
g.Disks[i].GuestAlias = setNotSet(n.GuestAlias, n.GuestAliasSet)
g.Disks[i].GuestAlias = util.SetNotSet(n.GuestAlias, n.GuestAliasSet)
g.Disks[i].DiskDependency = make([]string, len(n.Dependencies))
for j, k := range n.Dependencies {
g.Disks[i].DiskDependency[j] = setNotSet(k.Name, k.NameSet)
g.Disks[i].DiskDependency[j] = util.SetNotSet(k.Name, k.NameSet)
}
}
}
@ -155,23 +156,23 @@ func GetGuest(name string, conn *libvirt.Connect) (*VM, error) {
if len(info.Users) > 0 {
g.Users = make([]UserInfo, len(info.Users))
for i, n := range info.Users {
g.Users[i].Name = setNotSet(n.Name, n.NameSet)
g.Users[i].Domain = setNotSet(n.Domain, n.DomainSet)
g.Users[i].Name = util.SetNotSet(n.Name, n.NameSet)
g.Users[i].Domain = util.SetNotSet(n.Domain, n.DomainSet)
g.Users[i].LoginTime = time.Unix(int64(n.LoginTime), 0)
}
}
g.OS.ID = setNotSet(info.OS.ID, info.OS.IDSet)
g.OS.Name = setNotSet(info.OS.Name, info.OS.NameSet)
g.OS.Machine = setNotSet(info.OS.Machine, info.OS.MachineSet)
g.OS.Variant = setNotSet(info.OS.Variant, info.OS.VariantSet)
g.OS.Version = setNotSet(info.OS.Version, info.OS.VersionSet)
g.OS.VariantID = setNotSet(info.OS.VariantID, info.OS.VariantIDSet)
g.OS.VersionID = setNotSet(info.OS.VersionID, info.OS.VersionIDSet)
g.OS.PrettyName = setNotSet(info.OS.PrettyName, info.OS.PrettyNameSet)
g.OS.KernelRelease = setNotSet(info.OS.KernelRelease, info.OS.KernelReleaseSet)
g.OS.KernelVersion = setNotSet(info.OS.KernelVersion, info.OS.KernelVersionSet)
g.TimeZone.Name = setNotSet(info.TimeZone.Name, info.TimeZone.NameSet)
g.OS.ID = util.SetNotSet(info.OS.ID, info.OS.IDSet)
g.OS.Name = util.SetNotSet(info.OS.Name, info.OS.NameSet)
g.OS.Machine = util.SetNotSet(info.OS.Machine, info.OS.MachineSet)
g.OS.Variant = util.SetNotSet(info.OS.Variant, info.OS.VariantSet)
g.OS.Version = util.SetNotSet(info.OS.Version, info.OS.VersionSet)
g.OS.VariantID = util.SetNotSet(info.OS.VariantID, info.OS.VariantIDSet)
g.OS.VersionID = util.SetNotSet(info.OS.VersionID, info.OS.VersionIDSet)
g.OS.PrettyName = util.SetNotSet(info.OS.PrettyName, info.OS.PrettyNameSet)
g.OS.KernelRelease = util.SetNotSet(info.OS.KernelRelease, info.OS.KernelReleaseSet)
g.OS.KernelVersion = util.SetNotSet(info.OS.KernelVersion, info.OS.KernelVersionSet)
g.TimeZone.Name = util.SetNotSet(info.TimeZone.Name, info.TimeZone.NameSet)
g.TimeZone.Offset = info.TimeZone.Offset
// Set the hostname from the guest if it is set, otherwise use the VM name
@ -183,13 +184,13 @@ func GetGuest(name string, conn *libvirt.Connect) (*VM, error) {
if len(info.Interfaces) > 0 {
g.NetIFace = make([]NetIFaceInfo, len(info.Interfaces))
for i, n := range info.Interfaces {
g.NetIFace[i].HWAddr = setNotSet(n.Hwaddr, n.HwaddrSet)
g.NetIFace[i].Name = setNotSet(n.Name, n.NameSet)
g.NetIFace[i].HWAddr = util.SetNotSet(n.Hwaddr, n.HwaddrSet)
g.NetIFace[i].Name = util.SetNotSet(n.Name, n.NameSet)
if len(n.Addrs) > 0 {
g.NetIFace[i].IP = make([]IPInfo, len(n.Addrs))
for j, a := range n.Addrs {
g.NetIFace[i].IP[j].Addr = setNotSet(a.Addr, a.AddrSet)
g.NetIFace[i].IP[j].Type = setNotSet(a.Type, a.TypeSet)
g.NetIFace[i].IP[j].Addr = util.SetNotSet(a.Addr, a.AddrSet)
g.NetIFace[i].IP[j].Type = util.SetNotSet(a.Type, a.TypeSet)
g.NetIFace[i].IP[j].Prefix = a.Prefix
}
}
@ -210,14 +211,6 @@ func GetGuest(name string, conn *libvirt.Connect) (*VM, error) {
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
func (g *VM) Close() error {
log.Println("Closing VM", g.Name)

View File

@ -1,10 +1,16 @@
// 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 (
"fmt"
"log"
"git.staur.ca/stobbsm/clustvirt/lib/guest"
"git.staur.ca/stobbsm/clustvirt/util"
"libvirt.org/go/libvirt"
)
@ -13,16 +19,50 @@ import (
// try the attempted method again
type Host struct {
HostName string
SystemHomeName string
FreeMemory uint64
LibVersion uint32
HostInfo NodeInfo
HostSEVInfo SEVInfo
AvailableCPUTypes []string
uri *URI
conn *libvirt.Connect
close chan struct{}
closeErr chan error
}
// 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
}
// ConnectHost creates a host connection wrapper that can be used regularly
func ConnectHost(host string) (*Host, error) {
func ConnectHost(uri *URI, host string) (*Host, error) {
h := &Host{
HostName: host,
uri: uri,
}
if err := h.connect(); err != nil {
@ -32,6 +72,52 @@ func ConnectHost(host string) (*Host, error) {
h.close = make(chan struct{})
h.closeErr = make(chan error)
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
}
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
// 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)
}
go func() {
defer close(h.closeErr)
<-h.close
@ -44,9 +130,7 @@ func ConnectHost(host string) (*Host, error) {
// 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),
)
h.conn, err = libvirt.NewConnect(h.uri.ConnectionString(h.HostName))
return err
}

82
lib/host/uri.go Normal file
View File

@ -0,0 +1,82 @@
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)
}

View File

@ -9,11 +9,12 @@ import (
func main() {
log.Println("Starting clustvirt, the libvirt cluster manager")
venus, err := host.ConnectHost("venus.staur.ca")
venus, err := host.ConnectHost(host.URI_QEMU_SSH_SYSTEM, "venus.staur.ca")
if err != nil {
log.Fatal(err)
}
defer venus.Close()
log.Println(venus)
lm, err := venus.GetGuestByName("logan-minecraft")
if err != nil {
log.Fatal(err)

9
util/util.go Normal file
View File

@ -0,0 +1,9 @@
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"
}