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:
@@ -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
|
||||
}
|
||||
@@ -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
|
||||
|
||||
@@ -1,6 +1,9 @@
|
||||
{
|
||||
"module": "mesh-provisioner",
|
||||
"version": "2",
|
||||
"packages": {
|
||||
"builds": "0.1"
|
||||
},
|
||||
"runs-as": "mesh-provisioner",
|
||||
"claims": [
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user