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>
94 lines
3.4 KiB
Go
94 lines
3.4 KiB
Go
package kvm
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"reflect"
|
|
"testing"
|
|
|
|
libvirt "github.com/digitalocean/go-libvirt"
|
|
|
|
"gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider"
|
|
)
|
|
|
|
func lvErr(code libvirt.ErrorNumber) error {
|
|
return libvirt.Error{Code: uint32(code), Message: code.String()}
|
|
}
|
|
|
|
func TestClassify(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
err error
|
|
want error
|
|
}{
|
|
{"no domain", lvErr(libvirt.ErrNoDomain), provider.ErrNotFound},
|
|
{"no storage vol", lvErr(libvirt.ErrNoStorageVol), provider.ErrNotFound},
|
|
{"no storage pool", lvErr(libvirt.ErrNoStoragePool), provider.ErrPermanent},
|
|
{"xml error", lvErr(libvirt.ErrXMLError), provider.ErrPermanent},
|
|
{"xml detail", lvErr(libvirt.ErrXMLDetail), provider.ErrPermanent},
|
|
{"config unsupported", lvErr(libvirt.ErrConfigUnsupported), provider.ErrPermanent},
|
|
{"no support", lvErr(libvirt.ErrNoSupport), provider.ErrPermanent},
|
|
{"invalid arg", lvErr(libvirt.ErrInvalidArg), provider.ErrPermanent},
|
|
{"auth failed", lvErr(libvirt.ErrAuthFailed), provider.ErrPermanent},
|
|
{"access denied", lvErr(libvirt.ErrAccessDenied), provider.ErrPermanent},
|
|
{"operation invalid", lvErr(libvirt.ErrOperationInvalid), provider.ErrTransient},
|
|
{"operation failed", lvErr(libvirt.ErrOperationFailed), provider.ErrTransient},
|
|
{"internal error", lvErr(libvirt.ErrInternalError), provider.ErrTransient},
|
|
{"storage vol exists", lvErr(libvirt.ErrStorageVolExist), provider.ErrTransient},
|
|
{"wrapped libvirt error", fmt.Errorf("defining: %w", lvErr(libvirt.ErrNoDomain)), provider.ErrNotFound},
|
|
{"pre-tagged permanent", fmt.Errorf("%w: image returned HTTP 404", provider.ErrPermanent), provider.ErrPermanent},
|
|
{"pre-tagged transient wrapping libvirt not-found", fmt.Errorf("%w: %w", provider.ErrTransient, lvErr(libvirt.ErrNoDomain)), provider.ErrTransient},
|
|
{"transport eof", io.EOF, provider.ErrTransient},
|
|
{"context cancelled", context.Canceled, provider.ErrTransient},
|
|
{"plain error", errors.New("ssh: handshake failed"), provider.ErrTransient},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
if got := classify(tc.err); !errors.Is(got, tc.want) {
|
|
t.Errorf("classify(%v) = %v, want %v", tc.err, got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestHasCode(t *testing.T) {
|
|
t.Parallel()
|
|
err := fmt.Errorf("lookup: %w", lvErr(libvirt.ErrNoDomain))
|
|
if !hasCode(err, libvirt.ErrNoDomain) {
|
|
t.Error("hasCode(ErrNoDomain) = false through a wrap")
|
|
}
|
|
if hasCode(err, libvirt.ErrNoStorageVol) {
|
|
t.Error("hasCode(ErrNoStorageVol) = true for an ErrNoDomain error")
|
|
}
|
|
if hasCode(io.EOF, libvirt.ErrNoDomain) || isLibvirtError(io.EOF) {
|
|
t.Error("io.EOF classified as a libvirt error")
|
|
}
|
|
}
|
|
|
|
func TestLibvirtErrorFields(t *testing.T) {
|
|
t.Parallel()
|
|
tests := []struct {
|
|
name string
|
|
err error
|
|
want []any
|
|
}{
|
|
{"libvirt error", lvErr(libvirt.ErrNoDomain), []any{"libvirtCode", uint32(42), "libvirtCodeName", "ErrNoDomain", "libvirtMessage", "ErrNoDomain"}},
|
|
{"wrapped libvirt error", fmt.Errorf("looking up: %w", lvErr(libvirt.ErrNoDomain)), []any{"libvirtCode", uint32(42), "libvirtCodeName", "ErrNoDomain", "libvirtMessage", "ErrNoDomain"}},
|
|
{"transport error", io.EOF, nil},
|
|
{"cancelled", context.Canceled, nil},
|
|
}
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
t.Parallel()
|
|
got := libvirtErrorFields(tc.err)
|
|
if !reflect.DeepEqual(got, tc.want) {
|
|
t.Errorf("libvirtErrorFields() = %#v, want %#v", got, tc.want)
|
|
}
|
|
})
|
|
}
|
|
}
|