The resolver file of systemd-resolved (resolvers and kept facts) carries options timeout:3 attempts:2 edns0 in place of timeout:1; manifest test asserts both facts, guard test's literal follows. Builds: design 50 resolver-wait §1, §4 (and §2 of the main file, line 73)
126 lines
4.5 KiB
Go
126 lines
4.5 KiB
Go
package main
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// The manifest and this code say one thing: the paths the guard keeps are the files the mesh renders, the
|
|
// process runs this binary as the guard, and the resolver file and its kept copy are one template.
|
|
|
|
type manifest struct {
|
|
Claims []struct {
|
|
Name string `json:"name"`
|
|
Serves []string `json:"serves"`
|
|
} `json:"claims"`
|
|
Provides []struct {
|
|
Name string `json:"name"`
|
|
Reach string `json:"reach"`
|
|
} `json:"provides"`
|
|
Upgrade struct {
|
|
Policy string `json:"policy"`
|
|
Why string `json:"why"`
|
|
} `json:"upgrade"`
|
|
Facts map[string]struct {
|
|
Path string `json:"path"`
|
|
Template string `json:"template"`
|
|
} `json:"facts"`
|
|
Resources []map[string]any `json:"resources"`
|
|
Build struct {
|
|
Artifacts []struct {
|
|
Binary string `json:"binary"`
|
|
} `json:"artifacts"`
|
|
} `json:"build"`
|
|
}
|
|
|
|
func theManifest(t *testing.T) manifest {
|
|
t.Helper()
|
|
raw, err := os.ReadFile("../../module.json")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
var m manifest
|
|
if err := json.Unmarshal(raw, &m); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
return m
|
|
}
|
|
|
|
func TestTheManifestSaysWhatTheGuardKeeps(t *testing.T) {
|
|
m := theManifest(t)
|
|
if m.Facts["resolvers"].Path != ResolvConf || m.Facts["kept"].Path != KeptPath || m.Facts["suffix"].Path != SuffixPath {
|
|
t.Errorf("the rendered paths are not the ones the guard reads: %+v", m.Facts)
|
|
}
|
|
if m.Facts["resolvers"].Template != m.Facts["kept"].Template {
|
|
t.Error("the resolver file and the copy the guard puts back are not one template")
|
|
}
|
|
// Sorted before the live file, so the mesh writes the copy first and the guard never puts back the
|
|
// file it is about to be given.
|
|
if !("kept" < "resolvers") {
|
|
t.Error("the kept copy is not rendered before the file")
|
|
}
|
|
if !strings.HasPrefix(m.Facts["resolvers"].Template, "# Managed by the mesh") {
|
|
t.Error("the resolver file does not begin as the mesh's own does, which is how the uplink's verb reads it")
|
|
}
|
|
var lines []string
|
|
for _, l := range strings.Split(m.Facts["resolvers"].Template, "\n") {
|
|
if strings.Contains(l, "nameserver") && !strings.HasPrefix(l, "#") {
|
|
lines = append(lines, l)
|
|
}
|
|
}
|
|
if len(lines) != 1 || strings.Contains(m.Facts["resolvers"].Template, "search ") {
|
|
t.Errorf("the resolver file lists more than this machine's own resolver: %v", lines)
|
|
}
|
|
// Design 50 resolver-wait §1: both facts carry the longer wait, never the uplink files' timeout:1.
|
|
for _, fact := range []string{"resolvers", "kept"} {
|
|
if tpl := m.Facts[fact].Template; !strings.Contains(tpl, "\noptions timeout:3 attempts:2 edns0\n") || strings.Contains(tpl, "timeout:1") {
|
|
t.Errorf("fact %s does not carry options timeout:3 attempts:2 edns0: %q", fact, tpl)
|
|
}
|
|
}
|
|
conf := m.Facts["resolved"].Template
|
|
for _, want := range []string{`DNS={{range index .Holders "mesh-dns-resolver"}}`, "\nDomains=~.\n", "\nFallbackDNS=\n",
|
|
"DNSStubListenerExtra={{$own}}\n", "\nCache=no\n", "\nLLMNR=no\n", "\nMulticastDNS=no\n"} {
|
|
if !strings.Contains(conf, want) {
|
|
t.Errorf("resolved's drop-in lacks %q", want)
|
|
}
|
|
}
|
|
guard, service := false, false
|
|
for _, r := range m.Resources {
|
|
run, _ := r["run"].([]any)
|
|
if r["type"] == "process" && len(run) == 2 && run[0] == "./"+m.Build.Artifacts[0].Binary && run[1] == "guard" {
|
|
guard = r["user"] == nil && r["run-once"] == nil && r["health"] != nil
|
|
}
|
|
if r["type"] == "service" && r["unit"] == "systemd-resolved.service" {
|
|
on, _ := r["restart-on"].([]any)
|
|
service = r["state"] == "running" && len(on) == 1 && on[0] == "systemd-resolved.fact-resolved" && r["health"] != nil
|
|
}
|
|
}
|
|
if !guard || !service {
|
|
t.Errorf("the guard (root, long-running, its health said) %v; resolved (running, restarted on its drop-in) %v", guard, service)
|
|
}
|
|
if len(m.Claims) != 1 || m.Claims[0].Name != Seat || strings.Join(m.Claims[0].Serves, " ") != "routes route unroute" {
|
|
t.Errorf("the claim is %+v", m.Claims)
|
|
}
|
|
if len(m.Provides) != 1 || m.Provides[0].Name != "split-dns" || m.Provides[0].Reach != "machine" {
|
|
t.Errorf("split-dns is not provided at the machine's reach: %+v", m.Provides)
|
|
}
|
|
if m.Upgrade.Policy != "record" || m.Upgrade.Why == "" {
|
|
t.Error("a build of the machine's names rolls out on its own")
|
|
}
|
|
}
|
|
|
|
// Every verb the claim serves is a tool of the bundle, by the seat's name.
|
|
func TestTheBundleServesTheClaimedVerbs(t *testing.T) {
|
|
have := map[string]bool{}
|
|
for _, tool := range tools(&Resolver{}, "") {
|
|
have[tool.Name] = true
|
|
}
|
|
for _, v := range theManifest(t).Claims[0].Serves {
|
|
if !have[Seat+"."+v] {
|
|
t.Errorf("%s.%s is claimed and not served", Seat, v)
|
|
}
|
|
}
|
|
}
|