Keep assignments in the assignments bucket and move the rows there at start (issue 1093)

Once chain_moved records the kind, Assign, Unassign and Assigned write and read the keys <node>.<module>, and every join of the store that read assignments is made from the bucket: where a module runs, what is behind, what a machine awaits, a given value due for replacement, taking a module, and whether a module may be forgotten (refused while a standing assignment names it). An unassignment ends the key with a marker and keeps its earlier values, as it removed the row; no ended or pending mark is written yet. The generation rises with each change, and a seat held by an assignment goes with it: the foreign key from seat_holding is dropped by migration 0104 and the controller says both. The provisioner's plan is kept in the values by a pass of the tick that writes that one field by compare-and-set; the assignment-changed event is said from what the bucket holds, whoever wrote it. The start moves the rows with what each machine was last sent, keeping a plan the mirror put in a value.
This commit is contained in:
2026-10-18 03:15:44 +02:00
parent 275ddd76e8
commit 0e4feb0d3a
24 changed files with 1138 additions and 162 deletions
+49
View File
@@ -334,3 +334,52 @@ func TestAnEmptyBucketIsReadAsEmpty(t *testing.T) {
t.Fatalf("%v %v", all, err)
}
}
// **The one removal the chain has ends an assignment's key with a marker and keeps its earlier values** (issue 1093,
// ADR 0408 §5): the key reads as absent, can be made again, builds and rollouts have no removal, and a controller without
// the lease removes nothing.
func TestAnAssignmentsKeyEndsWithAMarkerAndKeepsItsHistory(t *testing.T) {
ctx := context.Background()
var away error
c := New(NewMemory(), NewMemory(), NewMemory(), func(context.Context) error { return away })
rev, err := c.Assignments.Create(ctx, "anchor.notes", Assignment{Node: "anchor", Module: "notes", Commit: "c1"})
if err != nil {
t.Fatal(err)
}
away = errors.New("another controller holds the lease")
if err := c.RemoveAssignment(ctx, "anchor.notes", rev); !errors.Is(err, ErrNotHeld) {
t.Fatalf("a removal without the lease answered %v", err)
}
away = nil
if err := c.RemoveAssignment(ctx, "anchor.notes", rev+1); !errors.Is(err, ErrMoved) {
t.Fatalf("a removal at a revision that moved answered %v", err)
}
if err := c.RemoveAssignment(ctx, "anchor.notes", rev); err != nil {
t.Fatal(err)
}
if _, ok, _ := c.Assignments.Get(ctx, "anchor.notes"); ok {
t.Fatal("a removed key reads as present")
}
if keys, _ := c.Assignments.Keys(ctx); len(keys) != 0 {
t.Fatalf("keys %v", keys)
}
if all, _ := c.Assignments.All(ctx); len(all) != 0 {
t.Fatalf("all %v", all)
}
if history, _ := c.Assignments.History(ctx, "anchor.notes"); len(history) != 1 || history[0].Value.Commit != "c1" {
t.Fatalf("the earlier values are %+v", history)
}
if _, err := c.Assignments.Create(ctx, "anchor.notes", Assignment{Node: "anchor", Module: "notes"}); err != nil {
t.Fatalf("a removed key could not be made again: %v", err)
}
if _, ok := any(c.Builds).(interface {
Remove(context.Context, string, uint64) error
}); ok {
t.Fatal("builds can be removed")
}
if _, ok := any(c.Rollouts).(interface {
Remove(context.Context, string, uint64) error
}); ok {
t.Fatal("rollouts can be removed")
}
}