blob: c842b6367b8658c5943633df0b6333fbcafe758d [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 (
20 "fmt"
Lorenz Brun547b33f2020-04-23 15:27:06 +020021 "math"
22 "os"
23 "os/exec"
24 "syscall"
25 "testing"
26
27 "github.com/stretchr/testify/require"
28 "golang.org/x/sys/unix"
29)
30
Serge Bazanski216fe7b2021-05-21 18:36:16 +020031// withinTolerance is a helper for asserting that a value is within a certain
32// percentage of the expected value. The tolerance is specified as a float
33// between 0 (exact match) and 1 (between 0 and twice the expected value).
Lorenz Brun547b33f2020-04-23 15:27:06 +020034func withinTolerance(t *testing.T, expected uint64, actual uint64, tolerance float64, name string) {
35 t.Helper()
36 delta := uint64(math.Round(float64(expected) * tolerance))
37 lowerBound := expected - delta
38 upperBound := expected + delta
39 if actual < lowerBound {
40 t.Errorf("Value %v (%v) is too low, expected between %v and %v", name, actual, lowerBound, upperBound)
41 }
42 if actual > upperBound {
43 t.Errorf("Value %v (%v) is too high, expected between %v and %v", name, actual, lowerBound, upperBound)
44 }
45}
46
47func TestBasic(t *testing.T) {
48 if os.Getenv("IN_KTEST") != "true" {
49 t.Skip("Not in ktest")
50 }
51 mkfsCmd := exec.Command("/mkfs.xfs", "-qf", "/dev/ram0")
52 if _, err := mkfsCmd.Output(); err != nil {
53 t.Fatal(err)
54 }
55 if err := os.Mkdir("/test", 0755); err != nil {
56 t.Error(err)
57 }
58
59 if err := unix.Mount("/dev/ram0", "/test", "xfs", unix.MS_NOEXEC|unix.MS_NODEV, "prjquota"); err != nil {
60 t.Fatal(err)
61 }
62 defer unix.Unmount("/test", 0)
63 defer os.RemoveAll("/test")
64 t.Run("SetQuota", func(t *testing.T) {
65 defer func() {
66 os.RemoveAll("/test/set")
67 }()
68 if err := os.Mkdir("/test/set", 0755); err != nil {
69 t.Fatal(err)
70 }
71 if err := SetQuota("/test/set", 1024*1024, 100); err != nil {
72 t.Fatal(err)
73 }
74 })
75 t.Run("SetQuotaAndExhaust", func(t *testing.T) {
76 defer func() {
77 os.RemoveAll("/test/sizequota")
78 }()
79 if err := os.Mkdir("/test/sizequota", 0755); err != nil {
80 t.Fatal(err)
81 }
82 const bytesQuota = 1024 * 1024 // 1MiB
83 if err := SetQuota("/test/sizequota", bytesQuota, 0); err != nil {
84 t.Fatal(err)
85 }
86 testfile, err := os.Create("/test/sizequota/testfile")
87 if err != nil {
88 t.Fatal(err)
89 }
90 testdata := make([]byte, 1024)
91 var bytesWritten int
92 for {
93 n, err := testfile.Write([]byte(testdata))
94 if err != nil {
95 if pathErr, ok := err.(*os.PathError); ok {
96 if pathErr.Err == syscall.ENOSPC {
97 // Running out of space is the only acceptable error to continue execution
98 break
99 }
100 }
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}