package kvm import ( "errors" libvirt "github.com/digitalocean/go-libvirt" "github.com/go-logr/logr" "gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider" ) // classify maps an error onto the provider taxonomy. Errors the package // already tagged with a sentinel (image download failures, placement // errors) keep their class. For libvirt errors: missing domain or volume → // NotFound; anything the host will keep rejecting (bad XML, unsupported // config, denied access, missing pool) → Permanent; every other libvirt // code, plus SSH/transport failures and cancellation, → Transient, because // retrying is safer than latching Failed on an error nobody taught this // function to recognise. libvirt exposes no quota signal, so nothing maps // to QuotaExceeded. func classify(err error) error { for _, class := range []error{provider.ErrNotFound, provider.ErrQuotaExceeded, provider.ErrPermanent, provider.ErrTransient} { if errors.Is(err, class) { return class } } var lerr libvirt.Error if !errors.As(err, &lerr) { return provider.ErrTransient } switch libvirt.ErrorNumber(lerr.Code) { case libvirt.ErrNoDomain, libvirt.ErrNoStorageVol: return provider.ErrNotFound case libvirt.ErrNoStoragePool, libvirt.ErrXMLError, libvirt.ErrXMLDetail, libvirt.ErrXMLInvalidSchema, libvirt.ErrConfigUnsupported, libvirt.ErrNoSupport, libvirt.ErrOperationUnsupported, libvirt.ErrArgumentUnsupported, libvirt.ErrInvalidArg, libvirt.ErrAuthFailed, libvirt.ErrAccessDenied, libvirt.ErrOperationDenied: return provider.ErrPermanent default: return provider.ErrTransient } } // isLibvirtError reports whether err came back from the libvirt daemon (as // opposed to the SSH or RPC transport), which is what decides whether the // cached connection is still trustworthy. func isLibvirtError(err error) bool { var lerr libvirt.Error return errors.As(err, &lerr) } // isClassified reports whether err already carries one of the provider's // sentinels, i.e. the package reached a verdict itself rather than hitting // a transport failure. func isClassified(err error) bool { for _, class := range []error{provider.ErrNotFound, provider.ErrQuotaExceeded, provider.ErrPermanent, provider.ErrTransient} { if errors.Is(err, class) { return true } } return false } func hasCode(err error, code libvirt.ErrorNumber) bool { var lerr libvirt.Error return errors.As(err, &lerr) && libvirt.ErrorNumber(lerr.Code) == code } // logAPIError records the raw libvirt code and message at V(1); classify // collapses them onto the coarser taxonomy, so this is the only place the // original detail survives. func logAPIError(log logr.Logger, op string, err error) { if !log.V(1).Enabled() { return } kv := append([]any{"op", op, "error", err.Error()}, libvirtErrorFields(err)...) log.V(1).Info("libvirt call failed", kv...) } // libvirtErrorFields renders a daemon-reported error as log key/values: // its constant name (ErrNoDomain, ...) and numeric code. The daemon's // message is already the error text. Nil for transport and other // non-libvirt errors. func libvirtErrorFields(err error) []any { var lerr libvirt.Error if !errors.As(err, &lerr) { return nil } return []any{"libvirtError", libvirt.ErrorNumber(lerr.Code).String(), "libvirtCode", lerr.Code} }