|
|
|
@@ -0,0 +1,479 @@
|
|
|
|
|
package grants
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"fmt"
|
|
|
|
|
"slices"
|
|
|
|
|
"sort"
|
|
|
|
|
"strings"
|
|
|
|
|
"time"
|
|
|
|
|
|
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/buscompose"
|
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// **The controller's lease, what still depends on the controller, and a dry run of the lease write's check** (issue 774,
|
|
|
|
|
// design 47 L3; novox/hq ADR 0547 §11, ADR 0561 §3, ADR 0571 §13). One read-only answer from bus state: the lease key,
|
|
|
|
|
// every manifest the indexer holds, and the bus module's streams. It writes nothing, and asks the controller nothing.
|
|
|
|
|
//
|
|
|
|
|
// It lives on the grants part because the part is where the lease write takes most of its effect and already reads all
|
|
|
|
|
// it needs: the lease (buscompose.NamedReaders names it), every manifest at the indexer's trunk, placement, and the bus's
|
|
|
|
|
// streams through the bus module's read tools. No record names the home of the lease write's check; ADR 0571 §13 names
|
|
|
|
|
// its test in three repositories, and this is the same judgement over the manifests the mesh holds now.
|
|
|
|
|
|
|
|
|
|
// ToolLeaseCheck is the tool's name.
|
|
|
|
|
const ToolLeaseCheck = "grants_lease_check"
|
|
|
|
|
|
|
|
|
|
// Controller is the module and the seat the check is about.
|
|
|
|
|
const Controller = "mesh-controller"
|
|
|
|
|
|
|
|
|
|
// The kinds of a dependency on the controller, as a manifest declares them.
|
|
|
|
|
const (
|
|
|
|
|
DependsRead = "read"
|
|
|
|
|
DependsInvoke = "invoke"
|
|
|
|
|
DependsConsume = "consume"
|
|
|
|
|
DependsUses = "uses"
|
|
|
|
|
DependsWaits = "waits"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// The phases of the lease write's check (ADR 0571 §13): before the lease write's own delivery the whole named list is
|
|
|
|
|
// allowed; that delivery drops every entry but the three kept to the write itself, and its check runs with those alone.
|
|
|
|
|
const (
|
|
|
|
|
PhaseBefore = "before-delivery"
|
|
|
|
|
PhaseWrite = "write"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Dependency is one declaration of a manifest other than the controller's on the controller: a read of one of its
|
|
|
|
|
// states, an invoke of one of its verbs or tools, a consume or a wait on one of its events, or a send to its seat.
|
|
|
|
|
type Dependency struct {
|
|
|
|
|
Module string `json:"module"`
|
|
|
|
|
// By is the account declaring it when it is not the module's own: `process <id>` or `tools <binary>`.
|
|
|
|
|
By string `json:"by,omitempty"`
|
|
|
|
|
Kind string `json:"kind"`
|
|
|
|
|
What string `json:"what"`
|
|
|
|
|
// Kept is the record that keeps it until the lease write, from the named list; empty when none does.
|
|
|
|
|
Kept string `json:"kept,omitempty"`
|
|
|
|
|
// ToTheWrite says it is one of the three kept to the write itself, which the lease write's own delivery keeps.
|
|
|
|
|
ToTheWrite bool `json:"to_the_write,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Kept is one entry of the named, temporary list ADR 0571 §13 allows: a read, invoke or consume another accepted record
|
|
|
|
|
// keeps until the lease write, with that record. ToTheWrite marks the three kept to the write itself.
|
|
|
|
|
type Kept struct {
|
|
|
|
|
Module, Kind, What, Record string
|
|
|
|
|
ToTheWrite bool
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// KeepList is the named list. Each entry names the clause that keeps it; a dependency not on it fails the check. The
|
|
|
|
|
// third entry kept to the write, the bus node agent's following of the controller's user list (ADR 0571 §8, ADR 0547
|
|
|
|
|
// §8), is no manifest's declaration but the node-engine's own, so no manifest matches it; it stays named here so the
|
|
|
|
|
// list reads as the record has it.
|
|
|
|
|
var KeepList = []Kept{
|
|
|
|
|
// Kept to the write itself (ADR 0571 §4, §8, §13).
|
|
|
|
|
{Module: "mesh-delivery-builder", Kind: DependsConsume, What: "mesh-controller.artifacts-gone", Record: "ADR 0571 §4, §13", ToTheWrite: true},
|
|
|
|
|
{Module: "artifact-store-tools", Kind: DependsInvoke, What: "seat:mesh-controller.collect", Record: "ADR 0571 §4, §13", ToTheWrite: true},
|
|
|
|
|
{Module: "node-engine", Kind: DependsRead, What: "the controller's user list of the bus module's composition", Record: "ADR 0571 §8, §13; ADR 0547 §8", ToTheWrite: true},
|
|
|
|
|
// Kept until the lease write's own delivery.
|
|
|
|
|
{Module: "mesh-delivery-assigner", Kind: DependsInvoke, What: "seat:mesh-controller.placement-export", Record: "ADR 0567 §7"},
|
|
|
|
|
{Module: "mesh-delivery-assigner", Kind: DependsInvoke, What: "seat:mesh-controller.assign", Record: "ADR 0567 §8"},
|
|
|
|
|
{Module: "mesh-delivery-assigner", Kind: DependsInvoke, What: "seat:mesh-controller.unassign", Record: "ADR 0567 §8"},
|
|
|
|
|
{Module: "mesh-delivery-assigner", Kind: DependsInvoke, What: "seat:mesh-controller.pin", Record: "ADR 0567 §8"},
|
|
|
|
|
{Module: "mesh-delivery-assigner", Kind: DependsInvoke, What: "seat:mesh-controller.unpin", Record: "ADR 0567 §8"},
|
|
|
|
|
{Module: "mesh-delivery-assigner", Kind: DependsRead, What: "mesh-controller.assignments", Record: "ADR 0567 §8"},
|
|
|
|
|
{Module: "mesh-delivery-assigner", Kind: DependsRead, What: "mesh-controller.composed", Record: "ADR 0571 §8"},
|
|
|
|
|
{Module: "mesh-grants", Kind: DependsRead, What: "mesh-controller.assignments", Record: "ADR 0549 §5"},
|
|
|
|
|
{Module: "mesh-grants", Kind: DependsConsume, What: "mesh-controller.condition-raised", Record: "ADR 0564 §9"},
|
|
|
|
|
{Module: "mesh-grants", Kind: DependsConsume, What: "mesh-controller.condition-cleared", Record: "ADR 0564 §9"},
|
|
|
|
|
{Module: "messenger", Kind: DependsConsume, What: "mesh-controller.condition-raised", Record: "ADR 0564 §9"},
|
|
|
|
|
{Module: "messenger", Kind: DependsConsume, What: "mesh-controller.condition-cleared", Record: "ADR 0564 §9"},
|
|
|
|
|
{Module: "messenger", Kind: DependsConsume, What: "mesh-controller.condition-changed", Record: "ADR 0564 §9"},
|
|
|
|
|
{Module: "messenger", Kind: DependsInvoke, What: "seat:mesh-controller.conditions", Record: "ADR 0564 §9"},
|
|
|
|
|
{Module: "mesh-watcher", Kind: DependsConsume, What: "mesh-controller.doctor-heartbeat", Record: "ADR 0564 §9"},
|
|
|
|
|
{Module: "mesh-conditions", Kind: DependsRead, What: "mesh-controller.conditions", Record: "ADR 0564 §9"},
|
|
|
|
|
{Module: "mesh-config", Kind: DependsRead, What: "mesh-controller.declared-config", Record: "ADR 0565 §4"},
|
|
|
|
|
{Module: "mesh-config", Kind: DependsRead, What: "mesh-controller.predefined", Record: "ADR 0565 §4"},
|
|
|
|
|
{Module: "mesh-healers", Kind: DependsRead, What: "mesh-controller.hand-acts", Record: "ADR 0565 §11"},
|
|
|
|
|
{Module: "mesh-hand-acts", Kind: DependsRead, What: "mesh-controller.hand-acts", Record: "ADR 0565 §11"},
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// EndedBy names, for a declaration no entry keeps, the record that already ended it, so a refusal says where it moved.
|
|
|
|
|
// The controller's `grants` is one: ADR 0571 §9 has no manifest read it from L7's delivery, which changes ADR 0567 §10
|
|
|
|
|
// and ADR 0547 §5's shadow compare, so neither the assigner's nor the grants part's read of it is kept.
|
|
|
|
|
var EndedBy = map[string]string{
|
|
|
|
|
DependsRead + " mesh-controller.grants": "ADR 0571 §9: no manifest reads the controller's grants; the grants part's key is the one read",
|
|
|
|
|
DependsInvoke + " seat:mesh-controller.hand-act": "ADR 0565 §11: every recorder calls mesh-hand-acts",
|
|
|
|
|
DependsInvoke + " seat:mesh-controller.warranted": "ADR 0565 §11: every recorder calls mesh-hand-acts",
|
|
|
|
|
DependsInvoke + " seat:mesh-controller.root-free": "ADR 0571 §11: root-free is the self-check's probe",
|
|
|
|
|
DependsInvoke + " seat:mesh-controller.held": "ADR 0571 §2: held is the seat mesh-artifact-sweep's",
|
|
|
|
|
DependsRead + " mesh-controller.declared-packages": "ADR 0565 §6: each node agent writes its own node's packages",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// keptFor is the list's entry for one dependency, or nil.
|
|
|
|
|
func keptFor(d Dependency) *Kept {
|
|
|
|
|
for i, k := range KeepList {
|
|
|
|
|
if k.Module == d.Module && k.Kind == d.Kind && k.What == d.What {
|
|
|
|
|
return &KeepList[i]
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// onController says whether a name `<owner>.<rest>` is the controller's.
|
|
|
|
|
func onController(name string) bool { return strings.HasPrefix(name, Controller+".") }
|
|
|
|
|
|
|
|
|
|
// invokesController says whether an invoke names a verb of the controller's seat or a tool of its module. `*` names none:
|
|
|
|
|
// it is the console's grant to every tool, judged by no record as a dependency.
|
|
|
|
|
func invokesController(invoke string) bool {
|
|
|
|
|
if seat, _, ok := catalogue.SeatInvoke(invoke); ok {
|
|
|
|
|
return seat == Controller
|
|
|
|
|
}
|
|
|
|
|
return onController(invoke)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Dependencies are every declaration on the controller in the manifests given, by module, each with the list's entry
|
|
|
|
|
// that keeps it. The controller's own manifest is not judged, nor a read of its lease key: it names the lease's holder,
|
|
|
|
|
// whoever that is, and parts read it to learn that the write happened (ADR 0571 §13).
|
|
|
|
|
func Dependencies(manifests map[string]catalogue.Manifest) []Dependency {
|
|
|
|
|
var out []Dependency
|
|
|
|
|
add := func(module, by, kind, what string) {
|
|
|
|
|
d := Dependency{Module: module, By: by, Kind: kind, What: what}
|
|
|
|
|
if k := keptFor(d); k != nil {
|
|
|
|
|
d.Kept, d.ToTheWrite = k.Record, k.ToTheWrite
|
|
|
|
|
}
|
|
|
|
|
if !slices.Contains(out, d) {
|
|
|
|
|
out = append(out, d)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for name, m := range manifests {
|
|
|
|
|
if name == Controller || m.Module == Controller {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
for _, r := range m.Reads {
|
|
|
|
|
if onController(r) && r != buscompose.ControllerLease {
|
|
|
|
|
add(name, "", DependsRead, r)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, i := range m.Invokes {
|
|
|
|
|
if invokesController(i) {
|
|
|
|
|
add(name, "", DependsInvoke, i)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, c := range m.Consumes {
|
|
|
|
|
if onController(c) {
|
|
|
|
|
add(name, "", DependsConsume, c)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, w := range m.Waits {
|
|
|
|
|
if onController(w) {
|
|
|
|
|
add(name, "", DependsWaits, w)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, u := range m.Uses {
|
|
|
|
|
if u == Controller {
|
|
|
|
|
add(name, "", DependsUses, u)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, a := range m.BusAccounts() {
|
|
|
|
|
by := "process " + a.ID
|
|
|
|
|
if a.Tools {
|
|
|
|
|
by = "tools " + a.ID
|
|
|
|
|
}
|
|
|
|
|
for _, i := range a.Bus.Invokes {
|
|
|
|
|
if invokesController(i) {
|
|
|
|
|
add(name, by, DependsInvoke, i)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
sort.Slice(out, func(i, j int) bool {
|
|
|
|
|
a, b := out[i], out[j]
|
|
|
|
|
if a.Module != b.Module {
|
|
|
|
|
return a.Module < b.Module
|
|
|
|
|
}
|
|
|
|
|
if a.Kind != b.Kind {
|
|
|
|
|
return a.Kind < b.Kind
|
|
|
|
|
}
|
|
|
|
|
if a.What != b.What {
|
|
|
|
|
return a.What < b.What
|
|
|
|
|
}
|
|
|
|
|
return a.By < b.By
|
|
|
|
|
})
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Verdict is the lease write's check in one phase.
|
|
|
|
|
type Verdict struct {
|
|
|
|
|
Phase string `json:"phase"`
|
|
|
|
|
Passes bool `json:"passes"`
|
|
|
|
|
Reasons []string `json:"reasons"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Coverage is how much of the indexer the dependencies were derived from: the manifests read, the modules the indexer
|
|
|
|
|
// holds (-1 when its keys could not be listed, which Unread then names), and each manifest that could not be read.
|
|
|
|
|
type Coverage struct {
|
|
|
|
|
Read, Indexed int
|
|
|
|
|
Unread []Gap
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Judge runs the lease write's check over the dependencies (ADR 0571 §13) in one phase: before the lease write's own
|
|
|
|
|
// delivery, every dependency must be on the named list; at the write, only the three kept to the write itself may stay.
|
|
|
|
|
// It passes only over every module the indexer holds: no manifest read, fewer read than the indexer holds, or one that
|
|
|
|
|
// could not be read fails it, since what was not read could hide a dependency. A lease already handed away leaves
|
|
|
|
|
// nothing to write.
|
|
|
|
|
func Judge(deps []Dependency, phase string, cov Coverage, handedAway bool, leaseSays string) Verdict {
|
|
|
|
|
v := Verdict{Phase: phase, Reasons: []string{}}
|
|
|
|
|
if handedAway {
|
|
|
|
|
v.Reasons = append(v.Reasons, "there is nothing to write: "+leaseSays)
|
|
|
|
|
}
|
|
|
|
|
switch {
|
|
|
|
|
case cov.Read == 0:
|
|
|
|
|
v.Reasons = append(v.Reasons, fmt.Sprintf("no manifest was read from the indexer (it holds %d modules): with nothing read, nothing on the controller can be judged absent", max(cov.Indexed, 0)))
|
|
|
|
|
case cov.Indexed >= 0 && cov.Read < cov.Indexed:
|
|
|
|
|
v.Reasons = append(v.Reasons, fmt.Sprintf("only %d of the %d modules the indexer holds had their manifest read, so what the others declare on the controller is not known", cov.Read, cov.Indexed))
|
|
|
|
|
}
|
|
|
|
|
for _, d := range deps {
|
|
|
|
|
who := d.Module
|
|
|
|
|
if d.By != "" {
|
|
|
|
|
who += " (" + d.By + ")"
|
|
|
|
|
}
|
|
|
|
|
switch {
|
|
|
|
|
case d.Kept == "":
|
|
|
|
|
why := fmt.Sprintf("%s declares %s %s, which no record keeps until the lease write (ADR 0571 §13)", who, d.Kind, d.What)
|
|
|
|
|
if ended := EndedBy[d.Kind+" "+d.What]; ended != "" {
|
|
|
|
|
why += "; ended by " + ended
|
|
|
|
|
}
|
|
|
|
|
v.Reasons = append(v.Reasons, why)
|
|
|
|
|
case phase == PhaseWrite && !d.ToTheWrite:
|
|
|
|
|
v.Reasons = append(v.Reasons, fmt.Sprintf("%s declares %s %s, kept by %s only until the lease write's own delivery, which drops it", who, d.Kind, d.What, d.Kept))
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
for _, g := range cov.Unread {
|
|
|
|
|
v.Reasons = append(v.Reasons, fmt.Sprintf("%s could not be read (%s), so what it declares on the controller is not known", g.Input, g.Why))
|
|
|
|
|
}
|
|
|
|
|
v.Passes = len(v.Reasons) == 0
|
|
|
|
|
return v
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LeaseNow is who holds the lease and what follows from it.
|
|
|
|
|
type LeaseNow struct {
|
|
|
|
|
HandedAway bool `json:"handed_away"`
|
|
|
|
|
Says string `json:"says"`
|
|
|
|
|
Holder *buscompose.LeaseHolder `json:"holder,omitempty"`
|
|
|
|
|
Since *time.Time `json:"since,omitempty"`
|
|
|
|
|
Does []string `json:"does"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// While the controller holds the lease, and once it is handed away: what the holder and the parts watching the key do
|
|
|
|
|
// because of it, by the records that say so.
|
|
|
|
|
var (
|
|
|
|
|
DoesWhileController = []string{
|
|
|
|
|
"the controller mints every new bus account; the vault mints none and says what it holds back (ADR 0561 §3)",
|
|
|
|
|
"the grants part creates no durable consumer an account needs, and only compares them (ADR 0561 §6)",
|
|
|
|
|
"the grants part publishes memberships on the controller's assignments stream beside its own state (ADR 0571 §10)",
|
|
|
|
|
"mesh-healers writes each act it would take in `would`, and acts on none (ADR 0564 §8)",
|
|
|
|
|
"the artifact sweep lets go of nothing; the controller's take-in sweeps and says `artifacts-gone` (ADR 0571 §4)",
|
|
|
|
|
"the watchman only lists dead letters; the controller keeps DEAD_LETTERS through its consumer of DEAD_LETTER_NOTICES (ADR 0564 §5)",
|
|
|
|
|
"placement proposals take only assignments, pins and config values, and the assigner keeps the controller's assignments in step (ADR 0567 §8, §9)",
|
|
|
|
|
"the controller keeps composing for the bus node, whose agent follows its user list, so the hand-backs of config, conditions, placement and the user list still answer (ADR 0547 §8, ADR 0564 §9, ADR 0565 §4, ADR 0567 §11)",
|
|
|
|
|
}
|
|
|
|
|
DoesHandedAway = []string{
|
|
|
|
|
"the vault mints every new bus account (ADR 0561 §1)",
|
|
|
|
|
"the grants part creates the durable consumers an account needs, and stops publishing on the assignments stream (ADR 0561 §6, ADR 0571 §10)",
|
|
|
|
|
"mesh-healers acts (ADR 0564 §8)",
|
|
|
|
|
"the artifact sweep sweeps and records every deletion in the hand-act log (ADR 0571 §4)",
|
|
|
|
|
"the watchman keeps the dead letters (ADR 0564 §5)",
|
|
|
|
|
"every hand-back is answered as gone (ADR 0547 §11, ADR 0564 §9, ADR 0565 §4, ADR 0567 §11)",
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// Lease reads the lease key and says who holds it, since when, and what follows.
|
|
|
|
|
func Lease(r Reader) LeaseNow {
|
|
|
|
|
raw, found, err := r.Get(ReadLease, LeaseKey)
|
|
|
|
|
away, says := buscompose.LeaseHandedAway(raw, found, err)
|
|
|
|
|
out := LeaseNow{HandedAway: away, Says: says, Does: DoesWhileController}
|
|
|
|
|
if away {
|
|
|
|
|
out.Does = DoesHandedAway
|
|
|
|
|
}
|
|
|
|
|
if err == nil && found && len(raw) > 0 {
|
|
|
|
|
var h buscompose.LeaseHolder
|
|
|
|
|
if json.Unmarshal(raw, &h) == nil {
|
|
|
|
|
out.Holder = &h
|
|
|
|
|
if !h.Taken.IsZero() {
|
|
|
|
|
t := h.Taken
|
|
|
|
|
out.Since = &t
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// StreamUse is one of the controller's streams (its buckets, the assignments stream, the dead-letter notices) as the bus
|
|
|
|
|
// module answers it: who consumes it now, and what it last carried.
|
|
|
|
|
type StreamUse struct {
|
|
|
|
|
Stream string `json:"stream"`
|
|
|
|
|
Consumers int `json:"consumers"`
|
|
|
|
|
Durable []string `json:"durable,omitempty"`
|
|
|
|
|
Messages int64 `json:"messages"`
|
|
|
|
|
Subjects int64 `json:"subjects_held,omitempty"`
|
|
|
|
|
Last string `json:"last,omitempty"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// The streams of the controller's that are not its buckets: the assignments stream it publishes memberships on, and the
|
|
|
|
|
// notices its consumer `controller` turns into dead letters until the lease write (ADR 0564 §5).
|
|
|
|
|
const (
|
|
|
|
|
StreamAssignments = "ASSIGNMENTS"
|
|
|
|
|
StreamDeadLetterNotices = "DEAD_LETTER_NOTICES"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// controllerStream says whether a stream is the controller's.
|
|
|
|
|
func controllerStream(name string) bool {
|
|
|
|
|
return strings.HasPrefix(name, "KV_"+Controller+"_") || name == StreamAssignments || name == StreamDeadLetterNotices
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Duties reads, from the bus module's streams, every stream of the controller's someone consumes, with its durable
|
|
|
|
|
// consumers by name (a watcher of a bucket is ephemeral and names nobody). Streams with no consumer are counted apart.
|
|
|
|
|
func Duties(c Caller) (used []StreamUse, idle []string, err error) {
|
|
|
|
|
raw, err := c.Call(CallStreams, map[string]any{})
|
|
|
|
|
if err != nil {
|
|
|
|
|
return nil, nil, fmt.Errorf("%s: %w", CallStreams, err)
|
|
|
|
|
}
|
|
|
|
|
type consumer struct {
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
Kind string `json:"kind"`
|
|
|
|
|
Max int `json:"max_deliver"`
|
|
|
|
|
Ack string `json:"ack_policy"`
|
|
|
|
|
}
|
|
|
|
|
type stream struct {
|
|
|
|
|
Name string `json:"name"`
|
|
|
|
|
Consumers int `json:"consumers"`
|
|
|
|
|
Messages int64 `json:"messages"`
|
|
|
|
|
Held int64 `json:"subjects_held"`
|
|
|
|
|
Last string `json:"last"`
|
|
|
|
|
Detail []consumer `json:"consumer_detail"`
|
|
|
|
|
}
|
|
|
|
|
var got struct {
|
|
|
|
|
Detail []stream `json:"detail"`
|
|
|
|
|
}
|
|
|
|
|
if err := json.Unmarshal(unwrap(raw), &got); err != nil {
|
|
|
|
|
return nil, nil, fmt.Errorf("%s answered no streams: %w", CallStreams, err)
|
|
|
|
|
}
|
|
|
|
|
for _, s := range got.Detail {
|
|
|
|
|
if !controllerStream(s.Name) {
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
if s.Consumers == 0 {
|
|
|
|
|
idle = append(idle, s.Name)
|
|
|
|
|
continue
|
|
|
|
|
}
|
|
|
|
|
u := StreamUse{Stream: s.Name, Consumers: s.Consumers, Messages: s.Messages, Subjects: s.Held, Last: s.Last}
|
|
|
|
|
if one, err := c.Call(CallStreams, map[string]any{"stream": s.Name}); err == nil {
|
|
|
|
|
var d struct {
|
|
|
|
|
Detail []stream `json:"detail"`
|
|
|
|
|
}
|
|
|
|
|
if json.Unmarshal(unwrap(one), &d) == nil && len(d.Detail) > 0 {
|
|
|
|
|
for _, cs := range d.Detail[0].Detail {
|
|
|
|
|
if durable(cs.Name) {
|
|
|
|
|
u.Durable = append(u.Durable, cs.Name)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
used = append(used, u)
|
|
|
|
|
}
|
|
|
|
|
sort.Slice(used, func(i, j int) bool { return used[i].Stream < used[j].Stream })
|
|
|
|
|
sort.Strings(idle)
|
|
|
|
|
return used, idle, nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// durable says whether a consumer's name is one a part gave it: the bus names an ephemeral watcher with eight random
|
|
|
|
|
// letters and digits, which no part of the mesh uses.
|
|
|
|
|
func durable(name string) bool {
|
|
|
|
|
if len(name) != 8 {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
for _, r := range name {
|
|
|
|
|
if !(r >= 'a' && r <= 'z' || r >= 'A' && r <= 'Z' || r >= '0' && r <= '9') {
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LeaseAnswer is the tool's answer.
|
|
|
|
|
type LeaseAnswer struct {
|
|
|
|
|
Lease LeaseNow `json:"lease"`
|
|
|
|
|
Dependencies []Dependency `json:"dependencies"`
|
|
|
|
|
// Unkept are the dependencies no record keeps, by module: what the check refuses in every phase.
|
|
|
|
|
Unkept map[string][]string `json:"unkept"`
|
|
|
|
|
Manifests int `json:"manifests_read"`
|
|
|
|
|
// Indexed is how many modules the indexer holds; -1 when its keys could not be listed.
|
|
|
|
|
Indexed int `json:"modules_indexed"`
|
|
|
|
|
Unread []Gap `json:"manifests_unread,omitempty"`
|
|
|
|
|
// Streams are the controller's streams someone consumes now; Idle those no one does.
|
|
|
|
|
Streams []StreamUse `json:"streams,omitempty"`
|
|
|
|
|
Idle []string `json:"streams_idle,omitempty"`
|
|
|
|
|
StreamsError string `json:"streams_unread,omitempty"`
|
|
|
|
|
// Assignments is how many of the controller's assignments stand, which it publishes memberships and grants for.
|
|
|
|
|
Assignments int `json:"assignments_standing"`
|
|
|
|
|
// Check is the dry run at the write (the three alone), Before the same over the whole list before the write's
|
|
|
|
|
// delivery.
|
|
|
|
|
Check Verdict `json:"check"`
|
|
|
|
|
Before Verdict `json:"check_before_delivery"`
|
|
|
|
|
Beside []string `json:"not_judged_here"`
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// NotJudgedHere are the lease write's other conditions, each with the tool that answers it (design 47 L3, ADR 0547 §11).
|
|
|
|
|
var NotJudgedHere = []string{
|
|
|
|
|
"L4 to L7 delivered: the issue tracker's milestones 1952, 1953, 1954 and 1955",
|
|
|
|
|
"every node's record trusts the vault's key: mesh-delivery-view.node_trusts (ADR 0550 §2)",
|
|
|
|
|
"mesh-config's compare empty and its take-over done, and mesh-wants.upgrade compare empty: mesh-config.compare, mesh-wants.upgrade (ADR 0565 §14)",
|
|
|
|
|
"the condition keeper's take-over done: mesh-conditions.compare (ADR 0564 §9)",
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// LeaseCheck answers the tool: who holds the lease and what follows, every dependency on the controller with what keeps
|
|
|
|
|
// it, the controller's streams in use, and the lease write's check run dry. Read-only.
|
|
|
|
|
func (p *Part) LeaseCheck() (any, error) {
|
|
|
|
|
in := ReadInputs(p.Read, &p.cache)
|
|
|
|
|
out := LeaseAnswer{Lease: Lease(p.Read), Unkept: map[string][]string{}, Manifests: len(in.Manifests), Unread: in.Refused,
|
|
|
|
|
Beside: NotJudgedHere}
|
|
|
|
|
for _, g := range in.Gaps {
|
|
|
|
|
if g.Failed && strings.HasPrefix(g.Input, ReadModules) {
|
|
|
|
|
out.Unread = append(out.Unread, g)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
out.Dependencies = Dependencies(in.Manifests)
|
|
|
|
|
if out.Dependencies == nil {
|
|
|
|
|
out.Dependencies = []Dependency{}
|
|
|
|
|
}
|
|
|
|
|
for _, d := range out.Dependencies {
|
|
|
|
|
if d.Kept == "" {
|
|
|
|
|
out.Unkept[d.Module] = append(out.Unkept[d.Module], d.Kind+" "+d.What)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
var gaps []Gap
|
|
|
|
|
for _, raw := range each(p.Read, ReadAssignments, &gaps) {
|
|
|
|
|
var a assignment
|
|
|
|
|
if json.Unmarshal(raw, &a) == nil && a.stands() {
|
|
|
|
|
out.Assignments++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
if used, idle, err := Duties(p.Call); err != nil {
|
|
|
|
|
out.StreamsError = err.Error()
|
|
|
|
|
} else {
|
|
|
|
|
out.Streams, out.Idle = used, idle
|
|
|
|
|
}
|
|
|
|
|
// Only the indexer's own modules count as read: a manifest the mesh provides itself must not stand in for one missing.
|
|
|
|
|
cov := Coverage{Indexed: -1, Unread: out.Unread}
|
|
|
|
|
if keys, err := p.Read.Keys(ReadModules); err == nil {
|
|
|
|
|
cov.Indexed = len(keys)
|
|
|
|
|
for _, k := range keys {
|
|
|
|
|
if _, ok := in.Manifests[k]; ok {
|
|
|
|
|
cov.Read++
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
out.Indexed = cov.Indexed
|
|
|
|
|
out.Check = Judge(out.Dependencies, PhaseWrite, cov, out.Lease.HandedAway, out.Lease.Says)
|
|
|
|
|
out.Before = Judge(out.Dependencies, PhaseBefore, cov, out.Lease.HandedAway, out.Lease.Says)
|
|
|
|
|
return out, nil
|
|
|
|
|
}
|