diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go b/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go index b1382a79..a97e47c7 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/holder.go @@ -306,7 +306,7 @@ type entry struct { func (h *Holder) outcome(key string, p *provisions.Provision) { p.Ask, p.Warrant = "", nil if !keepsEntries(p.Provider.Module) { - p.State, p.Why = provisions.Asked, p.Provider.Module+" writes no outcome yet: provided once it says it in its own entry" + h.haveOutcome(p) return } e, err := h.Store.Get(boundRead(p.Provider.Module), key) @@ -343,6 +343,43 @@ func (h *Holder) outcome(key string, p *provisions.Provision) { } } +// haveOutcome is the outcome of a provider that makes no credential per consumer: its own have on its node (the lead's +// reading of ADR 0570 clause 3, recorded as an insight). Running with health ok at the build its want names is +// provided; failed is failed with the have's words; anything else is asked, waiting for the provider to run. +func (h *Holder) haveOutcome(p *provisions.Provision) { + where := p.Provider.Module + " on " + p.Provider.Node + key := p.Provider.Node + "." + p.Provider.Module + e, err := h.Store.Get(HaveRead, key) + if err != nil { + p.State, p.Why = provisions.Asked, "the have of "+where+" cannot be read: "+err.Error() + return + } + if e == nil { + p.State, p.Why = provisions.Asked, "waits for "+where+" to run: it says nothing yet" + return + } + have, err := delivery.DecodeHave(e.Value) + if err != nil { + p.State, p.Why = provisions.Asked, "the have of "+where+" is not readable" + return + } + wantFP := "" + if we, err := h.Store.Get(WantsRead, "want."+key); err == nil && we != nil { + if w, err := delivery.DecodeWant(we.Value); err == nil && !w.Removed { + wantFP = w.FP + } + } + switch { + case have.Phase == delivery.PhaseFailed || have.Health == delivery.HealthFailed: + p.State, p.Why = provisions.Failed, orSaid(have.Detail, where+" failed") + case have.Phase == delivery.PhaseRunning && have.Health == delivery.HealthOK && (wantFP == "" || have.FP == wantFP): + p.State, p.Why = provisions.Provided, "" + default: + p.State, p.Why = provisions.Asked, fmt.Sprintf("waits for %s to run healthy at the build its want names (it is %s, "+ + "health %s)", where, have.Phase, have.Health) + } +} + func keepsEntries(module string) bool { for _, m := range Providers { if m == module { diff --git a/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go b/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go index 398a338f..4c319ee1 100644 --- a/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go +++ b/modules/mesh-provisioner/cmd/mesh-provisioner/holder_test.go @@ -140,9 +140,26 @@ func TestAKeysStateFollowsItsProvidersOutcome(t *testing.T) { if p := m.state(t, key); p.Reference == nil || p.Reference.ID != "pair/gitea-postgres" || p.Reference.Generation != 3 { t.Errorf("the vault's generation does not reach the reference: %+v", p.Reference) } - // The holder never sets provided by itself: a provider writing no outcome leaves its key asked (clause 3). - if p := m.state(t, "g14.gitea.split-dns"); p.State != provisions.Asked || !strings.Contains(p.Why, "writes no outcome") { - t.Errorf("a provider writing no outcome: %+v", p) + // A provider making no credential per consumer reports through its own have (clause 3): never provided by the + // holder alone. + split := "g14.gitea.split-dns" + for _, row := range []struct{ have, want, state, why string }{ + {"", "", provisions.Asked, "says nothing yet"}, + {`{"v":1,"fp":"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","phase":"applying","health":"unknown"}`, "", provisions.Asked, "applying"}, + {`{"v":1,"fp":"src2:aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa","phase":"running","health":"ok"}`, `{"v":1,"fp":"src2:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","self":true}`, provisions.Asked, "build its want"}, + {`{"v":1,"fp":"src2:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","phase":"failed","health":"failed","detail":"the unit did not start"}`, "", provisions.Failed, "did not start"}, + {`{"v":1,"fp":"src2:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","phase":"running","health":"ok"}`, `{"v":1,"fp":"src2:bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb","self":true}`, provisions.Provided, ""}, + } { + if row.have != "" { + m.s.write(t, HaveRead, "g14.systemd-resolved", row.have) + } + if row.want != "" { + m.s.write(t, WantsRead, "want.g14.systemd-resolved", row.want) + } + m.compose(t, "g14") + if p := m.state(t, split); p.State != row.state || !strings.Contains(p.Why, row.why) { + t.Errorf("the provider's have %s: %+v, want %s", row.have, p, row.state) + } } if e, _ := m.s.Get(PlansState, "g14.gitea"); e == nil { t.Error("the plan is not written")