//go:build linux && rootrunner package host import ( "context" "golang.org/x/sys/unix" "os" "os/exec" "path/filepath" "strconv" "strings" "testing" "time" ) func TestRootRebootFilesSeedTwoHostRenumberPhase1(t *testing.T) { TestRootRebootFilesSeedTwoHostPhase1(t) } func TestRootRebootFilesSeedTwoHostRenumberPhase2(t *testing.T) { TestRootRebootFilesSeedTwoHostPhase2(t) } func seedRunnerRebootDisk(t *testing.T) { t.Helper() command := func(args ...string) string { t.Helper() out, err := exec.CommandContext(t.Context(), args[0], args[1:]...).CombinedOutput() if err != nil { t.Fatalf("renumber disk %q: %v %s", args, err, out) } return strings.TrimSpace(string(out)) } create := func(path string, size int64) { t.Helper() f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0600) if err != nil { t.Fatal(err) } err = f.Truncate(size) closeErr := f.Close() if err != nil || closeErr != nil { t.Fatal(err, closeErr) } } detach := func(device string) { t.Cleanup(func() { ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second) defer cancel() if out, err := exec.CommandContext(ctx, "losetup", "--detach", device).CombinedOutput(); err != nil { t.Errorf("detach %s: %v %s", device, err, out) } }) } image := filepath.Join(seedRebootPath, "data.ext4") deviceFile := filepath.Join(seedRebootPath, "data.device") first := strings.HasSuffix(t.Name(), "Phase1") renumber := strings.Contains(t.Name(), "Renumber") device := "" if first { create(image, 2<<30) command("mkfs.ext4", "-q", "-F", image) } else { old, err := os.ReadFile(deviceFile) if err != nil { t.Fatal(err) } suffix := strings.TrimPrefix(string(old), "/dev/loop") if suffix == string(old) { t.Fatal("invalid loop device") } if _, err := strconv.ParseUint(suffix, 10, 32); err != nil { t.Fatal(err) } if renumber { blocker := filepath.Join(seedRebootPath, "blocker") create(blocker, 1<<20) command("losetup", string(old), blocker) detach(string(old)) } else { device = string(old) command("losetup", device, image) } } if device == "" { device = command("losetup", "--find", "--show", image) } detach(device) if first { if err := os.WriteFile(deviceFile, []byte(device), 0600); err != nil { t.Fatal(err) } } else { old, err := os.ReadFile(deviceFile) if err != nil || (string(old) != device) != renumber { t.Fatal("unexpected reboot device number", err) } } if err := unix.Mount(device, "/data", "ext4", unix.MS_NOSUID|unix.MS_NODEV, ""); err != nil { t.Fatal(err) } t.Cleanup(func() { if err := unix.Unmount("/data", 0); err != nil { t.Error(err) } }) t.Logf("two-host data filesystem attached at %s", device) } func TestRootServingHandoverDirectoryProof(t *testing.T) { servingRootRunner(t) TestServingHandoverRebootDirectoryProof(t) TestServingHandoverRebootEqualNumbers(t) } func TestRootServingParkRetainedDoesNotBlockRetirement(t *testing.T) { servingRootRunner(t) TestServingParkRetainedDoesNotBlockRetirement(t) TestServingParkRetainedReplacementWriteFailures(t) TestServingParkQuarantineRetainedParkAllowsRetirement(t) }