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
+15 -8
View File
@@ -33,7 +33,9 @@ func checkContext() (context.Context, *logCapture) {
}
// checkDialer hands out a distinct fake per host name, fails the hosts in
// errs, counts every dial, and, with block set, waits for ctx to end.
// errs, counts every dial, and, with block set, waits for ctx to end and
// then fails the way dialSSHSocket does when the TCP connect sees the ctx
// end: a dialError at the tcp stage wrapping the ctx's error.
type checkDialer struct {
backends map[string]*fakeBackend
errs map[string]error
@@ -46,7 +48,7 @@ func (d *checkDialer) dial(ctx context.Context, host provider.KVMHost) (backend,
d.dials.Add(1)
if d.block {
<-ctx.Done()
return nil, fmt.Errorf("%w: dialing %s: %w", provider.ErrTransient, host.Name, ctx.Err())
return nil, &dialError{stage: stageTCP, host: host.Name, err: fmt.Errorf("dial tcp %s: %w", host.Address, ctx.Err())}
}
if err := d.errs[host.Name]; err != nil {
return nil, err
@@ -140,13 +142,18 @@ func TestCheck_hostsCheckedConcurrently(t *testing.T) {
}
}
// TestCheck_expiredContextReturnsPromptly drives the production shape of a
// round that ran out of time during the dial: the dialer fails at the tcp
// stage wrapping context.DeadlineExceeded, which checkReason alone would
// call Unreachable; the verdict must still be Timeout because the round's
// ctx ended.
func TestCheck_expiredContextReturnsPromptly(t *testing.T) {
t.Parallel()
cfg := twoHosts()
p := newProvider(testProvider, cfg, &checkDialer{block: true})
ctx, _ := checkContext()
ctx, cancel := context.WithCancel(ctx)
cancel()
ctx, cancel := context.WithTimeout(ctx, 0)
defer cancel()
start := time.Now()
got := p.Check(ctx)
if elapsed := time.Since(start); elapsed > time.Second {
@@ -159,8 +166,8 @@ func TestCheck_expiredContextReturnsPromptly(t *testing.T) {
if ts.Ready || ts.Reason != provider.ReasonTimeout {
t.Errorf("target %s = ready %v reason %s, want not ready, Timeout", ts.Name, ts.Ready, ts.Reason)
}
if ts.Message == "" {
t.Errorf("target %s has no message", ts.Name)
if !strings.Contains(ts.Message, context.DeadlineExceeded.Error()) || !strings.Contains(ts.Message, string(stageTCP)) {
t.Errorf("target %s message = %q, want the tcp stage and the deadline named", ts.Name, ts.Message)
}
}
}
@@ -217,7 +224,7 @@ func TestCheck_reasonsTable(t *testing.T) {
wantReason: provider.ReasonUnreachable,
},
{
name: "tcp timed out",
name: "tcp timed out on the dial budget but ctx is fine",
dialErr: func(*testing.T) error {
return &dialError{stage: stageTCP, host: testHost, err: fmt.Errorf("dial tcp 192.0.2.1:22: %w", context.DeadlineExceeded)}
},
@@ -248,7 +255,7 @@ func TestCheck_reasonsTable(t *testing.T) {
wantReason: provider.ReasonUnreachable,
},
{
name: "ssh handshake timed out",
name: "ssh handshake timed out on the dial budget but ctx is fine",
dialErr: func(*testing.T) error { return sshHandshake(context.DeadlineExceeded) },
wantReason: provider.ReasonUnreachable,
},