package main import ( "bytes" "context" "encoding/json" "errors" "flag" "fmt" "os" "sort" "strings" "github.com/novox/mesh-controller/internal/broker" "github.com/novox/mesh-controller/internal/catalogue" "github.com/novox/mesh-controller/internal/inventory" "github.com/novox/mesh-controller/internal/licences" "github.com/novox/mesh-controller/internal/overlay" ) // working out what one machine should be. // // Split out of main.go, which had reached 2,769 lines because appending was always the // cheapest next step. That is how novox/hq ADR 0001 records `hal/sdk` reaching 34,636: // nothing in it was wrong, and no one edit was the one that should have been a new file. // notResolvable marks the one failure in planFor that is a statement about the node: its assigned // modules do not compose. Every other failure means the mesh could not be *asked* — the store was // unreachable, a key could not be read — and says nothing about the node at all. // // The distinction exists because three callers gather something across every machine and must carry // on when one machine's set is broken. Each of them read a plain error as "their set does not // resolve", and so read a store that was briefly unreachable as a machine that runs nothing. On the // roster of routed names that is not a degraded answer but a false one: it states, to every machine // at once, that another machine's names do not exist. A control node spent hours replacing every // container it ran, on a six-minute cycle, because each pass restarted the store this is read from, // the read failed, one name left the roster, and the roster is part of every container's identity // (novox/hq 04-ISSUES/152, and 04-ISSUES/151 for why a changed roster is a changed container). // // So: skip a node that cannot resolve, and never a node that could not be read. type notResolvable struct{ err error } func (n notResolvable) Error() string { return n.err.Error() } func (n notResolvable) Unwrap() error { return n.err } // unresolvable reports whether err is a node's own set failing to compose, rather than the mesh // being unable to answer. func unresolvable(err error) bool { var n notResolvable return errors.As(err, &n) } // planFor works out everything a node should run, from what was assigned to it. // // A failure to compose the node's own modules is wrapped as notResolvable; every other failure is // returned as it is. Callers gathering across the mesh must tell them apart — see notResolvable. func planFor(ctx context.Context, open *stores, nodeName string) (catalogue.Resolution, catalogue.SettingsBy, error) { inv := open.inventory shelf, err := inv.Catalogue(ctx) if err != nil { return catalogue.Resolution{}, nil, err } assigned, err := inv.Assigned(ctx, nodeName) if err != nil { return catalogue.Resolution{}, nil, err } capabilities, err := inv.ProfileOf(ctx, nodeName) if err != nil { return catalogue.Resolution{}, nil, err } places, err := inv.Overlays(ctx) if err != nil { return catalogue.Resolution{}, nil, err } var site string for _, p := range places { if p.Name == nodeName { site = p.Site } } world, err := theRestOfTheMesh(ctx, inv, shelf, nodeName) if err != nil { return catalogue.Resolution{}, nil, err } world.Pinned, err = inv.PinsFor(ctx, nodeName) if err != nil { return catalogue.Resolution{}, nil, err } onNetwork, err := whereEveryoneIs(ctx, inv, shelf) if err != nil { return catalogue.Resolution{}, nil, err } // What this mesh can answer with a record rather than a machine, and which record each of // this node's modules was put on. Read across a context boundary by name, which is what // crossing one is allowed to carry (novox/hq ADR 0008). world.Licences, world.Using, err = licencesFor(ctx, open, nodeName) if err != nil { return catalogue.Resolution{}, nil, err } // The domain this node composes its routed names under (novox/hq ADR 0066). A route // contribution carries only a label; the resolver joins