provider: extract nocloud seed builder and keyedlock from kvm2
kvm2's NoCloud seed builder moves to internal/provider/nocloud (exported, with an ErrEmptyName sentinel instead of permanentf) and its keyed locks to internal/provider/keyedlock, tests moved under their existing names. kvm2 behaviour is unchanged. proxmox gains the per-name lock and the pure seed helpers (seedISOName, seedVolID, buildSeed); unlike kvm2 (review finding S5.1), a seed staging failure there is Transient, not Permanent. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -6,6 +6,7 @@ package proxmox
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
@@ -13,6 +14,7 @@ import (
|
||||
logf "sigs.k8s.io/controller-runtime/pkg/log"
|
||||
|
||||
"gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider"
|
||||
"gitea.home.hrajfrisbee.cz/kacerr/egress-proxies-operator/internal/provider/keyedlock"
|
||||
)
|
||||
|
||||
const (
|
||||
@@ -57,6 +59,11 @@ type Provider struct {
|
||||
api api
|
||||
now func() time.Time
|
||||
budgets budgets
|
||||
|
||||
// nameLocks serialises Create and Delete per instance name (spec entry
|
||||
// 19), so the reconciler's Delete and GC's Delete never race on stop and
|
||||
// destroy, and a Delete cannot interleave with a Create's steps.
|
||||
nameLocks *keyedlock.Locks
|
||||
}
|
||||
|
||||
var _ provider.Provider = (*Provider)(nil)
|
||||
@@ -85,11 +92,12 @@ func New(ctx context.Context, pc provider.ProviderConfig) (provider.Provider, er
|
||||
// newProvider is the constructor tests use, with a in-memory api.
|
||||
func newProvider(name string, cfg provider.ProxmoxConfig, a api) *Provider {
|
||||
return &Provider{
|
||||
name: name,
|
||||
cfg: withDefaults(cfg),
|
||||
api: a,
|
||||
now: time.Now,
|
||||
budgets: defaultBudgets(),
|
||||
name: name,
|
||||
cfg: withDefaults(cfg),
|
||||
api: a,
|
||||
now: time.Now,
|
||||
budgets: defaultBudgets(),
|
||||
nameLocks: keyedlock.New(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -152,6 +160,17 @@ func (p *Provider) wrapErr(op, id string, err error) error {
|
||||
return provider.Wrap(classify(op, err), op, p.name, id, err)
|
||||
}
|
||||
|
||||
// lockName takes the per-name lock of spec entry 19, or gives up with ctx,
|
||||
// so a caller whose ctx ends while another caller's Create waits on a clone
|
||||
// returns promptly.
|
||||
func (p *Provider) lockName(ctx context.Context, name string) (func(), error) {
|
||||
release, err := p.nameLocks.Acquire(ctx, name)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%w: waiting for the lock on %s: %w", provider.ErrTransient, name, err)
|
||||
}
|
||||
return release, nil
|
||||
}
|
||||
|
||||
// The four contract methods are stubs until the phases that implement them
|
||||
// (Get and ListByTag in Phase 5, Create in 6, Delete in 7). Each answers
|
||||
// Transient, so a Proxy on this provider retries rather than latching.
|
||||
|
||||
Reference in New Issue
Block a user