From 4ecf05bf82a6ee78641398a36451f0fee2e6b626 Mon Sep 17 00:00:00 2001 From: Matthew Stobbs Date: Sun, 10 Mar 2024 20:32:11 -0600 Subject: [PATCH] Adding basic wrappers - Wrapper for Host connection called Host - Allows for easier usage of the API - Wrapper for Domains called GuestVM, which pulls VM information and allows other functionality in a better way --- README.md | 18 ++++++++++++++ domains.go | 53 +++++++++++++++++++++++++++++++++++++++++ host.go | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++ main.go | 16 ++++++------- 4 files changed, 147 insertions(+), 9 deletions(-) create mode 100644 domains.go create mode 100644 host.go diff --git a/README.md b/README.md index d437d29..768167e 100644 --- a/README.md +++ b/README.md @@ -54,3 +54,21 @@ Overall goals: - Connection management (to other libvirt hosts) - Create a simple WebUI with HTMX to monitor that stuff - Add the ability to manage that stuff once we can monitor it, through the WebUI. + +## Special XMLNS definitions + +### Domain + +- `xmlns:clustvirt="https://git.staur.ca/stobbsm/clustvirt"` + +```xml + + + true + true + host01 + host02 + rocky-9-base + + +``` diff --git a/domains.go b/domains.go new file mode 100644 index 0000000..d6d8671 --- /dev/null +++ b/domains.go @@ -0,0 +1,53 @@ +package main + +import ( + "fmt" + "log" + + "libvirt.org/go/libvirt" +) +// GuestVM holds a handle is used to communicate to Domains +type GuestVM struct { + Name string + ID uint + BackupXML string + + close chan struct{} + closeErr chan error + dom *libvirt.Domain +} + +// GetGuest loads guest information by name and returns it +func GetGuest(name string, conn *libvirt.Connect) (*GuestVM, error) { + g := &GuestVM{ + Name: name, + close: make(chan struct{}), + closeErr: make(chan error), + } + + var err error + + if g.dom, err = conn.LookupDomainByName(name); err != nil { + return nil, err + } + if g.ID, err = g.dom.GetID(); err != nil { + return nil, err + } + if g.BackupXML, err = g.dom.BackupGetXMLDesc(0); err != nil { + return nil, err + } + + go func() { + defer close(g.closeErr) + <-g.close + g.closeErr <- g.dom.Free() + }() + + return g, nil +} + +// Close closes an open connection +func (g *GuestVM) Close() error { + close(g.close) + return <-g.closeErr +} diff --git a/host.go b/host.go new file mode 100644 index 0000000..03b21e5 --- /dev/null +++ b/host.go @@ -0,0 +1,69 @@ +package main + +import ( + "fmt" + + "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) (*GuestVM, error) { + if g, err := GetGuest(name, h.conn); err == nil { + return g, nil + } else { + lverr, ok := err.(libvirt.Error) + if ok && lverr.Code == libvirt.ERR_INVALID_CONN { + // try again after creating a new connection + return GetGuest(name, h.conn) + } + return nil, err + } +} + +// Close triggers closing the host connection +func (h *Host) Close() error { + close(h.close) + return <-h.closeErr +} diff --git a/main.go b/main.go index 634696b..8f7de0f 100644 --- a/main.go +++ b/main.go @@ -2,23 +2,21 @@ package main import ( "log" - - "libvirt.org/go/libvirt" ) func main() { log.Println("Starting clustvirt, the libvirt cluster manager") // Try connecting to libvirt - conn, err := libvirt.NewConnect("qemu+ssh://earth.staur.ca/system") + doms, err := GetDomsInCluster("earth.staur.ca", "mars.staur.ca", "venus.staur.ca") if err != nil { log.Fatal(err) } - defer conn.Close() - - doms, err := conn.ListAllDomains(libvirt.CONNECT_LIST_DOMAINS_ACTIVE | libvirt.CONNECT_LIST_DOMAINS_INACTIVE) - if err != nil { - log.Fatal(err) + log.Printf("Domains in cluster: %d", len(doms)) + // Example of xmlns uri found in libvirt xml definition: + // xmlns:libosinfo="http://libosinfo.org/xmlns/libvirt/domain/1.0" + for _, d := range doms { + log.Println(d.GetMetadata(0, "", 0)) + d.Free() } - log.Printf("Domains on earth: %d", len(doms)) }