Every method of builds, build requests, rollouts, merges and assignments reads and writes the chain alone: the else branches that read the tables, the movers and MoveChain with their tests, chain_moved reads, ChainReads and the flags that switched a kind, the store's assignments mirror (KeepAssignmentPlans needs none of it: it uses only the AssignmentMirrored type, mirrors.go, and AssignmentValues, provisioning.go) and its tick, the move code of the controller's start, and ForTest's chain_moved row. The listed store tests of the move go with their functions; the tests of the readers, requests, assignments and plans that lived beside them stay in chain_store_test.go. Tests that replayed old data migrations over the build, assignment and pin tables are removed with the tables. The static test that no plan is written but through SavePlansMoving now reads the plan's chain path.
179 lines
6.3 KiB
Go
179 lines
6.3 KiB
Go
//go:build integration
|
|
|
|
package inventory
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"slices"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
// A base a build copies into the store is recorded where it is copied, and kept while a kept build stood
|
|
// on it (novox/hq ADR 0257).
|
|
|
|
func mirrorRef(repository string, n int) string {
|
|
return fmt.Sprintf("%s%s@sha256:%064x", catalogue.ArtifactStoreScheme, repository, n)
|
|
}
|
|
|
|
// builtOn records a successful build of a module that made one image and stood on these bases.
|
|
func builtOn(t *testing.T, inv *Inventory, id, module string, n int, mirrored []string, against ...string) {
|
|
t.Helper()
|
|
b := aBuild(id, module, "")
|
|
b.Made = []Artifact{{Name: "app", Kind: "image", Reference: ref(module, "app", n)}}
|
|
b.Against = against
|
|
b.Mirrored = mirrored
|
|
if err := inv.RecordBuild(context.Background(), b); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
|
|
func stateOf(t *testing.T, inv *Inventory, reference string) ArtifactState {
|
|
t.Helper()
|
|
states, err := inv.Artifacts(context.Background())
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
for _, s := range states {
|
|
if s.Reference == reference {
|
|
return s
|
|
}
|
|
}
|
|
return ArtifactState{Reference: reference, State: "unrecorded"}
|
|
}
|
|
|
|
func TestACopyAFailedBuildMadeIsRecordedAndNothingKeepsIt(t *testing.T) {
|
|
inv := fresh(t)
|
|
golang := mirrorRef("upstream/docker.io/library/golang", 1)
|
|
failed := aBuild("f1", "", "the recipe fetched an undeclared image")
|
|
failed.Mirrored = []string{golang}
|
|
if err := inv.RecordBuild(context.Background(), failed); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s := stateOf(t, inv, golang); s.State != ArtifactEligible {
|
|
t.Fatalf("a copy only a failed build made reads %+v; want eligible", s)
|
|
}
|
|
}
|
|
|
|
func TestACopyIsKeptByTheKeptBuildsThatStoodOnItAndByNothingElse(t *testing.T) {
|
|
inv := fresh(t)
|
|
ctx := context.Background()
|
|
holding(t, inv, "proxy")
|
|
old := mirrorRef("upstream/docker.io/library/golang", 1)
|
|
current := mirrorRef("upstream/docker.io/library/golang", 2)
|
|
|
|
// The first build stood on the old copy; six more on the current one. The old copy falls out of
|
|
// the five kept builds; the current one is stood on by all five.
|
|
builtOn(t, inv, "b01", "proxy", 1, []string{old}, old)
|
|
for i := 2; i <= 7; i++ {
|
|
builtOn(t, inv, fmt.Sprintf("b%02d", i), "proxy", i, []string{current}, current)
|
|
}
|
|
if s := stateOf(t, inv, old); s.State != ArtifactEligible {
|
|
t.Fatalf("a copy no kept build stood on reads %+v", s)
|
|
}
|
|
if s := stateOf(t, inv, current); s.State != ArtifactKept || !slices.Equal(s.Why, []string{KeptStoodOn}) {
|
|
t.Fatalf("a copy the kept builds stood on reads %+v", s)
|
|
}
|
|
|
|
// A definition naming the image upstream, by the digest every copy shares, keeps no copy.
|
|
m := catalogue.Manifest{Module: "proxy", Version: "2",
|
|
Build: &catalogue.Build{On: []catalogue.BuildsOn{{Arg: "GO_BASE", Image: "golang@" + strings.SplitN(old, "@", 2)[1]}}}}
|
|
if err := inv.RegisterModule(ctx, m, Source{Repository: "https://forge.invalid/proxy.git"}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s := stateOf(t, inv, old); s.State != ArtifactEligible {
|
|
t.Fatalf("a definition naming the upstream image kept the old copy: %+v", s)
|
|
}
|
|
go_, err := inv.ToCollect(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if !slices.Contains(go_, old) || slices.Contains(go_, current) {
|
|
t.Fatalf("offered %v to collect", go_)
|
|
}
|
|
|
|
// Once let go of, a copy stays let go of, even should a kept build still name it.
|
|
if err := inv.MarkCollected(ctx, []string{current}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s := stateOf(t, inv, current); s.State != ArtifactCollected {
|
|
t.Fatalf("a collected copy reads %+v", s)
|
|
}
|
|
}
|
|
|
|
func TestAModulesBaseIsNotKeptAsACopy(t *testing.T) {
|
|
// A base that is another module's artifact is kept by that module's own builds, as before: the
|
|
// stood-on reason is a copy's alone.
|
|
inv := fresh(t)
|
|
holding(t, inv, "app")
|
|
base := ref("go-base", "build", 1)
|
|
b := aBuild("g1", "go-base", "")
|
|
b.Made = []Artifact{{Name: "build", Kind: "image", Reference: base}}
|
|
if err := inv.RecordBuild(context.Background(), b); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
builtOn(t, inv, "a1", "app", 1, nil, base)
|
|
if s := stateOf(t, inv, base); s.State != ArtifactEligible {
|
|
t.Fatalf("a forgotten module's artifact read %+v because a build stood on it", s)
|
|
}
|
|
}
|
|
|
|
func TestAPersonRecordsACopyAndTheFirstRecordStands(t *testing.T) {
|
|
inv := fresh(t)
|
|
ctx := context.Background()
|
|
copyRef := mirrorRef("route-proxy/on-go_base", 3)
|
|
if err := inv.RecordMirrored(ctx, "", "copied before copies were recorded", []string{copyRef}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := inv.RecordMirrored(ctx, "b9", "", []string{copyRef}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
known, err := inv.Mirrored(ctx)
|
|
if err != nil || !known[copyRef] {
|
|
t.Fatalf("recorded copies %v %v", known, err)
|
|
}
|
|
var why string
|
|
if err := inv.store.Pool().QueryRow(ctx, `select why from artifact_mirrored where reference = $1`, copyRef).Scan(&why); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if why != "copied before copies were recorded" {
|
|
t.Fatalf("the first record did not stand: %q", why)
|
|
}
|
|
if s := stateOf(t, inv, copyRef); s.State != ArtifactEligible {
|
|
t.Fatalf("a copy a person recorded reads %+v", s)
|
|
}
|
|
}
|
|
|
|
// A copy let go of and copied again by a later build is held again: its record is back, and the sweep
|
|
// decides about it as before.
|
|
func TestACopyLetGoOfAndCopiedAgainIsHeldAgain(t *testing.T) {
|
|
inv := fresh(t)
|
|
ctx := context.Background()
|
|
holding(t, inv, "proxy")
|
|
golang := mirrorRef("upstream/docker.io/library/golang", 1)
|
|
builtOn(t, inv, "b01", "proxy", 1, []string{golang}, golang)
|
|
if err := inv.MarkCollected(ctx, []string{golang}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s := stateOf(t, inv, golang); s.State != ArtifactCollected {
|
|
t.Fatalf("a let-go copy reads %+v", s)
|
|
}
|
|
builtOn(t, inv, "b02", "proxy", 2, []string{golang}, golang)
|
|
if s := stateOf(t, inv, golang); s.State != ArtifactKept || !slices.Equal(s.Why, []string{KeptStoodOn}) {
|
|
t.Fatalf("a copy copied again reads %+v; want kept again", s)
|
|
}
|
|
// A person's record never undoes a collection: only a build that copied it says it is there.
|
|
if err := inv.MarkCollected(ctx, []string{golang}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := inv.RecordMirrored(ctx, "", "again", []string{golang}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if s := stateOf(t, inv, golang); s.State != ArtifactCollected {
|
|
t.Fatalf("a person's record brought a collected copy back: %+v", s)
|
|
}
|
|
}
|