kvm2: Create, Get, Delete and ListByTag with the contract tests
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
@@ -6,7 +6,7 @@ package kvm2
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"net/http"
|
||||
"os"
|
||||
"sync"
|
||||
@@ -36,9 +36,20 @@ type Provider struct {
|
||||
budgets budgets
|
||||
tracing []tracing.Option
|
||||
|
||||
// runningWait caps the wait for a started domain to report running,
|
||||
// polled every runningPoll (spec entry 18).
|
||||
runningWait time.Duration
|
||||
runningPoll time.Duration
|
||||
|
||||
// imageLocks serialises image fetches per (host, volume), spec entry 19.
|
||||
imageMu sync.Mutex
|
||||
imageLocks map[string]chan struct{}
|
||||
|
||||
// nameLocks serialises Create and Delete per instance name, spec entry
|
||||
// 19, so two Deletes never race on destroy and undefine and a Delete
|
||||
// cannot interleave with a Create's steps.
|
||||
nameMu sync.Mutex
|
||||
nameLocks map[string]chan struct{}
|
||||
}
|
||||
|
||||
// providerOption adjusts what newProvider builds; tests inject an HTTP
|
||||
@@ -57,6 +68,12 @@ func withTracing(opts ...tracing.Option) providerOption {
|
||||
return func(p *Provider) { p.tracing = opts }
|
||||
}
|
||||
|
||||
// withRunningWait shrinks the poll interval and the cap of the wait for a
|
||||
// domain to run.
|
||||
func withRunningWait(poll, cap time.Duration) providerOption {
|
||||
return func(p *Provider) { p.runningPoll, p.runningWait = poll, cap }
|
||||
}
|
||||
|
||||
var _ provider.Provider = (*Provider)(nil)
|
||||
|
||||
// New builds a Provider from pc.KVM2. It never dials a host: the credential
|
||||
@@ -90,13 +107,16 @@ func New(ctx context.Context, pc provider.ProviderConfig) (provider.Provider, er
|
||||
// per-request ctx carries the transfer budget) and the default budgets.
|
||||
func newProvider(name string, cfg provider.KVMConfig, d dialer, opts ...providerOption) *Provider {
|
||||
p := &Provider{
|
||||
name: name,
|
||||
cfg: withDefaults(cfg),
|
||||
now: time.Now,
|
||||
hosts: newHostTable(d),
|
||||
http: http.DefaultClient,
|
||||
budgets: defaultBudgets,
|
||||
imageLocks: make(map[string]chan struct{}),
|
||||
name: name,
|
||||
cfg: withDefaults(cfg),
|
||||
now: time.Now,
|
||||
hosts: newHostTable(d),
|
||||
http: http.DefaultClient,
|
||||
budgets: defaultBudgets,
|
||||
runningWait: defaultRunningWait,
|
||||
runningPoll: defaultRunningPoll,
|
||||
imageLocks: make(map[string]chan struct{}),
|
||||
nameLocks: make(map[string]chan struct{}),
|
||||
}
|
||||
for _, opt := range opts {
|
||||
opt(p)
|
||||
@@ -167,24 +187,21 @@ func (p *Provider) wrapErr(op, id string, err error) error {
|
||||
return provider.Wrap(classify(op, err), op, p.name, id, err)
|
||||
}
|
||||
|
||||
var errNotImplemented = errors.New("kvm2: not implemented")
|
||||
|
||||
// Create is not implemented yet.
|
||||
func (p *Provider) Create(_ context.Context, _ provider.CreateRequest) (string, error) {
|
||||
return "", provider.Wrap(provider.ErrPermanent, "create", p.name, "", errNotImplemented)
|
||||
}
|
||||
|
||||
// Get is not implemented yet.
|
||||
func (p *Provider) Get(_ context.Context, _ string) (*provider.Instance, error) {
|
||||
return nil, provider.Wrap(provider.ErrPermanent, "get", p.name, "", errNotImplemented)
|
||||
}
|
||||
|
||||
// Delete is not implemented yet.
|
||||
func (p *Provider) Delete(_ context.Context, _ string) error {
|
||||
return provider.Wrap(provider.ErrPermanent, "delete", p.name, "", errNotImplemented)
|
||||
}
|
||||
|
||||
// ListByTag is not implemented yet.
|
||||
func (p *Provider) ListByTag(_ context.Context) ([]provider.Instance, error) {
|
||||
return nil, provider.Wrap(provider.ErrPermanent, "list", p.name, "", errNotImplemented)
|
||||
// lockName takes the per-name lock of spec entry 19, or gives up with ctx.
|
||||
// A one-slot channel rather than a mutex, so a caller whose ctx ends while
|
||||
// another caller's Create is downloading an image returns promptly.
|
||||
func (p *Provider) lockName(ctx context.Context, name string) (func(), error) {
|
||||
p.nameMu.Lock()
|
||||
sem, ok := p.nameLocks[name]
|
||||
if !ok {
|
||||
sem = make(chan struct{}, 1)
|
||||
p.nameLocks[name] = sem
|
||||
}
|
||||
p.nameMu.Unlock()
|
||||
select {
|
||||
case sem <- struct{}{}:
|
||||
return func() { <-sem }, nil
|
||||
case <-ctx.Done():
|
||||
return nil, fmt.Errorf("%w: waiting for the lock on %s: %w", provider.ErrTransient, name, ctx.Err())
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user