The migration raises first unless chain_moved names builds, rollouts and assignments for every kind that holds rows, and unless pending_assignment holds no open or unanswered row; then it drops build_request, build, batched_merge, release_plan, pending_assignment, assignment, raise_assignment_generation(), node.sent_builds and chain_moved. build_gate stays.
117 lines
4.8 KiB
Go
117 lines
4.8 KiB
Go
//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")
|
|
}
|
|
}
|