package overlay import ( "strings" "testing" ) // Services are named under the machine they run on, so what must resolve is anything under a // node's name — not the node's name alone. // // This is what a hosts file cannot do: it answers exact names, and a wildcard there would mean // writing down every service in advance, which is the enumeration the arrangement exists to // avoid. func TestEverythingUnderANodesNameGoesToThatNode(t *testing.T) { written := Wildcards([]Node{ {Name: "novox", Address: "10.42.0.1"}, {Name: "ace", Address: "10.42.0.2"}, }) for _, want := range []string{ "address=/novox.internal/10.42.0.1", "address=/ace.internal/10.42.0.2", } { if !strings.Contains(written, want) { t.Fatalf("missing %q:\n%s", want, written) } } // Sorted, because this file is compared against its last version on every apply and a set // that reorders itself would rewrite it — and restart what reads it — for no change. if strings.Index(written, "/ace.") > strings.Index(written, "/novox.") { t.Fatalf("the machines are not in a stable order:\n%s", written) } } // A machine with no address is left out. // // A wildcard pointing at nothing is worse than no wildcard: every name under it resolves and then // hangs, where an unresolvable name fails at once and says which name it was. func TestAMachineWithNoAddressGetsNoWildcard(t *testing.T) { written := Wildcards([]Node{ {Name: "novox", Address: "10.42.0.1"}, {Name: "unplaced"}, }) if strings.Contains(written, "unplaced") { t.Fatalf("a machine with no address was given a wildcard:\n%s", written) } if !strings.Contains(written, "novox.internal") { t.Fatalf("the machine that does have one lost it:\n%s", written) } } // A mesh where nobody is on the private network says so rather than producing an empty file that // reads as "nothing was generated". func TestAMeshWithNoAddressesSaysSo(t *testing.T) { written := Wildcards(nil) if !strings.Contains(written, "No machine in this mesh has an address") { t.Fatalf("an empty answer is indistinguishable from a failure to answer:\n%s", written) } } // The suffix a mesh chose is used, not a hardcoded one. func TestTheMeshsOwnSuffixIsUsed(t *testing.T) { t.Setenv(SuffixVar, "mesh.example") written := Wildcards([]Node{{Name: "novox", Address: "10.42.0.1"}}) if !strings.Contains(written, "address=/novox.mesh.example/10.42.0.1") { t.Fatalf("the mesh's own suffix was not used:\n%s", written) } } // A machine not on the network is given no resolver data, which is an answer rather than an // error: a node assigned the module before it is placed is in exactly that state. func TestAMachineNotOnTheNetworkGetsNoResolverData(t *testing.T) { _, part, err := ResolverFor([]Node{{Name: "novox", Address: "10.42.0.1"}}).Resources("stranger") if err != nil { t.Fatal(err) } if part { t.Fatal("a machine not on the network was given the mesh's resolver data") } } // And a machine on it gets the whole set, including itself: a service on this machine reached by // its own mesh name must arrive the same way it would from anywhere else. func TestAMachineGetsTheWholeSetIncludingItself(t *testing.T) { got, part, err := ResolverFor([]Node{ {Name: "novox", Address: "10.42.0.1"}, {Name: "ace", Address: "10.42.0.2"}, }).Resources("novox") if err != nil { t.Fatal(err) } if !part || len(got) != 1 { t.Fatalf("expected one file for a machine on the network, got %d (part=%v)", len(got), part) } content, _ := got[0]["content"].(string) if !strings.Contains(content, "novox.internal") || !strings.Contains(content, "ace.internal") { t.Fatalf("the machine was not given the whole mesh:\n%s", content) } }