opstate,daemon: list in-flight operations via daemon.operations.list
Prerequisite for `banger update`'s preflight, which refuses to swap
binaries while anything is in flight. Today's opstate.Registry
exposes Insert/Get/Prune but no iteration; without a snapshot
accessor the update flow can't tell whether a vm.create is
mid-prepare-work-disk.
* opstate.Registry.List(): returns a freshly-allocated snapshot
of every entry. Mutating the slice doesn't poison the
registry. Pinned by tests covering the snapshot semantics
and the empty case.
* api.OperationSummary / OperationsListResult: a public-shape
record per op. Today the Kind is always "vm.create" — the
field exists so future async kinds (image.pull, kernel.pull)
plug in without an API change.
* Daemon.ListOperations + daemon.operations.list RPC:
walks vmService.createOps and emits OperationSummary entries.
Done ops are included in the snapshot; the update preflight
filters by Done itself.
* dispatch_test's documented-methods list updated.
No behaviour change for existing flows; this is a read-only
addition.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
775525b592
commit
3c0af3a2de
6 changed files with 117 additions and 2 deletions
|
|
@ -67,6 +67,46 @@ func TestRegistryPruneDropsCompletedOldOps(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestRegistryListReturnsSnapshot(t *testing.T) {
|
||||
var r Registry[*fakeOp]
|
||||
now := time.Now()
|
||||
|
||||
a := &fakeOp{id: "a", updatedAt: now}
|
||||
b := &fakeOp{id: "b", updatedAt: now}
|
||||
c := &fakeOp{id: "c", updatedAt: now}
|
||||
c.done.Store(true)
|
||||
r.Insert(a)
|
||||
r.Insert(b)
|
||||
r.Insert(c)
|
||||
|
||||
got := r.List()
|
||||
if len(got) != 3 {
|
||||
t.Fatalf("List() returned %d entries, want 3", len(got))
|
||||
}
|
||||
ids := map[string]bool{}
|
||||
for _, op := range got {
|
||||
ids[op.ID()] = true
|
||||
}
|
||||
for _, want := range []string{"a", "b", "c"} {
|
||||
if !ids[want] {
|
||||
t.Errorf("List() missing %q; got %v", want, ids)
|
||||
}
|
||||
}
|
||||
|
||||
// Mutating the returned slice must not poison the registry.
|
||||
got[0] = &fakeOp{id: "tampered"}
|
||||
if _, ok := r.Get("tampered"); ok {
|
||||
t.Error("List() returned the registry's internal map, not a copy")
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryListEmpty(t *testing.T) {
|
||||
var r Registry[*fakeOp]
|
||||
if got := r.List(); len(got) != 0 {
|
||||
t.Fatalf("List() on empty registry returned %d entries, want 0", len(got))
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryPruneNoOpOnEmpty(t *testing.T) {
|
||||
var r Registry[*fakeOp]
|
||||
// Just shouldn't panic.
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue