Register a new module from the builder's first done key and ask no first build; its condition reads the key and names a manifest word this controller does not read (issue 1851, A5a, A5b)
This commit is contained in:
@@ -47,7 +47,7 @@ func (f following) firstBuilds(ctx context.Context, m link.SourceMoved, entries
|
||||
case len(added) == 0:
|
||||
return false, nil
|
||||
}
|
||||
if err := askFirstBuilds(ctx, inv, m, now, added, why); err != nil {
|
||||
if err := askFirstBuilds(ctx, f.open, m, now, added, why); err != nil {
|
||||
return true, notNow(err)
|
||||
}
|
||||
return true, nil
|
||||
@@ -68,27 +68,35 @@ func keepFirstMerge(ctx context.Context, inv *inventory.Inventory, m link.Source
|
||||
return err
|
||||
}
|
||||
|
||||
// askFirstBuilds keeps the merge, asks the builds, and marks the merge as no longer waiting for them.
|
||||
func askFirstBuilds(ctx context.Context, inv *inventory.Inventory, m link.SourceMoved, now time.Time, added []string,
|
||||
// askFirstBuilds keeps the merge as answered and registers its modules from the builder's done keys (novox/hq ADR 0501
|
||||
// §3, §6): nothing asks their first builds. The merge kept is the registration's warrant (approvedMove reads it by the
|
||||
// commit the indexer's trunk value names); a module whose key is not done yet is registered when the key's write wakes
|
||||
// the take-in (new_modules.go).
|
||||
func askFirstBuilds(ctx context.Context, open *stores, m link.SourceMoved, now time.Time, added []string,
|
||||
why string) error {
|
||||
inv := open.inventory
|
||||
repository := m.Owner + "/" + m.Repo
|
||||
if err := keepFirstMerge(ctx, inv, m, now, false); err != nil {
|
||||
return err
|
||||
}
|
||||
fmt.Printf("%s merged into %s (%.8s) on the operator's Approve (%s): the mesh builds nothing from it yet, so the "+
|
||||
"first build of %s is asked\n", repository, m.Base, m.Commit, why, strings.Join(added, ", "))
|
||||
// The ask carries the warrant just checked, for this repository and these module paths alone: the guard of a build
|
||||
// through a verb (verbMayAsk) lets exactly that through.
|
||||
paths := make([]string, 0, len(added))
|
||||
for _, dir := range added {
|
||||
if dir == "." {
|
||||
dir = ""
|
||||
}
|
||||
paths = append(paths, dir)
|
||||
fmt.Printf("%s merged into %s (%.8s) on the operator's Approve (%s): the mesh builds nothing from it yet, so %s "+
|
||||
"are registered from the builder's done keys, asking no build\n", repository, m.Base, m.Commit, why,
|
||||
strings.Join(added, ", "))
|
||||
// Answered: it no longer waits, so the word's arrival again does nothing twice.
|
||||
if err := inv.AnswerMerge(ctx, repository, m.Commit, inventory.WaitingRecord, false); err != nil {
|
||||
return err
|
||||
}
|
||||
askBuilds(withFirstBuildWarrant(ctx, repository, paths, why), inv, m, buildSource{Repository: repository, Seat: gitSeat}, added)
|
||||
// Answered: it no longer waits, so the word's arrival again asks nothing twice.
|
||||
return inv.AnswerMerge(ctx, repository, m.Commit, inventory.WaitingRecord, false)
|
||||
modules, err := newModulesOf(ctx, repository)
|
||||
switch {
|
||||
case err != nil:
|
||||
// Said, not failed: the merge is answered, and the look at every module when a controller starts, or the next
|
||||
// write of the builder or the indexer for one of them, registers them.
|
||||
fmt.Printf(" the indexer's modules of %s cannot be read now (%v): they are registered when the builder's next "+
|
||||
"write of one wakes the take-in\n", repository, err)
|
||||
case len(modules) > 0:
|
||||
go takeNewModules(context.WithoutCancel(ctx), open, modules...)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// resumeFirstBuilds runs firstBuilds for the first merges waiting for the word mesh-delivery just announced for a pull
|
||||
@@ -126,7 +134,7 @@ func (f following) resumeFirstBuilds(ctx context.Context, d deliveryMerge) error
|
||||
case err != nil:
|
||||
return notNow(err)
|
||||
case len(added) > 0:
|
||||
if err := askFirstBuilds(ctx, inv, k.SourceMoved, time.Now().UTC(), added, why); err != nil {
|
||||
if err := askFirstBuilds(ctx, f.open, k.SourceMoved, time.Now().UTC(), added, why); err != nil {
|
||||
return notNow(err)
|
||||
}
|
||||
}
|
||||
@@ -174,7 +182,9 @@ func firstBuildsOf(ctx context.Context, inv catalogueReader, m link.SourceMoved,
|
||||
}
|
||||
|
||||
// heldFirstBuild says a first build was refused because its manifest uses a word the running build-agent does not read,
|
||||
// and its merge is kept waiting for a build-agent that reads it (novox/hq ADR 0482 §5, issue 1173).
|
||||
// and its merge is kept waiting for a build-agent that reads it (novox/hq ADR 0482 §5, issue 1173). Read from a merge kept
|
||||
// before R4e, never written since: nothing asks a first build (ADR 0501 §6), and a controller that starts answers such a
|
||||
// merge and registers its modules from their keys (answerHeldMerges).
|
||||
type heldFirstBuild struct {
|
||||
Module string `json:"module"`
|
||||
Path string `json:"path"`
|
||||
@@ -182,52 +192,6 @@ type heldFirstBuild struct {
|
||||
At time.Time `json:"at"`
|
||||
}
|
||||
|
||||
// judgeRefusedFirstBuild is the hold a first build's outcome puts its merge under: every module the merge holds already,
|
||||
// and this one, unless it is among them; nil unless the build failed on a word of its manifest the running build-agent
|
||||
// does not know, and was asked for a first merge, answered by the record of merges no plan answers or put back to waiting
|
||||
// by an earlier hold (a second refusal in one merge is held too, review of issue 1173).
|
||||
func judgeRefusedFirstBuild(result link.BuildResult, req inventory.BuildRequest, merge inventory.BatchedMerge) []heldFirstBuild {
|
||||
word := refusedWord(result.Failed)
|
||||
if word == "" || req.For != "merge" {
|
||||
return nil
|
||||
}
|
||||
var held []heldFirstBuild
|
||||
switch k := announcedOf(merge); {
|
||||
case merge.Plan == inventory.WaitingRecord:
|
||||
// Answered when its builds were asked: a hold from before (a build asked again) is over, and starts anew.
|
||||
case merge.Plan == "" && len(k.Held) > 0:
|
||||
held = k.Held
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
this := heldFirstBuild{Module: req.Name(), Path: strings.Trim(req.Path, "/"), Word: word, At: time.Now().UTC()}
|
||||
for i, h := range held {
|
||||
if strings.Trim(h.Path, "/") == this.Path {
|
||||
out := append([]heldFirstBuild(nil), held...)
|
||||
out[i] = this
|
||||
return out
|
||||
}
|
||||
}
|
||||
return append(append([]heldFirstBuild(nil), held...), this)
|
||||
}
|
||||
|
||||
// heldDirsOf is the directories a held merge asks again: its held modules' own, each once, "." for the repository's root.
|
||||
func heldDirsOf(k keptMerge) []string {
|
||||
var dirs []string
|
||||
seen := map[string]bool{}
|
||||
for _, h := range k.Held {
|
||||
dir := strings.Trim(h.Path, "/")
|
||||
if dir == "" {
|
||||
dir = "."
|
||||
}
|
||||
if !seen[dir] {
|
||||
seen[dir] = true
|
||||
dirs = append(dirs, dir)
|
||||
}
|
||||
}
|
||||
return dirs
|
||||
}
|
||||
|
||||
// unknownWord is how the strict reading of a manifest says a word is not one it knows: encoding/json's own words, which
|
||||
// the build-agent's answer carries whole. The build's answer has no typed field for it (Failed is the builder's words).
|
||||
var unknownWord = regexp.MustCompile(`json: unknown field "([^"]+)"`)
|
||||
@@ -240,55 +204,12 @@ func refusedWord(failed string) string {
|
||||
return ""
|
||||
}
|
||||
|
||||
// holdRefusedFirstBuild keeps a first build's merge waiting when the build was refused for a word the running build-agent
|
||||
// does not read (novox/hq ADR 0482 §5, issue 1173): no failed build is recorded, nothing expires the merge, and it is
|
||||
// named by a condition until a delivery of build-agent asks the build again. Answers whether the outcome was dealt with.
|
||||
func holdRefusedFirstBuild(ctx context.Context, inv *inventory.Inventory, result link.BuildResult) (bool, error) {
|
||||
if result.Failed == "" || refusedWord(result.Failed) == "" {
|
||||
return false, nil
|
||||
}
|
||||
req, found, err := inv.BuildRequestByID(ctx, result.ID)
|
||||
if err != nil || !found {
|
||||
return false, err
|
||||
}
|
||||
merge, found, err := inv.MergeOf(ctx, req.Repository, req.Commit)
|
||||
if err != nil || !found {
|
||||
return false, err
|
||||
}
|
||||
held := judgeRefusedFirstBuild(result, req, merge)
|
||||
if len(held) == 0 {
|
||||
return false, nil
|
||||
}
|
||||
k := announcedOf(merge)
|
||||
k.Held = held
|
||||
event, err := json.Marshal(k)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
if err := inv.HoldMerge(ctx, merge.Repository, merge.Commit, event); err != nil {
|
||||
return false, err
|
||||
}
|
||||
this := held[len(held)-1]
|
||||
for _, h := range held {
|
||||
if h.Path == strings.Trim(req.Path, "/") {
|
||||
this = h
|
||||
}
|
||||
}
|
||||
fmt.Printf("%s: the first build of %s was refused for the word %q, which the running build-agent does not read; the "+
|
||||
"merge %.8s of %s is kept waiting for a build-agent that reads it (holding %s), and the build is asked again when "+
|
||||
"one is delivered\n", result.ID, this.Module, this.Word, merge.Commit, merge.Repository, heldNames(held))
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// probeFirstBuildsID is the probe that reads the first merges waiting for mesh-delivery's word.
|
||||
const probeFirstBuildsID = "D18"
|
||||
|
||||
// kindFirstBuildUnasked is what a first merge waiting too long for mesh-delivery's word raises.
|
||||
const kindFirstBuildUnasked = "first-build-unasked"
|
||||
|
||||
// kindFirstBuildHeld is what a first merge held for a build-agent that reads its manifest raises.
|
||||
const kindFirstBuildHeld = "first-build-held"
|
||||
|
||||
// firstBuildWaitBound is how long a first merge waits for mesh-delivery's announcement of the operator's Approve before
|
||||
// it is said: a bound in the sense of ADR 0426 §3, not a delay. Nothing waits on it: the word's arrival asks the builds
|
||||
// at once, and the merge is said, never dropped, once it is past this (issue 1146).
|
||||
@@ -300,10 +221,6 @@ func firstBuildObservations(waiting []inventory.BatchedMerge, now time.Time) []c
|
||||
var out []conditions.Observation
|
||||
for _, w := range waiting {
|
||||
k := announcedOf(w)
|
||||
if len(k.Held) > 0 {
|
||||
out = append(out, heldFirstBuildObservation(k))
|
||||
continue
|
||||
}
|
||||
if !k.FirstBuild || now.Sub(w.Heard) <= firstBuildWaitBound {
|
||||
continue
|
||||
}
|
||||
@@ -318,108 +235,43 @@ func firstBuildObservations(waiting []inventory.BatchedMerge, now time.Time) []c
|
||||
Headline: fmt.Sprintf("The first build of %s is not asked: no Approve was announced", repository),
|
||||
Explanation: fmt.Sprintf("The merge %.8s into %s adds modules the mesh builds nothing from yet. It registers them "+
|
||||
"only when mesh-delivery announces that it merged the pull request on your Approve of that head, and no such "+
|
||||
"announcement has come in %s. The merge is kept, and the builds are asked the moment the announcement is kept.",
|
||||
"announcement has come in %s. The merge is kept, and the modules are registered from the builder's keys the moment "+
|
||||
"the announcement is kept.",
|
||||
k.Commit, repository, for_),
|
||||
Resolved: "mesh-delivery announces the merge, or a hand `build` registers the modules",
|
||||
Resolved: "mesh-delivery announces the merge, or the operator registers the modules at the controller's terminal",
|
||||
})
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// heldNames is the modules a merge holds, each with the word it waits for: "watchman (key-ttl), view (per-machine)".
|
||||
func heldNames(held []heldFirstBuild) string {
|
||||
names := make([]string, 0, len(held))
|
||||
for _, h := range held {
|
||||
names = append(names, fmt.Sprintf("%s (%q)", h.Module, h.Word))
|
||||
}
|
||||
return strings.Join(names, ", ")
|
||||
}
|
||||
|
||||
// heldFirstBuildObservation names a first merge held for a build-agent that reads its manifests: at once, since nothing
|
||||
// is lost and nothing times out, but the operator can see what the merge waits for. One condition names every module held.
|
||||
func heldFirstBuildObservation(k keptMerge) conditions.Observation {
|
||||
repository := k.Owner + "/" + k.Repo
|
||||
modules := make([]string, 0, len(k.Held))
|
||||
for _, h := range k.Held {
|
||||
modules = append(modules, h.Module)
|
||||
}
|
||||
held := heldNames(k.Held)
|
||||
return conditions.Observation{Scope: conditions.ScopeMerge, ID: fmt.Sprintf("%s_%.8s", repository, k.Commit),
|
||||
Kind: kindFirstBuildHeld, Severity: conditions.Warning,
|
||||
Summary: fmt.Sprintf("the merge %.8s of %s adds modules whose manifests use words the running build-agent does not "+
|
||||
"read: %s; the merge waits for a build-agent that reads them", k.Commit, repository, held),
|
||||
Said: fmt.Sprintf("%s at %s waits for a build-agent that reads the words of %s", repository, k.Commit, held),
|
||||
Headline: fmt.Sprintf("The first build of %s waits for a newer build-agent", strings.Join(modules, ", ")),
|
||||
Explanation: fmt.Sprintf("The merge %.8s into %s adds modules whose manifests use words the build-agent now "+
|
||||
"running does not know: %s. The merge is kept, not failed: their first builds are asked again when a delivery "+
|
||||
"of build-agent ends.", k.Commit, repository, held),
|
||||
Resolved: "a delivery of build-agent ends and the first builds register the modules, or a hand `build` does",
|
||||
}
|
||||
}
|
||||
|
||||
// probeFirstBuilds is D18: no first merge waits past its bound for mesh-delivery's word unsaid.
|
||||
func probeFirstBuilds(ctx context.Context, d *doctor) ([]conditions.Observation, error) {
|
||||
waiting, err := d.open.inventory.WaitingMerges(ctx)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return firstBuildObservations(waiting, time.Now().UTC()), nil
|
||||
now := time.Now().UTC()
|
||||
return append(firstBuildObservations(waiting, now), newModuleObservations(now)...), 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
|
||||
// answerHeldMerges answers every merge a controller of before held for a build-agent that reads its new modules'
|
||||
// manifests (issue 1173): nothing asks their first builds any more, and the look at every module when this controller
|
||||
// starts registers them from their keys (ADR 0501 §6, §7).
|
||||
func answerHeldMerges(ctx context.Context, inv *inventory.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
|
||||
fmt.Printf("the merges held for a newer build-agent could not be read: %v\n", err)
|
||||
return
|
||||
}
|
||||
asked := 0
|
||||
for _, w := range waiting {
|
||||
k := announcedOf(w)
|
||||
if len(k.Held) == 0 {
|
||||
continue
|
||||
}
|
||||
// Its own held modules, not the rule of a first merge: a module of the merge that passed is registered by now,
|
||||
// and the repository no longer counts as one the mesh builds nothing from (review of issue 1173).
|
||||
why, err := approvedAnnounced(ctx, inv, k.Owner, k.Repo, keptMerge{SourceMoved: k.SourceMoved})
|
||||
if err != nil {
|
||||
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
|
||||
}
|
||||
dirs := heldDirsOf(k)
|
||||
fmt.Printf("%s delivered build-agent: the first builds of %s are asked again\n", p.ID, heldNames(k.Held))
|
||||
paths := make([]string, 0, len(dirs))
|
||||
for _, dir := range dirs {
|
||||
if dir == "." {
|
||||
dir = ""
|
||||
if k := announcedOf(w); len(k.Held) > 0 {
|
||||
if err := inv.AnswerMerge(ctx, w.Repository, w.Commit, inventory.WaitingRecord, false); err != nil {
|
||||
fmt.Printf("the merge %.8s of %s held for a newer build-agent could not be answered: %v\n", w.Commit,
|
||||
w.Repository, err)
|
||||
continue
|
||||
}
|
||||
paths = append(paths, dir)
|
||||
fmt.Printf("the merge %.8s of %s was held for a newer build-agent; its modules are registered from the "+
|
||||
"builder's keys instead\n", w.Commit, w.Repository)
|
||||
}
|
||||
repository := k.Owner + "/" + k.Repo
|
||||
askBuilds(withFirstBuildWarrant(ctx, repository, paths, why), inv, k.SourceMoved,
|
||||
buildSource{Repository: repository, Seat: gitSeat}, dirs)
|
||||
// Answered: it no longer waits, so this end asks nothing twice; a refusal that comes again holds it anew.
|
||||
if err := inv.AnswerMerge(ctx, w.Repository, w.Commit, inventory.WaitingRecord, false); err != nil {
|
||||
fmt.Printf(" and the merge could not be marked answered: %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"
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user