Issue 258's fix let a mesh seat's holder elsewhere answer before this machine's own provider. Right for the resolver, which any provider answers alike; for the store's seat it re-bound every database consumer on a machine running its own store to the holder on another, each was given a fresh, empty database there, and nothing said so for twenty hours. - An offer says whether it keeps its consumers' data (`keeps-consumer-data`); unsaid, a provider that grants each consumer a credential does. For such a provision the seat's holder no longer overrules a provider beside the consumer; a pin still does. - Where each such consumer was sent is recorded (migration 0071). A resolution that would bind it elsewhere keeps the recorded provider and says the move; one whose provider is gone is refused, never answered by another. - A push says a kept move and raises it as an urgent condition at once; the self-check's D12 raises it every run, with a pinned move not yet sent as a warning and any unasked move as urgent.
141 lines
5.4 KiB
Go
141 lines
5.4 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
|
|
"github.com/novox/mesh-controller/internal/catalogue"
|
|
"github.com/novox/mesh-controller/internal/conditions"
|
|
)
|
|
|
|
// A binding to data moves only by a person (novox/hq ADR 0232, issue 273).
|
|
//
|
|
// The resolver keeps each consumer of a provision that keeps its data at the provider it was last
|
|
// sent (catalogue/bound.go) and says what it would have moved. This is where that is said: on the
|
|
// push that composed it, and by the self-check's D12 every run, as an urgent condition naming the
|
|
// consumer, both providers and the pin that confirms the move.
|
|
|
|
// The condition kinds of D12.
|
|
const (
|
|
// kindBindingKept is a move the resolver refused: the consumer is still where its data is.
|
|
kindBindingKept = "binding-kept"
|
|
// kindBindingMoved is a consumer about to be sent another provider than the one on record with
|
|
// no pin naming it — what the resolver exists to make impossible, said if it ever is not.
|
|
kindBindingMoved = "binding-moved"
|
|
// kindBindingMoving is a move a pin asked for, not yet sent: a person's act, said so that the
|
|
// data is moved before the push that carries it.
|
|
kindBindingMoving = "binding-moving"
|
|
)
|
|
|
|
// probeBindingsID is the self-check's id for this probe, and the source of what it raises.
|
|
const probeBindingsID = "D12"
|
|
|
|
// keptObservation is the urgent condition for one refused move.
|
|
func keptObservation(k catalogue.KeptBinding) conditions.Observation {
|
|
return conditions.Observation{Scope: conditions.ScopeMachine, ID: bindingID(k.Machine, k.Consumer, k.Provision),
|
|
Token: kindBindingKept, Kind: kindBindingKept, Machine: k.Machine, Also: otherMachines(k.Machine, k.Bound.Node, k.Would.Node),
|
|
Severity: conditions.Urgent, Resolver: conditions.ResolverOperator,
|
|
Summary: fmt.Sprintf("on %s, the mesh %s", k.Machine, k.String())}
|
|
}
|
|
|
|
func bindingID(machine, consumer, provision string) string {
|
|
return machine + "." + consumer + "." + provision
|
|
}
|
|
|
|
func otherMachines(machine string, nodes ...string) []string {
|
|
var out []string
|
|
seen := map[string]bool{machine: true}
|
|
for _, n := range nodes {
|
|
if n != "" && !seen[n] {
|
|
seen[n] = true
|
|
out = append(out, n)
|
|
}
|
|
}
|
|
return out
|
|
}
|
|
|
|
// reportKept says every move a machine's resolution refused, on the push composing it, and raises its
|
|
// condition at once where this process keeps the conditions: a push is when a person is looking.
|
|
func reportKept(ctx context.Context, plan catalogue.Resolution) {
|
|
for _, k := range plan.Kept {
|
|
fmt.Printf("%s: the mesh %s\n", plan.Node, k)
|
|
if conditionsFrom != nil {
|
|
o := keptObservation(k)
|
|
o.Source = probeBindingsID
|
|
if _, err := conditionsFrom.Observe(ctx, o); err != nil {
|
|
fmt.Printf("%s: and the condition for it could not be raised: %v\n", plan.Node, err)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
// probeBindings is D12: every consumer of a provision that keeps its data is bound where it was last
|
|
// sent, on every machine — the resolver kept it there (said, urgent, until a person pins), or a pin
|
|
// moves it (said, so the data goes first), and never anything else.
|
|
func probeBindings(ctx context.Context, d *doctor) ([]conditions.Observation, error) {
|
|
inv := d.open.inventory
|
|
nodes, err := inv.Nodes(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var out []conditions.Observation
|
|
for _, n := range nodes {
|
|
plan, _, err := planFor(ctx, d.open, n.Name)
|
|
if err != nil {
|
|
if unresolvable(err) {
|
|
// D1 says it, with the binding that refused it when that is why.
|
|
continue
|
|
}
|
|
return nil, fmt.Errorf("%s cannot be worked out: %w", n.Name, err)
|
|
}
|
|
bound, err := inv.BindingsFor(ctx, n.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
pins, err := inv.PinsFor(ctx, n.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
out = append(out, bindingFindings(plan, bound, pins)...)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// bindingFindings is what one machine's resolution says against its record.
|
|
func bindingFindings(plan catalogue.Resolution, bound map[string]map[string]catalogue.Chosen,
|
|
pins map[string]catalogue.Chosen) []conditions.Observation {
|
|
var out []conditions.Observation
|
|
for _, k := range plan.Kept {
|
|
out = append(out, keptObservation(k))
|
|
}
|
|
said := map[string]bool{}
|
|
for _, need := range plan.Needs {
|
|
if !need.KeepsData || need.ByRecord {
|
|
continue
|
|
}
|
|
was, recorded := bound[need.For][need.Name]
|
|
now := catalogue.Chosen{Node: need.From, Module: need.Module}
|
|
if !recorded || was == now || (was.Module == "" && was.Node == now.Node) {
|
|
continue
|
|
}
|
|
id := bindingID(plan.Node, need.For, need.Name)
|
|
if said[id] {
|
|
continue
|
|
}
|
|
said[id] = true
|
|
o := conditions.Observation{Scope: conditions.ScopeMachine, ID: id, Machine: plan.Node,
|
|
Also: otherMachines(plan.Node, was.Node, now.Node), Resolver: conditions.ResolverOperator}
|
|
if pin, pinned := pins[need.Name]; pinned && pin.Node == now.Node && (pin.Module == "" || pin.Module == now.Module) {
|
|
o.Token, o.Kind, o.Severity = kindBindingMoving, kindBindingMoving, conditions.Warning
|
|
o.Summary = fmt.Sprintf("on %s, %s's %s moves from %s to %s at the next push, by the pin — its data "+
|
|
"is on %s: move it first", plan.Node, need.For, need.Name, was, now, was)
|
|
} else {
|
|
o.Token, o.Kind, o.Severity = kindBindingMoved, kindBindingMoved, conditions.Urgent
|
|
o.Summary = fmt.Sprintf("on %s, %s's %s would be sent %s, and it is bound to %s, where its data is, "+
|
|
"with no pin naming %s — a move nothing asked for", plan.Node, need.For, need.Name, now, was, now)
|
|
}
|
|
out = append(out, o)
|
|
}
|
|
return out
|
|
}
|