kvm2: apply the milestone review

Shorter start wait with immediate verdicts for dead or paused domains,
ListByTag without state or address RPCs, Timeout for an expired check
round, no adoption of unmarked domains, reference-counted keyed locks,
dead options removed, HEAD fallback on any non-2xx.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
2026-09-16 21:19:50 +00:00
co-authored by Claude Fable 5.1
parent fcbf8ddc91
commit 2510842396
21 changed files with 586 additions and 254 deletions
+36 -12
View File
@@ -152,6 +152,17 @@ func serveStatus(code int) http.HandlerFunc {
return func(w http.ResponseWriter, _ *http.Request) { http.Error(w, http.StatusText(code), code) }
}
// headFails answers HEAD with code and hands every other method to next.
func headFails(code int, next http.HandlerFunc) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodHead {
http.Error(w, http.StatusText(code), code)
return
}
next(w, r)
}
}
func TestResolveImage_table(t *testing.T) {
t.Parallel()
cfg := withDefaults(provider.KVMConfig{Images: map[string]provider.KVMImage{
@@ -883,17 +894,30 @@ func TestImageSizeIfUncached_headThenGetFallback(t *testing.T) {
wantMethods: []string{http.MethodHead},
},
{
name: "HEAD not allowed falls back to GET with the body closed",
handler: func(w http.ResponseWriter, r *http.Request) {
if r.Method == http.MethodHead {
http.Error(w, "nope", http.StatusMethodNotAllowed)
return
}
serveBytes(body)(w, r)
},
name: "HEAD not allowed falls back to GET with the body closed",
handler: headFails(http.StatusMethodNotAllowed, serveBytes(body)),
wantSize: int64(len(body)),
wantMethods: []string{http.MethodHead, http.MethodGet},
},
{
name: "HEAD forbidden falls back to GET",
handler: headFails(http.StatusForbidden, serveBytes(body)),
wantSize: int64(len(body)),
wantMethods: []string{http.MethodHead, http.MethodGet},
},
{
name: "HEAD not implemented falls back to GET",
handler: headFails(http.StatusNotImplemented, serveBytes(body)),
wantSize: int64(len(body)),
wantMethods: []string{http.MethodHead, http.MethodGet},
},
{
name: "HEAD refused and GET 404 is permanent from the GET",
handler: headFails(http.StatusMethodNotAllowed, serveStatus(http.StatusNotFound)),
wantMethods: []string{http.MethodHead, http.MethodGet},
wantClass: provider.ErrPermanent,
wantErr: "404",
},
{
name: "marker present means nothing to download",
handler: serveBytes(body),
@@ -902,16 +926,16 @@ func TestImageSizeIfUncached_headThenGetFallback(t *testing.T) {
wantMethods: nil,
},
{
name: "404 is permanent",
name: "404 on both is permanent",
handler: serveStatus(http.StatusNotFound),
wantMethods: []string{http.MethodHead},
wantMethods: []string{http.MethodHead, http.MethodGet},
wantClass: provider.ErrPermanent,
wantErr: "404",
},
{
name: "503 is transient",
name: "503 on both is transient",
handler: serveStatus(http.StatusServiceUnavailable),
wantMethods: []string{http.MethodHead},
wantMethods: []string{http.MethodHead, http.MethodGet},
wantClass: provider.ErrTransient,
},
{