blob: c80700a5d670f361373dc41830e522436525100a [file] [log] [blame]
Tim Windelschmidt6d33a432025-02-04 14:34:25 +01001// Copyright The Monogon Project Authors.
2// SPDX-License-Identifier: Apache-2.0
3
Jan Schär652c2ad2024-11-19 17:40:50 +01004// This is a test for PersistentVolumes provided by our provisioner. It tests
5// that volumes have the right mount flags, and the expected quotas.
6//
7// The package here is a binary which will run in a Pod in our Kubernetes
8// end-to-end test. See the function makeTestStatefulSet in
9// metropolis/test/e2e/suites/kubernetes/kubernetes_helpers.go for how the Pod
10// is created.
11package main
12
13import (
14 "errors"
Jan Schär73beb692024-11-27 17:47:09 +010015 "flag"
Jan Schär652c2ad2024-11-19 17:40:50 +010016 "fmt"
17 "os"
18 "path/filepath"
19 "syscall"
20 "time"
21
22 "golang.org/x/sys/unix"
Jan Schärbe70c922024-11-21 11:16:03 +010023
24 "source.monogon.dev/osbase/blockdev"
Jan Schär652c2ad2024-11-19 17:40:50 +010025)
26
27// This is a copy of the constant in metropolis/node/kubernetes/provisioner.go.
28const inodeCapacityRatio = 4 * 512
29
Jan Schär73beb692024-11-27 17:47:09 +010030var runtimeClass = flag.String("runtimeclass", "", "Name of the runtime class")
31
Jan Schär652c2ad2024-11-19 17:40:50 +010032// checkFilesystemVolume checks that the filesystem containing path has the
33// given mount flags and capacity.
34func checkFilesystemVolume(path string, expectedFlags int64, expectedBytes uint64) error {
35 var statfs unix.Statfs_t
36 err := unix.Statfs(path, &statfs)
37 if err != nil {
38 return fmt.Errorf("failed to statfs volume %q: %w", path, err)
39 }
40
41 if statfs.Flags&unix.ST_RDONLY != expectedFlags&unix.ST_RDONLY {
42 return fmt.Errorf("volume %q has readonly flag %v, expected the opposite", path, statfs.Flags&unix.ST_RDONLY != 0)
43 }
44 if statfs.Flags&unix.ST_NOSUID != expectedFlags&unix.ST_NOSUID {
45 return fmt.Errorf("volume %q has nosuid flag %v, expected the opposite", path, statfs.Flags&unix.ST_NOSUID != 0)
46 }
47 if statfs.Flags&unix.ST_NODEV != expectedFlags&unix.ST_NODEV {
48 return fmt.Errorf("volume %q has nodev flag %v, expected the opposite", path, statfs.Flags&unix.ST_NODEV != 0)
49 }
50 if statfs.Flags&unix.ST_NOEXEC != expectedFlags&unix.ST_NOEXEC {
51 return fmt.Errorf("volume %q has noexec flag %v, expected the opposite", path, statfs.Flags&unix.ST_NOEXEC != 0)
52 }
53
54 sizeBytes := statfs.Blocks * uint64(statfs.Bsize)
55 if sizeBytes != expectedBytes {
56 return fmt.Errorf("volume %q has capacity %v bytes, expected %v bytes", path, sizeBytes, expectedBytes)
57 }
58 expectedFiles := expectedBytes / inodeCapacityRatio
59 if statfs.Files != expectedFiles {
60 return fmt.Errorf("volume %q has capacity for %v files, expected %v files", path, statfs.Files, expectedFiles)
61 }
62
63 // Try writing a file. This should only work if the volume is not read-only.
64 err = os.WriteFile(filepath.Join(path, "test.txt"), []byte("hello"), 0o644)
65 if expectedFlags&unix.ST_RDONLY != 0 {
66 if err == nil {
67 return fmt.Errorf("write did not fail in read-only volume %q", path)
68 } else if !errors.Is(err, syscall.EROFS) {
69 return fmt.Errorf("write failed with unexpected error in read-only volume %q: %w", path, err)
70 }
71 } else if err != nil {
72 return fmt.Errorf("failed to write file in volume %q: %w", path, err)
73 }
74
75 return nil
76}
77
Jan Schärbe70c922024-11-21 11:16:03 +010078func checkBlockVolume(path string, expectedBytes uint64) error {
79 blk, err := blockdev.Open(path)
80 if err != nil {
81 return fmt.Errorf("failed to open block device %q: %w", path, err)
82 }
83 defer blk.Close()
84 sizeBytes := blk.BlockCount() * blk.BlockSize()
85 if sizeBytes != int64(expectedBytes) {
86 return fmt.Errorf("block device %q has size %v bytes, expected %v bytes", path, sizeBytes, expectedBytes)
87 }
88 return nil
89}
90
Jan Schär652c2ad2024-11-19 17:40:50 +010091func testPersistentVolume() error {
92 if err := checkFilesystemVolume("/vol/default", 0, 1*1024*1024); err != nil {
93 return err
94 }
Jan Schär652c2ad2024-11-19 17:40:50 +010095 if err := checkFilesystemVolume("/vol/readonly", unix.ST_RDONLY, 1*1024*1024); err != nil {
96 return err
97 }
Jan Schär73beb692024-11-27 17:47:09 +010098 // Block volumes are not supported on gVisor.
99 if *runtimeClass != "gvisor" {
100 if err := checkBlockVolume("/vol/block", 1*1024*1024); err != nil {
101 return err
102 }
Jan Schärbe70c922024-11-21 11:16:03 +0100103 }
Jan Schär652c2ad2024-11-19 17:40:50 +0100104 return nil
105}
106
107func main() {
Jan Schär73beb692024-11-27 17:47:09 +0100108 flag.Parse()
109 fmt.Printf("PersistentVolume tests starting on %s...\n", *runtimeClass)
Jan Schär652c2ad2024-11-19 17:40:50 +0100110
111 if err := testPersistentVolume(); err != nil {
112 fmt.Println(err.Error())
113 // The final log line communicates the test outcome to the e2e test.
114 fmt.Println("[TESTS-FAILED]")
115 } else {
116 fmt.Println("[TESTS-PASSED]")
117 }
118
119 // Sleep forever, because if the process exits, Kubernetes will restart it.
120 for {
121 time.Sleep(time.Hour)
122 }
123}