mesh-provisioner reads the placement, wants, haves, compositions, modules and manifests through the seats' types and clients and the builder's builds through its published package (B5f; ADR 0634 §1, §4; issue 1966)

Claude-Session: https://claude.ai/code/session_01NcbgfFSnn24M3GcqttoU7i
This commit is contained in:
2026-11-05 15:49:06 +01:00
parent d4abb82b07
commit 6e4de66df7
9 changed files with 155 additions and 83 deletions
@@ -12,13 +12,15 @@ package main
import (
"context"
"encoding/json"
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
"os"
"strings"
"testing"
"time"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/delivery"
)
// today is a captured composition: the node, the builds its wants named, and the plans at them and at the newest.
@@ -48,7 +50,7 @@ func published(t *testing.T, s *memStore, c today) {
v["plan"] = plan
}
raw, _ := json.Marshal(v)
s.write(t, ComposedRead, mustKey(delivery.ComposedKey(c.Node, module, fp)), string(raw))
s.write(t, ComposedRead, mustKey(composedKey(c.Node, module, fp)), string(raw))
}
for module, fp := range fpsOf {
plan, has := c.Newest.Plans[module]
@@ -58,7 +60,7 @@ func published(t *testing.T, s *memStore, c today) {
plan, has := c.AtWants.Plans[module]
put(module, fp, plan, has)
}
s.write(t, ComposedRead, mustKey(delivery.ComposedNodeKey(c.Node)), `{"v":1,"node":"`+c.Node+`","resources":[]}`)
s.write(t, ComposedRead, mustKey(engineclient.HaveNodeKey(c.Node)), `{"v":1,"node":"`+c.Node+`","resources":[]}`)
}
// goldenMesh is the store as the provisioner reads it for frombustest's mesh: the indexer's modules, n1's placement, the
@@ -73,9 +75,9 @@ func goldenMesh(t *testing.T, c today) *memStore {
raw = `{"module":"postgres","version":"1","provides":[{"name":"postgres-database","scope":"mesh"}],` +
`"serves":{"postgres-database":{"port":5432}}}`
}
digest := delivery.ManifestDigest([]byte(raw))
value, _ := delivery.EncodeManifest(delivery.Manifest{Module: module, JSON: raw})
s.write(t, ManifestsRead, mustKey(delivery.ManifestsKey(digest)), string(value))
digest := catalogclient.ManifestDigest([]byte(raw))
value, _ := catalogclient.EncodeManifest(catalog.ManifestsByDigestValue{Module: module, Manifest: raw})
s.write(t, ManifestsRead, mustKey(catalogclient.ManifestsKey(digest)), string(value))
s.write(t, ModulesRead, module, `{"v":1,"fp2":"`+fp+`","manifest":"`+digest+`"}`)
}
s.write(t, PlacementRead, "placement."+c.Node, `{"v":1,"node":"`+c.Node+`","decided":{},"modules":[{"module":"store"},`+
@@ -140,7 +142,7 @@ func TestARefusedOrMissingCompositionHoldsItsModule(t *testing.T) {
t.Fatal(err)
}
plan, _ := s.Get(PlansState, "n1.restic")
key := mustKey(delivery.ComposedKey("n1", "restic", fpsOf["restic"]))
key := mustKey(composedKey("n1", "restic", fpsOf["restic"]))
s.write(t, ComposedRead, key, `{"v":1,"node":"n1","module":"restic","fp":"`+fpsOf["restic"]+`","resources":[],`+
`"refused":"restic requires postgres-database and no module offers it"}`)
out, err := h.ComposeNode(context.Background(), c.Node)
@@ -202,3 +204,12 @@ func valueOf(e *Entry) string {
}
return string(e.Value)
}
// composedKey is a module's key in the node-engine's composed state, from the fingerprint the test names it by.
func composedKey(node, module, fp string) (string, error) {
part, err := catalogclient.KeyPart(fp)
if err != nil {
return "", err
}
return engineclient.ComposedKey(node, module, part)
}
@@ -31,6 +31,12 @@ import (
"encoding/json"
"errors"
"fmt"
engineclient "git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient"
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
meshseat "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"slices"
"sort"
"strings"
@@ -42,7 +48,6 @@ import (
"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/delivery"
)
// The states it keeps, beside Registry, Needs, Applied and Collect. RefusedState holds, by `<node>.<module>`, why a
@@ -64,14 +69,14 @@ type Refusal struct {
// The reads it makes the plans from. ComposedRead is every node's published compositions, `composed-<node>` (ADR 0593
// §6), read as the node-engine's named state: its keys are `<node>.<module>.<fingerprint hex>` and `<node>.node`.
const (
PlacementRead = delivery.PlacementRead
WantsRead = delivery.AssignerModule + ".wants"
ModulesRead = delivery.IndexerModule + ".modules"
ManifestsRead = delivery.IndexerModule + ".manifests"
PlacementRead = (assignerModule + "." + meshseat.StatePlacement)
WantsRead = assignerModule + ".wants"
ModulesRead = indexerModule + "." + catalog.StateModules
ManifestsRead = indexerModule + "." + catalog.StateManifests
HaveRead = "node-engine.have"
// BuildsRead is the builder's builds, `<module>.<fingerprint part>`: the key of the build a provider runs names the
// digest of the manifest it was built from (novox/hq ADR 0603 clause 5).
BuildsRead = delivery.BuilderModule + ".builds"
BuildsRead = builds.BuilderModule + ".builds"
ComposedRead = "mesh-host.composed"
)
@@ -461,7 +466,7 @@ func (h *Holder) wantedOn(node string) (map[string]string, error) {
return nil, fmt.Errorf("the wants cannot be read: %w", err)
}
for _, k := range keys {
n, module, err := delivery.ParseWantKey(k)
n, module, err := parseWantKey(k)
if err != nil || n != node {
continue
}
@@ -469,8 +474,8 @@ func (h *Holder) wantedOn(node string) (map[string]string, error) {
if err != nil || e == nil {
continue
}
if w, err := delivery.DecodeWant(e.Value); err == nil && !w.Removed && w.FP != "" {
out[module] = w.FP
if w, err := decodeWant(e.Value); err == nil && !w.Removed && w.Fp != "" {
out[module] = w.Fp
}
}
return out, nil
@@ -485,7 +490,7 @@ func (h *Holder) runningOn(node string) (map[string]string, error) {
return nil, fmt.Errorf("the haves cannot be read: %w", err)
}
for _, k := range keys {
n, module, isNode, err := delivery.ParseHaveKey(k)
n, module, isNode, err := engineclient.ParseHaveKey(k)
if err != nil || isNode || n != node {
continue
}
@@ -493,8 +498,8 @@ func (h *Holder) runningOn(node string) (map[string]string, error) {
if err != nil || e == nil {
continue
}
if have, err := delivery.DecodeHave(e.Value); err == nil && have.FP != "" {
out[module] = have.FP
if have, err := engineclient.DecodeHave(e.Value); err == nil && have.Fp != "" {
out[module] = have.Fp
}
}
return out, nil
@@ -808,27 +813,27 @@ func (h *Holder) haveOutcome(p *meshprovisionerclient.Provision) {
p.State, p.Why = meshprovisionerclient.Asked, "waits for "+where+" to run: it says nothing yet"
return
}
have, err := delivery.DecodeHave(e.Value)
have, err := engineclient.DecodeHave(e.Value)
if err != nil {
p.State, p.Why = meshprovisionerclient.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
if w, err := decodeWant(we.Value); err == nil && !w.Removed {
wantFP = w.Fp
}
}
switch {
case have.Phase == delivery.PhaseFailed || have.Health == delivery.HealthFailed:
case have.Phase == engineclient.PhaseFailed || have.Health == engineclient.HealthFailed:
p.State, p.Why = meshprovisionerclient.Failed, orSaid(have.Detail, where+" failed")
case have.Phase == delivery.PhaseRunning && have.Health == delivery.HealthOK && (wantFP == "" || have.FP == wantFP):
case have.Phase == engineclient.PhaseRunning && have.Health == engineclient.HealthOK && (wantFP == "" || have.Fp == wantFP):
// A provider's have counts as its outcome only from a build whose manifest offers the provision (ADR 0603 clause 5).
// Refused only when that manifest is known and does not offer it: a have with no builder key (an old-form build, an
// adopted module) or an unreadable manifest keeps the outcome it had.
if offers, known := h.buildOffers(p.Provider.Module, have.FP, p.Provision); known && !offers {
if offers, known := h.buildOffers(p.Provider.Module, have.Fp, p.Provision); known && !offers {
p.State, p.Why = meshprovisionerclient.Asked, "waits for "+where+" to run a build that offers "+p.Provision+": the build it runs ("+
have.FP+") does not"
have.Fp+") does not"
return
}
p.State, p.Why = meshprovisionerclient.Provided, ""
@@ -842,7 +847,7 @@ func (h *Holder) haveOutcome(p *meshprovisionerclient.Provision) {
// offers the provision; known is false when the builder holds no such key, the key names no manifest, or the indexer holds
// no such manifest (the caller then does not refuse).
func (h *Holder) buildOffers(module, fingerprint, provision string) (offers, known bool) {
key, err := delivery.BuilderBuildKey(module, fingerprint)
key, err := builds.BuilderBuildKey(module, fingerprint)
if err != nil {
return false, false
}
@@ -850,11 +855,11 @@ func (h *Holder) buildOffers(module, fingerprint, provision string) (offers, kno
if err != nil || be == nil {
return false, false
}
b, err := delivery.DecodeBuilderBuild(be.Value)
b, err := builds.DecodeBuilderBuild(be.Value)
if err != nil || b.Manifest == "" {
return false, false
}
mk, err := delivery.ManifestsKey(b.Manifest)
mk, err := catalogclient.ManifestsKey(b.Manifest)
if err != nil {
return false, false
}
@@ -862,14 +867,14 @@ func (h *Holder) buildOffers(module, fingerprint, provision string) (offers, kno
if err != nil || me == nil {
return false, false
}
man, err := delivery.DecodeManifest(me.Value)
man, err := catalogclient.DecodeManifest(me.Value)
if err != nil {
return false, false
}
var decl struct {
Provides []json.RawMessage `json:"provides"`
}
if json.Unmarshal([]byte(man.JSON), &decl) != nil {
if json.Unmarshal([]byte(man.Manifest), &decl) != nil {
return false, false
}
for _, raw := range decl.Provides {
@@ -947,7 +952,7 @@ func keepsEntriesFor(manifest, provision string) (bool, error) {
// providerManifest is a provider's manifest JSON at the trunk, as the indexer holds it.
func (h *Holder) providerManifest(module string) (string, error) {
mk, err := delivery.ModulesKey(module)
mk, err := catalogclient.ModulesKey(module)
if err != nil {
return "", err
}
@@ -955,11 +960,11 @@ func (h *Holder) providerManifest(module string) (string, error) {
if err != nil || e == nil {
return "", fmt.Errorf("the indexer holds no modules.%s (%v)", module, err)
}
m, err := delivery.DecodeModule(e.Value)
m, err := catalogclient.DecodeModule(e.Value)
if err != nil || m.Manifest == "" {
return "", fmt.Errorf("the indexer's modules.%s names no manifest", module)
}
key, err := delivery.ManifestsKey(m.Manifest)
key, err := catalogclient.ManifestsKey(m.Manifest)
if err != nil {
return "", err
}
@@ -967,11 +972,11 @@ func (h *Holder) providerManifest(module string) (string, error) {
if err != nil || me == nil {
return "", fmt.Errorf("the indexer holds no manifest of %s (%v)", module, err)
}
man, err := delivery.DecodeManifest(me.Value)
man, err := catalogclient.DecodeManifest(me.Value)
if err != nil {
return "", err
}
return man.JSON, nil
return man.Manifest, nil
}
// retired says a retiring key the operator approved is retired: its provider's entry is gone.
@@ -1310,7 +1315,7 @@ func (h *Holder) put(key string, p meshprovisionerclient.Provision) {
// NodesOfPlacement is the node a placement key names, in either form (research 072 row 37).
func NodesOfPlacement(key string) []string {
if n, _, _, err := delivery.ParsePlacementKey(key); err == nil {
if n, _, _, err := assignerclient.ParseKey(key); err == nil {
return []string{n}
}
return nil
@@ -1318,7 +1323,7 @@ func NodesOfPlacement(key string) []string {
// NodesOfWant is the node a want key names.
func NodesOfWant(key string) []string {
if n, _, err := delivery.ParseWantKey(key); err == nil {
if n, _, err := parseWantKey(key); err == nil {
return []string{n}
}
return nil
@@ -1344,7 +1349,7 @@ func (h *Holder) NodesOfModule(module string) []string {
}
// placedOn is every node a module is placed on, sorted, from the placement's keys in either form
// (delivery.PlacementsOf; research 072 row 37). A node whose keys do not read whole is read as they stand.
// (assignerclient.PlacementsOf; research 072 row 37). A node whose keys do not read whole is read as they stand.
func (h *Holder) placedOn(module string) []string {
nodes := map[string]bool{}
raw := map[string][]byte{}
@@ -1355,7 +1360,7 @@ func (h *Holder) placedOn(module string) []string {
}
}
}
placements, _ := delivery.PlacementsOf(raw)
placements, _ := assignerclient.PlacementsOf(raw)
for n, p := range placements {
for _, m := range p.Modules {
if m.Module == module {
@@ -1517,7 +1522,7 @@ func FromCompositions(s Store) Composer {
}
}
// publishedPlan is what the holder reads of a published composition (delivery.Composed): its plan, or the engine's
// publishedPlan is what the holder reads of a published composition (the node-engine's composed state): its plan, or the engine's
// refusal. Read leniently, so a field the node-engine adds later is no failure here.
type publishedPlan struct {
Plan *catalogue.Plan `json:"plan"`
@@ -1526,7 +1531,11 @@ type publishedPlan struct {
// composedAt is a module's key in its node's compositions, or "" for a fingerprint no key can name.
func composedAt(node, module, fp string) string {
k, err := delivery.ComposedKey(node, module, fp)
part, err := catalogclient.KeyPart(fp)
if err != nil {
return ""
}
k, err := engineclient.ComposedKey(node, module, part)
if err != nil {
return ""
}
@@ -1541,7 +1550,7 @@ func publishedOn(s Store, node string) (map[string]publishedPlan, error) {
}
out := map[string]publishedPlan{}
for _, k := range keys {
n, _, _, isNode, err := delivery.ParseComposedKey(k)
n, _, _, isNode, err := engineclient.ParseComposedKey(k)
if err != nil || isNode || n != node {
continue
}
@@ -1566,25 +1575,25 @@ func placedModules(s Store, node string) ([]string, map[string]string, error) {
return nil, nil, fmt.Errorf("the placement cannot be read: %w", err)
}
for _, k := range keys {
if n, _, _, err := delivery.ParsePlacementKey(k); err != nil || n != node {
if n, _, _, err := assignerclient.ParseKey(k); err != nil || n != node {
continue
}
if e, err := s.Get(PlacementRead, k); err == nil && e != nil {
raw[k] = e.Value
}
}
placements, _ := delivery.PlacementsOf(raw)
placements, _ := assignerclient.PlacementsOf(raw)
var placed []string
fp2 := map[string]string{}
for _, m := range placements[node].Modules {
placed = append(placed, m.Module)
mk, err := delivery.ModulesKey(m.Module)
mk, err := catalogclient.ModulesKey(m.Module)
if err != nil {
continue
}
if e, err := s.Get(ModulesRead, mk); err == nil && e != nil {
if mod, err := delivery.DecodeModule(e.Value); err == nil {
fp2[m.Module] = mod.FP2
if mod, err := catalogclient.DecodeModule(e.Value); err == nil {
fp2[m.Module] = mod.Fp2
}
}
}
@@ -14,6 +14,9 @@ import (
"context"
"encoding/json"
"errors"
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"strings"
"testing"
"time"
@@ -24,7 +27,6 @@ import (
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
"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/delivery"
"git.novox.be/novox/mesh-sdk/go/testbus"
)
@@ -36,7 +38,7 @@ type kvStore struct {
func bucketOf(state string) string {
if state == ComposedRead { // the node-engine's named state: here n1's bucket, the one node that composes
return delivery.ComposedBucketPrefix + "n1"
return "composed-n1"
}
if owner, name, ok := strings.Cut(state, "."); ok {
return owner + "_" + name
@@ -131,9 +133,9 @@ func TestTheHolderPlansProvidesAndAsksOnTheBus(t *testing.T) {
mem := goldenMesh(t, c)
published(t, mem, c)
vaultManifest := `{"module":"mesh-vault","version":"1","state":["bound"]}` // the vault keeps its outcomes as entries
vaultDigest := delivery.ManifestDigest([]byte(vaultManifest))
vaultValue, _ := delivery.EncodeManifest(delivery.Manifest{Module: meshprovisionerclient.VaultModule, JSON: vaultManifest})
mem.write(t, ManifestsRead, mustKey(delivery.ManifestsKey(vaultDigest)), string(vaultValue))
vaultDigest := catalogclient.ManifestDigest([]byte(vaultManifest))
vaultValue, _ := catalogclient.EncodeManifest(catalog.ManifestsByDigestValue{Module: meshprovisionerclient.VaultModule, Manifest: vaultManifest})
mem.write(t, ManifestsRead, mustKey(catalogclient.ManifestsKey(vaultDigest)), string(vaultValue))
mem.write(t, ModulesRead, meshprovisionerclient.VaultModule, `{"v":1,"none2":"the test's","manifest":"`+vaultDigest+`"}`)
for state, keys := range mem.states {
for key, raw := range keys {
@@ -226,15 +228,15 @@ func TestTheHolderPlansProvidesAndAsksOnTheBus(t *testing.T) {
settled(HaveRead, node+".postgres")
// The running build's manifest, as the builder keyed it: one that does not offer the provision keeps the key asked
// (ADR 0603 clause 5), one that does lets the have count.
buildsKey := mustKey(delivery.BuilderBuildKey("postgres", fpsOf["postgres"]))
buildsKey := mustKey(builds.BuilderBuildKey("postgres", fpsOf["postgres"]))
offered := func(provides string) {
raw := `{"module":"postgres","version":"1","provides":` + provides + `}`
digest := delivery.ManifestDigest([]byte(raw))
value, _ := delivery.EncodeManifest(delivery.Manifest{Module: "postgres", JSON: raw})
if err := s.Put(ManifestsRead, mustKey(delivery.ManifestsKey(digest)), json.RawMessage(value)); err != nil {
digest := catalogclient.ManifestDigest([]byte(raw))
value, _ := catalogclient.EncodeManifest(catalog.ManifestsByDigestValue{Module: "postgres", Manifest: raw})
if err := s.Put(ManifestsRead, mustKey(catalogclient.ManifestsKey(digest)), json.RawMessage(value)); err != nil {
t.Fatal(err)
}
build, err := delivery.EncodeBuilderBuild(delivery.BuilderBuild{State: delivery.BuildDone, Manifest: digest})
build, err := builds.EncodeBuilderBuild(builds.BuilderBuild{State: builds.BuildDone, Manifest: digest})
if err != nil {
t.Fatal(err)
}
@@ -4,6 +4,11 @@ import (
"context"
"encoding/json"
"fmt"
assignerclient "git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient"
meshseat "git.novox.be/novox/mesh-contracts/placement/go/seats/meshmoduleassigner"
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
catalog "git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog"
builds "git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds"
"go/ast"
"go/parser"
"go/token"
@@ -18,7 +23,6 @@ import (
"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/delivery"
)
// router is the operator's channel as the holder asks on it: it holds an ask open, or, like the router past its open
@@ -87,10 +91,10 @@ func newMesh() *mesh {
for module, states := range map[string]string{"postgres": `["bound"]`, "keycloak": `["bound"]`, "mesh-vault": `["bound"]`,
"systemd-resolved": `[]`, "minio": `[]`} {
raw := `{"module":"` + module + `","version":"1","state":` + states + `}`
digest := delivery.ManifestDigest([]byte(raw))
value, _ := delivery.EncodeManifest(delivery.Manifest{Module: module, JSON: raw})
_ = m.s.Put(ManifestsRead, mustKey(delivery.ManifestsKey(digest)), json.RawMessage(value))
_ = m.s.Put(ModulesRead, mustKey(delivery.ModulesKey(module)),
digest := catalogclient.ManifestDigest([]byte(raw))
value, _ := catalogclient.EncodeManifest(catalog.ManifestsByDigestValue{Module: module, Manifest: raw})
_ = m.s.Put(ManifestsRead, mustKey(catalogclient.ManifestsKey(digest)), json.RawMessage(value))
_ = m.s.Put(ModulesRead, mustKey(catalogclient.ModulesKey(module)),
json.RawMessage(`{"v":1,"none2":"a test","manifest":"`+digest+`","repository":"novox/mesh-catalog","commit":"c1"}`))
}
return m
@@ -193,16 +197,16 @@ func TestAKeysStateFollowsItsProvidersOutcome(t *testing.T) {
// A have counts as the provider's outcome only from a build whose manifest offers the provision (ADR 0603 clause 5):
// refused when the build's manifest is known and does not offer it, provided when it does.
bfp := "src2:" + strings.Repeat("bb", 32)
buildKey := mustKey(delivery.BuilderBuildKey("systemd-resolved", bfp))
buildKey := mustKey(builds.BuilderBuildKey("systemd-resolved", bfp))
for _, row := range []struct{ name, provides, state, why string }{
{"a build whose manifest does not offer it", `["other"]`, meshprovisionerclient.Asked, "offers split-dns"},
{"a build whose manifest offers it", `[{"name":"split-dns"}]`, meshprovisionerclient.Provided, ""},
} {
raw := `{"module":"systemd-resolved","version":"1","provides":` + row.provides + `}`
digest := delivery.ManifestDigest([]byte(raw))
value, _ := delivery.EncodeManifest(delivery.Manifest{Module: "systemd-resolved", JSON: raw})
_ = m.s.Put(ManifestsRead, mustKey(delivery.ManifestsKey(digest)), json.RawMessage(value))
build, err := delivery.EncodeBuilderBuild(delivery.BuilderBuild{State: delivery.BuildDone, Manifest: digest})
digest := catalogclient.ManifestDigest([]byte(raw))
value, _ := catalogclient.EncodeManifest(catalog.ManifestsByDigestValue{Module: "systemd-resolved", Manifest: raw})
_ = m.s.Put(ManifestsRead, mustKey(catalogclient.ManifestsKey(digest)), json.RawMessage(value))
build, err := builds.EncodeBuilderBuild(builds.BuilderBuild{State: builds.BuildDone, Manifest: digest})
if err != nil {
t.Fatal(err)
}
@@ -222,7 +226,7 @@ func TestAKeysStateFollowsItsProvidersOutcome(t *testing.T) {
// provision depends on it (clauses 3, 6).
c := newMesh()
// The placement in its keys per module and node (research 072 row 37).
novox, err := delivery.PlacementValues(delivery.Placement{Node: "novox", Modules: []delivery.PlacedModule{{Module: "mesh-vault"}}})
novox, err := assignerclient.PlacementValues(meshseat.Placement{Node: "novox", Modules: []meshseat.PlacedModule{{Module: "mesh-vault"}}})
if err != nil {
t.Fatal(err)
}
@@ -507,10 +511,10 @@ func TestTheHolderHoldsNoTimer(t *testing.T) {
// manifestOf puts a provider's manifest into the indexer's reads as the holder finds it.
func (m *mesh) manifestOf(module, raw string) {
digest := delivery.ManifestDigest([]byte(raw))
value, _ := delivery.EncodeManifest(delivery.Manifest{Module: module, JSON: raw})
_ = m.s.Put(ManifestsRead, mustKey(delivery.ManifestsKey(digest)), json.RawMessage(value))
_ = m.s.Put(ModulesRead, mustKey(delivery.ModulesKey(module)),
digest := catalogclient.ManifestDigest([]byte(raw))
value, _ := catalogclient.EncodeManifest(catalog.ManifestsByDigestValue{Module: module, Manifest: raw})
_ = m.s.Put(ManifestsRead, mustKey(catalogclient.ManifestsKey(digest)), json.RawMessage(value))
_ = m.s.Put(ModulesRead, mustKey(catalogclient.ModulesKey(module)),
json.RawMessage(`{"v":1,"none2":"a test","manifest":"`+digest+`","repository":"novox/mesh-catalog","commit":"c1"}`))
}
@@ -9,6 +9,7 @@ package main
import (
"context"
"fmt"
catalogclient "git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient"
"os"
"strings"
"sync"
@@ -18,7 +19,6 @@ import (
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient"
oc "git.novox.be/novox/mesh-contracts/operator-and-conversation/go/seats/operatorchannel"
stdio "git.novox.be/novox/mesh-sdk/go"
"git.novox.be/novox/mesh-sdk/go/delivery"
"git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient"
"git.novox.be/novox/mesh-sdk/go/views"
@@ -176,20 +176,20 @@ func slowest(took []Took) time.Duration {
// The owners' read tools of the states the provisioner follows, which the manifest invokes (ADR 0615).
const (
PlacementTool = delivery.AssignerModule + ".assigner_read_placement"
WantsTool = delivery.AssignerModule + ".assigner_read_wants"
ModulesTool = delivery.IndexerModule + ".indexer_read_modules"
ManifestsTool = delivery.IndexerModule + ".indexer_read_manifests"
PlacementTool = assignerModule + ".assigner_read_placement"
WantsTool = assignerModule + ".assigner_read_wants"
ModulesTool = indexerModule + ".indexer_read_modules"
ManifestsTool = indexerModule + ".indexer_read_manifests"
)
// newViews is the provisioner's pictures of the assigner's placement and wants and the indexer's modules and manifests:
// each asked once of its owner's read tool, then followed by the owner's events (ADR 0615).
func newViews() *views.Reader {
v := &views.Reader{Module: Module, Logf: logf, Findings: meshconditionsclient.FindingWriter{State: stdio.State("findings")}, Acts: meshhandactsclient.Recorder{Ask: stdio.Ask}}
v.Follow(delivery.AssignerModule, "placement", PlacementTool)
v.Follow(delivery.AssignerModule, "wants", WantsTool)
v.Follow(delivery.IndexerModule, "modules", ModulesTool)
v.Follow(delivery.IndexerModule, "manifests", ManifestsTool)
v.Follow(assignerModule, "placement", PlacementTool)
v.Follow(assignerModule, "wants", WantsTool)
v.Follow(indexerModule, "modules", ModulesTool)
v.Follow(indexerModule, "manifests", ManifestsTool)
return v
}
@@ -253,7 +253,7 @@ func main() {
follow(ComposedRead, func(c stdio.StateChange) error { w.add(NodesOfNodeKey(c.Key)...); return nil })
follow(HaveRead, func(c stdio.StateChange) error { w.add(NodesOfNodeKey(c.Key)...); return nil })
follow(ModulesRead, func(c stdio.StateChange) error {
if module, err := delivery.ParseModulesKey(c.Key); err == nil {
if module, err := catalogclient.ParseModulesKey(c.Key); err == nil {
w.add(h.NodesOfModule(module)...)
}
return nil
@@ -296,10 +296,15 @@ func TestTheManifestDeclaresWhatTheCodeKeeps(t *testing.T) {
Consumes []string `json:"consumes"`
Tools []string `json:"tools"`
OwnSecrets map[string]string `json:"own-secrets"`
Packages map[string]string `json:"packages"`
}
if err := json.Unmarshal(raw, &m); err != nil {
t.Fatal(err)
}
// The builder's keys are read through its published package, taken by its series (ADR 0634 §1).
if m.Packages["builds"] != "0.1" {
t.Errorf("packages %v: want builds of series 0.1", m.Packages)
}
if m.Module != "mesh-provisioner" || m.RunsAs != "mesh-provisioner" || m.OwnSecrets["broker"] == "" {
t.Errorf("an account of its own: module %q, runs-as %q, own secrets %v", m.Module, m.RunsAs, m.OwnSecrets)
}
@@ -0,0 +1,31 @@
package main
import (
"encoding/json"
"fmt"
meshwants "git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants"
)
// The holders whose states this module reads, one constant each (a reader names its holder once; ADR 0634 §4).
const (
assignerModule = "mesh-delivery-assigner"
indexerModule = "mesh-delivery-indexer"
)
// parseWantKey reads a want's key, `want.<node>.<module>`; the want of a node's own and a held fingerprint's key are
// not one.
func parseWantKey(key string) (node, module string, err error) {
node, module, ok := meshwants.ParseWantsByWantNodeModuleKey(key)
if !ok {
return "", "", fmt.Errorf("%q is not the key of a module's want on a node", key)
}
return node, module, nil
}
// decodeWant reads a want of a module on a node, as the wants seat types it.
func decodeWant(value []byte) (meshwants.WantsByWantNodeModuleValue, error) {
var w meshwants.WantsByWantNodeModuleValue
err := json.Unmarshal(value, &w)
return w, err
}
+7
View File
@@ -3,6 +3,13 @@ module mesh-provisioner
go 1.26.0
require (
git.novox.be/novox/mesh-contracts/placement/go/seats/meshwants v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/go/clients/nodeengineclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/clients/meshcatalogclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/contracts/go/seats/meshcatalog v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-delivery/modules/mesh-delivery-builder/builds v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/placement/go/clients/meshmoduleassignerclient 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/provisioning/go/clients/meshprovisionerclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshconditionsclient v0.0.0-00010101000000-000000000000
git.novox.be/novox/mesh-contracts/health-and-repair/go/clients/meshhandactsclient v0.0.0-00010101000000-000000000000
+3
View File
@@ -1,6 +1,9 @@
{
"module": "mesh-provisioner",
"version": "2",
"packages": {
"builds": "0.1"
},
"runs-as": "mesh-provisioner",
"claims": [
{