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: int64(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)
|
|
}
|
|
}
|
|
}
|