package inventory import ( "strings" "testing" "time" ) // A walk counts a provisioned module applied on a machine only once its files are placed (novox/hq ADR 0362, design // 57 P6, issue 672). The walk of issue 582 (one module, three machines of the rest), with g14's send having handed // dunst to the provisioner: g14's own report comes 5 s after its send, shanks's, the last of the others, 15 s // after its own. const dunstTemplates = "sha256:" + "dddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddddd" func walkHandingDunst(t *testing.T) sendRest582 { t.Helper() c := walk582(t) s := c.walk.Modules["build-agent"].Rest["g14"] s.Provisioned = map[string]string{"build-agent": dunstTemplates} c.walk.Modules["build-agent"].Rest["g14"] = s return c } // provisioning is a walk's lookup with the machines' clients saying reports. func (w sendRest582) provisioning(reports map[string]ProvReport) AppliedLookup { return func(node string, send SentDeclaration, walkSent time.Time) (AppliedReport, bool) { r, ok := w.lookup(node, send, walkSent) return ProvisionedSend(node, send, r, ok, func(node, module string) (ProvReport, bool) { p, said := reports[node+"."+module] return p, said }) } } func restOf(w *WalkPhases, node string) RestReport { for _, r := range w.Rest { if r.Node == node { return r } } return RestReport{} } // The node-engine's report alone does not end the walk: it waits for prov.applied; with it, the walk ends, and // its delivery time runs to the later of the two reports. func TestAWalkWaitsForTheProvisionersReportAndEndsAtTheLater(t *testing.T) { c := walkHandingDunst(t) g14Sent := c.walk.Modules["build-agent"].Rest["g14"].At w := c.walk.WalkPhases(g14Sent.Add(time.Minute), c.provisioning(nil)) if w.End != nil || restOf(w, "g14").State != RestWaiting || !strings.Contains(phase(w, PhaseApply, -1).Said, "waiting for g14") { t.Fatalf("the walk did not wait for g14's prov.applied: end %v, g14 %+v, apply %+v", w.End, restOf(w, "g14"), phase(w, PhaseApply, -1)) } // A report on an older build of the templates is not this send's. older := map[string]ProvReport{"g14.build-agent": {Status: ProvApplied, Templates: "sha256:" + strings.Repeat("0", 64), At: g14Sent.Add(2 * time.Second)}} if w := c.walk.WalkPhases(g14Sent.Add(time.Minute), c.provisioning(older)); w.End != nil { t.Fatalf("a report on an older build ended the walk at %v", w.End) } placed := g14Sent.Add(50 * time.Second) // after shanks's report, 35 s after the judgement w = c.walk.WalkPhases(g14Sent.Add(time.Hour), c.provisioning(map[string]ProvReport{ "g14.build-agent": {Status: ProvApplied, Templates: dunstTemplates, At: placed}})) sumsUp(t, w) if w.End == nil || !w.End.Equal(placed) { t.Fatalf("the walk ends at %v, want the provisioner's report at %s: %+v", w.End, placed, w.Rest) } if g := restOf(w, "g14"); g.State != RestApplied || !g.Reported.Equal(placed) { t.Fatalf("g14 is not applied at its later report: %+v", g) } } // A failed prov.applied is the machine's failed apply, named in the walk's end with its reason. func TestAFailedProvisioningIsAFailedApply(t *testing.T) { c := walkHandingDunst(t) g14Sent := c.walk.Modules["build-agent"].Rest["g14"].At w := c.walk.WalkPhases(g14Sent.Add(time.Hour), c.provisioning(map[string]ProvReport{ "g14.build-agent": {Status: ProvFailed, Reason: "the templates cannot be fetched", At: g14Sent.Add(3 * time.Second)}})) if w.End != nil { t.Fatalf("a failed provisioning ended the walk: %v", w.End) } if g := restOf(w, "g14"); g.State != RestFailed || !strings.Contains(g.Outcome, "the templates cannot be fetched") { t.Fatalf("g14 is not named failed with the reason: %+v", g) } if said := phase(w, PhaseApply, -1).Said; !strings.Contains(said, "not applied on g14 (provisioning failed (build-agent: "+ "the templates cannot be fetched))") { t.Fatalf("the walk's end does not name g14's failure: %q", said) } // A failure said before the send, of no build, is not this send's. stale := map[string]ProvReport{"g14.build-agent": {Status: ProvFailed, Reason: "old", At: g14Sent.Add(-time.Hour)}} if g := restOf(c.walk.WalkPhases(g14Sent.Add(time.Minute), c.provisioning(stale)), "g14"); g.State != RestWaiting { t.Fatalf("a failure from before the send was read as this send's: %+v", g) } } // None within ApplySilentAfter names the machine silent, never counted applied; the others end the walk. func TestNoProvisioningReportWithinTheBoundIsSilence(t *testing.T) { c := walkHandingDunst(t) g14Sent := c.walk.Modules["build-agent"].Rest["g14"].At waiting := map[string]ProvReport{"g14.build-agent": {Status: ProvWaiting, WaitsFor: []string{"config:font-size"}, At: g14Sent.Add(3 * time.Second)}} w := c.walk.WalkPhases(g14Sent.Add(ApplySilentAfter+time.Minute), c.provisioning(waiting)) sumsUp(t, w) if g := restOf(w, "g14"); g.State != RestSilent { t.Fatalf("g14 is not silent past ApplySilentAfter: %+v", g) } if len(w.Silent) != 1 || w.Silent[0] != "g14" || w.End == nil || !w.End.Equal(c.judged.Add(35*time.Second)) { t.Fatalf("the walk does not end on the others, naming g14 silent: end %v, silent %v", w.End, w.Silent) } } // A send that handed nothing ends on the node-engine's report alone, as before. func TestASendThatHandedNothingEndsOnTheEnginesReport(t *testing.T) { c := walk582(t) before := c.walk.WalkPhases(c.judged.Add(time.Hour), c.lookup) now := c.walk.WalkPhases(c.judged.Add(time.Hour), c.provisioning(nil)) if before.End == nil || now.End == nil || !now.End.Equal(*before.End) { t.Fatalf("a walk handing nothing ends differently: before %v, now %v", before.End, now.End) } }