Builds of one module in flight together finish in any order, and the mesh took whatever it heard last as what the module is: RegisterModule overwrote the module's manifest unconditionally, and Held/BuiltAgainst/ReadRepositories ordered builds by when they were recorded. A postgres build asked before the mesh-tools runtime fix finished after the one asked after it, and the next push deployed the stale image (novox/hq issue 219). A build is now ordered by when it was asked, read from the build-<nanos> id the controller writes: build.asked and module.built_asked (migration 0055). A registration from an earlier request than the module's current one is recorded and refused as superseded. A plan takes as its outcome only a build asked at or after its own ask, so an earlier plan's leftover build cannot settle a later plan. Ids of any other shape keep the old order.
125 lines
4.3 KiB
Go
125 lines
4.3 KiB
Go
package inventory
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
)
|
|
|
|
// novox/hq 04-ISSUES/219: two builds of one module in flight together, the one asked first heard
|
|
// last. The newer request stood on the newer base; the older one's late outcome is recorded and is
|
|
// not what the module is.
|
|
|
|
func postgresBuild(id string, asked time.Time, image string) Build {
|
|
b := aBuild(id, "postgres", "")
|
|
b.Asked = asked
|
|
b.Against = []string{"mesh-tools/runtime@sha256:" + id}
|
|
b.Made = []Artifact{{Name: "server", Kind: "image", Reference: "postgres@sha256:" + image}}
|
|
return b
|
|
}
|
|
|
|
func TestAnOlderRequestFinishingLaterIsNotWhatTheModuleHolds(t *testing.T) {
|
|
inv := fresh(t)
|
|
ctx := context.Background()
|
|
older := time.Date(2026, 10, 3, 21, 33, 45, 0, time.UTC)
|
|
newer := time.Date(2026, 10, 3, 21, 51, 57, 0, time.UTC)
|
|
|
|
// The newer request finishes first, the older one last — recorded in that order.
|
|
if err := inv.RecordBuild(ctx, postgresBuild("newer", newer, "4bcd5f73")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := inv.RecordBuild(ctx, postgresBuild("older", older, "0ab07fa9")); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
|
|
held, err := inv.Held(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := held["postgres/server"]; got != "postgres@sha256:4bcd5f73" {
|
|
t.Errorf("postgres holds %q; want the newer request's image 4bcd5f73", got)
|
|
}
|
|
against, err := inv.BuiltAgainst(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := against["postgres"]; len(got) != 1 || got[0] != "mesh-tools/runtime@sha256:newer" {
|
|
t.Errorf("postgres stands on %v; want what the newer request stood on", got)
|
|
}
|
|
|
|
// Both are still recorded, the late one first as what happened lately.
|
|
builds, err := inv.Builds(ctx, "postgres", 5)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if len(builds) != 2 || builds[0].ID != "older" || !builds[0].Asked.Equal(older) {
|
|
t.Fatalf("both builds, newest heard first, with when they were asked: %+v", builds)
|
|
}
|
|
}
|
|
|
|
func TestABuildWithNoKnownRequestTimeIsOrderedByWhenItWasRecorded(t *testing.T) {
|
|
// What the mesh did before it kept the request time, so a row from before still answers.
|
|
inv := fresh(t)
|
|
ctx := context.Background()
|
|
for _, id := range []string{"first", "second"} {
|
|
b := aBuild(id, "shell", "")
|
|
b.Made = []Artifact{{Name: "config", Kind: "archive", Reference: "…/" + id}}
|
|
if err := inv.RecordBuild(ctx, b); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
held, err := inv.Held(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := held["shell/config"]; got != "…/second" {
|
|
t.Errorf("shell holds %q; want the one recorded last", got)
|
|
}
|
|
}
|
|
|
|
func TestARegistrationFromAnOlderRequestDoesNotReplaceANewerOne(t *testing.T) {
|
|
inv := fresh(t)
|
|
ctx := context.Background()
|
|
older := time.Date(2026, 10, 3, 21, 33, 45, 0, time.UTC)
|
|
newer := time.Date(2026, 10, 3, 21, 51, 57, 0, time.UTC)
|
|
from := func(asked time.Time) Source {
|
|
return Source{Repository: "novox/mesh-catalog", Seat: "git", Path: "modules/postgres",
|
|
BuiltFrom: "efff5415", Asked: asked}
|
|
}
|
|
|
|
if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "postgres", Version: "fixed"}, from(newer)); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "postgres", Version: "stale"}, from(older))
|
|
if !errors.Is(err, ErrSuperseded) {
|
|
t.Fatalf("an older request's registration was not refused as superseded: %v", err)
|
|
}
|
|
shelf, err := inv.Catalogue(ctx)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if got := shelf["postgres"].Version; got != "fixed" {
|
|
t.Fatalf("postgres is %q; want the newer request's manifest", got)
|
|
}
|
|
|
|
// A later request, and a manifest handed over by hand — asked when it is handed over — both
|
|
// replace it as before.
|
|
if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "postgres", Version: "later"},
|
|
from(newer.Add(time.Minute))); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := inv.RegisterModule(ctx, catalogue.Manifest{Module: "postgres", Version: "by-hand"}, Source{}); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if shelf, _ := inv.Catalogue(ctx); shelf["postgres"].Version != "by-hand" {
|
|
t.Fatalf("postgres is %q; want the manifest handed over by hand", shelf["postgres"].Version)
|
|
}
|
|
src, err := inv.SourceOf(ctx, "postgres")
|
|
if err != nil || src.Repository != "novox/mesh-catalog" {
|
|
t.Fatalf("a hand registration erased the provenance: %+v %v", src, err)
|
|
}
|
|
}
|