Delivery 2 deleted the move and chain_moved, so the layout of the builds under their modules is made by the controller's start (LayBuildsOut), recorded by the key layout.done in the builds bucket, retried every half minute with an urgent condition while it fails; the chain on the bus is refused until it exists. Its source readers use the module and pointer reads.
112 lines
4.8 KiB
Go
112 lines
4.8 KiB
Go
package inventory
|
|
|
|
import (
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/chain"
|
|
)
|
|
|
|
// **What delivery 1 recorded in node.sent_builds is copied onto the keys of the assignments, once** (issue 1109, review
|
|
// round 1): a module pulled in by an assignment had no commit on its key, so it would fall out of SentBuilds. The copy
|
|
// creates a standing pulled-in key where none stands, fills a standing key that names no commit (commit, and the build
|
|
// when one is known, no rollout), and overwrites nothing: a key with a commit, a hand send's unknown, a wait for a build.
|
|
func TestWhatTheColumnRecordedIsCopiedOntoTheKeysAndOverwritesNothing(t *testing.T) {
|
|
inv, c := anInventoryOnTheChain(t)
|
|
ctx := t.Context()
|
|
at := time.Date(2026, 10, 18, 4, 0, 0, 0, time.UTC)
|
|
if err := inv.chainRecordBuild(ctx, c, Build{ID: "b-wg", Module: "mesh-wireguard", Commit: "c-wg"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
put := func(module string, a chain.Assignment) {
|
|
a.Node, a.Module = "laptop", module
|
|
if _, err := c.Assignments.Create(ctx, assignmentKey("laptop", module), a); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
put("notes", chain.Assignment{Assigned: at}) // assigned, no commit yet
|
|
put("web", chain.Assignment{Assigned: at, Commit: "c-web-new", Build: "b1"}) // already has one
|
|
put("hand", chain.Assignment{Assigned: at, SentUnknown: at}) // a hand send since
|
|
put("waits", chain.Assignment{Assigned: at, State: chain.StatePending}) // a wait for a build
|
|
put("ended", chain.Assignment{Assigned: at, Ended: at}) // unassigned
|
|
sent := map[string]string{"notes": "c-notes", "web": "c-web-old", "mesh-wireguard": "c-wg", "hand": "c-hand",
|
|
"waits": "c-waits", "ended": "c-ended"}
|
|
|
|
for pass := 0; pass < 2; pass++ { // and the second pass changes nothing
|
|
if err := inv.copySentBuildsOf(ctx, c, "laptop", sent, at); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
get := func(module string) chain.Assignment {
|
|
t.Helper()
|
|
k, found, err := c.Assignments.Get(ctx, assignmentKey("laptop", module))
|
|
if err != nil || !found {
|
|
t.Fatalf("%s: %v %v", module, found, err)
|
|
}
|
|
return k.Value
|
|
}
|
|
if a := get("notes"); a.Commit != "c-notes" || a.Rollout != "" || !a.Stands() || a.PulledIn {
|
|
t.Errorf("notes: %+v", a)
|
|
}
|
|
if a := get("web"); a.Commit != "c-web-new" || a.Build != "b1" {
|
|
t.Errorf("a key with a commit was overwritten: %+v", a)
|
|
}
|
|
if a := get("hand"); a.Commit != "" || a.SentUnknown.IsZero() {
|
|
t.Errorf("a hand send's unknown was overwritten: %+v", a)
|
|
}
|
|
if a := get("waits"); a.Commit != "" || a.State != chain.StatePending {
|
|
t.Errorf("a wait was overwritten: %+v", a)
|
|
}
|
|
if a := get("mesh-wireguard"); a.Commit != "c-wg" || a.Build != "b-wg" || !a.PulledIn || !a.Stands() || a.Rollout != "" {
|
|
t.Errorf("the pulled-in module has no standing key of its own: %+v", a)
|
|
}
|
|
if a := get("ended"); a.Commit != "" || a.Stands() {
|
|
t.Errorf("an ended key was revived or given a commit: %+v", a)
|
|
}
|
|
}
|
|
}
|
|
|
|
// The copy reads the column of the store, writes the keys, and empties the column it has copied: SentBuilds then answers the
|
|
// pulled-in module, and a second copy finds nothing to do.
|
|
func TestTheColumnOfASentMachineIsCopiedOntoItsKeysAndEmptied(t *testing.T) {
|
|
inv := ForTest(t)
|
|
ctx := t.Context()
|
|
if _, err := inv.AddNode(ctx, "laptop"); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
inv.ExecForTest(t, `update node set sent_builds = '{"mesh-wireguard": "c-wg"}'::jsonb, sent_at = now() where name = 'laptop'`)
|
|
if n, err := inv.CopySentBuilds(ctx); err != nil || n != 1 {
|
|
t.Fatalf("the copy did %d nodes (%v)", n, err)
|
|
}
|
|
got, known, err := inv.SentBuilds(ctx, "laptop")
|
|
if err != nil || !known || got["mesh-wireguard"] != "c-wg" {
|
|
t.Fatalf("SentBuilds answers %v, known %v (%v)", got, known, err)
|
|
}
|
|
if rows := inv.QueryForTest(t, `select count(*) from node where sent_builds is not null`); rows[0][0] != "0" {
|
|
t.Fatalf("the column was not emptied: %v", rows)
|
|
}
|
|
if n, err := inv.CopySentBuilds(ctx); err != nil || n != 0 {
|
|
t.Fatalf("a second copy did %d nodes (%v)", n, err)
|
|
}
|
|
}
|
|
|
|
// A machine delivery 2 has sent has its column emptied by the send itself, so a copy after it leaves a key the send cleared
|
|
// without a commit.
|
|
func TestASendEmptiesTheColumnSoALaterCopyLeavesAClearedKeyBare(t *testing.T) {
|
|
inv := ForTest(t)
|
|
ctx := t.Context()
|
|
record, err := inv.AddNode(ctx, "laptop")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
inv.ExecForTest(t, `update node set sent_builds = '{"notes": "c-old"}'::jsonb where name = 'laptop'`)
|
|
if err := inv.RecordSentUnder(ctx, record.ID, "d1", map[string]string{}, 1); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if rows := inv.QueryForTest(t, `select count(*) from node where sent_builds is not null`); rows[0][0] != "0" {
|
|
t.Fatalf("a send left the column: %v", rows)
|
|
}
|
|
if n, err := inv.CopySentBuilds(ctx); err != nil || n != 0 {
|
|
t.Fatalf("the copy did %d nodes (%v)", n, err)
|
|
}
|
|
}
|