Take the composition, catalogue and validator from mesh-sdk and delete the controller's copies (design 47 R4b, issues 1337, 1381-1391) #405
@@ -0,0 +1,392 @@
|
||||
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 and no settings given: the modules whose settings are read, and the
|
||||
// needs whose pair credentials are asked of the stores. Nothing is written by it.
|
||||
plans := map[string]compose.Resolved{}
|
||||
for _, n := range in.Nodes {
|
||||
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 := readSettings(ctx, open, &in, plans); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if err := readCredentialsAndBuilds(ctx, open, &in, plans, shape); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
composeInputBuilt()
|
||||
return &meshInput{Input: in}, nil
|
||||
}
|
||||
|
||||
// readCredentialsAndBuilds is the credential and build half of the Input, which issue 1383 fills.
|
||||
func readCredentialsAndBuilds(context.Context, *stores, *compose.Input, map[string]compose.Resolved, inputShape) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// meshInput is the Input an operation composes over.
|
||||
type meshInput struct {
|
||||
compose.Input
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// 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{}}
|
||||
|
||||
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 {
|
||||
node, err := readNode(ctx, open, n, placed[n.Name])
|
||||
if err != nil {
|
||||
return in, shape, err
|
||||
}
|
||||
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) (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.
|
||||
kept, err := recordedKept(ctx, open, n.Name)
|
||||
if err != nil {
|
||||
return out, err
|
||||
}
|
||||
if len(kept) > 0 {
|
||||
out.KeptBuilds = kept
|
||||
if out.Kept, err = keptManifests(ctx, open, n.Name, kept); err != nil {
|
||||
return out, err
|
||||
}
|
||||
}
|
||||
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 the modules it resolves to: the ones the composition reads for it and for
|
||||
// the rest of the mesh, and no module nothing resolves (a module's settings may fail to read where nothing runs it).
|
||||
func readSettings(ctx context.Context, open *stores, in *compose.Input, plans map[string]compose.Resolved) error {
|
||||
modules := map[string]map[string]bool{}
|
||||
for node, resolved := range plans {
|
||||
modules[node] = map[string]bool{}
|
||||
for _, m := range resolved.Plan.Modules {
|
||||
modules[node][m.Module] = true
|
||||
}
|
||||
}
|
||||
// The rest of the mesh reads the settings of the modules that offer something on another node (servedOnNode), which are
|
||||
// among the modules that node resolves to.
|
||||
for i := range in.Nodes {
|
||||
n := &in.Nodes[i]
|
||||
names := make([]string, 0, len(modules[n.Name]))
|
||||
for m := range modules[n.Name] {
|
||||
names = append(names, m)
|
||||
}
|
||||
sort.Strings(names)
|
||||
for _, module := range names {
|
||||
layers, err := open.inventory.SettingsFor(ctx, n.Name, module)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if len(layers) == 0 {
|
||||
continue
|
||||
}
|
||||
if n.Settings == nil {
|
||||
n.Settings = map[string][]catalogue.Layer{}
|
||||
}
|
||||
n.Settings[module] = layers
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -0,0 +1,251 @@
|
||||
//go:build integration
|
||||
|
||||
package main
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"strings"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"git.novox.be/novox/mesh-sdk/go/compose"
|
||||
"github.com/novox/mesh-controller/internal/link"
|
||||
)
|
||||
|
||||
// 1382: the adapter's Input names, for every node, what the inventory answers for it, each compared with the read named
|
||||
// beside the field in go/compose/input.go (novox/hq ADR 0453 clause 3).
|
||||
func TestTheAdaptersInputNamesWhatTheInventoryAnswersForEachNode(t *testing.T) {
|
||||
open, _ := aMeshWithSecrets(t)
|
||||
ctx := t.Context()
|
||||
inv := open.inventory
|
||||
if err := inv.SetPublicDomain(ctx, "anchor", "example.test"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := inv.PortFor(ctx, "laptop", "files", 8080, false); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
in, err := readComposeInput(ctx, open)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
shelf, err := inv.Catalogue(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(in.Catalogue, shelf) {
|
||||
t.Error("the Input's Catalogue is not the shelf the inventory holds")
|
||||
}
|
||||
nodes, err := inv.Nodes(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(in.Nodes) != len(nodes) {
|
||||
t.Fatalf("the Input names %d node(s), the inventory has %d", len(in.Nodes), len(nodes))
|
||||
}
|
||||
places, err := inv.Overlays(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, record := range nodes {
|
||||
n, ok := nodeOf(in.Input, record.Name)
|
||||
if !ok {
|
||||
t.Fatalf("%s is not in the Input", record.Name)
|
||||
}
|
||||
for _, p := range places {
|
||||
if p.Name == record.Name {
|
||||
if want := (compose.Place{Key: p.Key, Endpoint: p.Endpoint, Site: p.Site, Hub: p.Hub, Address: p.Address}); n.Place != want {
|
||||
t.Errorf("%s: Place is %+v, the inventory's %+v", record.Name, n.Place, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
assigned, _ := inv.Assigned(ctx, record.Name)
|
||||
if !reflect.DeepEqual(n.Assigned, assigned) {
|
||||
t.Errorf("%s: Assigned is %v, the inventory's %v", record.Name, n.Assigned, assigned)
|
||||
}
|
||||
caps, _ := inv.ProfileOf(ctx, record.Name)
|
||||
if !reflect.DeepEqual(n.Capabilities, caps) {
|
||||
t.Errorf("%s: Capabilities are %v, the inventory's %v", record.Name, n.Capabilities, caps)
|
||||
}
|
||||
domain, _ := inv.PublicDomainOf(ctx, record.Name)
|
||||
if n.PublicDomain != domain {
|
||||
t.Errorf("%s: PublicDomain is %q, the inventory's %q", record.Name, n.PublicDomain, domain)
|
||||
}
|
||||
if n.Account != record.Account || n.AccountHome != record.AccountHome || n.AgentAccount != record.AgentAccount ||
|
||||
n.AgentAccountHome != record.AgentAccountHome || n.Adopted != record.Adopted {
|
||||
t.Errorf("%s: the accounts or the mode differ from the inventory's: %+v", record.Name, n)
|
||||
}
|
||||
pins, _ := inv.PinsFor(ctx, record.Name)
|
||||
if len(pins) != len(n.Pins) || (len(pins) > 0 && !reflect.DeepEqual(n.Pins, pins)) {
|
||||
t.Errorf("%s: Pins are %v, the inventory's %v", record.Name, n.Pins, pins)
|
||||
}
|
||||
bound, _ := inv.BindingsFor(ctx, record.Name)
|
||||
if len(bound) != len(n.Bound) || (len(bound) > 0 && !reflect.DeepEqual(n.Bound, bound)) {
|
||||
t.Errorf("%s: Bound is %v, the inventory's %v", record.Name, n.Bound, bound)
|
||||
}
|
||||
key, _ := inv.SealingKeyOf(ctx, record.Name)
|
||||
if n.SealingKey != key {
|
||||
t.Errorf("%s: SealingKey is %q, the inventory's %q", record.Name, n.SealingKey, key)
|
||||
}
|
||||
ports, _ := inv.PortsFor(ctx, record.Name)
|
||||
want := map[string]map[int]int{}
|
||||
for _, a := range ports {
|
||||
if want[a.Module] == nil {
|
||||
want[a.Module] = map[int]int{}
|
||||
}
|
||||
want[a.Module][a.Wanted] = a.Machine
|
||||
}
|
||||
if len(want) != len(n.Ports) || (len(want) > 0 && !reflect.DeepEqual(n.Ports, want)) {
|
||||
t.Errorf("%s: Ports are %v, the inventory's %v", record.Name, n.Ports, want)
|
||||
}
|
||||
// The settings of every module it resolves to, layer for layer.
|
||||
resolved, err := compose.Resolve(in.Input, record.Name)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
for _, m := range resolved.Plan.Modules {
|
||||
layers, err := inv.SettingsFor(ctx, record.Name, m.Module)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(layers) != len(n.Settings[m.Module]) || (len(layers) > 0 && !reflect.DeepEqual(n.Settings[m.Module], layers)) {
|
||||
t.Errorf("%s: the settings of %s are %v, the inventory's %v", record.Name, m.Module, n.Settings[m.Module], layers)
|
||||
}
|
||||
}
|
||||
}
|
||||
if domain := nodeDomain(in.Input, "anchor"); domain != "example.test" {
|
||||
t.Errorf("anchor's PublicDomain is %q", domain)
|
||||
}
|
||||
if got := nodePorts(in.Input, "laptop"); got["files"][8080] == 0 {
|
||||
t.Errorf("laptop's port for files:8080 is not in the Input: %v", got)
|
||||
}
|
||||
}
|
||||
|
||||
func nodeDomain(in compose.Input, name string) string {
|
||||
n, _ := nodeOf(in, name)
|
||||
return n.PublicDomain
|
||||
}
|
||||
|
||||
func nodePorts(in compose.Input, name string) map[string]map[int]int {
|
||||
n, _ := nodeOf(in, name)
|
||||
return n.Ports
|
||||
}
|
||||
|
||||
// 1382/AC2: a node keeping a recorded module at the build it runs has that module in Kept and KeptBuilds, and the Input's
|
||||
// Catalogue is the current shelf, not the kept manifest (R4a review round 1, finding 1).
|
||||
func TestANodeKeepingARecordedBuildHasItInKeptAndTheCatalogueStaysCurrent(t *testing.T) {
|
||||
open := aMesh(t)
|
||||
ctx := t.Context()
|
||||
inv := open.inventory
|
||||
start := time.Now().Add(-time.Hour)
|
||||
image := "registry.invalid:5000/postgres/server@sha256:" + strings.Repeat("a", 64)
|
||||
for _, b := range []link.BuildResult{
|
||||
aContainerBuild(t, "postgres", "c1111111", catalogue.PolicyRecord, start, map[string][2]string{"server": {image, ""}}),
|
||||
} {
|
||||
if _, _, err := takeIn(ctx, inv, asTheOperator(t, inv, b)); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
}
|
||||
if _, err := inv.Assign(ctx, "anchor", "postgres"); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := inv.RecordSent(ctx, nodeID(t, open, "anchor"), "d-anchor", map[string]string{"postgres": "c1111111"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, _, err := takeIn(ctx, inv, aContainerBuild(t, "postgres", "c2222222", catalogue.PolicyRecord, start.Add(time.Minute),
|
||||
map[string][2]string{"server": {image, "runtime"}})); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
|
||||
// A send that is not a person's push keeps the build the machine runs.
|
||||
in, err := readComposeInput(keepingRecorded(ctx), open)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
anchor, _ := nodeOf(in.Input, "anchor")
|
||||
if anchor.KeptBuilds["postgres"] != "c1111111" {
|
||||
t.Fatalf("anchor keeps %v, want postgres at c1111111", anchor.KeptBuilds)
|
||||
}
|
||||
kept, found := anchor.Kept["postgres"]
|
||||
if !found {
|
||||
t.Fatalf("the kept manifest of postgres is not in anchor's Kept: %v", anchor.Kept)
|
||||
}
|
||||
current, err := inv.Catalogue(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(in.Catalogue["postgres"], current["postgres"]) {
|
||||
t.Error("the Input's Catalogue is not the current shelf")
|
||||
}
|
||||
if reflect.DeepEqual(kept, current["postgres"]) {
|
||||
t.Error("the kept manifest is the current one: nothing is kept")
|
||||
}
|
||||
if laptop, _ := nodeOf(in.Input, "laptop"); len(laptop.Kept) != 0 || len(laptop.KeptBuilds) != 0 {
|
||||
t.Errorf("laptop keeps what only anchor runs: %v", laptop.KeptBuilds)
|
||||
}
|
||||
|
||||
// A person's push keeps nothing.
|
||||
pushed, err := readComposeInput(ctx, open)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if a, _ := nodeOf(pushed.Input, "anchor"); len(a.Kept) != 0 || len(a.KeptBuilds) != 0 {
|
||||
t.Errorf("a person's push keeps %v", a.KeptBuilds)
|
||||
}
|
||||
}
|
||||
|
||||
// 1382/AC3: the seats on record, the tunnels, the adopted tunnel, its carried peers and the nodes joining equal the
|
||||
// inventory's answers; the range and the suffix are read from the environment once, here.
|
||||
func TestTheAdaptersInputCarriesTheNetworkAsTheInventoryHoldsIt(t *testing.T) {
|
||||
open, _ := aMeshWithSecrets(t)
|
||||
ctx := t.Context()
|
||||
inv := open.inventory
|
||||
t.Setenv(OverlayCIDRVar, "10.99.0.0/16")
|
||||
t.Setenv("MESH_INTERNAL_SUFFIX", "mesh.test")
|
||||
in, err := readComposeInput(ctx, open)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
holdings, err := inv.Holdings(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if !reflect.DeepEqual(in.Holdings, holdings) {
|
||||
t.Errorf("Holdings are %v, the inventory's %v", in.Holdings, holdings)
|
||||
}
|
||||
tunnels, err := inv.Tunnels(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(tunnels) != len(in.Tunnels) {
|
||||
t.Errorf("Tunnels are %v, the inventory's %v", in.Tunnels, tunnels)
|
||||
}
|
||||
if _, hub, _, err := inv.AdoptedTunnel(ctx); err != nil {
|
||||
t.Fatal(err)
|
||||
} else if (hub == "") != (in.Adopted == nil) {
|
||||
t.Errorf("Adopted is %v, the inventory's hub is %q", in.Adopted, hub)
|
||||
}
|
||||
carried, err := inv.CarriedPeers(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(carried) != len(in.CarriedPeers) {
|
||||
t.Errorf("CarriedPeers are %v, the inventory's %v", in.CarriedPeers, carried)
|
||||
}
|
||||
joining, err := inv.NodesWithALiveToken(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if len(joining) != len(in.Joining) {
|
||||
t.Errorf("Joining is %v, the inventory's %v", in.Joining, joining)
|
||||
}
|
||||
if in.OverlayCIDR != "10.99.0.0/16" || in.InternalSuffix != "mesh.test" {
|
||||
t.Errorf("the range and the suffix are %q and %q", in.OverlayCIDR, in.InternalSuffix)
|
||||
}
|
||||
// Read once: what the environment says afterwards moves nothing already read.
|
||||
t.Setenv(OverlayCIDRVar, "10.1.0.0/16")
|
||||
if in.OverlayCIDR != "10.99.0.0/16" {
|
||||
t.Error("the range moved with the environment")
|
||||
}
|
||||
}
|
||||
@@ -187,6 +187,13 @@ func keepRecorded(ctx context.Context, open *stores, node string, shelf map[stri
|
||||
if err != nil || len(kept) == 0 {
|
||||
return nil, err
|
||||
}
|
||||
return kept, applyKept(ctx, open, node, kept, shelf)
|
||||
}
|
||||
|
||||
// applyKept puts the manifest of each module kept at the build its machine runs into a shelf, and refuses, in the words of
|
||||
// keepRecorded, a build the records no longer hold. The adapter to the composition's input (compose_input.go) calls it with
|
||||
// a scratch shelf, to read the manifests it gives the node as its own Kept.
|
||||
func applyKept(ctx context.Context, open *stores, node string, kept map[string]string, shelf map[string]catalogue.Manifest) error {
|
||||
names := make([]string, 0, len(kept))
|
||||
for m := range kept {
|
||||
names = append(names, m)
|
||||
@@ -195,28 +202,28 @@ func keepRecorded(ctx context.Context, open *stores, node string, shelf map[stri
|
||||
for _, m := range names {
|
||||
ran, found, err := open.inventory.ManifestAt(ctx, m, kept[m])
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return err
|
||||
}
|
||||
if !found && settingsKeptOn(ctx, node) {
|
||||
return nil, fmt.Errorf("%w: %s runs %s's build %s, which the build records no longer hold (novox/hq ADR 0344)",
|
||||
return fmt.Errorf("%w: %s runs %s's build %s, which the build records no longer hold (novox/hq ADR 0344)",
|
||||
errSettingsCannotKeep, node, m, short(kept[m]))
|
||||
}
|
||||
if !found && everyBuildKept(ctx, node) {
|
||||
return nil, fmt.Errorf("%s runs %s's build %s, which the build records no longer hold: the bus's user "+
|
||||
return fmt.Errorf("%s runs %s's build %s, which the build records no longer hold: the bus's user "+
|
||||
"list cannot be sent there alone with it kept (novox/hq issue 490)", node, m, short(kept[m]))
|
||||
}
|
||||
if !found && guarded(coreComponent(m)) {
|
||||
return nil, fmt.Errorf("%s was last sent %s's build %s, newer than the one the mesh holds, and the build records "+
|
||||
return fmt.Errorf("%s was last sent %s's build %s, newer than the one the mesh holds, and the build records "+
|
||||
"no longer hold it: this send cannot keep it and does not move it back — `push %s` sends the older build on "+
|
||||
"a person's word, and a newer merge or `rebuild %s` moves the record forward (novox/hq issue 201)",
|
||||
node, m, short(kept[m]), node, m)
|
||||
}
|
||||
if !found {
|
||||
return nil, fmt.Errorf("%s records rather than rolls out, and %s runs its build %s, which the build "+
|
||||
return fmt.Errorf("%s records rather than rolls out, and %s runs its build %s, which the build "+
|
||||
"records no longer hold: this send cannot keep it and does not move it — `push %s` sends the new "+
|
||||
"one on a person's word (novox/hq ADR 0245)", m, node, short(kept[m]), node)
|
||||
}
|
||||
shelf[m] = ran
|
||||
}
|
||||
return kept, nil
|
||||
return nil
|
||||
}
|
||||
|
||||
Vendored
+1
-2
@@ -2,6 +2,7 @@
|
||||
## explicit; go 1.26.0
|
||||
git.novox.be/novox/mesh-sdk/go/asks
|
||||
git.novox.be/novox/mesh-sdk/go/catalogue
|
||||
git.novox.be/novox/mesh-sdk/go/compose
|
||||
git.novox.be/novox/mesh-sdk/go/declaration
|
||||
git.novox.be/novox/mesh-sdk/go/delivery
|
||||
git.novox.be/novox/mesh-sdk/go/overlay
|
||||
@@ -86,9 +87,7 @@ github.com/nats-io/nkeys
|
||||
github.com/nats-io/nuid
|
||||
# github.com/novox/mesh-host v0.0.0 => git.novox.be/novox/mesh-host v0.0.0-20261018171808-9d9e3a46c4f5
|
||||
## explicit; go 1.26.0
|
||||
github.com/novox/mesh-host/internal/declaration
|
||||
github.com/novox/mesh-host/rootsearch
|
||||
github.com/novox/mesh-host/validate
|
||||
# github.com/therootcompany/xz v1.0.1
|
||||
## explicit; go 1.15
|
||||
github.com/therootcompany/xz
|
||||
|
||||
Reference in New Issue
Block a user