Ask a held first build again when a delivery of build-agent ends (issue 1173)
ADR 0482 section 5: the build waiting for a build-agent that reads its manifest is asked once the plan delivering it ends.
This commit is contained in:
@@ -310,3 +310,58 @@ func probeFirstBuilds(ctx context.Context, d *doctor) ([]conditions.Observation,
|
||||
}
|
||||
return firstBuildObservations(waiting, time.Now().UTC()), nil
|
||||
}
|
||||
|
||||
// askHeldFirstBuildsAgain asks again the first build of each first merge held for a build-agent that reads its manifest,
|
||||
// when the plan that ended delivered build-agent (novox/hq issue 1173): once for each end, since the ask answers the merge
|
||||
// and a refusal that comes again holds it anew. Answers how many merges it asked for.
|
||||
func askHeldFirstBuildsAgain(ctx context.Context, open *stores, p inventory.Plan) int {
|
||||
if !deliversBuildAgent(p) {
|
||||
return 0
|
||||
}
|
||||
actingOnMerges.Lock()
|
||||
defer actingOnMerges.Unlock()
|
||||
inv := open.inventory
|
||||
waiting, err := inv.WaitingMerges(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf("%s delivered build-agent, and the first merges held for it could not be read: %v\n", p.ID, err)
|
||||
return 0
|
||||
}
|
||||
asked := 0
|
||||
for _, w := range waiting {
|
||||
k := announcedOf(w)
|
||||
if k.Held == nil {
|
||||
continue
|
||||
}
|
||||
entries, err := inv.Catalogued(ctx)
|
||||
if err != nil {
|
||||
fmt.Printf("%s delivered build-agent, and the catalogue could not be read: %v\n", p.ID, err)
|
||||
return asked
|
||||
}
|
||||
read, err := readForPlanning(ctx, inv)
|
||||
if err != nil {
|
||||
fmt.Printf("%s delivered build-agent, and what the builds read could not be read: %v\n", p.ID, err)
|
||||
return asked
|
||||
}
|
||||
added, why, err := firstBuildsOf(ctx, inv, k.SourceMoved, entries, read)
|
||||
if err != nil || len(added) == 0 {
|
||||
fmt.Printf("%s delivered build-agent, and the merge %.8s of %s held for it asks no build now (%v): it stays "+
|
||||
"waiting\n", p.ID, w.Commit, w.Repository, err)
|
||||
continue
|
||||
}
|
||||
fmt.Printf("%s delivered build-agent: the first build of %s, held for the word %q, is asked again\n", p.ID,
|
||||
k.Held.Module, k.Held.Word)
|
||||
if err := askFirstBuilds(ctx, inv, k.SourceMoved, time.Now().UTC(), added, why); err != nil {
|
||||
fmt.Printf(" and could not be asked: %v\n", err)
|
||||
continue
|
||||
}
|
||||
asked++
|
||||
}
|
||||
return asked
|
||||
}
|
||||
|
||||
// deliversBuildAgent says a delivery plan ended delivered with build-agent built in it: the reader a held first build
|
||||
// waits for is the one now running.
|
||||
func deliversBuildAgent(p inventory.Plan) bool {
|
||||
m := p.Modules["build-agent"]
|
||||
return p.WalkSaid == inventory.WalkEnded && p.Outcome() == inventory.WalkDelivered && m != nil && m.State == "built"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,91 @@
|
||||
//go:build integration
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/asks"
|
||||
"github.com/novox/mesh-controller/internal/inventory"
|
||||
"github.com/novox/mesh-controller/internal/link"
|
||||
)
|
||||
|
||||
// 1173/AC1, 1173/AC3 whole against the store: a first merge's build refused for a word the running build-agent does not
|
||||
// read keeps the merge waiting with no failed build recorded; a delivery plan that does not deliver build-agent asks
|
||||
// nothing; the one that ends delivering it asks the first build again once, and its pass registers the module.
|
||||
func TestAHeldFirstMergeIsAskedAgainWhenABuildAgentIsDelivered(t *testing.T) {
|
||||
const head, commit = "a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1a1", "0177ea7a00000000"
|
||||
open := aMesh(t)
|
||||
ctx := t.Context()
|
||||
inv := open.inventory
|
||||
|
||||
a := aDeliveryMergeOf(t, head, map[string]string{"novox/mesh-delivery@a1a1a1a1a1a1": head})
|
||||
wasKept, wasAsk, realAsk := keptDeliveryMerge, routerAskOf, askABuild
|
||||
keptDeliveryMerge = func(context.Context, string, string, int, string) (deliveryMerge, bool, error) {
|
||||
return a.merge, true, nil
|
||||
}
|
||||
routerAskOf = func(context.Context, catalogueReader, string, string) (*asks.Ask, *asks.Warrant, error) {
|
||||
return &a.ask, &a.warrant, nil
|
||||
}
|
||||
var asked []string
|
||||
askABuild = func(_ context.Context, source buildSource, path, ref string) (string, error) {
|
||||
id := "build-" + string(rune('1'+len(asked)))
|
||||
asked = append(asked, id)
|
||||
return id, nil
|
||||
}
|
||||
t.Cleanup(func() { keptDeliveryMerge, routerAskOf, askABuild = wasKept, wasAsk, realAsk })
|
||||
|
||||
m := link.SourceMoved{Owner: "novox", Repo: "mesh-delivery", Base: "main", HeadSHA: head, Commit: commit, Number: 7,
|
||||
MergedAt: time.Now().Add(-time.Hour).UTC().Format(time.RFC3339Nano), Paths: []string{"module.json", "main.go"},
|
||||
ModuleDirs: []string{"."}, ModuleDirsSaid: true}
|
||||
if err := (following{open: open}).SourceMoved(ctx, m); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(asked) != 1 {
|
||||
t.Fatalf("1173/AC3: the first merge asked %v, want the one first build", asked)
|
||||
}
|
||||
|
||||
// The running build-agent refuses the manifest's word.
|
||||
refused := onTrunk("build-1", "novox/mesh-delivery", "git", "", nil)
|
||||
refused.Failed, refused.Manifest, refused.Commit = refusedKeyTTL, nil, commit
|
||||
if err := (builds{inv: inv, open: open}).Built(ctx, refused); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, recorded, _ := inv.BuildByID(ctx, "build-1"); recorded {
|
||||
t.Fatal("1173/AC1: a refused first build was recorded as a failed build")
|
||||
}
|
||||
waiting, err := inv.WaitingMerges(ctx)
|
||||
if err != nil || len(waiting) != 1 || announcedOf(waiting[0]).Held == nil {
|
||||
t.Fatalf("1173/AC1: the merges waiting are %+v (%v), want the first merge, held", waiting, err)
|
||||
}
|
||||
if obs := firstBuildObservations(waiting, time.Now()); len(obs) != 1 || obs[0].Kind != kindFirstBuildHeld {
|
||||
t.Fatalf("1173/AC2: the held merge is named by %+v", obs)
|
||||
}
|
||||
|
||||
// A plan that ended without delivering build-agent asks nothing; one that delivered it asks once.
|
||||
other := inventory.Plan{ID: "p1", State: inventory.PlanDone, WalkSaid: inventory.WalkEnded,
|
||||
Modules: map[string]*inventory.PlanModule{"mesh-delivery": {State: "built"}}}
|
||||
if n := askHeldFirstBuildsAgain(ctx, open, other); n != 0 || len(asked) != 1 {
|
||||
t.Fatalf("1173/AC3: a plan that delivered no build-agent asked %d builds again (%v)", n, asked)
|
||||
}
|
||||
delivered := inventory.Plan{ID: "p2", State: inventory.PlanDone, WalkSaid: inventory.WalkEnded,
|
||||
Modules: map[string]*inventory.PlanModule{"build-agent": {State: "built"}}}
|
||||
if n := askHeldFirstBuildsAgain(ctx, open, delivered); n != 1 || len(asked) != 2 {
|
||||
t.Fatalf("1173/AC3: the plan that delivered build-agent asked %d builds again (%v), want one", n, asked)
|
||||
}
|
||||
if n := askHeldFirstBuildsAgain(ctx, open, delivered); n != 0 || len(asked) != 2 {
|
||||
t.Fatalf("1173/AC3: the same end asked again: %d (%v)", n, asked)
|
||||
}
|
||||
|
||||
// Its pass registers the module.
|
||||
pass := onTrunk("build-2", "novox/mesh-delivery", "git", "", map[string]any{"module": "mesh-delivery-watchman", "version": "1"})
|
||||
pass.Commit = commit
|
||||
if err := (builds{inv: inv, open: open}).Built(ctx, pass); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, known := inventoryHolds(t, inv, "mesh-delivery-watchman"); !known {
|
||||
t.Fatal("1173/AC3: the module is not registered after the first build passed")
|
||||
}
|
||||
}
|
||||
@@ -83,3 +83,25 @@ func TestAFirstBuildFailingForAnyOtherReasonIsNotHeld(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1173/AC3 (the part that needs no store): only a plan that ended delivered with build-agent built in it is the end a held
|
||||
// first build waits for.
|
||||
func TestOnlyADeliveredPlanOfBuildAgentIsWhatAHeldFirstBuildWaitsFor(t *testing.T) {
|
||||
plan := func(state, walk string, modules map[string]*inventory.PlanModule) inventory.Plan {
|
||||
return inventory.Plan{ID: "p", State: state, WalkSaid: walk, Modules: modules}
|
||||
}
|
||||
built := map[string]*inventory.PlanModule{"build-agent": {State: "built"}}
|
||||
if !deliversBuildAgent(plan(inventory.PlanDone, inventory.WalkEnded, built)) {
|
||||
t.Fatal("1173/AC3: a plan that delivered build-agent is not the end a held first build waits for")
|
||||
}
|
||||
for what, p := range map[string]inventory.Plan{
|
||||
"a plan still running": plan(inventory.PlanBuilding, "", built),
|
||||
"a plan that failed": plan(inventory.PlanFailed, inventory.WalkEnded, built),
|
||||
"a failed build-agent": plan(inventory.PlanDone, inventory.WalkEnded, map[string]*inventory.PlanModule{"build-agent": {State: "failed"}}),
|
||||
"another module's plan": plan(inventory.PlanDone, inventory.WalkEnded, map[string]*inventory.PlanModule{"mailu": {State: "built"}}),
|
||||
} {
|
||||
if deliversBuildAgent(p) {
|
||||
t.Fatalf("1173/AC3: %s is the end a held first build waits for", what)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -209,6 +209,10 @@ func servingPlanSaved(ctx context.Context, bus link.Bus, open *stores) func(*inv
|
||||
keepBuildingKey(ctx, p)
|
||||
if p.WalkSaid == inventory.WalkEnded {
|
||||
go forgetChecksOfWalk(context.WithoutCancel(ctx), open.inventory, p)
|
||||
// A first build refused for a word only a newer build-agent reads is asked again (novox/hq issue 1173).
|
||||
if deliversBuildAgent(p) {
|
||||
go askHeldFirstBuildsAgain(context.WithoutCancel(ctx), open, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user