//go:build integration package inventory import ( "context" "errors" "reflect" "testing" "github.com/novox/mesh-controller/internal/chain" ) // **What a machine was sent is read from the keys of its assignments that name a commit** (ADR 0408, issue 1109): an assigned // module's, and a module only pulled in, whose key is a standing value marked pulled-in and never a state. func TestSentBuildsAnswersFromTheAssignmentsKeysOfTheNode(t *testing.T) { inv, node := aNodeWithModules(t, "web", "cache") ctx := t.Context() record, _ := inv.NodeByName(ctx, node) if _, err := inv.Assign(ctx, node, "web"); err != nil { t.Fatal(err) } if err := inv.RecordSent(ctx, record.ID, "d1", map[string]string{"web": "c1", "cache": "c2"}); err != nil { t.Fatal(err) } if builds, known, err := inv.SentBuilds(ctx, node); err != nil || !known || !reflect.DeepEqual(builds, map[string]string{"web": "c1", "cache": "c2"}) { t.Fatalf("sent %v %v %v", builds, known, err) } pulled, found, _ := inv.AssignmentValue(ctx, node, "cache") if !found || !pulled.Stands() || pulled.State != "" || !pulled.PulledIn || !pulled.Assigned.IsZero() || pulled.Commit != "c2" { t.Fatalf("the module only carried has the key %+v", pulled) } if on, _ := inv.Assigned(ctx, node); !reflect.DeepEqual(on, []string{"web"}) { t.Fatalf("a module only pulled in is assigned: %v", on) } // Assigned later, it keeps what it was sent and is pulled in no longer. if _, err := inv.Assign(ctx, node, "cache"); err != nil { t.Fatal(err) } made, _, _ := inv.AssignmentValue(ctx, node, "cache") if made.PulledIn || made.Assigned.IsZero() || made.Commit != "c2" { t.Fatalf("assigned over a pulled-in key: %+v", made) } } // **A send replaces the whole map of the node, and a hand send makes it unknown until the next send that names its builds** // (issue 1109): a module no longer carried loses its commit, the node reads unknown when any key says so. func TestASendReplacesTheWholeMapOfTheNodeAndAHandSendMakesItUnknown(t *testing.T) { inv, node := aNodeWithModules(t, "web", "cache") ctx := t.Context() record, _ := inv.NodeByName(ctx, node) if _, err := inv.Assign(ctx, node, "web"); err != nil { t.Fatal(err) } if err := inv.RecordSent(ctx, record.ID, "d1", map[string]string{"web": "c1", "cache": "c2"}); err != nil { t.Fatal(err) } if err := inv.RecordSent(ctx, record.ID, "d2", map[string]string{"web": "c3"}); err != nil { t.Fatal(err) } if builds, known, _ := inv.SentBuilds(ctx, node); !known || !reflect.DeepEqual(builds, map[string]string{"web": "c3"}) { t.Fatalf("the second send did not replace the map: %v %v", builds, known) } if old, _, _ := inv.AssignmentValue(ctx, node, "cache"); old.Commit != "" || old.Build != "" { t.Fatalf("a module no longer carried keeps %+v", old) } // By hand: unknown, with a time and no commit anywhere. if err := inv.RecordSent(ctx, record.ID, "d3", nil); err != nil { t.Fatal(err) } if builds, known, _ := inv.SentBuilds(ctx, node); known || builds != nil { t.Fatalf("a hand send reads as %v %v", builds, known) } web, _, _ := inv.AssignmentValue(ctx, node, "web") if web.SentUnknown.IsZero() || web.Commit != "" || !web.Stands() { t.Fatalf("web after a hand send: %+v", web) } if sent, _ := inv.Outstanding(ctx, node); sent != "d3" { t.Fatalf("the digest of the hand send: %q", sent) } // A send that names its builds makes it known again. if err := inv.RecordSent(ctx, record.ID, "d4", map[string]string{"web": "c4"}); err != nil { t.Fatal(err) } if builds, known, _ := inv.SentBuilds(ctx, node); !known || !reflect.DeepEqual(builds, map[string]string{"web": "c4"}) { t.Fatalf("after a send that names its builds: %v %v", builds, known) } if again, _, _ := inv.AssignmentValue(ctx, node, "web"); !again.SentUnknown.IsZero() { t.Fatalf("web still says unknown: %+v", again) } } // failingToWrite is an assignments bucket that fails every write. type failingToWrite struct{ chain.Backend } func (f failingToWrite) Create(context.Context, string, []byte) (uint64, error) { return 0, errors.New("the bus went away") } func (f failingToWrite) Update(context.Context, string, []byte, uint64) (uint64, error) { return 0, errors.New("the bus went away") } // **A links write that fails fails the record of the send, loudly** (issue 1109): what a machine was sent is read from the // keys, so a send recorded without them would read as one that never happened. func TestAFailedWriteOfTheLinksFailsTheRecordOfTheSend(t *testing.T) { inv, node := aNodeWithModules(t, "web") ctx := t.Context() record, _ := inv.NodeByName(ctx, node) inv.UseChain(chain.New(chain.NewMemory(), chain.NewMemory(), failingToWrite{chain.NewMemory()}, func(context.Context) error { return nil })) err := inv.RecordSent(ctx, record.ID, "d1", map[string]string{"web": "c1"}) if err == nil { t.Fatal("a send whose links could not be written was recorded") } }