302 lines
11 KiB
Go
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)
|
|
}
|
|
})
|
|
}
|