diff --git a/modules/mesh-self-check/cmd/mesh-self-check/main.go b/modules/mesh-self-check/cmd/mesh-self-check/main.go index 71fefc4b..288bb79b 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/main.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/main.go @@ -13,14 +13,22 @@ import ( "time" stdio "git.novox.be/novox/mesh-sdk/go" + "git.novox.be/novox/mesh-sdk/go/delivery" + + "git.novox.be/novox/mesh-delivery/sdk/views" ) func logf(format string, a ...any) { fmt.Fprintf(os.Stderr, format+"\n", a...) } // runtimeStore is the bus, through the runtime that launched this bundle. -type runtimeStore struct{} +type runtimeStore struct{ views *views.Reader } -func (runtimeStore) Get(state, key string) ([]byte, bool, error) { +func (r runtimeStore) followed(state string) bool { return r.views != nil && r.views.Follows(state) } + +func (r runtimeStore) Get(state, key string) ([]byte, bool, error) { + if r.followed(state) { + return r.views.Get(state, key) + } e, err := stdio.State(state).Get(key) if err != nil || e == nil { return nil, false, err @@ -35,9 +43,17 @@ func (runtimeStore) Put(state, key string, value any) error { func (runtimeStore) Delete(state, key string) error { return stdio.State(state).Delete(key) } -func (runtimeStore) Keys(state string) ([]string, error) { return stdio.State(state).Keys() } +func (r runtimeStore) Keys(state string) ([]string, error) { + if r.followed(state) { + return r.views.Keys(state) + } + return stdio.State(state).Keys() +} -func (runtimeStore) Watch(state string, handle func(key string, value []byte, gone bool)) error { +func (r runtimeStore) Watch(state string, handle func(key string, value []byte, gone bool)) error { + if r.followed(state) { + return r.views.Watch(state, handle) + } return stdio.State(state).Watch("", func(ch stdio.StateChange) error { handle(ch.Key, ch.Value, ch.Op != stdio.OpPut) return nil @@ -46,20 +62,43 @@ func (runtimeStore) Watch(state string, handle func(key string, value []byte, go // NewChecker is the self-check over the runtime. func NewChecker() *Checker { - return &Checker{Store: runtimeStore{}, Registry: Registry(), Now: time.Now, Logf: logf, Host: os.Getenv("MESH_NODE"), + store := runtimeStore{views: newViews()} + return &Checker{Store: store, Registry: Registry(), Now: time.Now, Logf: logf, Host: os.Getenv("MESH_NODE"), Enabled: enabledProbes, Ask: func(key string, body any) (json.RawMessage, error) { return stdio.Ask(key, body) }, Publish: stdio.SeatPublish} } +// newViews is the self-check's pictures of the indexer's and the assigner's states it probes (ADR 0615): each asked once of +// its owner's read tool, then followed by the owner's events. A first view unanswered past its bound is a finding of the +// self-check's own (first-view-unanswered), written into its state findings. +func newViews() *views.Reader { + r := &views.Reader{Module: Module, Logf: logf, Findings: findings{}} + r.Follow(delivery.AssignerModule, delivery.PlacementState, PlacementTool) + r.Follow(delivery.AssignerModule, delivery.SeatHoldersState, SeatHoldersTool) + r.Follow(delivery.IndexerModule, "modules", ModulesTool) + r.Follow(delivery.IndexerModule, "manifests", ManifestsTool) + return r +} + +// findings writes the self-check's own state findings for the reader. +type findings struct{} + +func (findings) Put(key string, value []byte) error { + _, err := stdio.State(FindingsState).Put(key, json.RawMessage(value)) + return err +} +func (findings) Delete(key string) error { return stdio.State(FindingsState).Delete(key) } + func main() { c := NewChecker() go func() { <-stdio.Served() + c.Store.(runtimeStore).views.Start(context.Background()) c.WatchKept(context.Background()) c.Keep(context.Background()) }() - if err := stdio.Serve("", Tools(c)); err != nil { + if err := stdio.Serve("", append(Tools(c), c.Store.(runtimeStore).views.Tools("mesh_self_check")...)); err != nil { logf("%v", err) os.Exit(1) } diff --git a/modules/mesh-self-check/cmd/mesh-self-check/manifest_test.go b/modules/mesh-self-check/cmd/mesh-self-check/manifest_test.go index e288898c..5e9ea1fc 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/manifest_test.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/manifest_test.go @@ -19,6 +19,8 @@ type manifest struct { Emits []string `json:"emits"` Invokes []string `json:"invokes"` Reads []string `json:"reads"` + Consume []string `json:"consumes"` + Tools []string `json:"tools"` State []json.RawMessage `json:"state"` } @@ -57,6 +59,9 @@ func TestTheManifestSaysWhatTheCodeDoes(t *testing.T) { if !slices.Equal(m.Reads, Reads) { t.Errorf("reads %v, want what the probes read, %v", m.Reads, Reads) } + if !slices.Equal(m.Consume, Consumes) { + t.Errorf("consumes %v, want the views families the probes follow, %v", m.Consume, Consumes) + } states := map[string]int{} for _, s := range m.State { var d struct { diff --git a/modules/mesh-self-check/cmd/mesh-self-check/probes.go b/modules/mesh-self-check/cmd/mesh-self-check/probes.go index 150c4265..54d5a238 100644 --- a/modules/mesh-self-check/cmd/mesh-self-check/probes.go +++ b/modules/mesh-self-check/cmd/mesh-self-check/probes.go @@ -33,8 +33,20 @@ const ( // Invokes are the tools and seat verbs the probes ask, and Reads the states they read: the manifest's `invokes` and // `reads`, which its test holds to these. var ( - Invokes = []string{StreamsTool, ClosedTool, SignalsVerb, BannedVerb} - Reads = []string{PlacementRead, SeatHoldersRead, PresenceRead, HealthRead, ReadingsRead, GoodRead, ModulesRead, ManifestsRead} + Invokes = []string{StreamsTool, ClosedTool, SignalsVerb, BannedVerb, PlacementTool, SeatHoldersTool, ModulesTool, ManifestsTool} + Reads = []string{PresenceRead, HealthRead, ReadingsRead, GoodRead} + // Consumes are the views families the probes follow (ADR 0615): the indexer's and the assigner's states are asked + // once, by their owners' read tools, and then followed by their events, never read. + Consumes = []string{"mesh-delivery-assigner.views.placement", "mesh-delivery-assigner.views.seat-holders", + "mesh-delivery-indexer.views.modules", "mesh-delivery-indexer.views.manifests"} +) + +// The owners' read tools of the states the probes follow, which the manifest invokes. +const ( + PlacementTool = "mesh-delivery-assigner.assigner_read_placement" + SeatHoldersTool = "mesh-delivery-assigner.assigner_read_seat_holders" + ModulesTool = "mesh-delivery-indexer.indexer_read_modules" + ManifestsTool = "mesh-delivery-indexer.indexer_read_manifests" ) // Registry is the self-check's registry, in to-be 45's order. **The registry is the design's live form**: a probe diff --git a/modules/mesh-self-check/module.json b/modules/mesh-self-check/module.json index e4037e98..e40b4b62 100644 --- a/modules/mesh-self-check/module.json +++ b/modules/mesh-self-check/module.json @@ -16,17 +16,23 @@ "nats.nats_streams", "nats.nats_closed_connections", "seat:mesh-watchdogs.signals", - "seat:node-intrusion-prevention.banned" + "seat:node-intrusion-prevention.banned", + "mesh-delivery-assigner.assigner_read_placement", + "mesh-delivery-assigner.assigner_read_seat_holders", + "mesh-delivery-indexer.indexer_read_modules", + "mesh-delivery-indexer.indexer_read_manifests" ], "reads": [ - "mesh-delivery-assigner.placement", - "mesh-delivery-assigner.seat-holders", "mesh-delivery-watchman.presence", "mesh-host.health", "disk-load.readings", - "restic.good", - "mesh-delivery-indexer.modules", - "mesh-delivery-indexer.manifests" + "restic.good" + ], + "consumes": [ + "mesh-delivery-assigner.views.placement", + "mesh-delivery-assigner.views.seat-holders", + "mesh-delivery-indexer.views.modules", + "mesh-delivery-indexer.views.manifests" ], "state": [ "findings",