The judge's tests run against files and fakes; the drill runs it against a real resolver file and real resolvers, rewritten as the VPN client does, and records the statements the controller's replay raises and clears from.
102 lines
3.8 KiB
Go
102 lines
3.8 KiB
Go
package network
|
|
|
|
import (
|
|
"encoding/json"
|
|
"os"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/novox/mesh-host/internal/link"
|
|
)
|
|
|
|
// TestDrillAResolverFileRewrittenInAThrowawayMachine is the drill of novox/hq ADR 0241: on a machine of
|
|
// its own — a throwaway container, never a machine of the mesh — the resolver file is what was declared,
|
|
// then rewritten the way a VPN client rewrites it, then written back; the judge must say it healthy,
|
|
// unhealthy on the second look naming the writer and the names it costs, and healthy again on the first
|
|
// look after. Each statement is written, as the engine says it, to MESH_NETWORK_DRILL_OUT, for the
|
|
// controller's replay of the same drill (mesh-controller testdata/network-drill.json).
|
|
//
|
|
// Run only where MESH_NETWORK_DRILL names the mesh name to ask, because it rewrites /etc/resolv.conf:
|
|
//
|
|
// CGO_ENABLED=0 go test -c -o drill ./internal/network
|
|
// docker run --rm -v $PWD/drill:/drill:ro -v $PWD/out:/out -e MESH_NETWORK_DRILL=<mesh name> \
|
|
// -e MESH_NETWORK_DRILL_OUT=/out/network-drill.json alpine /drill -test.run Drill -test.v
|
|
//
|
|
// The clock moves a look on per look; the file, the resolvers and the answers are the machine's own.
|
|
func TestDrillAResolverFileRewrittenInAThrowawayMachine(t *testing.T) {
|
|
meshName := os.Getenv("MESH_NETWORK_DRILL")
|
|
if meshName == "" {
|
|
t.Skip("a drill rewrites /etc/resolv.conf: run it in a throwaway container with MESH_NETWORK_DRILL=<mesh name>")
|
|
}
|
|
declared, err := os.ReadFile(ResolvConf)
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
now := time.Now()
|
|
j := New(Machine{Now: func() time.Time { return now }, Linked: func() bool { return true }}, meshName)
|
|
j.Declare(string(declared), "networkmanager", true)
|
|
|
|
var said []link.Health
|
|
look := func(want string) Statement {
|
|
t.Helper()
|
|
now = now.Add(LookEvery)
|
|
st, _ := j.Look(t.Context())
|
|
h := link.Health{Contract: link.ReadinessContract, At: st.At.UTC(), Resources: []link.ResourceHealth{},
|
|
Network: &link.NetworkHealth{State: st.State, Since: st.Since.UTC(), Parts: []link.NetworkPart{}}}
|
|
for _, p := range st.Parts {
|
|
h.Network.Parts = append(h.Network.Parts, link.NetworkPart{Part: p.Part, State: p.State, Reason: p.Reason,
|
|
Said: p.Said, Writer: p.Writer, Owner: p.Owner, Toward: p.Toward, Since: p.Since.UTC(), Streak: p.Streak})
|
|
}
|
|
said = append(said, h)
|
|
raw, _ := json.Marshal(st)
|
|
t.Logf("%s", raw)
|
|
if st.State != want {
|
|
t.Fatalf("want %s, the judge says %s", want, st.State)
|
|
}
|
|
return st
|
|
}
|
|
write := func(content string) {
|
|
t.Helper()
|
|
// In place, as the VPN client's rename leaves a file of the same name: the judge reads by path.
|
|
if err := os.WriteFile(ResolvConf, []byte(content), 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
defer write(string(declared))
|
|
|
|
look(Healthy)
|
|
look(Healthy)
|
|
// What the VPN client writes on connect: its header, its own resolvers — addresses that answer
|
|
// nothing here — and its search domains.
|
|
write("# Dynamic resolv.conf(5) file for glibc resolver(3) generated by forticlient\n" +
|
|
"# The original file is backed up and will be restored after the VPN disconnects.\n" +
|
|
"nameserver 192.0.2.53\nsearch corp.example\noptions timeout:1\n")
|
|
look(Healthy) // one look is not a finding
|
|
st := look(Unhealthy)
|
|
for _, p := range st.Parts {
|
|
switch p.Part {
|
|
case PartResolvConf:
|
|
if p.State != Unhealthy || p.Writer != "FortiClient" || p.Owner != "networkmanager" {
|
|
t.Fatalf("the file is said %+v", p)
|
|
}
|
|
case PartNames:
|
|
if p.State != Unhealthy || !strings.Contains(p.Said, "192.0.2.53") {
|
|
t.Fatalf("the names through it are said %+v", p)
|
|
}
|
|
}
|
|
}
|
|
write(string(declared))
|
|
look(Healthy)
|
|
|
|
if out := os.Getenv("MESH_NETWORK_DRILL_OUT"); out != "" {
|
|
raw, err := json.MarshalIndent(said, "", " ")
|
|
if err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
if err := os.WriteFile(out, raw, 0o644); err != nil {
|
|
t.Fatal(err)
|
|
}
|
|
}
|
|
}
|