resolveImage maps placement.image to a configured key or URL; imageCache streams the download through StorageVolUpload with a sha256 tee, writes a marker volume when the upload is verified, and re-fetches when the marker is missing or the configured checksum changed. Errors carry taxonomy sentinels that classify now honours. Adds the in-memory fake libvirt API used from here on. Step 3b of docs/plans/2026-09-04-1404-kvm-provider.md. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
70 lines
2.7 KiB
Go
70 lines
2.7 KiB
Go
package kvm
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"io"
|
|
"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")
|
|
}
|
|
}
|