CAT-RD5: mesh-provisioner asks the assigner's placement and wants and the indexer's modules and manifests once and follows their events, no reads
This commit is contained in:
@@ -8,15 +8,20 @@ package main
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
stdio "git.novox.be/novox/mesh-sdk/go"
|
||||
"git.novox.be/novox/mesh-sdk/go/asks"
|
||||
"git.novox.be/novox/mesh-sdk/go/delivery"
|
||||
"git.novox.be/novox/mesh-sdk/go/health"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/sdk/views"
|
||||
)
|
||||
|
||||
// Seat is the role this module holds, once for the mesh, and Verb its one verb.
|
||||
@@ -169,9 +174,73 @@ func slowest(took []Took) time.Duration {
|
||||
return time.Duration(took[len(took)-1].MS) * time.Millisecond
|
||||
}
|
||||
|
||||
// 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"
|
||||
)
|
||||
|
||||
// viewFindings writes the provisioner's finding of a first view unanswered into its own state findings, which the
|
||||
// condition keeper raises the condition from.
|
||||
type viewFindings struct{}
|
||||
|
||||
func (viewFindings) Put(key string, value []byte) error {
|
||||
_, err := stdio.State(health.FindingsState).Put(key, json.RawMessage(value))
|
||||
return err
|
||||
}
|
||||
func (viewFindings) Delete(key string) error { return stdio.State(health.FindingsState).Delete(key) }
|
||||
|
||||
// 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: viewFindings{}}
|
||||
v.Follow(delivery.AssignerModule, "placement", PlacementTool)
|
||||
v.Follow(delivery.AssignerModule, "wants", WantsTool)
|
||||
v.Follow(delivery.IndexerModule, "modules", ModulesTool)
|
||||
v.Follow(delivery.IndexerModule, "manifests", ManifestsTool)
|
||||
return v
|
||||
}
|
||||
|
||||
// watchedOver is the copy's watch of a state: a followed state is handed from its picture (the first view's values as
|
||||
// the current ones, then each change), any other from the runtime's watch.
|
||||
func watchedOver(v *views.Reader) func(string, func(stdio.StateChange) error) error {
|
||||
return func(state string, handle func(stdio.StateChange) error) error {
|
||||
if !v.Follows(state) {
|
||||
return stdio.State(state).Watch("", handle)
|
||||
}
|
||||
var landed atomic.Bool
|
||||
err := v.Watch(state, func(key string, value []byte, gone bool) {
|
||||
op := stdio.OpPut
|
||||
if gone {
|
||||
op = stdio.OpDelete
|
||||
}
|
||||
if err := handle(stdio.StateChange{State: state, Key: key, Op: op, Value: value, Current: !landed.Load()}); err != nil {
|
||||
logf("[%s] %s is not taken: %v", Module, key, err)
|
||||
}
|
||||
})
|
||||
landed.Store(true)
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
func main() {
|
||||
s := runtimeStore{}
|
||||
ws := stdio.NewWatchedStates() // one copy of what it reads, each state by its watch (ADR 0580 clause 2)
|
||||
v := newViews()
|
||||
// One copy of what it reads, each state by its watch (ADR 0580 clause 2); the indexer's and the assigner's by their
|
||||
// pictures, a manifest by its digest from the picture too.
|
||||
ws := stdio.WatchedStatesOver(watchedOver(v), func(state, key string) ([]byte, bool, error) {
|
||||
if v.Follows(state) {
|
||||
return v.Get(state, key)
|
||||
}
|
||||
e, err := stdio.State(state).Get(key)
|
||||
if err != nil || e == nil {
|
||||
return nil, false, err
|
||||
}
|
||||
return e.Value, true, nil
|
||||
})
|
||||
v.Start(context.Background())
|
||||
h := &Holder{Store: Watching(s, ws), Asks: runtimeAsks{}, Now: time.Now, Logf: logf}
|
||||
h.Compose = FromCompositions(h.Store) // the node-engines' published compositions, from the copy (ADR 0593 §8)
|
||||
w := &work{pending: map[string]bool{}, wake: make(chan struct{}, 1)}
|
||||
@@ -234,7 +303,7 @@ func main() {
|
||||
time.Sleep(wait)
|
||||
}
|
||||
}()
|
||||
if err := stdio.Serve("", append(Tools(s), holderTools(h)...)); err != nil {
|
||||
if err := stdio.Serve("", append(append(Tools(s), holderTools(h)...), v.Tools("provisioner")...)); err != nil {
|
||||
logf("%v", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
|
||||
@@ -8,6 +8,9 @@ import (
|
||||
"strings"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-delivery/sdk/views"
|
||||
"git.novox.be/novox/mesh-sdk/go/health"
|
||||
)
|
||||
|
||||
// memStore is the bus as mesh-provisioner reaches it, in memory: the states it keeps.
|
||||
@@ -289,6 +292,8 @@ func TestTheManifestDeclaresWhatTheCodeKeeps(t *testing.T) {
|
||||
} `json:"claims"`
|
||||
State []string `json:"state"`
|
||||
Reads []string `json:"reads"`
|
||||
Invokes []string `json:"invokes"`
|
||||
Consumes []string `json:"consumes"`
|
||||
Tools []string `json:"tools"`
|
||||
OwnSecrets map[string]string `json:"own-secrets"`
|
||||
}
|
||||
@@ -303,13 +308,13 @@ func TestTheManifestDeclaresWhatTheCodeKeeps(t *testing.T) {
|
||||
t.Errorf("the claim: %+v", m.Claims)
|
||||
}
|
||||
// needs stays declared this release so its first start can empty it (ADR 0570 clause 1).
|
||||
if want := []string{Registry, Needs, Applied, Collect, ProvisionsState, PlansState, RefusedState}; !slices.Equal(m.State, want) {
|
||||
if want := []string{Registry, Needs, Applied, Collect, ProvisionsState, PlansState, RefusedState, health.FindingsState}; !slices.Equal(m.State, want) {
|
||||
t.Errorf("the state is %v, want %v", m.State, want)
|
||||
}
|
||||
// Every state and act has its tool (ADR 0570 Consequences): the verb reads the clients' states, the holder's tools
|
||||
// its own and the composition it does on a change.
|
||||
served := map[string]bool{}
|
||||
for _, tool := range append(Tools(newStore()), holderTools(&Holder{})...) {
|
||||
for _, tool := range append(append(Tools(newStore()), holderTools(&Holder{})...), (&views.Reader{}).Tools("provisioner")...) {
|
||||
served[tool.Name] = true
|
||||
}
|
||||
for what, tool := range map[string]string{Registry: Seat + "." + Verb, Needs: Seat + "." + Verb,
|
||||
@@ -326,11 +331,25 @@ func TestTheManifestDeclaresWhatTheCodeKeeps(t *testing.T) {
|
||||
}
|
||||
// The builds read serves only haveOutcome's offer check (ADR 0603 clause 5: which manifest the build a provider runs
|
||||
// was made from) and makes no plan, so ADR 0593 §8 ("it composes nothing") holds.
|
||||
for _, r := range append(slices.Clone(WatchedReads), ManifestsRead, BuildsRead) {
|
||||
// The assigner's placement and wants and the indexer's modules and manifests are asked once of their owners' read
|
||||
// tools and followed by their events, not read (ADR 0615 §1).
|
||||
followed := []string{PlacementRead, WantsRead, ModulesRead, ManifestsRead}
|
||||
for _, r := range append(slices.Clone(WatchedReads), BuildsRead) {
|
||||
if slices.Contains(followed, r) {
|
||||
if slices.Contains(m.Reads, r) {
|
||||
t.Errorf("the manifest reads %s, which the holder follows by its owner's events", r)
|
||||
}
|
||||
continue
|
||||
}
|
||||
if !slices.Contains(m.Reads, r) {
|
||||
t.Errorf("the manifest does not read %s, which the holder reads", r)
|
||||
}
|
||||
}
|
||||
if !slices.Equal(m.Invokes, []string{PlacementTool, WantsTool, ModulesTool, ManifestsTool}) ||
|
||||
!slices.Equal(m.Consumes, []string{"mesh-delivery-assigner.views.placement", "mesh-delivery-assigner.views.wants",
|
||||
"mesh-delivery-indexer.views.modules", "mesh-delivery-indexer.views.manifests"}) {
|
||||
t.Errorf("invokes %v, consumes %v", m.Invokes, m.Consumes)
|
||||
}
|
||||
// It composes nothing (ADR 0593 §8): no config value is read to make a plan.
|
||||
for _, r := range []string{"mesh-config.resolved"} {
|
||||
if slices.Contains(m.Reads, r) {
|
||||
|
||||
@@ -15,10 +15,6 @@
|
||||
"operator-channel"
|
||||
],
|
||||
"reads": [
|
||||
"mesh-delivery-assigner.placement",
|
||||
"mesh-delivery-assigner.wants",
|
||||
"mesh-delivery-indexer.modules",
|
||||
"mesh-delivery-indexer.manifests",
|
||||
"node-engine.have",
|
||||
"mesh-delivery-builder.builds",
|
||||
"mesh-host.composed",
|
||||
@@ -30,12 +26,26 @@
|
||||
"node-tools.bound",
|
||||
"mesh-vault.bound"
|
||||
],
|
||||
"invokes": [
|
||||
"mesh-delivery-assigner.assigner_read_placement",
|
||||
"mesh-delivery-assigner.assigner_read_wants",
|
||||
"mesh-delivery-indexer.indexer_read_modules",
|
||||
"mesh-delivery-indexer.indexer_read_manifests"
|
||||
],
|
||||
"consumes": [
|
||||
"mesh-delivery-assigner.views.placement",
|
||||
"mesh-delivery-assigner.views.wants",
|
||||
"mesh-delivery-indexer.views.modules",
|
||||
"mesh-delivery-indexer.views.manifests"
|
||||
],
|
||||
"tools": [
|
||||
"provisioner_provisions",
|
||||
"provisioner_plans",
|
||||
"provisioner_compose",
|
||||
"provisioner_decide",
|
||||
"provisioner_health"
|
||||
"provisioner_health",
|
||||
"provisioner_first_views",
|
||||
"provisioner_first_view_again"
|
||||
],
|
||||
"state": [
|
||||
"registry",
|
||||
@@ -44,7 +54,8 @@
|
||||
"collect",
|
||||
"provisions",
|
||||
"plans",
|
||||
"refused"
|
||||
"refused",
|
||||
"findings"
|
||||
],
|
||||
"core-credentials": true,
|
||||
"own-secrets": {
|
||||
|
||||
Reference in New Issue
Block a user