package bind import ( "os" "os/exec" "path/filepath" "strings" "testing" ) func requireShell(t *testing.T, tools ...string) string { t.Helper() sh, err := exec.LookPath("sh") if err != nil { t.Skipf("no POSIX shell: %v", err) } for _, tool := range tools { if _, err := exec.LookPath(tool); err != nil { t.Skipf("seed script needs %s: %v", tool, err) } } return sh } // inFlightZone lays out the state the operator actually hit in production: a // zone file a previous reconcile clobbered back to serial 1, with the journal // that carries the live records up to 16. func inFlightZone(t *testing.T) (dir, path string) { t.Helper() dir = t.TempDir() path = filepath.Join(dir, "db.example.com") if err := os.WriteFile(path, []byte(renderSeedZone("example.com", "10.0.0.1", nil, 1)), 0o600); err != nil { t.Fatal(err) } if err := os.WriteFile(JournalPath(path), journalHeader(";BIND LOG V9.2\n", 10, 16), 0o600); err != nil { t.Fatal(err) } return dir, path } func runSeedScript(t *testing.T, sh, path string, plan SeedPlan, content, stdin string) error { t.Helper() return runSeedScriptWithPath(t, sh, path, plan, content, stdin, "") } func runSeedScriptWithPath(t *testing.T, sh, path string, plan SeedPlan, content, stdin, pathEnv string) error { t.Helper() cmd := exec.Command(sh, "-c", seedScript(path, plan, len(content))) cmd.Stdin = strings.NewReader(stdin) if pathEnv != "" { cmd.Env = append(os.Environ(), "PATH="+pathEnv) } return cmd.Run() } // shimPath puts a stand-in for tool at the front of a PATH, so the generated // script meets a failing command where a real image would meet a working one. func shimPath(t *testing.T, tool, body string) string { t.Helper() dir := t.TempDir() if err := os.WriteFile(filepath.Join(dir, tool), []byte("#!/bin/sh\n"+body+"\n"), 0o755); err != nil { t.Fatal(err) } return dir + string(os.PathListSeparator) + os.Getenv("PATH") } func realTool(t *testing.T, tool string) string { t.Helper() p, err := exec.LookPath(tool) if err != nil { t.Skipf("need %s: %v", tool, err) } return p } // assertZoneUntouched checks the in-flight layout survived a failed run whole: // live serial 1 still at path, journal still there, nothing quarantined and no // staging file left behind. func assertZoneUntouched(t *testing.T, dir, path string) { t.Helper() found := siblings(t, dir) serial, ok := ParseZoneSerial(found[filepath.Base(path)]) if !ok || serial != 1 { t.Errorf("zone file was replaced by a failed run: serial = (%d,%v)", serial, ok) } if _, ok := found[filepath.Base(JournalPath(path))]; !ok { t.Error("the journal was lost by a failed run") } for name := range found { if strings.Contains(name, quarantineMarker) { t.Errorf("a failed run must not leave a quarantined file: %s", name) } } } func probeState(t *testing.T, sh, path string) ZoneDiskState { t.Helper() out, err := exec.Command(sh, "-c", zoneStateProbe(path)).Output() if err != nil { t.Fatalf("probe failed: %v", err) } st, ok := parseZoneDiskState(string(out)) if !ok { t.Fatalf("probe output rejected: %q", out) } return st } func siblings(t *testing.T, dir string) map[string]string { t.Helper() entries, err := os.ReadDir(dir) if err != nil { t.Fatal(err) } found := map[string]string{} for _, e := range entries { b, err := os.ReadFile(filepath.Join(dir, e.Name())) if err != nil { t.Fatal(err) } found[e.Name()] = string(b) } return found } // A stdin stream cut mid-transfer gives cat a short file and exits 0. The seed // must refuse to install it, and must not have quarantined anything on the way: // a run that stopped here has to leave the zone exactly as it found it. func TestSeedScriptInterruptedWriteLeavesDiskUntouched(t *testing.T) { sh := requireShell(t, "wc", "tr", "mv", "rm", "mkdir", "dirname") dir, path := inFlightZone(t) plan := PlanSeed(probeState(t, sh, path)) if !plan.WriteSeed || !plan.QuarantineZoneFile || !plan.QuarantineJournal { t.Fatalf("expected a reseed over both files, got %+v", plan) } content := renderSeedZone("example.com", "10.0.0.1", nil, plan.Serial) if err := runSeedScript(t, sh, path, plan, content, content[:len(content)/2]); err == nil { t.Fatal("a truncated seed write must fail rather than install a torn zone file") } serial, ok := ParseZoneSerial(siblings(t, dir)[filepath.Base(path)]) if !ok || serial != 1 { t.Errorf("the zone file was damaged by the interrupted seed: serial = (%d,%v)", serial, ok) } for name := range siblings(t, dir) { if strings.Contains(name, quarantineMarker) { t.Errorf("nothing should have been quarantined before the write landed: %s", name) } if strings.HasSuffix(name, seedTempSuffix) { t.Errorf("the staging file should have been cleaned up: %s", name) } } // The retry must still see the journal and reseed above it, not at 1. retry := PlanSeed(probeState(t, sh, path)) if retry != plan { t.Errorf("retry planned %+v, want the original %+v", retry, plan) } } func TestSeedScriptInstallsOverQuarantinedFiles(t *testing.T) { sh := requireShell(t, "wc", "tr", "mv", "rm", "mkdir", "dirname") dir, path := inFlightZone(t) plan := PlanSeed(probeState(t, sh, path)) content := renderSeedZone("example.com", "10.0.0.1", nil, plan.Serial) if err := runSeedScript(t, sh, path, plan, content, content); err != nil { t.Fatalf("seed script: %v", err) } st := probeState(t, sh, path) if !st.ZoneFile || st.ZoneSerial != plan.Serial { t.Errorf("installed state = %+v want serial %d", st, plan.Serial) } if st.Journal { t.Error("the unreplayable journal should have been moved aside") } found := siblings(t, dir) for _, want := range []string{"db.example.com.orphaned-16", "db.example.com.jnl.orphaned-16"} { if _, ok := found[want]; !ok { t.Errorf("missing preserved file %s: %v", want, keys(found)) } } if _, ok := found[filepath.Base(path)+seedTempSuffix]; ok { t.Error("the staging file should have been renamed into place") } if next := PlanSeed(st); next != (SeedPlan{}) { t.Errorf("second reconcile should be a no-op, got %+v", next) } } // The seed has to work on a PVC that has never held this zone, directories // included. func TestSeedScriptFreshInstall(t *testing.T) { sh := requireShell(t, "wc", "tr", "mv", "rm", "mkdir", "dirname") path := filepath.Join(t.TempDir(), "zones", "db.example.com") plan := PlanSeed(ZoneDiskState{}) content := renderSeedZone("example.com", "10.0.0.1", nil, plan.Serial) if err := runSeedScript(t, sh, path, plan, content, content); err != nil { t.Fatalf("seed script: %v", err) } if st := probeState(t, sh, path); !st.ZoneFile || st.ZoneSerial != 1 { t.Errorf("fresh install state = %+v want serial 1", st) } } func keys(m map[string]string) []string { out := make([]string, 0, len(m)) for k := range m { out = append(out, k) } return out } // A rename that fails leaves live data where it is, so the install must not // happen: the skeleton beside a higher-serial journal is the production failure // this seed exists to avoid. func TestSeedScriptFailedQuarantineAbortsInstall(t *testing.T) { sh := requireShell(t, "wc", "tr", "mv", "rm", "mkdir", "dirname") dir, path := inFlightZone(t) pathEnv := shimPath(t, "mv", `case "$*" in *`+quarantineMarker+`*) exit 1;; esac exec `+realTool(t, "mv")+` "$@"`) plan := PlanSeed(probeState(t, sh, path)) content := renderSeedZone("example.com", "10.0.0.1", nil, plan.Serial) if err := runSeedScriptWithPath(t, sh, path, plan, content, content, pathEnv); err == nil { t.Fatal("a failed quarantine must fail the seed") } assertZoneUntouched(t, dir, path) } // The size guard has to fail closed: an image without wc cannot measure the // staged file, and an unverified file must never be installed. func TestSeedScriptUnmeasurableStagingAbortsInstall(t *testing.T) { sh := requireShell(t, "wc", "tr", "mv", "rm", "mkdir", "dirname") dir, path := inFlightZone(t) pathEnv := shimPath(t, "wc", "exit 127") plan := PlanSeed(probeState(t, sh, path)) content := renderSeedZone("example.com", "10.0.0.1", nil, plan.Serial) if err := runSeedScriptWithPath(t, sh, path, plan, content, content, pathEnv); err == nil { t.Fatal("an unmeasurable staging file must fail the seed") } assertZoneUntouched(t, dir, path) if _, ok := siblings(t, dir)[filepath.Base(path)+seedTempSuffix]; ok { t.Error("the staging file should have been cleaned up") } } func TestSeedScriptFailedMkdirAbortsInstall(t *testing.T) { sh := requireShell(t, "wc", "tr", "mv", "rm", "mkdir", "dirname") dir, path := inFlightZone(t) pathEnv := shimPath(t, "mkdir", "exit 1") plan := PlanSeed(probeState(t, sh, path)) content := renderSeedZone("example.com", "10.0.0.1", nil, plan.Serial) if err := runSeedScriptWithPath(t, sh, path, plan, content, content, pathEnv); err == nil { t.Fatal("a failed mkdir must fail the seed") } assertZoneUntouched(t, dir, path) } // The probe decides whether there is anything to preserve, so a tool it cannot // run must be an error rather than a state that reads as "nothing readable". func TestZoneStateProbeFailedToolIsAnError(t *testing.T) { sh := requireShell(t, "head", "od", "tr") _, path := inFlightZone(t) pathEnv := shimPath(t, "tr", "exit 127") cmd := exec.Command(sh, "-c", zoneStateProbe(path)) cmd.Env = append(os.Environ(), "PATH="+pathEnv) out, err := cmd.Output() if err == nil { t.Fatalf("probe reported success without reading the journal header: %q", out) } }