Drill the network judge in a throwaway machine (hq ADR 0241)
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.
This commit is contained in:
@@ -0,0 +1,101 @@
|
||||
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)
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user