assigner, view, watchman: provisions come from meshprovisionerclient, not mesh-sdk go/provisions (ADR 0634 §4; issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 16:45:25 +01:00
parent e52d9d7791
commit 738c8ca036
15 changed files with 91 additions and 84 deletions
@@ -37,13 +37,13 @@ import (
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
mmac "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
mma "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/announce"
"git.novox.be/novox/mesh-sdk/go/buscompose"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/health"
"git.novox.be/novox/mesh-sdk/go/process"
"git.novox.be/novox/mesh-sdk/go/provisions"
"mesh-delivery-assigner/internal/judge"
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
@@ -526,15 +526,15 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
appDigest := b.manifest(t, "app", `{"module":"app","version":"1","requires":["database"]}`)
toolDigest := b.manifest(t, "tool", `{"module":"tool","version":"1"}`)
asked := func(state, why string) {
raw, err := provisions.Encode(provisions.Provision{V: provisions.Version, Provision: "database", Consumer: provisions.End{Module: "app", Node: "ace"},
Provider: provisions.End{Module: "db", Node: "ace"}, State: state, Why: why, At: time.Now()})
raw, err := meshprovisionerclient.Encode(meshprovisionerclient.Provision{V: meshprovisionerclient.Version, Provision: "database", Consumer: meshprovisionerclient.End{Module: "app", Node: "ace"},
Provider: meshprovisionerclient.End{Module: "db", Node: "ace"}, State: state, Why: why, At: time.Now()})
if err != nil {
t.Fatal(err)
}
b.putJSON(t, provisions.ProvisionsBucket, "ace.app.database", raw)
b.putJSON(t, meshprovisionerclient.ProvisionsBucket, "ace.app.database", raw)
}
// The provisioner keys it from the manifests, as soon as they are known and whether the provider is placed yet or not.
asked(provisions.Asked, "the provider's build is not done")
asked(meshprovisionerclient.Asked, "the provider's build is not done")
fpOf := func(c byte) string { return meshcatalogclient.SourcePrefix2 + strings.Repeat(string(c), 64) }
toolOld := fpOf('1')
for module, row := range map[string]struct {
@@ -565,7 +565,7 @@ func TestWantsAreWrittenAndNoDifferenceIs(t *testing.T) {
if e, err := b.buckets[buckets.Wants].Get(b.ctx, "want.ace.app"); err == nil {
t.Errorf("app was wanted while its provision was asked: %s", e.Value())
}
asked(provisions.Provided, "")
asked(meshprovisionerclient.Provided, "")
if w := b.wantNow(t, "ace", "app", "the consumer's want once its provision is provided", func(w mwc.Want) bool { return w.Fp == fpOf('3') }); w.Fp != fpOf('3') {
t.Errorf("app's want = %+v", w)
}
@@ -26,10 +26,10 @@ import (
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
mma "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
"git.novox.be/novox/mesh-sdk/go/announce"
"git.novox.be/novox/mesh-sdk/go/health"
"git.novox.be/novox/mesh-sdk/go/process"
"git.novox.be/novox/mesh-sdk/go/provisions"
"git.novox.be/novox/mesh-delivery/sdk/enginereads"
"git.novox.be/novox/mesh-sdk/go/views"
@@ -64,7 +64,7 @@ var readBuckets = []string{
buckets.Builds,
buckets.Presence,
buckets.Have,
provisions.ProvisionsBucket,
meshprovisionerclient.ProvisionsBucket,
buckets.Source,
buckets.Merges,
}
@@ -140,7 +140,7 @@ func open(ctx context.Context, js process.BucketOpener, logf func(string, ...any
logf("the bucket %s cannot be opened (%v): it is bound once the bus says it was made", name, err)
continue
}
if name == provisions.ProvisionsBucket && err != nil {
if name == meshprovisionerclient.ProvisionsBucket && err != nil {
// The seat mesh-provisioner's state, with its own delivery (ADR 0570 §4): bound once the bus says it was made;
// until then no want waits for a provision, as before.
logf("the bucket %s cannot be opened (%v): it is bound once the bus says it was made", name, err)
@@ -187,7 +187,7 @@ func open(ctx context.Context, js process.BucketOpener, logf func(string, ...any
// states and those new with a delivery of their own, bound once made, and the two that choose the grants' source.
func startsWithout(name string) bool {
switch name {
case plc.SeatHoldersBucket, provisions.ProvisionsBucket, assign.PoliciesBucket, assign.FindingsBucket,
case plc.SeatHoldersBucket, meshprovisionerclient.ProvisionsBucket, assign.PoliciesBucket, assign.FindingsBucket,
buckets.Puts, buckets.Source, buckets.Merges, engineReadsBucket:
return true
}
@@ -282,7 +282,7 @@ var watched = []string{
buckets.Differences,
buckets.Wants,
assign.PoliciesBucket,
provisions.ProvisionsBucket,
meshprovisionerclient.ProvisionsBucket,
plc.SeatHoldersBucket,
buckets.Source,
buckets.Merges,
@@ -428,7 +428,7 @@ func follow(ctx context.Context, js jetstream.JetStream, nc process.Subscriber,
}
}
// And the two that choose where grants are read from, with the same delivery as the grants part that writes one.
for _, name := range []string{buckets.Grants, assign.PoliciesBucket, provisions.ProvisionsBucket, plc.SeatHoldersBucket} {
for _, name := range []string{buckets.Grants, assign.PoliciesBucket, meshprovisionerclient.ProvisionsBucket, plc.SeatHoldersBucket} {
if kvs[name] == nil && js != nil {
name := name
process.AwaitBucket(ctx, js, nc, name, "until then the bucket is not read",
@@ -19,8 +19,8 @@ import (
"time"
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/provisions"
"git.novox.be/novox/mesh-delivery/sdk/enginereads"
@@ -940,37 +940,37 @@ func chosenOf(chosen map[string]catalogue.PersonPolicy, module string) *catalogu
// mesh-provisioner's state; nil while the state is not read (the seat not delivered yet), so no want waits on it. A failed
// provision is also said as the assigner's finding `provider.<key>.provision-failed` (a waiting one is the watchman's
// needs-operator), deleted once it is no longer failed.
func (p *passer) provisionsOf() func(module string) func(node string) map[string]provisions.Provision {
if p.kvs[provisions.ProvisionsBucket] == nil {
return func(string) func(string) map[string]provisions.Provision { return nil }
func (p *passer) provisionsOf() func(module string) func(node string) map[string]meshprovisionerclient.Provision {
if p.kvs[meshprovisionerclient.ProvisionsBucket] == nil {
return func(string) func(string) map[string]meshprovisionerclient.Provision { return nil }
}
by := map[string]map[string]map[string]provisions.Provision{} // module, node, key
failed := map[string]provisions.Provision{}
for key, raw := range p.w.read(provisions.ProvisionsBucket) {
pr, err := provisions.Decode(raw)
by := map[string]map[string]map[string]meshprovisionerclient.Provision{} // module, node, key
failed := map[string]meshprovisionerclient.Provision{}
for key, raw := range p.w.read(meshprovisionerclient.ProvisionsBucket) {
pr, err := meshprovisionerclient.Decode(raw)
if err != nil {
continue
}
node, module := pr.Consumer.Node, pr.Consumer.Module
if by[module] == nil {
by[module] = map[string]map[string]provisions.Provision{}
by[module] = map[string]map[string]meshprovisionerclient.Provision{}
}
if by[module][node] == nil {
by[module][node] = map[string]provisions.Provision{}
by[module][node] = map[string]meshprovisionerclient.Provision{}
}
by[module][node][key] = pr
if pr.State == provisions.Failed {
if pr.State == meshprovisionerclient.Failed {
failed[key] = pr
}
}
p.provisionFindings(failed)
return func(module string) func(string) map[string]provisions.Provision {
return func(node string) map[string]provisions.Provision { return by[module][node] }
return func(module string) func(string) map[string]meshprovisionerclient.Provision {
return func(node string) map[string]meshprovisionerclient.Provision { return by[module][node] }
}
}
// provisionFindings writes the finding of each failed provision and deletes the ones no longer failed.
func (p *passer) provisionFindings(failed map[string]provisions.Provision) {
func (p *passer) provisionFindings(failed map[string]meshprovisionerclient.Provision) {
if p.findings.Put == nil {
return
}
@@ -20,10 +20,10 @@ import (
mmac "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
mma "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/health"
"git.novox.be/novox/mesh-sdk/go/provisions"
"github.com/nats-io/nats.go/jetstream"
"mesh-delivery-assigner/internal/assign"
@@ -325,23 +325,23 @@ func TestTheSeatsVerbs(t *testing.T) {
})
t.Run("1954/D1: a failed provision is the assigner's finding until it is provided", func(t *testing.T) {
findings := map[string][]byte{}
p := &passer{w: newWorld(), logf: func(string, ...any) {}, kvs: kvSet{provisions.ProvisionsBucket: nil}, findings: assign.Findings{
p := &passer{w: newWorld(), logf: func(string, ...any) {}, kvs: kvSet{meshprovisionerclient.ProvisionsBucket: nil}, findings: assign.Findings{
Put: func(k string, v []byte) error { findings[k] = v; return nil },
Delete: func(k string) error { delete(findings, k); return nil }}}
failed, _ := provisions.Encode(provisions.Provision{Provision: "db", Consumer: provisions.End{Module: "photos", Node: "ace"},
Provider: provisions.End{Module: "postgres", Node: "novox"}, State: provisions.Failed, Why: "the role exists already"})
p.w.putEntry(provisions.ProvisionsBucket, "ace.photos.db", failed, 1, time.Now())
p.kvs[provisions.ProvisionsBucket] = &memKV{}
failed, _ := meshprovisionerclient.Encode(meshprovisionerclient.Provision{Provision: "db", Consumer: meshprovisionerclient.End{Module: "photos", Node: "ace"},
Provider: meshprovisionerclient.End{Module: "postgres", Node: "novox"}, State: meshprovisionerclient.Failed, Why: "the role exists already"})
p.w.putEntry(meshprovisionerclient.ProvisionsBucket, "ace.photos.db", failed, 1, time.Now())
p.kvs[meshprovisionerclient.ProvisionsBucket] = &memKV{}
of := p.provisionsOf()
if got := of("photos")("ace"); got["ace.photos.db"].State != provisions.Failed {
if got := of("photos")("ace"); got["ace.photos.db"].State != meshprovisionerclient.Failed {
t.Fatalf("provisions = %+v", got)
}
if _, ok := findings[assign.ProvisionFailedKey("ace.photos.db")]; !ok || len(findings) != 1 {
t.Fatalf("findings = %v", findings)
}
provided, _ := provisions.Encode(provisions.Provision{Provision: "db", Consumer: provisions.End{Module: "photos", Node: "ace"},
Provider: provisions.End{Module: "postgres", Node: "novox"}, State: provisions.Provided})
p.w.putEntry(provisions.ProvisionsBucket, "ace.photos.db", provided, 2, time.Now())
provided, _ := meshprovisionerclient.Encode(meshprovisionerclient.Provision{Provision: "db", Consumer: meshprovisionerclient.End{Module: "photos", Node: "ace"},
Provider: meshprovisionerclient.End{Module: "postgres", Node: "novox"}, State: meshprovisionerclient.Provided})
p.w.putEntry(meshprovisionerclient.ProvisionsBucket, "ace.photos.db", provided, 2, time.Now())
p.provisionsOf()
if len(findings) != 0 {
t.Errorf("findings after provided = %v", findings)
+1
View File
@@ -3,6 +3,7 @@ module mesh-delivery-assigner
go 1.26.0
require (
git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient v0.0.0
git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants v0.0.0-00010101000000-000000000000
@@ -10,9 +10,9 @@ import (
"time"
occ "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/clients/operatorchannelclient"
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/health"
"git.novox.be/novox/mesh-sdk/go/provisions"
)
// A person's upgrade policy is the assigner's (novox/hq ADR 0565 §7, §8, issue 1953 D4, D5): kept in its state `policies`, one
@@ -65,7 +65,7 @@ func ProvisionFailedKey(key string) string {
}
// ProvisionFailedFinding is the finding of a failed provision, naming its consumer, its provider and why.
func ProvisionFailedFinding(key string, p provisions.Provision, at time.Time) ([]byte, error) {
func ProvisionFailedFinding(key string, p meshprovisionerclient.Provision, at time.Time) ([]byte, error) {
summary := fmt.Sprintf("%s for %s on %s failed at %s on %s: %s, so %s is not moved there until it is provided", p.Provision,
p.Consumer.Module, p.Consumer.Node, p.Provider.Module, p.Provider.Node, p.Why, p.Consumer.Module)
return health.EncodeFinding(health.Finding{Key: ProvisionFailedKey(key), Kind: KindProvisionFailed,
@@ -14,8 +14,8 @@ import (
"strings"
"time"
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/provisions"
"git.novox.be/novox/mesh-delivery/sdk/forms"
"git.novox.be/novox/mesh-delivery/sdk/graph"
@@ -40,7 +40,7 @@ type Module struct {
Manifest catalogue.Manifest
// Provisions is the seat mesh-provisioner's provisions this module consumes on a node, by key (ADR 0570 §4): the want
// waits until each is provided. Nil: the seat's state is not read (not delivered yet), and nothing waits on it.
Provisions func(node string) map[string]provisions.Provision
Provisions func(node string) map[string]meshprovisionerclient.Provision
// Answering is the manifests of the modules placed somewhere that provide a provision: what catalogue.Awaited
// judges a want's provisions by (issue 774). Nil: none known, and no key is waited for that is not there.
Answering func(provision string) []catalogue.Manifest
@@ -242,7 +242,7 @@ func (m Module) provisionWaiting(node string) (string, bool) {
}
}
for _, key := range slices.Sorted(maps.Keys(keyed)) {
if p := keyed[key]; p.State != provisions.Provided && slices.Contains(needed, p.Provision) {
if p := keyed[key]; p.State != meshprovisionerclient.Provided && slices.Contains(needed, p.Provision) {
words := fmt.Sprintf("the provision %s of %s on %s is %s, not provided", key, m.Name, node, p.State)
if p.Why != "" {
words += ": " + p.Why
@@ -14,8 +14,8 @@ import (
"testing"
"time"
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/provisions"
"git.novox.be/novox/mesh-delivery/sdk/graph"
)
@@ -81,8 +81,8 @@ func TestEachRuleHoldsTheWantBack(t *testing.T) {
},
// 1954/D1 (ADR 0570 §4): a provision of the module on that node still asked holds its want.
mwc.RuleProvisions: func(m *Module, _ *Nodes) {
m.Provisions = func(node string) map[string]provisions.Provision {
return map[string]provisions.Provision{node + ".mail.database": {Provision: "database", State: provisions.Asked}, node + ".mail.oidc": {Provision: "oidc", State: provisions.Provided}}
m.Provisions = func(node string) map[string]meshprovisionerclient.Provision {
return map[string]meshprovisionerclient.Provision{node + ".mail.database": {Provision: "database", State: meshprovisionerclient.Asked}, node + ".mail.oidc": {Provision: "oidc", State: meshprovisionerclient.Provided}}
}
},
} {
@@ -103,9 +103,9 @@ func TestEachRuleHoldsTheWantBack(t *testing.T) {
}
}
// 1954/D1: every provision provided, the want moves; with the seat's state not read, nothing waits on it.
for _, all := range []func(string) map[string]provisions.Provision{
func(node string) map[string]provisions.Provision {
return map[string]provisions.Provision{node + ".mail.database": {Provision: "database", State: provisions.Provided}}
for _, all := range []func(string) map[string]meshprovisionerclient.Provision{
func(node string) map[string]meshprovisionerclient.Provision {
return map[string]meshprovisionerclient.Provision{node + ".mail.database": {Provision: "database", State: meshprovisionerclient.Provided}}
}, nil} {
m, n := ready()
m.Provisions = all
@@ -216,13 +216,13 @@ func TestEachRuleHoldsTheWantBack(t *testing.T) {
name string
state, older string
moves bool
}{{"the replaced edge provided, the older edge retiring", provisions.Provided, provisions.Retiring, true},
{"the replaced edge provided, the older edge asked", provisions.Provided, provisions.Asked, true},
{"the replaced edge asked, the older edge retiring", provisions.Asked, provisions.Retiring, false}} {
}{{"the replaced edge provided, the older edge retiring", meshprovisionerclient.Provided, meshprovisionerclient.Retiring, true},
{"the replaced edge provided, the older edge asked", meshprovisionerclient.Provided, meshprovisionerclient.Asked, true},
{"the replaced edge asked, the older edge retiring", meshprovisionerclient.Asked, meshprovisionerclient.Retiring, false}} {
m, n := ready()
m.Manifest.Requires = []string{"oidc"}
m.Provisions = func(node string) map[string]provisions.Provision {
return map[string]provisions.Provision{node + ".mail.oidc": {Provision: "oidc", State: c.state},
m.Provisions = func(node string) map[string]meshprovisionerclient.Provision {
return map[string]meshprovisionerclient.Provision{node + ".mail.oidc": {Provision: "oidc", State: c.state},
node + ".mail.database": {Provision: "database", State: c.older, Why: "the operator's answer"}}
}
out := Wants(m, n, wantedOld(), nil)
@@ -267,7 +267,9 @@ func TestEachRuleHoldsTheWantBack(t *testing.T) {
} {
m, n := ready()
m.Manifest, m.Answering = c.manifest, answering
m.Provisions = func(string) map[string]provisions.Provision { return map[string]provisions.Provision{} }
m.Provisions = func(string) map[string]meshprovisionerclient.Provision {
return map[string]meshprovisionerclient.Provision{}
}
out := Wants(m, n, wantedOld(), nil)
if c.missing == "" {
if out.Wants["g14"].Fp != fNew || len(m.MissingProvisions("g14")) != 0 {
@@ -292,7 +294,9 @@ func TestEachRuleHoldsTheWantBack(t *testing.T) {
m.Manifest.Requires = []string{c.requires}
m.Answering = func(string) []catalogue.Manifest { return nil }
m.Offered = func(p string) bool { return c.offered && p == c.requires }
m.Provisions = func(string) map[string]provisions.Provision { return map[string]provisions.Provision{} }
m.Provisions = func(string) map[string]meshprovisionerclient.Provision {
return map[string]meshprovisionerclient.Provision{}
}
out := Wants(m, n, wantedOld(), nil)
if got := out.Wants["g14"].Fp == fNew; got != c.offered || (!c.offered && (out.Rules["g14"].Rule != mwc.RuleProvisions ||
!strings.Contains(out.Rules["g14"].Words, "no module offers database yet") || !strings.Contains(out.Rules["g14"].Words, "past its bound"))) {
@@ -14,6 +14,7 @@ import (
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient"
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
"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-view/viewstate"
@@ -21,7 +22,6 @@ import (
"git.novox.be/novox/mesh-delivery/sdk/enginereads"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/busretry"
"git.novox.be/novox/mesh-sdk/go/provisions"
"git.novox.be/novox/mesh-sdk/go/views"
)
@@ -414,7 +414,7 @@ func main() {
r.ConditionsState, r.AttemptsState = read(stdio.State(view.StatusConditionsState)), read(stdio.State(view.StatusAttemptsState))
r.PresenceState, r.StuckState = read(stdio.State(view.StatusPresenceState)), read(stdio.State(view.StatusStuckState))
r.SourceState = read(stdio.State(view.GateModule + ".source"))
r.ProvisionsState = read(stdio.State(provisions.ProvisionsRead))
r.ProvisionsState = read(stdio.State(meshprovisionerclient.ProvisionsRead))
r.BuildLog = stdio.Log(builds.BuildLogRead).Since
r.Pending = pendingOf
u := view.UsageReader{Haves: r.HavesState, Resolved: read(stdio.State("mesh-config.resolved")), Tries: read(stdio.State("mesh-delivery-checker.tries")),
@@ -14,12 +14,12 @@ import (
"git.novox.be/novox/mesh-contracts/go/seats/nodeengine"
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient"
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
"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-indexer/takein"
"git.novox.be/novox/mesh-delivery/sdk/graph"
"git.novox.be/novox/mesh-sdk/go/provisions"
)
// encoded is v as its contract's encoder writes it, failing the test when it refuses.
@@ -85,9 +85,9 @@ func tiersToolSaysEachModulesWait(t *testing.T) {
}.reader().State
r.WantsState.History = func(string) ([]view.Revision, error) { return []view.Revision{{At: since}}, nil }
r.HavesState = fake{key(nodeengineclient.HaveKey("ace", "db")): have(nodeengine.HaveByNodeModuleValue{Fp: fp("a"), Phase: nodeengineclient.PhaseRunning, Health: nodeengineclient.HealthOK})}.reader().State
p, err := provisions.Encode(provisions.Provision{Provision: "database", Consumer: provisions.End{Module: "web", Node: "ace"},
Provider: provisions.End{Module: "db", Node: "ace"}, State: provisions.Asked, Why: "the build db runs does not offer it", At: since})
r.ProvisionsState = fake{key(provisions.Key("ace", "web", "database", "")): encoded(t, p, err)}.reader().State
p, err := meshprovisionerclient.Encode(meshprovisionerclient.Provision{Provision: "database", Consumer: meshprovisionerclient.End{Module: "web", Node: "ace"},
Provider: meshprovisionerclient.End{Module: "db", Node: "ace"}, State: meshprovisionerclient.Asked, Why: "the build db runs does not offer it", At: since})
r.ProvisionsState = fake{key(meshprovisionerclient.Key("ace", "web", "database", "")): encoded(t, p, err)}.reader().State
off, err := meshwatchdogsclient.EncodeNodePresence(meshwatchdogsclient.NodePresence{Online: false})
r.PresenceState = fake{key(meshwatchdogsclient.PresenceNodeKey("g14")): encoded(t, off, err)}.reader().State
+1
View File
@@ -3,6 +3,7 @@ module mesh-delivery-view
go 1.26.0
require (
git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/identity-and-access/go/clients/meshvaultclient v0.0.0-00010101000000-000000000000
@@ -12,13 +12,13 @@ import (
"git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient"
"git.novox.be/novox/mesh-contracts/placement/go/clients/meshwantsclient"
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
"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/seats/meshcatalog"
"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/sdk/graph"
"git.novox.be/novox/mesh-sdk/go/provisions"
)
// The tiers of a source revision are a view, worked out at each call and never stored (novox/hq ADR 0603 §1, §6): the
@@ -389,7 +389,7 @@ func provisionWaits(node, module, a string, provided map[string]json.RawMessage,
if perr != nil {
return []TierWait{{Edge: edge, Node: node, Words: "the provisioner's keys cannot be read: " + perr.Error()}}
}
prefix, _ := provisions.Key(node, module, a, "")
prefix, _ := meshprovisionerclient.Key(node, module, a, "")
var out []TierWait
found := false
for _, key := range slices.Sorted(maps.Keys(provided)) {
@@ -397,12 +397,12 @@ func provisionWaits(node, module, a string, provided map[string]json.RawMessage,
continue
}
found = true
p, err := provisions.Decode(provided[key])
p, err := meshprovisionerclient.Decode(provided[key])
if err != nil {
out = append(out, TierWait{Edge: edge, Node: node, Words: "the provision key " + key + " does not read: " + err.Error()})
continue
}
if p.State == provisions.Provided {
if p.State == meshprovisionerclient.Provided {
continue
}
words := "the provision " + key + " is " + p.State
@@ -26,10 +26,10 @@ import (
"github.com/nats-io/nats.go/jetstream"
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient"
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
"git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
"git.novox.be/novox/mesh-delivery/modules/mesh-delivery-indexer/takein"
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/provisions"
"mesh-delivery-watchman/internal/socket"
"mesh-delivery-watchman/internal/watch"
@@ -382,21 +382,21 @@ func run(ctx context.Context, getenv func(string) string, logOut io.Writer) erro
provisionsWatch := func(kv jetstream.KeyValue) (jetstream.KeyWatcher, error) {
pw, err := settle(ctx, kv, func(e jetstream.KeyValueEntry) { w.Provision(e.Key(), e.Value(), opOf(e)) })
if err == nil {
w.ReadWhole(provisions.ProvisionsBucket)
w.ReadWhole(meshprovisionerclient.ProvisionsBucket)
}
return pw, err
}
var provWatch jetstream.KeyWatcher
if kv, err := js.KeyValue(ctx, provisions.ProvisionsBucket); err == nil {
if kv, err := js.KeyValue(ctx, meshprovisionerclient.ProvisionsBucket); err == nil {
if provWatch, err = provisionsWatch(kv); err != nil {
process.NotWatched(logger.Printf, provisions.ProvisionsBucket, err, "no provision waiting for the operator is said until the watchman starts again")
process.NotWatched(logger.Printf, meshprovisionerclient.ProvisionsBucket, err, "no provision waiting for the operator is said until the watchman starts again")
}
} else {
process.AwaitBucket(ctx, js, conn, provisions.ProvisionsBucket, "no provision waiting for the operator is said",
process.AwaitBucket(ctx, js, conn, meshprovisionerclient.ProvisionsBucket, "no provision waiting for the operator is said",
logger.Printf).Ready(func(kv jetstream.KeyValue) {
pw, err := provisionsWatch(kv)
if err != nil {
process.NotWatched(logger.Printf, provisions.ProvisionsBucket, err, "no provision waiting for the operator is said until the watchman starts again")
process.NotWatched(logger.Printf, meshprovisionerclient.ProvisionsBucket, err, "no provision waiting for the operator is said until the watchman starts again")
return
}
go func() {
+1
View File
@@ -3,6 +3,7 @@ module mesh-delivery-watchman
go 1.26.0
require (
git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/go/seats/nodeengine v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshwatchdogsclient v0.0.0-00010101000000-000000000000
@@ -17,8 +17,8 @@ import (
"git.novox.be/novox/mesh-delivery/sdk/buildstep"
"git.novox.be/novox/mesh-sdk/go/health"
"git.novox.be/novox/mesh-contracts/provisioning/go/clients/meshprovisionerclient"
"git.novox.be/novox/mesh-delivery/sdk/heard"
"git.novox.be/novox/mesh-sdk/go/provisions"
)
func mustEncode(t *testing.T, f Finding) []byte {
@@ -474,20 +474,20 @@ func TestTheWatchmansFindings(t *testing.T) {
}, map[string]string{"module.fail2ban.novox.unhealthy": Warning}},
{"1954/D2: a provision in waits is its consumer's needs-operator; provided, it ends", func(t *testing.T, w *Watchman, o *fakeOut, c *clock) {
enc := func(state, why string) []byte {
b, err := provisions.Encode(provisions.Provision{Provision: "oidc", Consumer: provisions.End{Module: "photos", Node: "ace"},
Provider: provisions.End{Module: "keycloak", Node: "novox"}, State: state, Why: why, At: t0})
b, err := meshprovisionerclient.Encode(meshprovisionerclient.Provision{Provision: "oidc", Consumer: meshprovisionerclient.End{Module: "photos", Node: "ace"},
Provider: meshprovisionerclient.End{Module: "keycloak", Node: "novox"}, State: state, Why: why, At: t0})
if err != nil {
t.Fatal(err)
}
return b
}
w.Provision("ace.photos.oidc", enc(provisions.Waits, "the operator gives the client's redirect"), Put)
w.Provision("ace.photos.db", enc(provisions.Provided, ""), Put)
w.Provision("ace.photos.oidc", enc(meshprovisionerclient.Waits, "the operator gives the client's redirect"), Put)
w.Provision("ace.photos.db", enc(meshprovisionerclient.Provided, ""), Put)
if f := o.findings["module.photos.ace.needs-operator"]; !strings.Contains(f.Summary, "redirect") || f.Machine != "ace" {
t.Fatalf("findings = %v", o.findings)
}
w.Provision("ace.photos.oidc", enc(provisions.Provided, ""), Put)
w.Provision("ace.web.oidc", enc(provisions.Waits, "x"), Put)
w.Provision("ace.photos.oidc", enc(meshprovisionerclient.Provided, ""), Put)
w.Provision("ace.web.oidc", enc(meshprovisionerclient.Waits, "x"), Put)
w.Provision("ace.web.oidc", nil, Delete)
}, map[string]string{}},
{"774: a node's own report: its network, the resolver file rewritten, failed units, a relogin, a directory used as found, a login fallen back", func(t *testing.T, w *Watchman, o *fakeOut, c *clock) {
@@ -674,10 +674,10 @@ func TestTheWatchmansFindings(t *testing.T) {
w.SetStepBound(30 * time.Minute)
held, _ := meshwantsclient.EncodeWant(meshwantsclient.Want{Fp: fpOld, Self: true, Newest: fpNew, Rule: meshwantsclient.RuleCanary, Words: "the canary ace does not run it yet"})
released, _ := meshwantsclient.EncodeWant(meshwantsclient.Want{Fp: fpNew, Self: true})
asked, _ := provisions.Encode(provisions.Provision{Provision: "oidc", Consumer: provisions.End{Module: "photos", Node: "ace"},
Provider: provisions.End{Module: "keycloak", Node: "novox"}, State: provisions.Asked, At: t0})
provided, _ := provisions.Encode(provisions.Provision{Provision: "oidc", Consumer: provisions.End{Module: "photos", Node: "ace"},
Provider: provisions.End{Module: "keycloak", Node: "novox"}, State: provisions.Provided, At: t0})
asked, _ := meshprovisionerclient.Encode(meshprovisionerclient.Provision{Provision: "oidc", Consumer: meshprovisionerclient.End{Module: "photos", Node: "ace"},
Provider: meshprovisionerclient.End{Module: "keycloak", Node: "novox"}, State: meshprovisionerclient.Asked, At: t0})
provided, _ := meshprovisionerclient.Encode(meshprovisionerclient.Provision{Provision: "oidc", Consumer: meshprovisionerclient.End{Module: "photos", Node: "ace"},
Provider: meshprovisionerclient.End{Module: "keycloak", Node: "novox"}, State: meshprovisionerclient.Provided, At: t0})
bkey, _ := builds.BuilderBuildKey("web", fpNew)
queued, _ := builds.EncodeBuilderBuild(builds.BuilderBuild{State: builds.BuildQueued, Waits: "mesh-sdk@v0.1.14"})
building, _ := builds.EncodeBuilderBuild(builds.BuilderBuild{State: builds.BuildBuilding, By: "n1"})
@@ -685,15 +685,15 @@ func TestTheWatchmansFindings(t *testing.T) {
w.Want("want.ace.web", held, Put, t0)
w.Provision("ace.photos.oidc", asked, Put)
w.Build(bkey, queued, Put, t0)
waits := map[string]string{AssignerWantsBucket: "want.ace.web", provisions.ProvisionsBucket: "ace.photos.oidc", BuilderBuildsBucket: bkey}
waits := map[string]string{AssignerWantsBucket: "want.ace.web", meshprovisionerclient.ProvisionsBucket: "ace.photos.oidc", BuilderBuildsBucket: bkey}
if len(o.pulses) != 3 || len(o.findings) != 0 {
t.Fatalf("pulses %v findings %v", o.pulses, o.findings)
}
// The keys are written again as their parts move on, with new words and a new time: the wait still began at t0.
c.t = c.t.Add(20 * time.Minute)
held2, _ := meshwantsclient.EncodeWant(meshwantsclient.Want{Fp: fpOld, Self: true, Newest: fpNew, Rule: meshwantsclient.RuleCanary, Words: "the canary ace runs it, not healthy yet"})
asked2, _ := provisions.Encode(provisions.Provision{Provision: "oidc", Consumer: provisions.End{Module: "photos", Node: "ace"},
Provider: provisions.End{Module: "keycloak", Node: "novox"}, State: provisions.Asked, Why: "the provider is applying", At: c.t})
asked2, _ := meshprovisionerclient.Encode(meshprovisionerclient.Provision{Provision: "oidc", Consumer: meshprovisionerclient.End{Module: "photos", Node: "ace"},
Provider: meshprovisionerclient.End{Module: "keycloak", Node: "novox"}, State: meshprovisionerclient.Asked, Why: "the provider is applying", At: c.t})
w.Want("want.ace.web", held2, Put, c.t)
w.Provision("ace.photos.oidc", asked2, Put)
w.Build(bkey, queued, Put, c.t)