The keeper follows meshcatalogclient's branch follower and the seat list on its views reader, and plans again at each move of a branch's view, the registrations or the seat list. Each local name is planned from the first of the module's watched branches in its registration's order; another branch asking it with other settings is said drifted, naming the branch. A module of the new form whose resolved seat definition has a verb or an event gets its sent store; one including only parts, and a foundation part, none. A module in no watched branch's newest revision loses its plans key, its entries orphans. The sum of the caps counts the old sections the entries declare. The manifest consumes the catalog half's and the seat list's events and invokes their verbs; manifest-accepted and indexer_bus_state go. Builds: design 47 G §344, §433, §532, §533, §537, §538, §547, §591 Claude-Session: https://claude.ai/code/session_01NcbgfGKaxw984XyAPfLm4h
392 lines
14 KiB
Go
392 lines
14 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"fmt"
|
|
"sort"
|
|
"time"
|
|
|
|
stdio "git.novox.be/novox/mesh-sdk/go"
|
|
"git.novox.be/novox/mesh-sdk/go/membership"
|
|
"github.com/nats-io/nats.go/jetstream"
|
|
|
|
"git.novox.be/novox/mesh-contracts/go/seats/meshbroker"
|
|
|
|
"nats-tools/internal/keeper"
|
|
)
|
|
|
|
// The seat mesh-broker's reading verbs (design 25 § 12.3), served by this bundle: every entry the keeper made, one
|
|
// entry, what the bus holds against what was asked, and the orphans; each read from the keeper's own state asked and
|
|
// the bus's monitoring endpoint, never from another part's state. And nats_history: every event the bus module said of
|
|
// one entry, in order.
|
|
|
|
// heldEntries reads the keeper's own state asked: every module it holds.
|
|
type heldEntries func() (map[string]keeper.Asked, error)
|
|
|
|
// HeldInState reads asked through the runtime that launched this bundle.
|
|
func HeldInState() (map[string]keeper.Asked, error) {
|
|
s := stdio.State(keeper.StateAsked)
|
|
keys, err := s.Keys()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("the keeper's own %s cannot be read: %w", keeper.StateAsked, err)
|
|
}
|
|
out := map[string]keeper.Asked{}
|
|
for _, key := range keys {
|
|
if key == keeper.KeyStatus {
|
|
continue
|
|
}
|
|
e, err := s.Get(key)
|
|
if err != nil || e == nil {
|
|
continue
|
|
}
|
|
var a keeper.Asked
|
|
if json.Unmarshal(e.Value, &a) == nil {
|
|
out[key] = a
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// capsOf is the sum of the bus's caps against its limits (design 47 G §547, §591, §661), in MiB: every entry made and
|
|
// every old section the catalog graph's entries declare, as the keeper's status last says them; a limit the keeper has
|
|
// not read, or that is none, is absent. The sum is over every module, whatever list is narrowed to.
|
|
func capsOf(all map[string]keeper.Asked, status func() (keeper.Status, error)) *meshbroker.ListAnswerCaps {
|
|
sum := keeper.CapSum(all, "", "")
|
|
c := &meshbroker.ListAnswerCaps{}
|
|
if s, err := status(); err == nil {
|
|
sum += s.OldBytes
|
|
c.AccountLimitMiB, c.ServerLimitMiB = s.Limits.AccountBytes/keeper.MiB, s.Limits.ServerBytes/keeper.MiB
|
|
}
|
|
c.SumMiB = sum / keeper.MiB
|
|
return c
|
|
}
|
|
|
|
// busStream is one stream as the monitoring endpoint answers it.
|
|
type busStream struct {
|
|
Config jetstream.StreamConfig `json:"config"`
|
|
State struct {
|
|
Messages uint64 `json:"messages"`
|
|
Bytes uint64 `json:"bytes"`
|
|
} `json:"state"`
|
|
}
|
|
|
|
// streamsHeld reads every stream the bus holds from the monitoring endpoint's jsz, by name.
|
|
func streamsHeld(jsz []byte) (map[string]busStream, error) {
|
|
var doc struct {
|
|
Accounts []struct {
|
|
Streams []struct {
|
|
Name string `json:"name"`
|
|
busStream
|
|
} `json:"stream_detail"`
|
|
} `json:"account_details"`
|
|
}
|
|
if err := json.Unmarshal(jsz, &doc); err != nil {
|
|
return nil, fmt.Errorf("the bus's streams cannot be read: %w", err)
|
|
}
|
|
out := map[string]busStream{}
|
|
for _, a := range doc.Accounts {
|
|
for _, s := range a.Streams {
|
|
out[s.Name] = s.busStream
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// heldOf is one entry as the seat's list, show and orphans answer it (mesh-contracts meshbroker.Held): its bus name, its
|
|
// template, its options or configuration as the manifest asked them, the settings the bus holds of each of its objects
|
|
// by stream, and its fill summed over them against their caps.
|
|
func heldOf(module, local string, m keeper.Made, held map[string]busStream) meshbroker.Held {
|
|
h := meshbroker.Held{BusName: keeper.BusName(module, local), Module: module, LocalName: local, StorageType: m.Plan.Storage,
|
|
Template: m.Plan.Template, Options: anyOf(m.Plan.Entry.Options), Configuration: anyOf(m.Plan.Entry.Configuration)}
|
|
settings := map[string]any{}
|
|
var fill meshbroker.Fill
|
|
for _, o := range m.Plan.Objects {
|
|
s, ok := held[o.Stream()]
|
|
if !ok {
|
|
continue
|
|
}
|
|
settings[o.Stream()] = keeper.SettingsOf(o.Kind, s.Config)
|
|
fill.Bytes += int64(s.State.Bytes)
|
|
fill.Count += int64(s.State.Messages)
|
|
fill.Cap += s.Config.MaxBytes
|
|
}
|
|
if len(settings) > 0 {
|
|
h.Settings, h.Fill = settings, &fill
|
|
// The binding the entry's created and recreated carry, from its plan (design 25 bindings-first-view §2): present on an
|
|
// entry whose objects the bus holds, absent on one it does not.
|
|
h.Binding = bindingOf(m.Plan.Binding)
|
|
}
|
|
return h
|
|
}
|
|
|
|
// bindingOf is a plan's binding in the seat's form, the one the events carry (the seat's Binding takes the fields of mesh-sdk's
|
|
// membership binding, so the same JSON is both).
|
|
func bindingOf(b membership.BusBinding) *meshbroker.Binding {
|
|
raw, err := json.Marshal(b)
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
var out meshbroker.Binding
|
|
if json.Unmarshal(raw, &out) != nil {
|
|
return nil
|
|
}
|
|
return &out
|
|
}
|
|
|
|
// differencesOf are an entry's objects the bus holds otherwise than asked, or not at all, as the seat's check answers
|
|
// them: each object's settings asked and held by stream, and whether the difference only grows (apply may act on it).
|
|
func differencesOf(module, local string, m keeper.Made, held map[string]busStream) (meshbroker.CheckAnswerDifferencesItem, bool) {
|
|
d := meshbroker.CheckAnswerDifferencesItem{Module: module, LocalName: local, Asked: map[string]any{}, Held: map[string]any{}, Grows: true}
|
|
differs := false
|
|
for _, o := range m.Plan.Objects {
|
|
s, ok := held[o.Stream()]
|
|
if !ok {
|
|
d.Asked[o.Stream()], d.Grows, differs = o.Settings, false, true
|
|
continue
|
|
}
|
|
h := keeper.SettingsOf(o.Kind, s.Config)
|
|
if keeper.Differs(o.Settings, h) == "" {
|
|
continue
|
|
}
|
|
d.Asked[o.Stream()], d.Held[o.Stream()], differs = o.Settings, h, true
|
|
d.Grows = d.Grows && keeper.Grows(o.Settings, h)
|
|
}
|
|
return d, differs
|
|
}
|
|
|
|
// anyOf is a manifest's options or configuration as the contract carries them.
|
|
func anyOf(m map[string]json.RawMessage) map[string]any {
|
|
if len(m) == 0 {
|
|
return nil
|
|
}
|
|
out := map[string]any{}
|
|
for k, v := range m {
|
|
var x any
|
|
if json.Unmarshal(v, &x) == nil {
|
|
out[k] = x
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// brokerTools are the four reading verbs and nats_history.
|
|
func brokerTools(read heldEntries, status func() (keeper.Status, error), jsz func() ([]byte, error), history func(module, local string, limit int) ([]keeper.Event, error)) []stdio.Tool {
|
|
both := func() (map[string]keeper.Asked, map[string]busStream, error) {
|
|
entries, err := read()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
doc, err := jsz()
|
|
if err != nil {
|
|
return nil, nil, fmt.Errorf("the bus's monitoring endpoint cannot be read: %w", err)
|
|
}
|
|
held, err := streamsHeld(doc)
|
|
return entries, held, err
|
|
}
|
|
each := func(entries map[string]keeper.Asked, held map[string]busStream, keep func(keeper.Made) bool) []meshbroker.Held {
|
|
out := []meshbroker.Held{}
|
|
for _, module := range sortedNames(entries) {
|
|
for _, local := range sortedNames(entries[module].Made) {
|
|
if m := entries[module].Made[local]; keep(m) {
|
|
out = append(out, heldOf(module, local, m, held))
|
|
}
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
entryArgs := map[string]any{"module": str("the module, e.g. mesh-knowledge"), "local-name": str("its local name, e.g. records")}
|
|
return []stdio.Tool{
|
|
{Name: "mesh-broker.list",
|
|
Description: "Every module's kept data on the bus that the bus module created (design 25 § 12.3): each entry's bus name, template and options as asked, the settings the bus holds of each of its objects, its fill against its cap, and its binding (the names on the bus a module receives, as created carried them) where the bus holds its objects. Narrowed by a module.",
|
|
Input: map[string]any{"module": str("one module")},
|
|
Run: func(a map[string]any) (any, error) {
|
|
entries, held, err := both()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if m := arg(a, "module"); m != "" {
|
|
entries = map[string]keeper.Asked{m: entries[m]}
|
|
}
|
|
all, err := read()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return meshbroker.ListAnswer{Caps: capsOf(all, status), Entries: each(entries, held, func(keeper.Made) bool { return true })}, nil
|
|
}},
|
|
{Name: "mesh-broker.show",
|
|
Description: "One entry of a module's kept data on the bus: its bus name, template and options as asked, the settings the bus holds of each of its objects, and its fill against its cap. Whether it is refused, waiting, drifted, full or orphaned is what nats_history says of it.",
|
|
Input: entryArgs,
|
|
Run: func(a map[string]any) (any, error) {
|
|
entries, held, err := both()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
module, local := arg(a, "module"), arg(a, "local-name")
|
|
m, ok := entries[module].Made[local]
|
|
if !ok {
|
|
return nil, fmt.Errorf("the bus module made no entry %q of %q", local, module)
|
|
}
|
|
return heldOf(module, local, m, held), nil
|
|
}},
|
|
{Name: "mesh-broker.check",
|
|
Description: "What the bus holds against what was asked, entry by entry: each object missing, or differing from its manifest's settings, with both. A difference that only grows is applied by the bus module itself; any other is a drift, raised to the operator.",
|
|
Input: map[string]any{"module": str("one module")},
|
|
Run: func(a map[string]any) (any, error) {
|
|
entries, held, err := both()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if m := arg(a, "module"); m != "" {
|
|
entries = map[string]keeper.Asked{m: entries[m]}
|
|
}
|
|
out := meshbroker.CheckAnswer{Differences: []meshbroker.CheckAnswerDifferencesItem{}}
|
|
for _, module := range sortedNames(entries) {
|
|
for _, local := range sortedNames(entries[module].Made) {
|
|
m := entries[module].Made[local]
|
|
if !m.Created || m.Orphaned {
|
|
continue
|
|
}
|
|
if d, differs := differencesOf(module, local, m, held); differs {
|
|
out.Differences = append(out.Differences, d)
|
|
}
|
|
}
|
|
}
|
|
return out, nil
|
|
}},
|
|
{Name: "mesh-broker.orphans",
|
|
Description: "Kept data on the bus that no accepted manifest asks for any more: each goes only by the bus module's drop, on the operator's warrant (nats_drop). Never an entry of a refused or set-aside manifest, nor a foundation part's old section.",
|
|
Run: func(map[string]any) (any, error) {
|
|
entries, held, err := both()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return meshbroker.OrphansAnswer{Entries: each(entries, held, func(m keeper.Made) bool { return m.Orphaned })}, nil
|
|
}},
|
|
{Name: "nats_history",
|
|
Description: "Every event the bus module said of one entry of a module's kept data, in order (created, refused, drifted, drift-cleared, full, full-cleared, orphaned, recreated, dropped), from its own stream, kept 30 days.",
|
|
Input: map[string]any{"module": str("the module"), "local": str("its local name"), "limit": num("at most this many, the newest (default 100)")},
|
|
Run: func(a map[string]any) (any, error) {
|
|
module, local := arg(a, "module"), arg(a, "local")
|
|
if module == "" || local == "" {
|
|
return nil, fmt.Errorf("history names one entry: a module and its local name")
|
|
}
|
|
events, err := history(module, local, limitOf(a, 100))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return map[string]any{"module": module, "local": local, "events": events}, nil
|
|
}},
|
|
}
|
|
}
|
|
|
|
// StreamHistory reads the bus module's events of one entry from its own stream, on the tools' own credential.
|
|
func StreamHistory(bus Bus) func(module, local string, limit int) ([]keeper.Event, error) {
|
|
return func(module, local string, limit int) ([]keeper.Event, error) {
|
|
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
|
|
defer cancel()
|
|
js, done, err := bus(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
defer done()
|
|
cons, err := js.OrderedConsumer(ctx, BrokerEventsStream, jetstream.OrderedConsumerConfig{})
|
|
if err != nil {
|
|
return nil, fmt.Errorf("the bus module's stream %s cannot be read: %w", BrokerEventsStream, err)
|
|
}
|
|
info, err := cons.Info(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var out []keeper.Event
|
|
for left := info.NumPending; left > 0; {
|
|
batch, err := cons.Fetch(int(min(left, 256)), jetstream.FetchMaxWait(5*time.Second))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
n := uint64(0)
|
|
for m := range batch.Messages() {
|
|
n++
|
|
var e keeper.Event
|
|
if json.Unmarshal(m.Data(), &e) == nil && e.Module == module && e.Local == local {
|
|
out = append(out, e)
|
|
}
|
|
}
|
|
if n == 0 {
|
|
break
|
|
}
|
|
left -= min(n, left)
|
|
}
|
|
if len(out) > limit {
|
|
out = out[len(out)-limit:]
|
|
}
|
|
return out, nil
|
|
}
|
|
}
|
|
|
|
// BrokerEventsStream is the bus module's own stream on the bus (catalogue.StreamBusName("broker-events")).
|
|
const BrokerEventsStream = "BROKER_EVENTS"
|
|
|
|
func sortedNames[V any](m map[string]V) []string {
|
|
out := make([]string, 0, len(m))
|
|
for k := range m {
|
|
out = append(out, k)
|
|
}
|
|
sort.Strings(out)
|
|
return out
|
|
}
|
|
|
|
// PlansInState reads the keeper's state plans (design 47 G §538): each module's entries as last planned.
|
|
func PlansInState() (map[string]keeper.Planned, error) {
|
|
s := stdio.State(keeper.StatePlans)
|
|
keys, err := s.Keys()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("the keeper's own %s cannot be read: %w", keeper.StatePlans, err)
|
|
}
|
|
out := map[string]keeper.Planned{}
|
|
for _, key := range keys {
|
|
e, err := s.Get(key)
|
|
if err != nil || e == nil {
|
|
continue
|
|
}
|
|
var p keeper.Planned
|
|
if json.Unmarshal(e.Value, &p) == nil {
|
|
out[key] = p
|
|
}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// plansTool is nats_plans: what the bus module planned for each module, from its own state plans (design 47 G §538).
|
|
func plansTool(read func() (map[string]keeper.Planned, error)) stdio.Tool {
|
|
return stdio.Tool{Name: "nats_plans",
|
|
Description: "What the bus module planned for each module: the bus-state entries it last planned, their digest (a verdict whose entries digest the same plans nothing), the revision of the verdict and when. Narrowed by a module.",
|
|
Input: map[string]any{"module": str("one module")},
|
|
Run: func(a map[string]any) (any, error) {
|
|
plans, err := read()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
type line struct {
|
|
Module string `json:"module"`
|
|
Digest string `json:"digest"`
|
|
Revision uint64 `json:"revision"`
|
|
Entries []string `json:"entries"`
|
|
At string `json:"at"`
|
|
}
|
|
out := []line{}
|
|
for _, module := range sortedNames(plans) {
|
|
if m := arg(a, "module"); m != "" && m != module {
|
|
continue
|
|
}
|
|
p := plans[module]
|
|
names := []string{}
|
|
for _, e := range p.Entries {
|
|
names = append(names, e.Name)
|
|
}
|
|
out = append(out, line{Module: module, Digest: p.Digest, Revision: p.Revision, Entries: names, At: p.At.UTC().Format(time.RFC3339)})
|
|
}
|
|
return map[string]any{"plans": out}, nil
|
|
}}
|
|
}
|