Trace every libvirt call in the KVM provider at V(1), visible in the integration test

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>
This commit is contained in:
2026-09-10 19:13:51 +00:00
co-authored by Claude Fable 5.1
parent aa8b36e1d0
commit f50d30e58e
13 changed files with 904 additions and 39 deletions
+40 -7
View File
@@ -25,6 +25,14 @@ package kvm
// KVM_TEST_SOCKET libvirt socket path on the host
// KVM_TEST_TIMEOUT overall budget incl. the image download (default 20m)
// KVM_TEST_KEEP set to anything to leave the VM behind for inspection
// KVM_TEST_LOG_LEVEL provider log verbosity shown in -v output (default 1:
// one line per libvirt call; 2 adds XML, metadata and
// addresses; 0 leaves only the t.Log lines)
//
// Provider logs go through a testr logger, so they interleave with t.Log
// output. A run that hits its timeout may log one more line from the RPC
// the pool abandoned, after the test has ended; that is a symptom of the
// timeout, not a bug in the tracer.
import (
"context"
@@ -34,11 +42,15 @@ import (
"fmt"
"net"
"os"
"strconv"
"testing"
"time"
libvirt "github.com/digitalocean/go-libvirt"
"github.com/go-logr/logr"
"github.com/go-logr/logr/testr"
"k8s.io/apimachinery/pkg/types"
logf "sigs.k8s.io/controller-runtime/pkg/log"
"gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider"
)
@@ -50,9 +62,10 @@ const (
)
type integrationEnv struct {
cfg provider.ProviderConfig
timeout time.Duration
keep bool
cfg provider.ProviderConfig
timeout time.Duration
keep bool
logLevel int
}
func loadIntegrationEnv(t *testing.T) integrationEnv {
@@ -97,7 +110,26 @@ func loadIntegrationEnv(t *testing.T) integrationEnv {
}
timeout = d
}
return integrationEnv{cfg: pc, timeout: timeout, keep: os.Getenv("KVM_TEST_KEEP") != ""}
logLevel := 1
if v := os.Getenv("KVM_TEST_LOG_LEVEL"); v != "" {
n, err := strconv.Atoi(v)
if err != nil {
t.Fatalf("KVM_TEST_LOG_LEVEL: %v", err)
}
logLevel = n
}
return integrationEnv{cfg: pc, timeout: timeout, keep: os.Getenv("KVM_TEST_KEEP") != "", logLevel: logLevel}
}
// integrationLogger routes provider logs into the test output at the
// requested verbosity, and installs it as controller-runtime's root logger
// so nothing falls back to the unfulfilled delegating sink (which would
// print its "SetLogger was never called" warning and swallow the lines).
func integrationLogger(t *testing.T, verbosity int) logr.Logger {
t.Helper()
log := testr.NewWithOptions(t, testr.Options{LogTimestamp: true, Verbosity: verbosity})
logf.SetLogger(log)
return log
}
func randomUID(t *testing.T) string {
@@ -114,7 +146,8 @@ func randomUID(t *testing.T) string {
// stage depends on the state the previous one left on the hypervisor.
func TestKVM_lifecycle(t *testing.T) {
env := loadIntegrationEnv(t)
ctx, cancel := context.WithTimeout(context.Background(), env.timeout)
log := integrationLogger(t, env.logLevel)
ctx, cancel := context.WithTimeout(logr.NewContext(context.Background(), log), env.timeout)
defer cancel()
prov, err := New(ctx, env.cfg)
@@ -143,7 +176,7 @@ func TestKVM_lifecycle(t *testing.T) {
t.Logf("KVM_TEST_KEEP set: leaving domain %s and its volumes on %s", req.Name, host.Address)
return
}
cctx, ccancel := context.WithTimeout(context.Background(), 2*time.Minute)
cctx, ccancel := context.WithTimeout(logr.NewContext(context.Background(), log), 2*time.Minute)
defer ccancel()
if err := p.Delete(cctx, formatProviderID(host.Name, req.Name)); err != nil {
t.Errorf("cleanup Delete: %v", err)
@@ -217,7 +250,7 @@ func TestKVM_lifecycle(t *testing.T) {
if err != nil {
t.Fatal(err)
}
err = p.hosts.call(ctx, host, func(api libvirtAPI) error {
err = p.hosts.call(ctx, p.logger(ctx).WithValues("host", host.Name), host, func(api libvirtAPI) error {
pool, err := api.StoragePoolLookupByName(host.Pool)
if err != nil {
return err