The first pass of the adapter resolved every machine with no settings in the Input, so a provider whose served fact says ${setting:domain} could not be settled and the whole Input failed (the equality try on the live facts). The settings of every module that has any are read first; a module's settings that do not read are an error only for a machine that resolves to it. A recorded module that cannot be kept at the build a machine runs refuses that machine alone, as planFor did, and no longer the whole Input. The bus's refusal (no user has a credential) is carried for the machine that runs the bus.
437 lines
14 KiB
Go
437 lines
14 KiB
Go
package main
|
|
|
|
import (
|
|
"context"
|
|
"os"
|
|
"sort"
|
|
"strings"
|
|
|
|
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
|
"git.novox.be/novox/mesh-sdk/go/compose"
|
|
"git.novox.be/novox/mesh-sdk/go/overlay"
|
|
"github.com/novox/mesh-controller/internal/inventory"
|
|
)
|
|
|
|
// The controller's adapter from its stores to mesh-sdk's composition input (novox/hq ADR 0453 clause 3, design 47 R4b,
|
|
// issues 1382 and 1383).
|
|
//
|
|
// **The adapter reads, and perform writes.** readComposeInput asks the stores once for everything the composition reads
|
|
// and returns compose.Input, which the composition never changes; it makes, seals, allocates and records nothing. A
|
|
// pair credential, a port, an own secret or a core box that the next send would make is not made here: the composition
|
|
// names it foreseen, and compose_perform.go makes it for a send, then reads the stores again and composes again. A
|
|
// question (status, facts, probes, the merge gate's reading) never performs.
|
|
//
|
|
// **The Input is built once per operation.** A verb that composes every node reads the stores once and hands the Input to
|
|
// each node's composition of it.
|
|
|
|
// composeInputBuilt is called once for each Input the adapter builds: the seam a test counts the reads of an operation by
|
|
// (issue 1382, criterion 5).
|
|
var composeInputBuilt = func() {}
|
|
|
|
// readComposeInput reads the stores once and returns what the composition needs. ctx carries what a send keeps (the
|
|
// recorded modules kept at the build their machine runs, recorded_kept.go): the same contexts every send composes under.
|
|
func readComposeInput(ctx context.Context, open *stores) (*meshInput, error) {
|
|
in, shape, err := readMeshAndNodes(ctx, open)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
// What each node resolves to, with no credential given (the settings are read before it): the modules whose settings are read, and the
|
|
// needs whose pair credentials are asked of the stores. Nothing is written by it.
|
|
unread, err := readSettings(ctx, open, &in)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
plans := map[string]compose.Resolved{}
|
|
for _, n := range in.Nodes {
|
|
if shape.refused[n.Name] != nil {
|
|
continue // refused for what it keeps: nothing of it is read further
|
|
}
|
|
resolved, err := compose.Resolve(in, n.Name)
|
|
switch {
|
|
case compose.IsUnresolvable(err):
|
|
// Their set does not compose: nothing of theirs is read further, as the controller never read a thing for a
|
|
// node that did not resolve.
|
|
continue
|
|
case err != nil:
|
|
return nil, err
|
|
}
|
|
plans[n.Name] = resolved
|
|
}
|
|
if err := unreadSettings(unread, plans); err != nil {
|
|
return nil, err
|
|
}
|
|
var busRefused error
|
|
wouldMake, err := readCredentialsAndBuilds(ctx, open, &in, plans, shape, &busRefused)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
composeInputBuilt()
|
|
return &meshInput{Input: in, wouldMake: wouldMake, busRefused: busRefused, refused: shape.refused}, nil
|
|
}
|
|
|
|
// meshInput is the Input an operation composes over, and the pair credentials its reading found none held for: the ones the
|
|
// next send makes, which perform makes.
|
|
type meshInput struct {
|
|
compose.Input
|
|
wouldMake []inventory.PairCredential
|
|
// busRefused is why the bus's user list cannot be composed (no user has a credential): the machine that runs the bus is
|
|
// refused with it, as the controller's own composition refused it, and no other machine is.
|
|
busRefused error
|
|
// refused is, by node, why that machine alone cannot be composed (nodeRefusal).
|
|
refused map[string]error
|
|
}
|
|
|
|
// nodeOf is the node of that name in an Input.
|
|
func nodeOf(in compose.Input, name string) (compose.Node, bool) {
|
|
for _, n := range in.Nodes {
|
|
if n.Name == name {
|
|
return n, true
|
|
}
|
|
}
|
|
return compose.Node{}, false
|
|
}
|
|
|
|
// inputShape is what the first half of the reading found that the second half needs.
|
|
type inputShape struct {
|
|
sent map[string]map[string]string // by node: module to the commit last sent
|
|
kept map[string]map[string]string // by node: module to the commit kept
|
|
current map[string]inventory.CurrentBuild
|
|
// refused is, by node, why a recorded module cannot be kept at the build the machine runs (keepRecorded's refusal, in
|
|
// its words): that machine alone is refused with it, as planFor refused it, and nothing else of the mesh is.
|
|
refused map[string]error
|
|
}
|
|
|
|
// readMeshAndNodes is the mesh-wide and per-node half of the Input (issue 1382).
|
|
func readMeshAndNodes(ctx context.Context, open *stores) (compose.Input, inputShape, error) {
|
|
inv := open.inventory
|
|
var in compose.Input
|
|
shape := inputShape{sent: map[string]map[string]string{}, kept: map[string]map[string]string{}, refused: map[string]error{}}
|
|
|
|
shelf, err := inv.Catalogue(ctx)
|
|
if err != nil {
|
|
return in, shape, err
|
|
}
|
|
in.Catalogue = shelf
|
|
if in.Holdings, err = inv.Holdings(ctx); err != nil {
|
|
return in, shape, err
|
|
}
|
|
if in.Licences, err = readLicences(ctx, open); err != nil {
|
|
return in, shape, err
|
|
}
|
|
|
|
nodes, err := inv.Nodes(ctx)
|
|
if err != nil {
|
|
return in, shape, err
|
|
}
|
|
places, err := inv.Overlays(ctx)
|
|
if err != nil {
|
|
return in, shape, err
|
|
}
|
|
placed := map[string]inventory.Overlay{}
|
|
for _, p := range places {
|
|
placed[p.Name] = p
|
|
}
|
|
if shape.current, err = inv.CurrentBuilds(ctx); err != nil {
|
|
return in, shape, err
|
|
}
|
|
|
|
// The range the mesh allocates from and what its internal names end in, read from the environment here and nowhere else.
|
|
in.OverlayCIDR = strings.TrimSpace(os.Getenv(OverlayCIDRVar))
|
|
in.InternalSuffix = overlay.Suffix()
|
|
if in.Tunnels, err = readTunnels(ctx, inv); err != nil {
|
|
return in, shape, err
|
|
}
|
|
if in.Adopted, err = readAdopted(ctx, inv); err != nil {
|
|
return in, shape, err
|
|
}
|
|
carried, err := inv.CarriedPeers(ctx)
|
|
if err != nil {
|
|
return in, shape, err
|
|
}
|
|
for _, c := range carried {
|
|
in.CarriedPeers = append(in.CarriedPeers, compose.CarriedPeer{
|
|
PublicKey: c.PublicKey, Address: c.Address, EnrolledAs: c.EnrolledAs, Named: c.Named})
|
|
}
|
|
if in.Joining, err = inv.NodesWithALiveToken(ctx); err != nil {
|
|
return in, shape, err
|
|
}
|
|
|
|
keepsSecrets := false
|
|
for _, m := range shelf {
|
|
keepsSecrets = keepsSecrets || m.Keeps != ""
|
|
}
|
|
|
|
for _, n := range nodes {
|
|
var refusal error
|
|
node, err := readNode(ctx, open, n, placed[n.Name], &refusal)
|
|
if err != nil {
|
|
return in, shape, err
|
|
}
|
|
if refusal != nil {
|
|
shape.refused[n.Name] = refusal
|
|
}
|
|
shape.sent[n.Name], shape.kept[n.Name] = node.SentBuilds, node.KeptBuilds
|
|
in.Nodes = append(in.Nodes, node)
|
|
}
|
|
if keepsSecrets {
|
|
if in.OperatorExport, err = inv.OperatorExport(ctx); err != nil {
|
|
return in, shape, err
|
|
}
|
|
}
|
|
return in, shape, nil
|
|
}
|
|
|
|
// readNode is one node of the Input: everything the composition reads about it, in the order the controller read it.
|
|
func readNode(ctx context.Context, open *stores, n inventory.Node, place inventory.Overlay,
|
|
refused *error) (compose.Node, error) {
|
|
inv := open.inventory
|
|
out := compose.Node{
|
|
Name: n.Name,
|
|
Place: compose.Place{Key: place.Key, Endpoint: place.Endpoint, Site: place.Site, Hub: place.Hub,
|
|
Address: place.Address},
|
|
Account: n.Account, AccountHome: n.AccountHome, AgentAccount: n.AgentAccount, AgentAccountHome: n.AgentAccountHome,
|
|
Adopted: n.Adopted,
|
|
}
|
|
var err error
|
|
if out.Assigned, err = inv.Assigned(ctx, n.Name); err != nil {
|
|
return out, err
|
|
}
|
|
if out.Capabilities, err = inv.ProfileOf(ctx, n.Name); err != nil {
|
|
return out, err
|
|
}
|
|
if out.PublicDomain, err = inv.PublicDomainOf(ctx, n.Name); err != nil {
|
|
return out, err
|
|
}
|
|
if out.Pins, err = inv.PinsFor(ctx, n.Name); err != nil {
|
|
return out, err
|
|
}
|
|
if out.Bound, err = inv.BindingsFor(ctx, n.Name); err != nil {
|
|
return out, err
|
|
}
|
|
given, err := inv.PortsFor(ctx, n.Name)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
for _, a := range given {
|
|
if out.Ports == nil {
|
|
out.Ports = map[string]map[int]int{}
|
|
}
|
|
if out.Ports[a.Module] == nil {
|
|
out.Ports[a.Module] = map[int]int{}
|
|
}
|
|
out.Ports[a.Module][a.Wanted] = a.Machine
|
|
}
|
|
if out.SealingKey, err = inv.SealingKeyOf(ctx, n.Name); err != nil {
|
|
return out, err
|
|
}
|
|
if n.Adopted {
|
|
if out.Taken, err = inv.Taken(ctx, n.Name); err != nil {
|
|
return out, err
|
|
}
|
|
}
|
|
if out.OutwardLinks, err = inv.OutwardLinksOf(ctx, n.Name); err != nil {
|
|
return out, err
|
|
}
|
|
account, had, err := inv.MachineOf(ctx, n.Name)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
if had && account.Machine != nil {
|
|
out.NetworksSaid = true
|
|
for _, a := range account.Machine.Networks {
|
|
out.Networks = append(out.Networks, catalogue.AttachedNetwork{Link: a.Link, Network: a.Network})
|
|
}
|
|
}
|
|
if out.Sequence, err = inv.Sequence(ctx, n.ID); err != nil {
|
|
return out, err
|
|
}
|
|
if out.Epoch, err = inv.SentEpoch(ctx, n.ID); err != nil {
|
|
return out, err
|
|
}
|
|
if out.Generation, err = inv.SentGeneration(ctx, n.ID); err != nil {
|
|
return out, err
|
|
}
|
|
if out.ReadsModules, err = inv.ReadsModules(ctx, n.ID); err != nil {
|
|
return out, err
|
|
}
|
|
if out.SentBuilds, out.SentKnown, err = inv.SentBuilds(ctx, n.Name); err != nil {
|
|
return out, err
|
|
}
|
|
runs, err := inv.AssignmentRuns(ctx, n.Name)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
if len(runs) > 0 {
|
|
out.Runs = make(map[string]compose.Run, len(runs))
|
|
for module, ref := range runs {
|
|
out.Runs[module] = compose.Run{Commit: ref.Commit, Build: ref.Build}
|
|
}
|
|
}
|
|
// A recorded module kept at the build this machine runs, in the context a send composes under (recorded_kept.go): its
|
|
// manifest and the commit, applied to this node's own resolution only.
|
|
// What cannot be kept refuses this machine alone (keepRecorded's refusal, as planFor returned it).
|
|
kept, err := recordedKept(ctx, open, n.Name)
|
|
if err != nil {
|
|
*refused = err
|
|
} else if len(kept) > 0 {
|
|
out.KeptBuilds = kept
|
|
if out.Kept, err = keptManifests(ctx, open, n.Name, kept); err != nil {
|
|
*refused = err
|
|
out.KeptBuilds, out.Kept = nil, nil
|
|
}
|
|
}
|
|
stated, has, err := inv.HealthOf(ctx, n.Name)
|
|
if err != nil {
|
|
return out, err
|
|
}
|
|
out.Stated = has
|
|
if has {
|
|
out.Contract = stated.Contract
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// keptManifests is the manifest of each module kept at the build a machine runs: keepRecorded's reads, and its refusals in its
|
|
// words, without putting any of it into a shelf (that is the node's own Kept, applied by the composition).
|
|
func keptManifests(ctx context.Context, open *stores, node string, kept map[string]string) (map[string]catalogue.Manifest, error) {
|
|
// applyKept puts each manifest into the shelf it is given and refuses a build that cannot be read; the shelf here is a
|
|
// scratch one, whose entries are the kept manifests.
|
|
scratch := map[string]catalogue.Manifest{}
|
|
if err := applyKept(ctx, open, node, kept, scratch); err != nil {
|
|
return nil, err
|
|
}
|
|
return scratch, nil
|
|
}
|
|
|
|
func readTunnels(ctx context.Context, inv *inventory.Inventory) (map[string]compose.FoundTunnel, error) {
|
|
found, err := inv.Tunnels(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if len(found) == 0 {
|
|
return nil, nil
|
|
}
|
|
out := make(map[string]compose.FoundTunnel, len(found))
|
|
for name, t := range found {
|
|
out[name] = compose.FoundTunnel{Tunnel: tunnelOf(t.Tunnel), NodeAdopted: t.NodeAdopted}
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
func readAdopted(ctx context.Context, inv *inventory.Inventory) (*compose.AdoptedTunnel, error) {
|
|
tunnel, hub, adopted, err := inv.AdoptedTunnel(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if hub == "" {
|
|
return nil, nil
|
|
}
|
|
return &compose.AdoptedTunnel{Tunnel: tunnelOf(tunnel), Hub: hub, Adopted: adopted}, nil
|
|
}
|
|
|
|
func tunnelOf(t inventory.Tunnel) compose.Tunnel {
|
|
out := compose.Tunnel{Interface: t.Interface, Unit: t.Unit, Config: t.Config, Port: t.Port, MTU: t.MTU,
|
|
Address: t.Address, Range: t.Range, PublicKey: t.PublicKey}
|
|
for _, p := range t.Peers {
|
|
out.Peers = append(out.Peers, compose.TunnelPeer{PublicKey: p.PublicKey, Address: p.Address})
|
|
}
|
|
return out
|
|
}
|
|
|
|
// readLicences is every licence the mesh holds, with who uses it and the key sealed to each holder: given even where empty,
|
|
// so a holder the mesh holds no key for composes as that, and not as a key that was not asked for.
|
|
func readLicences(ctx context.Context, open *stores) ([]compose.Licence, error) {
|
|
held, err := open.Licences(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
all, err := held.All(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
var out []compose.Licence
|
|
for _, one := range all {
|
|
l := compose.Licence{Name: one.Name, Serves: one.Serves, Keys: map[compose.Holder]string{}}
|
|
holders, err := held.HoldersOf(ctx, one.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for _, h := range holders {
|
|
holder := compose.Holder{Node: h.Node, Module: h.Module}
|
|
l.Holders = append(l.Holders, holder)
|
|
key, err := held.KeyFor(ctx, one.Name, h.Node, h.Module)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
l.Keys[holder] = key
|
|
}
|
|
node, module, err := held.ManagerOf(ctx, one.Name)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
l.Manager = compose.Holder{Node: node, Module: module}
|
|
out = append(out, l)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// readSettings is each node's settings layers for every module that has any, read BEFORE any node is resolved: the first pass
|
|
// resolves a node whose set takes a provider's served facts from the node that runs it, with that node's settings on them
|
|
// (servedOnNode), so a mesh whose provider says ${setting:domain} resolves only with the settings in the Input. A module's
|
|
// settings that fail to read are returned in unread, by node and module, and are an error only for a module a node resolves to
|
|
// (unreadSettings): the controller read none for a module nothing runs.
|
|
func readSettings(ctx context.Context, open *stores, in *compose.Input) (unread map[string]map[string]error, err error) {
|
|
have, err := open.inventory.ModulesWithSettings(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
names := make([]string, 0, len(have))
|
|
for module := range have {
|
|
if _, onShelf := in.Catalogue[module]; onShelf {
|
|
names = append(names, module)
|
|
}
|
|
}
|
|
sort.Strings(names)
|
|
for i := range in.Nodes {
|
|
n := &in.Nodes[i]
|
|
for _, module := range names {
|
|
layers, err := open.inventory.SettingsFor(ctx, n.Name, module)
|
|
if err != nil {
|
|
if unread == nil {
|
|
unread = map[string]map[string]error{}
|
|
}
|
|
if unread[n.Name] == nil {
|
|
unread[n.Name] = map[string]error{}
|
|
}
|
|
unread[n.Name][module] = err
|
|
continue
|
|
}
|
|
if len(layers) == 0 {
|
|
continue
|
|
}
|
|
if n.Settings == nil {
|
|
n.Settings = map[string][]catalogue.Layer{}
|
|
}
|
|
n.Settings[module] = layers
|
|
}
|
|
}
|
|
return unread, nil
|
|
}
|
|
|
|
// unreadSettings is the error of a module's settings that did not read, for a module the node resolves to.
|
|
func unreadSettings(unread map[string]map[string]error, plans map[string]compose.Resolved) error {
|
|
names := make([]string, 0, len(plans))
|
|
for node := range plans {
|
|
names = append(names, node)
|
|
}
|
|
sort.Strings(names)
|
|
for _, node := range names {
|
|
for _, m := range plans[node].Plan.Modules {
|
|
if err := unread[node][m.Module]; err != nil {
|
|
return err
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|