The node-engine owns the composition it alone runs (H1, H2)
ADR 0593 §1 and §7: compose, its bus reader and runtime reader, takein, validate, the overlay generator, the composer's run and mesh-compose, and the judgement of a move leave mesh-sdk for the node-engine's internal packages, so no other module composes and a change to them rebuilds only the node-engine. The shapes others read (the composer's request and verdict, the overlay's names, compositions and answers) stay in mesh-sdk. The node's grant is now a golden file of what the grants part composes, since the composer of the bus's user list leaves mesh-sdk too. Claude-Session: https://claude.ai/code/session_01NcbgfJ9Gum47zkFKWRaovL
This commit is contained in:
@@ -0,0 +1,306 @@
|
||||
package compose
|
||||
|
||||
import (
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"git.novox.be/novox/mesh-sdk/go/catalogue"
|
||||
"github.com/novox/mesh-host/internal/overlay"
|
||||
)
|
||||
|
||||
// onTheTunnel makes the hub of an Input what mesh-controller's network_test.go makes of anchor in
|
||||
// TestTheOverlaysRangeIsTheAdoptedTunnels: adopted, enrolled with the found tunnel's key, and presenting the tunnel
|
||||
// (Tunnel{wg0, wg-quick@wg0, port 51900, 192.0.2.1/24, range 192.0.2.0/24, one peer PEER-TWO}). Placed on it when
|
||||
// replaced is true: its address the tunnel's, its endpoint on the tunnel's port, as `overlay place anchor --endpoint
|
||||
// 198.51.100.10:51900 --site hosting --hub` leaves it.
|
||||
func onTheTunnel(t *testing.T, in *Input, replaced bool) {
|
||||
t.Helper()
|
||||
const key = "THE-TUNNELS-KEY========================="
|
||||
tunnel := Tunnel{Interface: "wg0", Unit: "wg-quick@wg0", Config: "/etc/wireguard/wg0.conf", Port: 51900,
|
||||
Address: "192.0.2.1/24", Range: "192.0.2.0/24", PublicKey: key,
|
||||
Peers: []TunnelPeer{{PublicKey: "PEER-TWO", Address: "192.0.2.2"}}}
|
||||
hub := nodeOf(t, in, "anchor")
|
||||
hub.Adopted = true
|
||||
hub.Place.Key = key
|
||||
if replaced {
|
||||
hub.Place.Address, hub.Place.Endpoint, hub.Place.Site = "192.0.2.1", "198.51.100.10:51900", "hosting"
|
||||
}
|
||||
in.Tunnels = map[string]FoundTunnel{"anchor": {Tunnel: tunnel, NodeAdopted: true}}
|
||||
in.Adopted = &AdoptedTunnel{Tunnel: tunnel, Hub: "anchor", Adopted: true}
|
||||
in.CarriedPeers = []CarriedPeer{{PublicKey: "PEER-TWO", Address: "192.0.2.2"}}
|
||||
}
|
||||
|
||||
// novox/hq ADR 0105 (mesh-controller network_test.go TestTheOverlaysRangeIsTheAdoptedTunnels): the range every address is
|
||||
// composed from is the adopted tunnel's, read from the tunnel the hub holds, never stored anywhere else. The environment
|
||||
// value of the controller is Input.OverlayCIDR here.
|
||||
func TestTheOverlaysRangeIsTheAdoptedTunnels(t *testing.T) {
|
||||
in := aMesh(t)
|
||||
in.OverlayCIDR = "10.99.0.0/16"
|
||||
if got := newComposer(in).overlayRange(); got != "10.99.0.0/16" {
|
||||
t.Fatalf("without an adopted tunnel the range is not what genesis said: %q", got)
|
||||
}
|
||||
in.OverlayCIDR = ""
|
||||
if got := newComposer(in).overlayRange(); got != DefaultOverlayCIDR {
|
||||
t.Fatalf("with nothing said the range is %q, not the default", got)
|
||||
}
|
||||
in.OverlayCIDR = "10.99.0.0/16"
|
||||
onTheTunnel(t, &in, true)
|
||||
if got := newComposer(in).overlayRange(); got != "192.0.2.0/24" {
|
||||
t.Fatalf("with an adopted tunnel the range is %q, not the tunnel's", got)
|
||||
}
|
||||
// A tunnel the hub found and did not take (its key is not the tunnel's) does not give its range.
|
||||
in.Adopted.Adopted = false
|
||||
if got := newComposer(in).overlayRange(); got != "10.99.0.0/16" {
|
||||
t.Fatalf("a tunnel the hub did not take gave its range: %q", got)
|
||||
}
|
||||
in.Adopted.Adopted = true
|
||||
|
||||
// And the hub's declaration carries the peer and the takeover.
|
||||
nodes, computed, err := Graph(in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var hubNode overlay.Node
|
||||
for _, n := range nodes {
|
||||
if n.Name == "anchor" {
|
||||
hubNode = n
|
||||
}
|
||||
}
|
||||
if hubNode.TakesOver == nil || hubNode.TakesOver.Unit != "wg-quick@wg0" {
|
||||
t.Errorf("the hub is not told to take over the found tunnel: %+v", hubNode)
|
||||
}
|
||||
carried := false
|
||||
for _, p := range computed["anchor"] {
|
||||
if p.Key == "PEER-TWO" && p.Allowed == "192.0.2.2/32" {
|
||||
carried = true
|
||||
}
|
||||
}
|
||||
if !carried {
|
||||
t.Errorf("the hub's peer list does not carry the tunnel's peer: %+v", computed["anchor"])
|
||||
}
|
||||
}
|
||||
|
||||
// A takeover is composed only for a hub whose placement agrees with the tunnel (mesh-controller network_test.go
|
||||
// TestATakeoverIsNotComposedForAHubPlacedOffItsTunnel): an address or an endpoint port that differs would have the host stop
|
||||
// the found interface and raise the mesh's where no peer is listening.
|
||||
func TestATakeoverIsNotComposedForAHubPlacedOffItsTunnel(t *testing.T) {
|
||||
in := aMesh(t)
|
||||
onTheTunnel(t, &in, false)
|
||||
// aMesh placed anchor at 10.77.0.1 on :51820, the record of a hub placed before it took the tunnel over.
|
||||
_, _, err := Graph(in)
|
||||
if err == nil {
|
||||
t.Fatal("a takeover was composed for a hub whose address and port are not the tunnel's")
|
||||
}
|
||||
for _, want := range []string{"10.77.0.1", "192.0.2.1", "51820", "51900", "overlay place anchor"} {
|
||||
if !strings.Contains(err.Error(), want) {
|
||||
t.Errorf("the refusal does not say %q: %v", want, err)
|
||||
}
|
||||
}
|
||||
// Re-placed on the tunnel, it composes.
|
||||
onTheTunnel(t, &in, true)
|
||||
if _, _, err := Graph(in); err != nil {
|
||||
t.Fatalf("re-placed on the tunnel, the graph still refuses: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 1360/AC2: the generator gives the addresses, peers and names of aMesh: anchor the hub at 10.77.0.1, laptop at 10.77.0.2
|
||||
// peering through it, both written for their machines.
|
||||
func TestTheGeneratorGivesTheMeshItsAddressesAndPeers(t *testing.T) {
|
||||
in := aMesh(t)
|
||||
gens, err := Generators(in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
g, ok := gens[overlay.Name].(*overlay.Generator)
|
||||
if !ok {
|
||||
t.Fatalf("the generator is %T", gens[overlay.Name])
|
||||
}
|
||||
addr := map[string]string{}
|
||||
for _, n := range g.Nodes() {
|
||||
addr[n.Name] = n.Address
|
||||
}
|
||||
if addr["anchor"] != "10.77.0.1" || addr["laptop"] != "10.77.0.2" || len(addr) != 2 {
|
||||
t.Fatalf("the addresses are %v", addr)
|
||||
}
|
||||
peers := g.Graph()
|
||||
if len(peers["laptop"]) != 1 || peers["laptop"][0].Name != "anchor" || peers["laptop"][0].Endpoint != "anchor.example:51820" {
|
||||
t.Errorf("laptop's peers are %+v, want the hub", peers["laptop"])
|
||||
}
|
||||
if len(peers["anchor"]) != 1 || peers["anchor"][0].Name != "laptop" || peers["anchor"][0].Allowed != "10.77.0.2/32" {
|
||||
t.Errorf("anchor's peers are %+v, want laptop", peers["anchor"])
|
||||
}
|
||||
for _, node := range []string{"anchor", "laptop"} {
|
||||
res, on, err := g.Resources(node)
|
||||
if err != nil || !on || len(res) == 0 {
|
||||
t.Errorf("%s is given no network: %v %v %d", node, on, err, len(res))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1360/AC3: a node not on the private network (it runs nothing that puts it there) gets no address and no network, and the
|
||||
// one that stayed keeps its own.
|
||||
func TestANodeNotOnThePrivateNetworkGetsNoAddressAndNoNetwork(t *testing.T) {
|
||||
in := aMesh(t)
|
||||
nodeOf(t, &in, "laptop").Assigned = nil // it keeps its place and its address, and stops running the network
|
||||
gens, err := Generators(in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
g := gens[overlay.Name].(*overlay.Generator)
|
||||
if len(g.Nodes()) != 1 || g.Nodes()[0].Name != "anchor" {
|
||||
t.Fatalf("the network is over %+v, want anchor alone", g.Nodes())
|
||||
}
|
||||
if res, on, err := g.Resources("laptop"); on || err != nil || len(res) != 0 {
|
||||
t.Errorf("a machine that left the network is given one: %v %v %v", res, on, err)
|
||||
}
|
||||
if _, in := g.Graph()["laptop"]; in {
|
||||
t.Error("a machine that left the network has peers")
|
||||
}
|
||||
}
|
||||
|
||||
// 1360/AC3: a node whose resolution is refused is said with today's words: the network missing its hub names the
|
||||
// machine that could not be resolved, which is almost always why. The hub here holds no complete place (no key): a refused
|
||||
// machine with one keeps it on the network (issue 1833), so only one without is missing from it.
|
||||
func TestARefusedNodeIsSaidWhenTheNetworkCannotBeComposed(t *testing.T) {
|
||||
in := aMesh(t)
|
||||
nodeOf(t, &in, "anchor").Place.Key = ""
|
||||
one := catalogue.Manifest{Module: "rival-one", Version: "1", Claims: []catalogue.Claim{{Name: "the-seat", Scope: catalogue.ScopeNode}}}
|
||||
two := catalogue.Manifest{Module: "rival-two", Version: "1", Claims: []catalogue.Claim{{Name: "the-seat", Scope: catalogue.ScopeNode}}}
|
||||
in.Catalogue["rival-one"], in.Catalogue["rival-two"] = one, two
|
||||
hub := nodeOf(t, &in, "anchor")
|
||||
hub.Assigned = append(hub.Assigned, "rival-one", "rival-two") // its own set is incoherent: the hub does not resolve
|
||||
_, err := Generators(in)
|
||||
if err == nil {
|
||||
t.Fatal("a network without its hub was composed")
|
||||
}
|
||||
for _, want := range []string{overlay.ErrNoHub.Error(), "and 1 node(s) could not be resolved at all, which is probably why:", " anchor: "} {
|
||||
if !strings.Contains(err.Error(), want) {
|
||||
t.Errorf("the refusal does not say %q:\n%v", want, err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 1360/AC4: Generators changes nothing in its Input.
|
||||
func TestGeneratorsChangesNothingInItsInput(t *testing.T) {
|
||||
unchanged(t, func() Input {
|
||||
in := aMesh(t)
|
||||
onTheTunnel(t, &in, true)
|
||||
in.Joining = []string{"laptop"}
|
||||
return in
|
||||
}, func(in Input) {
|
||||
if _, err := Generators(in); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Each field of Input that the network reads reaches the generator: change it and the generator changes.
|
||||
func TestEachFieldOfTheInputReachesTheGenerator(t *testing.T) {
|
||||
addressesOf := func(in Input) map[string]string {
|
||||
t.Helper()
|
||||
gens, err := Generators(in)
|
||||
if err != nil {
|
||||
t.Fatalf("%v", err)
|
||||
}
|
||||
out := map[string]string{}
|
||||
for _, n := range gens[overlay.Name].(*overlay.Generator).Nodes() {
|
||||
out[n.Name] = n.Address
|
||||
}
|
||||
return out
|
||||
}
|
||||
base := addressesOf(aMesh(t))
|
||||
if len(base) != 2 {
|
||||
t.Fatalf("the base mesh has %v", base)
|
||||
}
|
||||
t.Run("Catalogue: without the module offering it nobody is on the network", func(t *testing.T) {
|
||||
in := aMesh(t)
|
||||
// The module stays known, so every machine still resolves: a machine that does not resolve keeps its place (issue 1833).
|
||||
in.Catalogue = map[string]catalogue.Manifest{overlay.Name: {Module: overlay.Name, Version: "1"}}
|
||||
if got := addressesOf(in); len(got) != 0 {
|
||||
t.Errorf("got %v", got)
|
||||
}
|
||||
})
|
||||
t.Run("Nodes.Place: an address", func(t *testing.T) {
|
||||
in := aMesh(t)
|
||||
nodeOf(t, &in, "laptop").Place.Address = "10.77.0.9"
|
||||
if got := addressesOf(in); got["laptop"] != "10.77.0.9" {
|
||||
t.Errorf("got %v", got)
|
||||
}
|
||||
})
|
||||
t.Run("Nodes.Assigned", func(t *testing.T) {
|
||||
in := aMesh(t)
|
||||
nodeOf(t, &in, "laptop").Assigned = nil
|
||||
if got := addressesOf(in); len(got) != 1 {
|
||||
t.Errorf("got %v", got)
|
||||
}
|
||||
})
|
||||
t.Run("Nodes.Capabilities: a capability the module needs", func(t *testing.T) {
|
||||
in := aMesh(t)
|
||||
m := in.Catalogue[overlay.Name]
|
||||
m.Capabilities = []string{"wireguard"}
|
||||
in.Catalogue[overlay.Name] = m
|
||||
nodeOf(t, &in, "laptop").Capabilities = map[string]bool{}
|
||||
got, err := Generators(in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if n := got[overlay.Name].(*overlay.Generator).Nodes(); len(n) != 1 || n[0].Name != "anchor" {
|
||||
t.Errorf("a machine without the capability is on the network: %+v", n)
|
||||
}
|
||||
})
|
||||
t.Run("OverlayCIDR", func(t *testing.T) {
|
||||
in := aMesh(t)
|
||||
in.OverlayCIDR = "10.99.0.0/16"
|
||||
_, graph, err := Graph(in)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
found := false
|
||||
for _, p := range graph["laptop"] {
|
||||
found = found || p.Allowed == "10.99.0.0/16"
|
||||
}
|
||||
if !found {
|
||||
t.Errorf("the hub's route is not the range given: %+v", graph["laptop"])
|
||||
}
|
||||
})
|
||||
t.Run("Tunnels and Adopted and CarriedPeers", func(t *testing.T) {
|
||||
in := aMesh(t)
|
||||
onTheTunnel(t, &in, true)
|
||||
nodes, graph, err := Graph(in)
|
||||
if err != nil || len(nodes) != 2 {
|
||||
t.Fatalf("%v %v", nodes, err)
|
||||
}
|
||||
if len(graph["anchor"]) < 2 {
|
||||
t.Errorf("the hub's peers do not include the carried one: %+v", graph["anchor"])
|
||||
}
|
||||
})
|
||||
t.Run("Joining: a machine with a key and an address is on the network before it is anything else", func(t *testing.T) {
|
||||
in := aMesh(t)
|
||||
in.Nodes = append(in.Nodes, Node{Name: "newcomer", Place: Place{Key: keyOf(t, "newcomer"), Address: "10.77.0.3"}})
|
||||
if got := addressesOf(in); got["newcomer"] != "" {
|
||||
t.Fatalf("a machine that runs nothing is on the network: %v", got)
|
||||
}
|
||||
in.Joining = []string{"newcomer"}
|
||||
if got := addressesOf(in); got["newcomer"] != "10.77.0.3" {
|
||||
t.Errorf("a joining machine is not on the network: %v", got)
|
||||
}
|
||||
})
|
||||
t.Run("Holdings: a holder on record settles a seat two assignments could hold", func(t *testing.T) {
|
||||
withSeat(t)
|
||||
in := aMesh(t)
|
||||
for _, name := range []string{"holder-one", "holder-two"} {
|
||||
in.Catalogue[name] = aBrokerClaiming(name)
|
||||
}
|
||||
laptop := nodeOf(t, &in, "laptop")
|
||||
laptop.Assigned = append(laptop.Assigned, "holder-one", "holder-two")
|
||||
// Without a key: a machine that does not resolve and holds a complete place keeps it on the network (issue 1833).
|
||||
laptop.Place.Key = ""
|
||||
if got := addressesOf(in); got["laptop"] != "" {
|
||||
t.Fatalf("a machine whose set claims one seat twice is on the network: %v", got)
|
||||
}
|
||||
in.Holdings = []catalogue.Held{{Claim: "mesh-broker", Scope: catalogue.ScopeMesh, Node: "laptop", Module: "holder-one"}}
|
||||
if got := addressesOf(in); got["laptop"] != "10.77.0.2" {
|
||||
t.Errorf("a holder on record did not settle the seat: %v", got)
|
||||
}
|
||||
})
|
||||
}
|
||||
Reference in New Issue
Block a user