carlos / platform Public

Clone
git clone https://amadan.net/carlos/platform

Plain git — no account needed to clone.

Download

Download this file

1//go:build linux && rootrunner
2
3package host
4
5import (
6 "context"
7 "os"
8 "os/exec"
9 "path/filepath"
10 "strconv"
11 "strings"
12 "testing"
13 "time"
14
15 "golang.org/x/sys/unix"
16)
17
18func TestRootRebootServingParkGrownDiskPhase1(t *testing.T) {
19 TestRootRebootServingParkChangedMountPhase1(t)
20}
21
22func TestRootRebootServingParkGrownDiskPhase2(t *testing.T) {
23 TestRootRebootServingParkChangedMountPhase2(t)
24}
25
26// Capture retirement on a small ext4 filesystem, grow it online, then attach it
27// under a different device number after reboot. Neither identity change is a seam.
28func servingRunnerGrownDisk(t *testing.T, data string, first bool) func() {
29 t.Helper()
30 command := func(args ...string) string {
31 t.Helper()
32 out, err := exec.CommandContext(t.Context(), args[0], args[1:]...).CombinedOutput()
33 if err != nil {
34 t.Fatalf("grown disk %q: %v %s", args, err, out)
35 }
36 return strings.TrimSpace(string(out))
37 }
38 image := filepath.Join(servingRebootRoot, "grow.ext4")
39 deviceFile := filepath.Join(servingRebootRoot, "grow.device")
40 uuidFile := filepath.Join(servingRebootRoot, "grow.uuid")
41 create := func(path string, size int64) {
42 t.Helper()
43 f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_RDWR, 0600)
44 if err != nil {
45 t.Fatal(err)
46 }
47 err = f.Truncate(size)
48 closeErr := f.Close()
49 if err != nil || closeErr != nil {
50 t.Fatal(err, closeErr)
51 }
52 }
53 detach := func(device string) {
54 t.Cleanup(func() {
55 ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
56 defer cancel()
57 if out, err := exec.CommandContext(ctx, "losetup", "--detach", device).CombinedOutput(); err != nil {
58 t.Errorf("detach %s: %v %s", device, err, out)
59 }
60 })
61 }
62 if first {
63 if err := os.Mkdir(data, 0700); err != nil {
64 t.Fatal(err)
65 }
66 create(image, 128<<20)
67 command("mkfs.ext4", "-q", "-F", image)
68 } else {
69 old, err := os.ReadFile(deviceFile)
70 if err != nil {
71 t.Fatal(err)
72 }
73 suffix := strings.TrimPrefix(string(old), "/dev/loop")
74 if suffix == string(old) {
75 t.Fatal("invalid loop path")
76 }
77 if _, err := strconv.ParseUint(suffix, 10, 32); err != nil {
78 t.Fatal(err)
79 }
80 blocker := filepath.Join(servingRebootRoot, "blocker")
81 create(blocker, 1<<20)
82 command("losetup", string(old), blocker)
83 detach(string(old))
84 }
85 device := command("losetup", "--find", "--show", image)
86 detach(device)
87 uuid := command("blkid", "-s", "UUID", "-o", "value", device)
88 if first {
89 if err := os.WriteFile(deviceFile, []byte(device), 0600); err != nil {
90 t.Fatal(err)
91 }
92 if err := os.WriteFile(uuidFile, []byte(uuid), 0600); err != nil {
93 t.Fatal(err)
94 }
95 } else {
96 old, err := os.ReadFile(deviceFile)
97 if err != nil || string(old) == device {
98 t.Fatal("device did not change", err)
99 }
100 saved, err := os.ReadFile(uuidFile)
101 if err != nil || uuid == "" || string(saved) != uuid {
102 t.Fatal("filesystem UUID changed", err)
103 }
104 }
105 if err := unix.Mount(device, data, "ext4", unix.MS_NOSUID|unix.MS_NODEV, ""); err != nil {
106 t.Fatal(err)
107 }
108 t.Cleanup(func() {
109 if err := unix.Unmount(data, 0); err != nil {
110 t.Error(err)
111 }
112 })
113 var st unix.Statfs_t
114 if err := unix.Statfs(data, &st); err != nil {
115 t.Fatal(err)
116 }
117 t.Logf("grown filesystem: device=%s uuid=%s blocks=%d blocksize=%d", device, uuid, st.Blocks, st.Bsize)
118 if !first {
119 if st.Blocks*uint64(st.Bsize) <= 128<<20 {
120 t.Fatal("grown capacity lost across reboot")
121 }
122 return nil
123 }
124 return func() {
125 t.Helper()
126 if err := os.Truncate(image, 256<<20); err != nil {
127 t.Fatal(err)
128 }
129 command("losetup", "--set-capacity", device)
130 t.Log(command("resize2fs", device))
131 var grown unix.Statfs_t
132 if err := unix.Statfs(data, &grown); err != nil {
133 t.Fatal(err)
134 }
135 if grown.Blocks <= st.Blocks || grown.Fsid != st.Fsid || command("blkid", "-s", "UUID", "-o", "value", device) != uuid {
136 t.Fatal("grow changed identity or did not increase capacity")
137 }
138 command("sync", "-f", data)
139 t.Logf("grew filesystem: blocks %d -> %d, filesystem identity unchanged", st.Blocks, grown.Blocks)
140 }
141}
142