blob: 4729dac4787740020fc59c046964cb0461d2b522 [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"
21 "io/ioutil"
22 "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
32// withinTolerance is a helper for asserting that a value is within a certain percentage of the
33// expected value. The tolerance is specified as a float between 0 (exact match)
34// and 1 (between 0 and twice the expected value).
35func 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 {
94 n, err := testfile.Write([]byte(testdata))
95 if err != nil {
96 if pathErr, ok := err.(*os.PathError); ok {
97 if pathErr.Err == syscall.ENOSPC {
98 // Running out of space is the only acceptable error to continue execution
99 break
100 }
101 }
102 t.Fatal(err)
103 }
104 bytesWritten += n
105 }
106 if bytesWritten > bytesQuota {
107 t.Errorf("Wrote %v bytes, quota is only %v bytes", bytesWritten, bytesQuota)
108 }
109 })
110 t.Run("GetQuotaReadbackAndUtilization", func(t *testing.T) {
111 defer func() {
112 os.RemoveAll("/test/readback")
113 }()
114 if err := os.Mkdir("/test/readback", 0755); err != nil {
115 t.Fatal(err)
116 }
117 const bytesQuota = 1024 * 1024 // 1MiB
118 const inodesQuota = 100
119 if err := SetQuota("/test/readback", bytesQuota, inodesQuota); err != nil {
120 t.Fatal(err)
121 }
122 sizeFileData := make([]byte, 512*1024)
123 if err := ioutil.WriteFile("/test/readback/512kfile", sizeFileData, 0644); err != nil {
124 t.Fatal(err)
125 }
126
127 quotaUtil, err := GetQuota("/test/readback")
128 if err != nil {
129 t.Fatal(err)
130 }
131 require.Equal(t, uint64(bytesQuota), quotaUtil.Bytes, "bytes quota readback incorrect")
132 require.Equal(t, uint64(inodesQuota), quotaUtil.Inodes, "inodes quota readback incorrect")
133
134 // Give 10% tolerance for quota used values to account for metadata overhead and internal
135 // structures that are also in there. If it's out by more than that it's an issue anyways.
136 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++ {
140 if err := ioutil.WriteFile(fmt.Sprintf("/test/readback/ifile%v", i), []byte("test"), 0644); err != nil {
141 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}