blob: 4044b60dc84eb1ee689385923e0e10aff1720cf2 [file] [log] [blame]
Lorenz Brun547b33f2020-04-23 15:27:06 +02001// Copyright 2020 The Monogon Project Authors.
2//
3// SPDX-License-Identifier: Apache-2.0
4//
5// Licensed under the Apache License, Version 2.0 (the "License");
6// you may not use this file except in compliance with the License.
7// You may obtain a copy of the License at
8//
9// http://www.apache.org/licenses/LICENSE-2.0
10//
11// Unless required by applicable law or agreed to in writing, software
12// distributed under the License is distributed on an "AS IS" BASIS,
13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14// See the License for the specific language governing permissions and
15// limitations under the License.
16
17package fsquota
18
19import (
Tim Windelschmidtaf821c82024-04-23 15:03:52 +020020 "errors"
Lorenz Brun547b33f2020-04-23 15:27:06 +020021 "fmt"
Lorenz Brun547b33f2020-04-23 15:27:06 +020022 "math"
23 "os"
24 "os/exec"
25 "syscall"
26 "testing"
27
28 "github.com/stretchr/testify/require"
29 "golang.org/x/sys/unix"
30)
31
Serge Bazanski216fe7b2021-05-21 18:36:16 +020032// withinTolerance is a helper for asserting that a value is within a certain
33// percentage of the expected value. The tolerance is specified as a float
34// between 0 (exact match) and 1 (between 0 and twice the expected value).
Lorenz Brun547b33f2020-04-23 15:27:06 +020035func withinTolerance(t *testing.T, expected uint64, actual uint64, tolerance float64, name string) {
36 t.Helper()
37 delta := uint64(math.Round(float64(expected) * tolerance))
38 lowerBound := expected - delta
39 upperBound := expected + delta
40 if actual < lowerBound {
41 t.Errorf("Value %v (%v) is too low, expected between %v and %v", name, actual, lowerBound, upperBound)
42 }
43 if actual > upperBound {
44 t.Errorf("Value %v (%v) is too high, expected between %v and %v", name, actual, lowerBound, upperBound)
45 }
46}
47
48func TestBasic(t *testing.T) {
49 if os.Getenv("IN_KTEST") != "true" {
50 t.Skip("Not in ktest")
51 }
52 mkfsCmd := exec.Command("/mkfs.xfs", "-qf", "/dev/ram0")
53 if _, err := mkfsCmd.Output(); err != nil {
54 t.Fatal(err)
55 }
56 if err := os.Mkdir("/test", 0755); err != nil {
57 t.Error(err)
58 }
59
60 if err := unix.Mount("/dev/ram0", "/test", "xfs", unix.MS_NOEXEC|unix.MS_NODEV, "prjquota"); err != nil {
61 t.Fatal(err)
62 }
63 defer unix.Unmount("/test", 0)
64 defer os.RemoveAll("/test")
65 t.Run("SetQuota", func(t *testing.T) {
66 defer func() {
67 os.RemoveAll("/test/set")
68 }()
69 if err := os.Mkdir("/test/set", 0755); err != nil {
70 t.Fatal(err)
71 }
72 if err := SetQuota("/test/set", 1024*1024, 100); err != nil {
73 t.Fatal(err)
74 }
75 })
76 t.Run("SetQuotaAndExhaust", func(t *testing.T) {
77 defer func() {
78 os.RemoveAll("/test/sizequota")
79 }()
80 if err := os.Mkdir("/test/sizequota", 0755); err != nil {
81 t.Fatal(err)
82 }
83 const bytesQuota = 1024 * 1024 // 1MiB
84 if err := SetQuota("/test/sizequota", bytesQuota, 0); err != nil {
85 t.Fatal(err)
86 }
87 testfile, err := os.Create("/test/sizequota/testfile")
88 if err != nil {
89 t.Fatal(err)
90 }
91 testdata := make([]byte, 1024)
92 var bytesWritten int
93 for {
Tim Windelschmidt5e460a92024-04-11 01:33:09 +020094 n, err := testfile.Write(testdata)
Lorenz Brun547b33f2020-04-23 15:27:06 +020095 if err != nil {
Tim Windelschmidtaf821c82024-04-23 15:03:52 +020096 var pathErr *os.PathError
97 if errors.As(err, &pathErr) && errors.Is(pathErr.Err, syscall.ENOSPC) {
98 // Running out of space is the only acceptable error to continue execution
99 break
Lorenz Brun547b33f2020-04-23 15:27:06 +0200100 }
101 t.Fatal(err)
102 }
103 bytesWritten += n
104 }
105 if bytesWritten > bytesQuota {
106 t.Errorf("Wrote %v bytes, quota is only %v bytes", bytesWritten, bytesQuota)
107 }
108 })
109 t.Run("GetQuotaReadbackAndUtilization", func(t *testing.T) {
110 defer func() {
111 os.RemoveAll("/test/readback")
112 }()
113 if err := os.Mkdir("/test/readback", 0755); err != nil {
114 t.Fatal(err)
115 }
116 const bytesQuota = 1024 * 1024 // 1MiB
117 const inodesQuota = 100
118 if err := SetQuota("/test/readback", bytesQuota, inodesQuota); err != nil {
119 t.Fatal(err)
120 }
121 sizeFileData := make([]byte, 512*1024)
Lorenz Brun764a2de2021-11-22 16:26:36 +0100122 if err := os.WriteFile("/test/readback/512kfile", sizeFileData, 0644); err != nil {
Lorenz Brun547b33f2020-04-23 15:27:06 +0200123 t.Fatal(err)
124 }
125
126 quotaUtil, err := GetQuota("/test/readback")
127 if err != nil {
128 t.Fatal(err)
129 }
130 require.Equal(t, uint64(bytesQuota), quotaUtil.Bytes, "bytes quota readback incorrect")
131 require.Equal(t, uint64(inodesQuota), quotaUtil.Inodes, "inodes quota readback incorrect")
132
Serge Bazanski216fe7b2021-05-21 18:36:16 +0200133 // Give 10% tolerance for quota used values to account for metadata
134 // overhead and internal structures that are also in there. If it's out
135 // by more than that it's an issue anyways.
Lorenz Brun547b33f2020-04-23 15:27:06 +0200136 withinTolerance(t, uint64(len(sizeFileData)), quotaUtil.BytesUsed, 0.1, "BytesUsed")
137
138 // Write 50 inodes for a total of 51 (with the 512K file)
139 for i := 0; i < 50; i++ {
Lorenz Brun764a2de2021-11-22 16:26:36 +0100140 if err := os.WriteFile(fmt.Sprintf("/test/readback/ifile%v", i), []byte("test"), 0644); err != nil {
Lorenz Brun547b33f2020-04-23 15:27:06 +0200141 t.Fatal(err)
142 }
143 }
144
145 quotaUtil, err = GetQuota("/test/readback")
146 if err != nil {
147 t.Fatal(err)
148 }
149
150 withinTolerance(t, 51, quotaUtil.InodesUsed, 0.1, "InodesUsed")
151 })
152}