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 }