Files
mesh-sdk/go/compose/network_test.go
T

302 lines
11 KiB
Go

package compose
import (
"strings"
"testing"
"git.novox.be/novox/mesh-sdk/go/catalogue"
"git.novox.be/novox/mesh-sdk/go/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.
func TestARefusedNodeIsSaidWhenTheNetworkCannotBeComposed(t *testing.T) {
in := aMesh(t)
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 nobody is on the network", func(t *testing.T) {
in := aMesh(t)
in.Catalogue = map[string]catalogue.Manifest{}
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")
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)
}
})
}