Merge pull request 'The delivery view names a lagging node's builds by fingerprint and what moved, and says a failed build as failed (issue 1966)' (#194) from fix/1966-view-names-builds into main
This commit was merged in pull request #194.
This commit is contained in:
@@ -10,6 +10,7 @@ import (
|
|||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshdeliveryclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
|
||||||
"git.novox.be/novox/mesh-delivery/sdk/trunk"
|
"git.novox.be/novox/mesh-delivery/sdk/trunk"
|
||||||
)
|
)
|
||||||
@@ -195,6 +196,7 @@ func (m *Model) moduleReach(name string, mod meshcatalog.ModulesByModuleValue, h
|
|||||||
if n := len(hist); n == 0 || hist[n-1].Fp2 != mod.Fp2 || hist[n-1].Commit != mod.Commit {
|
if n := len(hist); n == 0 || hist[n-1].Fp2 != mod.Fp2 || hist[n-1].Commit != mod.Commit {
|
||||||
hist = append(slices.Clone(hist), mod) // the value watched now is the newest, whatever the history watch has given
|
hist = append(slices.Clone(hist), mod) // the value watched now is the newest, whatever the history watch has given
|
||||||
}
|
}
|
||||||
|
var values []meshcatalog.ModulesByModuleValue // the catalogue's value of each change, in step with mr.Changes
|
||||||
for i := len(hist) - 1; i >= 0 && len(mr.Changes) < viewstate.MostChanges; i-- {
|
for i := len(hist) - 1; i >= 0 && len(mr.Changes) < viewstate.MostChanges; i-- {
|
||||||
c := viewstate.Change{Commit: hist[i].Commit, FP: hist[i].Fp2, Src: uint64(hist[i].Src)}
|
c := viewstate.Change{Commit: hist[i].Commit, FP: hist[i].Fp2, Src: uint64(hist[i].Src)}
|
||||||
if c.Src == 0 {
|
if c.Src == 0 {
|
||||||
@@ -202,9 +204,11 @@ func (m *Model) moduleReach(name string, mod meshcatalog.ModulesByModuleValue, h
|
|||||||
}
|
}
|
||||||
if n := len(mr.Changes); n > 0 && mr.Changes[n-1].FP == c.FP {
|
if n := len(mr.Changes); n > 0 && mr.Changes[n-1].FP == c.FP {
|
||||||
mr.Changes[n-1] = c // the same fingerprint again, back to back: its first appearance is the older commit
|
mr.Changes[n-1] = c // the same fingerprint again, back to back: its first appearance is the older commit
|
||||||
|
values[n-1] = hist[i]
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
mr.Changes = append(mr.Changes, c)
|
mr.Changes = append(mr.Changes, c)
|
||||||
|
values = append(values, hist[i])
|
||||||
}
|
}
|
||||||
var waiting []string
|
var waiting []string
|
||||||
away := m.awayNodes()
|
away := m.awayNodes()
|
||||||
@@ -264,7 +268,7 @@ func (m *Model) moduleReach(name string, mod meshcatalog.ModulesByModuleValue, h
|
|||||||
return mr, noBound, ""
|
return mr, noBound, ""
|
||||||
}
|
}
|
||||||
next := mr.Changes[floor-1]
|
next := mr.Changes[floor-1]
|
||||||
words := fmt.Sprintf("%s on %s runs the build of %.12s, not yet that of %.12s", name, floorNode, mr.Floor, next.Commit)
|
words := m.laggingWords(name, floorNode, mr.Changes[floor], next, values[floor], values[floor-1], floor == 1)
|
||||||
if next.Src == 0 {
|
if next.Src == 0 {
|
||||||
// The next change's order is not known: the head is held at the floor's own revision, or before every head when that is
|
// The next change's order is not known: the head is held at the floor's own revision, or before every head when that is
|
||||||
// not known either.
|
// not known either.
|
||||||
@@ -276,6 +280,90 @@ func (m *Model) moduleReach(name string, mod meshcatalog.ModulesByModuleValue, h
|
|||||||
return mr, next.Src, words
|
return mr, next.Src, words
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// laggingWords says what a node's running build lacks of the module's next change: both builds by fingerprint with their
|
||||||
|
// commits, which input moved when the commit is the same (the catalogue holds the module's inputs, not the version a mesh
|
||||||
|
// package resolved to), and a next build that failed, with the first words of its error, instead of "not yet".
|
||||||
|
func (m *Model) laggingWords(name, node string, run, next viewstate.Change, runV, nextV meshcatalog.ModulesByModuleValue, newest bool) string {
|
||||||
|
words := fmt.Sprintf("%s on %s runs build %s (%.8s)", name, node, shortFP(run.FP), run.Commit)
|
||||||
|
nextIs := fmt.Sprintf("%s (%.8s)", shortFP(next.FP), next.Commit)
|
||||||
|
if run.Commit == next.Commit {
|
||||||
|
if moved := inputsMoved(runV, nextV); moved != "" {
|
||||||
|
nextIs = fmt.Sprintf("%s (%.8s, %s)", shortFP(next.FP), next.Commit, moved)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if key, err := builds.BuilderBuildKey(name, next.FP); err == nil {
|
||||||
|
if k, ok := m.keys[key]; ok && k.err == "" && k.value.State == builds.BuildFailed {
|
||||||
|
which := "the build it waits for"
|
||||||
|
if newest {
|
||||||
|
which = "its newest build"
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s, %s %s failed: %s", words, which, nextIs, firstWords(k.value.Error))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%s, not yet %s", words, nextIs)
|
||||||
|
}
|
||||||
|
|
||||||
|
// inputsMoved names what of a module's inputs differs between two values with the same commit, or "".
|
||||||
|
func inputsMoved(a, b meshcatalog.ModulesByModuleValue) string {
|
||||||
|
if a.Inputs2 == nil || b.Inputs2 == nil {
|
||||||
|
return ""
|
||||||
|
}
|
||||||
|
x, y := a.Inputs2, b.Inputs2
|
||||||
|
var moved []string
|
||||||
|
if x.Tree != y.Tree {
|
||||||
|
moved = append(moved, "tree")
|
||||||
|
}
|
||||||
|
for _, name := range slices.Sorted(maps.Keys(mergedKeys(x.Locks, y.Locks))) {
|
||||||
|
if x.Locks[name] != y.Locks[name] {
|
||||||
|
moved = append(moved, "lock "+name)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !slices.Equal(x.Bases, y.Bases) {
|
||||||
|
moved = append(moved, "a base module")
|
||||||
|
}
|
||||||
|
if len(moved) == 0 && x.Builder != y.Builder {
|
||||||
|
moved = append(moved, "builder")
|
||||||
|
}
|
||||||
|
if len(moved) == 0 && len(y.Packages) > 0 {
|
||||||
|
// Nothing of the module's own moved: a mesh package it resolves at its latest published version did.
|
||||||
|
moved = append(moved, "a mesh package of "+strings.Join(y.Packages, ", "))
|
||||||
|
}
|
||||||
|
return strings.Join(moved, ", ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func mergedKeys(a, b map[string]string) map[string]bool {
|
||||||
|
out := map[string]bool{}
|
||||||
|
for k := range a {
|
||||||
|
out[k] = true
|
||||||
|
}
|
||||||
|
for k := range b {
|
||||||
|
out[k] = true
|
||||||
|
}
|
||||||
|
return out
|
||||||
|
}
|
||||||
|
|
||||||
|
// firstWords is the start of an error, up to eighty characters cut at a word.
|
||||||
|
func firstWords(s string) string {
|
||||||
|
s = strings.Join(strings.Fields(s), " ")
|
||||||
|
if len(s) <= 80 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
s = s[:80]
|
||||||
|
if i := strings.LastIndexByte(s, ' '); i > 0 {
|
||||||
|
s = s[:i]
|
||||||
|
}
|
||||||
|
return s + "..."
|
||||||
|
}
|
||||||
|
|
||||||
|
// shortFP is a fingerprint's first eight characters, after its form.
|
||||||
|
func shortFP(fp string) string {
|
||||||
|
fp = strings.TrimPrefix(fp, meshcatalogclient.SourcePrefix2)
|
||||||
|
if len(fp) > 8 {
|
||||||
|
fp = fp[:8]
|
||||||
|
}
|
||||||
|
return fp
|
||||||
|
}
|
||||||
|
|
||||||
// runs is whether a have in this phase runs the fingerprint it names: running, being checked after its apply, or held after a
|
// runs is whether a have in this phase runs the fingerprint it names: running, being checked after its apply, or held after a
|
||||||
// failed backup, which keeps running what it has (ADR 0539 clause 16). A have backing up or applying, failed or removed runs
|
// failed backup, which keeps running what it has (ADR 0539 clause 16). A have backing up or applying, failed or removed runs
|
||||||
// nothing the derivation can count.
|
// nothing the derivation can count.
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ import (
|
|||||||
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshwantsclient"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
|
||||||
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
"git.novox.be/novox/mesh-delivery/contracts/go/seats/meshdelivery"
|
||||||
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
|
||||||
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
|
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-view/viewstate"
|
||||||
"git.novox.be/novox/mesh-delivery/sdk/trunk"
|
"git.novox.be/novox/mesh-delivery/sdk/trunk"
|
||||||
@@ -31,6 +32,22 @@ func TestTheViewDerivesHowFarEachRepositoryReached(t *testing.T) {
|
|||||||
}
|
}
|
||||||
return Entry{Bucket: ModulesBucket, Key: module, Value: v, At: t0, History: history}
|
return Entry{Bucket: ModulesBucket, Key: module, Value: v, At: t0, History: history}
|
||||||
}
|
}
|
||||||
|
modIn := func(fp, commit string, src uint64, in *meshcatalog.ModulesByModuleValueInputs2, history bool) Entry {
|
||||||
|
v, err := meshcatalogclient.EncodeModule(meshcatalog.ModulesByModuleValue{Fp2: fp, Manifest: strings.Repeat("a", 64), Repository: web, Commit: commit, Src: int64(src), Path: "web", Inputs2: in})
|
||||||
|
if err != nil {
|
||||||
|
t.Fatal(err)
|
||||||
|
}
|
||||||
|
return Entry{Bucket: ModulesBucket, Key: "web", Value: v, At: t0, History: history}
|
||||||
|
}
|
||||||
|
in := func(tree string, pkgs ...string) *meshcatalog.ModulesByModuleValueInputs2 {
|
||||||
|
return &meshcatalog.ModulesByModuleValueInputs2{Tree: tree, Packages: pkgs}
|
||||||
|
}
|
||||||
|
// A later build of web (fpLater) that g14, running fpNew, lacks; issue 1966: the hold names both builds, what moved when the
|
||||||
|
// commit is the same, and a build that failed as failed.
|
||||||
|
const fpLater = "src2:9999999999999999999999999999999999999999999999999999999999999999"
|
||||||
|
later := func(commit string, to *meshcatalog.ModulesByModuleValueInputs2, extra ...Entry) []Entry {
|
||||||
|
return append([]Entry{modIn(fpLater, commit, 12, to, true), modIn(fpLater, commit, 12, to, false)}, extra...)
|
||||||
|
}
|
||||||
src := func(rev uint64, webHead string) Entry {
|
src := func(rev uint64, webHead string) Entry {
|
||||||
v, err := meshdeliveryclient.EncodeSource(meshdelivery.SourceByMainValue{Group: "g", Heads: map[string]string{web: webHead, sdk: commitS}})
|
v, err := meshdeliveryclient.EncodeSource(meshdelivery.SourceByMainValue{Group: "g", Heads: map[string]string{web: webHead, sdk: commitS}})
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -58,8 +75,8 @@ func TestTheViewDerivesHowFarEachRepositoryReached(t *testing.T) {
|
|||||||
current := Entry{Bucket: SourceBucket, Key: meshdeliveryclient.SourceMainKey, Revision: 12, At: t0}
|
current := Entry{Bucket: SourceBucket, Key: meshdeliveryclient.SourceMainKey, Revision: 12, At: t0}
|
||||||
current.Value, _ = meshdeliveryclient.EncodeSource(cur)
|
current.Value, _ = meshdeliveryclient.EncodeSource(cur)
|
||||||
return []Entry{src(10, commitA), src(11, commitB), src(12, commitC), current,
|
return []Entry{src(10, commitA), src(11, commitB), src(12, commitC), current,
|
||||||
mod("web", fpOld, commitA, 10, true), mod("web", fpNew, commitC, 12, true), mod("mail", fpMail, commitA, 0, true),
|
mod("web", fpOld, commitA, 10, true), modIn(fpNew, commitC, 12, in("t1", "mesh-sdk/go"), true), mod("mail", fpMail, commitA, 0, true),
|
||||||
mod("web", fpNew, commitC, 12, false), mod("mail", fpMail, commitA, 0, false),
|
modIn(fpNew, commitC, 12, in("t1", "mesh-sdk/go"), false), mod("mail", fpMail, commitA, 0, false),
|
||||||
taken(web, commitC), taken(sdk, commitS),
|
taken(web, commitC), taken(sdk, commitS),
|
||||||
wantE(t, "ace", "web", meshwantsclient.Want{Fp: fpNew}), haveFP(t, "ace", "web", fpNew, nodeengineclient.HealthOK),
|
wantE(t, "ace", "web", meshwantsclient.Want{Fp: fpNew}), haveFP(t, "ace", "web", fpNew, nodeengineclient.HealthOK),
|
||||||
wantE(t, "g14", "web", meshwantsclient.Want{Fp: fpNew}), haveFP(t, "g14", "web", fpNew, nodeengineclient.HealthOK),
|
wantE(t, "g14", "web", meshwantsclient.Want{Fp: fpNew}), haveFP(t, "g14", "web", fpNew, nodeengineclient.HealthOK),
|
||||||
@@ -82,8 +99,22 @@ func TestTheViewDerivesHowFarEachRepositoryReached(t *testing.T) {
|
|||||||
then: []Entry{haveFP(t, "g14", "web", fpOld, nodeengineclient.HealthOK), presenceE(t, "g14", false, t0)}},
|
then: []Entry{haveFP(t, "g14", "web", fpOld, nodeengineclient.HealthOK), presenceE(t, "g14", false, t0)}},
|
||||||
{name: "a node returned lagging holds the head again", reached: commitB, holds: "web on g14", waiting: "not yet the",
|
{name: "a node returned lagging holds the head again", reached: commitB, holds: "web on g14", waiting: "not yet the",
|
||||||
then: []Entry{haveFP(t, "g14", "web", fpOld, nodeengineclient.HealthOK), presenceE(t, "g14", false, t0), presenceE(t, "g14", true, t0)}},
|
then: []Entry{haveFP(t, "g14", "web", fpOld, nodeengineclient.HealthOK), presenceE(t, "g14", false, t0), presenceE(t, "g14", true, t0)}},
|
||||||
{name: "a want held back at an older build holds the head as a lag does", reached: commitB, holds: "web on g14 runs the build of",
|
{name: "a want held back at an older build holds the head as a lag does", reached: commitB, holds: "web on g14 runs build",
|
||||||
then: []Entry{wantE(t, "g14", "web", meshwantsclient.Want{Fp: fpOld, Newest: fpNew, Rule: 2}), haveFP(t, "g14", "web", fpOld, nodeengineclient.HealthOK)}},
|
then: []Entry{wantE(t, "g14", "web", meshwantsclient.Want{Fp: fpOld, Newest: fpNew, Rule: 2}), haveFP(t, "g14", "web", fpOld, nodeengineclient.HealthOK)}},
|
||||||
|
{name: "a later build of the same commit names the mesh package that moved", reached: commitB,
|
||||||
|
holds: "web on ace runs build 22222222 (cccccccc), not yet 99999999 (cccccccc, a mesh package of mesh-sdk/go)",
|
||||||
|
then: later(commitC, in("t1", "mesh-sdk/go"))},
|
||||||
|
{name: "a later build of the same commit names the tree that moved", reached: commitB,
|
||||||
|
holds: "runs build 22222222 (cccccccc), not yet 99999999 (cccccccc, tree)", then: later(commitC, in("t2", "mesh-sdk/go"))},
|
||||||
|
{name: "a later build of another commit names no input", reached: commitB,
|
||||||
|
holds: "runs build 22222222 (cccccccc), not yet 99999999 (eeeeeeee)", then: later(commitX, in("t2"))},
|
||||||
|
{name: "the newest build, failed, is said failed with its first words", reached: commitB,
|
||||||
|
holds: "its newest build 99999999 (eeeeeeee) failed: go build ./...: undefined: Foo", then: later(commitX, in("t2"),
|
||||||
|
builderKey(t, "web", fpLater, builds.BuilderBuild{State: builds.BuildFailed, Error: "go build ./...: undefined: Foo"}))},
|
||||||
|
{name: "a failed build that is not the newest is the build the node waits for", reached: commitB,
|
||||||
|
holds: "the build it waits for 99999999 (eeeeeeee) failed: go build", then: append(later(commitX, in("t2"),
|
||||||
|
builderKey(t, "web", fpLater, builds.BuilderBuild{State: builds.BuildFailed, Error: "go build ./...: undefined: Foo"})),
|
||||||
|
modIn("src2:"+strings.Repeat("8", 64), commitD, 12, in("t3"), true), modIn("src2:"+strings.Repeat("8", 64), commitD, 12, in("t3"), false))},
|
||||||
{name: "a failing health check is said and holds nothing", reached: commitC, waiting: "health check failed",
|
{name: "a failing health check is said and holds nothing", reached: commitC, waiting: "health check failed",
|
||||||
then: []Entry{haveFP(t, "g14", "web", fpNew, nodeengineclient.HealthFailed)}},
|
then: []Entry{haveFP(t, "g14", "web", fpNew, nodeengineclient.HealthFailed)}},
|
||||||
{name: "a node with no running have holds no head back and its module's merges are not yet", reached: commitC, waiting: "not running",
|
{name: "a node with no running have holds no head back and its module's merges are not yet", reached: commitC, waiting: "not running",
|
||||||
|
|||||||
Reference in New Issue
Block a user