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
+21 -6
View File
@@ -15,9 +15,11 @@ import (
// The wait after DomainCreate (spec entry 18): Create returns only once the
// domain is running, so Get never sees a domain kvm2 defined but did not
// start (spec entry 9).
// start (spec entry 9). DomainCreate is synchronous, so the first read
// normally succeeds; the cap only covers state settling and must not pin a
// reconcile worker for long.
const (
defaultRunningWait = 2 * time.Minute
defaultRunningWait = 15 * time.Second
defaultRunningPoll = 500 * time.Millisecond
)
@@ -75,6 +77,9 @@ func (p *Provider) create(ctx context.Context, log logr.Logger, be backend, host
if err != nil {
return err
}
if !meta.managed() {
return permanentf("domain %s exists but carries no kvm2 mark; it was not created by this provider", req.Name)
}
if meta.UID != "" && meta.UID != req.UID {
return permanentf("domain %s belongs to proxy uid %s, not %s", req.Name, meta.UID, req.UID)
}
@@ -211,14 +216,19 @@ func (p *Provider) writeSeed(ctx context.Context, log logr.Logger, be backend, p
// ensureRunning starts the domain unless it is active already, then waits
// until libvirt reports it running. ErrOperationInvalid from DomainCreate
// means another caller started it between the state read and the call.
// means another caller started it between the state read and the call. A
// paused or pmsuspended domain is an admin's doing and DomainCreate would
// refuse it; the verdict is Permanent so the reconciler does not retry it.
func (p *Provider) ensureRunning(ctx context.Context, log logr.Logger, be backend, dom libvirt.Domain) error {
state, _, err := be.DomainGetState(ctx, dom, 0)
if err != nil {
return fmt.Errorf("reading state of domain %s: %w", dom.Name, err)
}
if isActive(libvirt.DomainState(state)) {
switch s := libvirt.DomainState(state); {
case isActive(s):
return nil
case s == libvirt.DomainPaused || s == libvirt.DomainPmsuspended:
return permanentf("domain %s is %s and cannot be started by the operator; resume or destroy it on the host", dom.Name, s)
}
switch err := be.DomainCreate(ctx, dom); {
case err == nil:
@@ -233,7 +243,9 @@ func (p *Provider) ensureRunning(ctx context.Context, log logr.Logger, be backen
// waitRunning polls DomainGetState until the domain is active, the cap
// passes (Transient: the next Create attempt finds the domain defined and
// tries again) or ctx ends.
// tries again) or ctx ends. Only nostate is worth another poll; a domain
// that is shut off, crashed or paused right after its start is not going
// to come up on its own, so that is Transient at once.
func (p *Provider) waitRunning(ctx context.Context, be backend, dom libvirt.Domain) error {
deadline := time.NewTimer(p.runningWait)
defer deadline.Stop()
@@ -244,8 +256,11 @@ func (p *Provider) waitRunning(ctx context.Context, be backend, dom libvirt.Doma
if err != nil {
return fmt.Errorf("reading state of domain %s: %w", dom.Name, err)
}
if isActive(libvirt.DomainState(state)) {
switch s := libvirt.DomainState(state); {
case isActive(s):
return nil
case s != libvirt.DomainNostate:
return transientf("domain %s went %s right after start", dom.Name, s)
}
select {
case <-ticker.C: