blob: 38cf329b74c6a2a1bf9dad41f649fae26e15e5d8 [file] [log] [blame]
Jan Schär652c2ad2024-11-19 17:40:50 +01001// This is a test for PersistentVolumes provided by our provisioner. It tests
2// that volumes have the right mount flags, and the expected quotas.
3//
4// The package here is a binary which will run in a Pod in our Kubernetes
5// end-to-end test. See the function makeTestStatefulSet in
6// metropolis/test/e2e/suites/kubernetes/kubernetes_helpers.go for how the Pod
7// is created.
8package main
9
10import (
11 "errors"
12 "fmt"
13 "os"
14 "path/filepath"
15 "syscall"
16 "time"
17
18 "golang.org/x/sys/unix"
19)
20
21// This is a copy of the constant in metropolis/node/kubernetes/provisioner.go.
22const inodeCapacityRatio = 4 * 512
23
24// checkFilesystemVolume checks that the filesystem containing path has the
25// given mount flags and capacity.
26func checkFilesystemVolume(path string, expectedFlags int64, expectedBytes uint64) error {
27 var statfs unix.Statfs_t
28 err := unix.Statfs(path, &statfs)
29 if err != nil {
30 return fmt.Errorf("failed to statfs volume %q: %w", path, err)
31 }
32
33 if statfs.Flags&unix.ST_RDONLY != expectedFlags&unix.ST_RDONLY {
34 return fmt.Errorf("volume %q has readonly flag %v, expected the opposite", path, statfs.Flags&unix.ST_RDONLY != 0)
35 }
36 if statfs.Flags&unix.ST_NOSUID != expectedFlags&unix.ST_NOSUID {
37 return fmt.Errorf("volume %q has nosuid flag %v, expected the opposite", path, statfs.Flags&unix.ST_NOSUID != 0)
38 }
39 if statfs.Flags&unix.ST_NODEV != expectedFlags&unix.ST_NODEV {
40 return fmt.Errorf("volume %q has nodev flag %v, expected the opposite", path, statfs.Flags&unix.ST_NODEV != 0)
41 }
42 if statfs.Flags&unix.ST_NOEXEC != expectedFlags&unix.ST_NOEXEC {
43 return fmt.Errorf("volume %q has noexec flag %v, expected the opposite", path, statfs.Flags&unix.ST_NOEXEC != 0)
44 }
45
46 sizeBytes := statfs.Blocks * uint64(statfs.Bsize)
47 if sizeBytes != expectedBytes {
48 return fmt.Errorf("volume %q has capacity %v bytes, expected %v bytes", path, sizeBytes, expectedBytes)
49 }
50 expectedFiles := expectedBytes / inodeCapacityRatio
51 if statfs.Files != expectedFiles {
52 return fmt.Errorf("volume %q has capacity for %v files, expected %v files", path, statfs.Files, expectedFiles)
53 }
54
55 // Try writing a file. This should only work if the volume is not read-only.
56 err = os.WriteFile(filepath.Join(path, "test.txt"), []byte("hello"), 0o644)
57 if expectedFlags&unix.ST_RDONLY != 0 {
58 if err == nil {
59 return fmt.Errorf("write did not fail in read-only volume %q", path)
60 } else if !errors.Is(err, syscall.EROFS) {
61 return fmt.Errorf("write failed with unexpected error in read-only volume %q: %w", path, err)
62 }
63 } else if err != nil {
64 return fmt.Errorf("failed to write file in volume %q: %w", path, err)
65 }
66
67 return nil
68}
69
70func testPersistentVolume() error {
71 if err := checkFilesystemVolume("/vol/default", 0, 1*1024*1024); err != nil {
72 return err
73 }
74 if err := checkFilesystemVolume("/vol/local-strict", unix.ST_NOSUID|unix.ST_NODEV|unix.ST_NOEXEC, 5*1024*1024); err != nil {
75 return err
76 }
77 if err := checkFilesystemVolume("/vol/readonly", unix.ST_RDONLY, 1*1024*1024); err != nil {
78 return err
79 }
80 return nil
81}
82
83func main() {
84 fmt.Println("PersistentVolume tests starting...")
85
86 if err := testPersistentVolume(); err != nil {
87 fmt.Println(err.Error())
88 // The final log line communicates the test outcome to the e2e test.
89 fmt.Println("[TESTS-FAILED]")
90 } else {
91 fmt.Println("[TESTS-PASSED]")
92 }
93
94 // Sleep forever, because if the process exits, Kubernetes will restart it.
95 for {
96 time.Sleep(time.Hour)
97 }
98}