package catalogue import ( "strings" "testing" ) // A zone names the listen that answers it, or there is nothing to forward to (novox/hq ADR 0199). func TestAZoneMustNameOneOfTheModulesListens(t *testing.T) { _, err := ParseManifest([]byte(`{"module":"lab","version":"1", "listens":[{"name":"dns","port":5353,"protocol":"udp","from":"mesh","why":"the lab's names"}], "zone":{"name":"${setting:zone}","listen":"web"}}`)) if err == nil || !strings.Contains(err.Error(), `has no listen of that name`) { t.Fatalf("a zone answered by a listen the module does not have was accepted: %v", err) } if _, err := ParseManifest([]byte(`{"module":"lab","version":"1", "listens":[{"name":"dns","port":5353,"protocol":"udp","from":"mesh","why":"the lab's names"}], "zone":{"name":"${setting:zone}","listen":"dns"}}`)); err != nil { t.Fatalf("a well-formed zone was refused: %v", err) } } // The zone is the operator's (a setting) and the port is where this machine publishes the listen — // neither is the definition's to state. func TestAZoneIsPlacedFromTheNodesSettingAndPublishedPort(t *testing.T) { m := Manifest{Module: "lab", Zone: &Zone{Name: "${setting:zone}", Listen: "dns"}, Listens: []Listening{{Name: "dns", Port: 5353, From: FromMesh}}} z, err := ZoneOn(m, []Layer{{From: "node", Values: map[string]any{"zone": "Incus."}}}, map[int]int{5353: 15353}, "workstation", "10.77.0.3") if err != nil { t.Fatal(err) } if z.Zone != "incus" || z.Address != "10.77.0.3" || z.Port != 15353 || z.Node != "workstation" { t.Fatalf("the zone was placed as %+v", *z) } if _, err := ZoneOn(m, nil, nil, "workstation", "10.77.0.3"); err == nil { t.Fatal("a zone nobody named was placed") } } // One module answers a zone, and none may shadow the mesh's names or a public domain. func TestTheMeshRefusesAZoneTwiceOrOneThatShadows(t *testing.T) { one := ZoneAt{Zone: "incus", Node: "workstation", Module: "lab", Address: "10.77.0.3", Port: 53} if p := ZonesProblems([]ZoneAt{one}, "internal", []string{"example.tld"}); len(p) != 0 { t.Fatalf("one ordinary zone was refused: %v", p) } twice := one twice.Node, twice.Module = "laptop", "other" cases := map[string][]ZoneAt{ "declared by": {one, twice}, "mesh's own suffix": {{Zone: "lab.internal", Node: "a", Module: "m"}}, "public domain": {{Zone: "dev.example.tld", Node: "a", Module: "m"}}, } for want, zones := range cases { p := strings.Join(ZonesProblems(zones, "internal", []string{"example.tld"}), "\n") if !strings.Contains(p, want) { t.Errorf("not refused for %q: %q", want, p) } } } // The resolver's template sees every zone with where it is answered, in zone order. func TestTheResolversTemplateRangesTheZones(t *testing.T) { m := Manifest{Module: "dnsmasq", Facts: map[string]RosterFile{"zones": { Path: "/etc/mesh-resolver/zones.conf", Template: "{{range .Zones}}server=/{{.Zone}}/{{.Address}}#{{.Port}}\n{{end}}", }}} out, err := FactsWithZonesInto(m, Resolution{Node: "anchor"}, nil, nil, nil, "", []ZoneAt{ {Zone: "zeta", Address: "10.77.0.2", Port: 53}, {Zone: "incus", Address: "10.77.0.3", Port: 15353}, }) if err != nil { t.Fatal(err) } if got := out[0]["content"]; got != "server=/incus/10.77.0.3#15353\nserver=/zeta/10.77.0.2#53\n" { t.Fatalf("the resolver was told %q", got) } }