blob: 577c34329b91d25ee6d2de6207005b7add9aa71b [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"
Jan Schärbe70c922024-11-21 11:16:03 +010019
20 "source.monogon.dev/osbase/blockdev"
Jan Schär652c2ad2024-11-19 17:40:50 +010021)
22
23// This is a copy of the constant in metropolis/node/kubernetes/provisioner.go.
24const inodeCapacityRatio = 4 * 512
25
26// checkFilesystemVolume checks that the filesystem containing path has the
27// given mount flags and capacity.
28func checkFilesystemVolume(path string, expectedFlags int64, expectedBytes uint64) error {
29 var statfs unix.Statfs_t
30 err := unix.Statfs(path, &statfs)
31 if err != nil {
32 return fmt.Errorf("failed to statfs volume %q: %w", path, err)
33 }
34
35 if statfs.Flags&unix.ST_RDONLY != expectedFlags&unix.ST_RDONLY {
36 return fmt.Errorf("volume %q has readonly flag %v, expected the opposite", path, statfs.Flags&unix.ST_RDONLY != 0)
37 }
38 if statfs.Flags&unix.ST_NOSUID != expectedFlags&unix.ST_NOSUID {
39 return fmt.Errorf("volume %q has nosuid flag %v, expected the opposite", path, statfs.Flags&unix.ST_NOSUID != 0)
40 }
41 if statfs.Flags&unix.ST_NODEV != expectedFlags&unix.ST_NODEV {
42 return fmt.Errorf("volume %q has nodev flag %v, expected the opposite", path, statfs.Flags&unix.ST_NODEV != 0)
43 }
44 if statfs.Flags&unix.ST_NOEXEC != expectedFlags&unix.ST_NOEXEC {
45 return fmt.Errorf("volume %q has noexec flag %v, expected the opposite", path, statfs.Flags&unix.ST_NOEXEC != 0)
46 }
47
48 sizeBytes := statfs.Blocks * uint64(statfs.Bsize)
49 if sizeBytes != expectedBytes {
50 return fmt.Errorf("volume %q has capacity %v bytes, expected %v bytes", path, sizeBytes, expectedBytes)
51 }
52 expectedFiles := expectedBytes / inodeCapacityRatio
53 if statfs.Files != expectedFiles {
54 return fmt.Errorf("volume %q has capacity for %v files, expected %v files", path, statfs.Files, expectedFiles)
55 }
56
57 // Try writing a file. This should only work if the volume is not read-only.
58 err = os.WriteFile(filepath.Join(path, "test.txt"), []byte("hello"), 0o644)
59 if expectedFlags&unix.ST_RDONLY != 0 {
60 if err == nil {
61 return fmt.Errorf("write did not fail in read-only volume %q", path)
62 } else if !errors.Is(err, syscall.EROFS) {
63 return fmt.Errorf("write failed with unexpected error in read-only volume %q: %w", path, err)
64 }
65 } else if err != nil {
66 return fmt.Errorf("failed to write file in volume %q: %w", path, err)
67 }
68
69 return nil
70}
71
Jan Schärbe70c922024-11-21 11:16:03 +010072func checkBlockVolume(path string, expectedBytes uint64) error {
73 blk, err := blockdev.Open(path)
74 if err != nil {
75 return fmt.Errorf("failed to open block device %q: %w", path, err)
76 }
77 defer blk.Close()
78 sizeBytes := blk.BlockCount() * blk.BlockSize()
79 if sizeBytes != int64(expectedBytes) {
80 return fmt.Errorf("block device %q has size %v bytes, expected %v bytes", path, sizeBytes, expectedBytes)
81 }
82 return nil
83}
84
Jan Schär652c2ad2024-11-19 17:40:50 +010085func testPersistentVolume() error {
86 if err := checkFilesystemVolume("/vol/default", 0, 1*1024*1024); err != nil {
87 return err
88 }
89 if err := checkFilesystemVolume("/vol/local-strict", unix.ST_NOSUID|unix.ST_NODEV|unix.ST_NOEXEC, 5*1024*1024); err != nil {
90 return err
91 }
92 if err := checkFilesystemVolume("/vol/readonly", unix.ST_RDONLY, 1*1024*1024); err != nil {
93 return err
94 }
Jan Schärbe70c922024-11-21 11:16:03 +010095 if err := checkBlockVolume("/vol/block", 1*1024*1024); err != nil {
96 return err
97 }
Jan Schär652c2ad2024-11-19 17:40:50 +010098 return nil
99}
100
101func main() {
102 fmt.Println("PersistentVolume tests starting...")
103
104 if err := testPersistentVolume(); err != nil {
105 fmt.Println(err.Error())
106 // The final log line communicates the test outcome to the e2e test.
107 fmt.Println("[TESTS-FAILED]")
108 } else {
109 fmt.Println("[TESTS-PASSED]")
110 }
111
112 // Sleep forever, because if the process exits, Kubernetes will restart it.
113 for {
114 time.Sleep(time.Hour)
115 }
116}