A tracingAPI decorator wraps the 17-method libvirt seam and logs one "libvirt call" line per RPC after it returns: method, the identifiers it was given, curated results, duration, and on failure the error with its libvirt code, name and message. At V(2) the same line carries the domain and volume XML, metadata and addresses; upload bodies are never logged. The pool applies it in call() when V(1) is enabled and logs connect, dial-failure and drop events, taking the caller's logger explicitly like every other helper in the package. The integration test installs a testr logger at KVM_TEST_LOG_LEVEL (default 1) so the trace interleaves with t.Log output, which also ends the "log.SetLogger(...) was never called" warning. Docs gained a Logging section and the test env var; the KVM provider execution log records run 4 (guest hang at GRUB) that prompted this. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
202 lines
6.9 KiB
Go
202 lines
6.9 KiB
Go
package kvm
|
|
|
|
import (
|
|
"fmt"
|
|
"io"
|
|
"time"
|
|
|
|
libvirt "github.com/digitalocean/go-libvirt"
|
|
"github.com/go-logr/logr"
|
|
)
|
|
|
|
// tracingAPI wraps a libvirtAPI and logs one V(1) line per call after it
|
|
// returns: method, the identifiers it was given, curated results, duration,
|
|
// and the libvirt error fields on failure. At V(2) the same line also
|
|
// carries XML bodies, metadata and address lists. Upload and download
|
|
// bodies are never logged: the seed ISO carries cloud-init.
|
|
type tracingAPI struct {
|
|
inner libvirtAPI
|
|
log logr.Logger
|
|
}
|
|
|
|
type tracedCall struct {
|
|
t *tracingAPI
|
|
method string
|
|
start time.Time
|
|
kv []any
|
|
}
|
|
|
|
func (t *tracingAPI) begin(method string, kv ...any) *tracedCall {
|
|
return &tracedCall{t: t, method: method, start: time.Now(), kv: kv}
|
|
}
|
|
|
|
// detail adds key/values that are only worth the bytes at V(2).
|
|
func (c *tracedCall) detail(kv ...any) *tracedCall {
|
|
if c.t.log.V(2).Enabled() {
|
|
c.kv = append(c.kv, kv...)
|
|
}
|
|
return c
|
|
}
|
|
|
|
func (c *tracedCall) done(err error, kv ...any) {
|
|
fields := make([]any, 0, 2+len(c.kv)+len(kv)+10)
|
|
fields = append(fields, "method", c.method)
|
|
fields = append(fields, c.kv...)
|
|
fields = append(fields, kv...)
|
|
fields = append(fields, "duration", time.Since(c.start).Round(time.Microsecond))
|
|
if err != nil {
|
|
fields = append(fields, "error", err.Error())
|
|
fields = append(fields, libvirtErrorFields(err)...)
|
|
}
|
|
c.t.log.V(1).Info("libvirt call", fields...)
|
|
}
|
|
|
|
// flagKV renders a flags value only when something is set; go-libvirt's
|
|
// generated stringers do not know bitmask combinations.
|
|
func flagKV(v int64) []any {
|
|
if v == 0 {
|
|
return nil
|
|
}
|
|
return []any{"flags", v}
|
|
}
|
|
|
|
func optString(s libvirt.OptString) string {
|
|
if len(s) == 0 {
|
|
return ""
|
|
}
|
|
return s[0]
|
|
}
|
|
|
|
func (t *tracingAPI) DomainLookupByName(name string) (libvirt.Domain, error) {
|
|
c := t.begin("DomainLookupByName", "domain", name)
|
|
d, err := t.inner.DomainLookupByName(name)
|
|
c.done(err)
|
|
return d, err
|
|
}
|
|
|
|
func (t *tracingAPI) DomainDefineXML(xml string) (libvirt.Domain, error) {
|
|
c := t.begin("DomainDefineXML", "xmlBytes", len(xml)).detail("xml", xml)
|
|
d, err := t.inner.DomainDefineXML(xml)
|
|
c.done(err, "domain", d.Name)
|
|
return d, err
|
|
}
|
|
|
|
func (t *tracingAPI) DomainCreate(d libvirt.Domain) error {
|
|
c := t.begin("DomainCreate", "domain", d.Name)
|
|
err := t.inner.DomainCreate(d)
|
|
c.done(err)
|
|
return err
|
|
}
|
|
|
|
func (t *tracingAPI) DomainDestroy(d libvirt.Domain) error {
|
|
c := t.begin("DomainDestroy", "domain", d.Name)
|
|
err := t.inner.DomainDestroy(d)
|
|
c.done(err)
|
|
return err
|
|
}
|
|
|
|
func (t *tracingAPI) DomainUndefineFlags(d libvirt.Domain, flags libvirt.DomainUndefineFlagsValues) error {
|
|
c := t.begin("DomainUndefineFlags", append([]any{"domain", d.Name}, flagKV(int64(flags))...)...)
|
|
err := t.inner.DomainUndefineFlags(d, flags)
|
|
c.done(err)
|
|
return err
|
|
}
|
|
|
|
func (t *tracingAPI) DomainGetState(d libvirt.Domain, flags uint32) (int32, int32, error) {
|
|
c := t.begin("DomainGetState", "domain", d.Name)
|
|
state, reason, err := t.inner.DomainGetState(d, flags)
|
|
c.done(err, "state", libvirt.DomainState(state), "reason", reason)
|
|
return state, reason, err
|
|
}
|
|
|
|
func (t *tracingAPI) DomainGetMetadata(d libvirt.Domain, typ int32, uri libvirt.OptString, flags libvirt.DomainModificationImpact) (string, error) {
|
|
c := t.begin("DomainGetMetadata", "domain", d.Name, "type", typ, "uri", optString(uri))
|
|
s, err := t.inner.DomainGetMetadata(d, typ, uri, flags)
|
|
c.detail("metadata", s).done(err, "metadataBytes", len(s))
|
|
return s, err
|
|
}
|
|
|
|
func (t *tracingAPI) DomainInterfaceAddresses(d libvirt.Domain, source uint32, flags uint32) ([]libvirt.DomainInterface, error) {
|
|
c := t.begin("DomainInterfaceAddresses", "domain", d.Name, "source", libvirt.DomainInterfaceAddressesSource(source))
|
|
ifaces, err := t.inner.DomainInterfaceAddresses(d, source, flags)
|
|
c.detail("addresses", formatAddresses(ifaces)).done(err, "interfaces", len(ifaces))
|
|
return ifaces, err
|
|
}
|
|
|
|
func formatAddresses(ifaces []libvirt.DomainInterface) []string {
|
|
var out []string
|
|
for _, iface := range ifaces {
|
|
for _, a := range iface.Addrs {
|
|
out = append(out, fmt.Sprintf("%s=%s/%d", iface.Name, a.Addr, a.Prefix))
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
func (t *tracingAPI) ConnectListAllDomains(needResults int32, flags libvirt.ConnectListAllDomainsFlags) ([]libvirt.Domain, uint32, error) {
|
|
c := t.begin("ConnectListAllDomains", flagKV(int64(flags))...)
|
|
doms, n, err := t.inner.ConnectListAllDomains(needResults, flags)
|
|
names := make([]string, 0, len(doms))
|
|
for _, d := range doms {
|
|
names = append(names, d.Name)
|
|
}
|
|
c.detail("names", names).done(err, "count", len(doms))
|
|
return doms, n, err
|
|
}
|
|
|
|
func (t *tracingAPI) StoragePoolLookupByName(name string) (libvirt.StoragePool, error) {
|
|
c := t.begin("StoragePoolLookupByName", "pool", name)
|
|
pool, err := t.inner.StoragePoolLookupByName(name)
|
|
c.done(err)
|
|
return pool, err
|
|
}
|
|
|
|
func (t *tracingAPI) StoragePoolRefresh(pool libvirt.StoragePool, flags uint32) error {
|
|
c := t.begin("StoragePoolRefresh", "pool", pool.Name)
|
|
err := t.inner.StoragePoolRefresh(pool, flags)
|
|
c.done(err)
|
|
return err
|
|
}
|
|
|
|
func (t *tracingAPI) StorageVolLookupByName(pool libvirt.StoragePool, name string) (libvirt.StorageVol, error) {
|
|
c := t.begin("StorageVolLookupByName", "pool", pool.Name, "volume", name)
|
|
vol, err := t.inner.StorageVolLookupByName(pool, name)
|
|
c.done(err)
|
|
return vol, err
|
|
}
|
|
|
|
func (t *tracingAPI) StorageVolGetPath(vol libvirt.StorageVol) (string, error) {
|
|
c := t.begin("StorageVolGetPath", "pool", vol.Pool, "volume", vol.Name)
|
|
path, err := t.inner.StorageVolGetPath(vol)
|
|
c.done(err, "path", path)
|
|
return path, err
|
|
}
|
|
|
|
func (t *tracingAPI) StorageVolCreateXML(pool libvirt.StoragePool, xml string, flags libvirt.StorageVolCreateFlags) (libvirt.StorageVol, error) {
|
|
c := t.begin("StorageVolCreateXML", append([]any{"pool", pool.Name, "xmlBytes", len(xml)}, flagKV(int64(flags))...)...).detail("xml", xml)
|
|
vol, err := t.inner.StorageVolCreateXML(pool, xml, flags)
|
|
c.done(err, "volume", vol.Name)
|
|
return vol, err
|
|
}
|
|
|
|
func (t *tracingAPI) StorageVolUpload(vol libvirt.StorageVol, r io.Reader, offset, length uint64, flags libvirt.StorageVolUploadFlags) error {
|
|
c := t.begin("StorageVolUpload", append([]any{"pool", vol.Pool, "volume", vol.Name, "offset", offset, "length", length}, flagKV(int64(flags))...)...)
|
|
err := t.inner.StorageVolUpload(vol, r, offset, length, flags)
|
|
c.done(err)
|
|
return err
|
|
}
|
|
|
|
func (t *tracingAPI) StorageVolDownload(vol libvirt.StorageVol, w io.Writer, offset, length uint64, flags libvirt.StorageVolDownloadFlags) error {
|
|
c := t.begin("StorageVolDownload", append([]any{"pool", vol.Pool, "volume", vol.Name, "offset", offset, "length", length}, flagKV(int64(flags))...)...)
|
|
err := t.inner.StorageVolDownload(vol, w, offset, length, flags)
|
|
c.done(err)
|
|
return err
|
|
}
|
|
|
|
func (t *tracingAPI) StorageVolDelete(vol libvirt.StorageVol, flags libvirt.StorageVolDeleteFlags) error {
|
|
c := t.begin("StorageVolDelete", append([]any{"pool", vol.Pool, "volume", vol.Name}, flagKV(int64(flags))...)...)
|
|
err := t.inner.StorageVolDelete(vol, flags)
|
|
c.done(err)
|
|
return err
|
|
}
|