|
@@ -0,0 +1,161 @@
|
|
|
+// +build linux
|
|
|
+
|
|
|
+package quota
|
|
|
+
|
|
|
+import (
|
|
|
+ "io"
|
|
|
+ "io/ioutil"
|
|
|
+ "os"
|
|
|
+ "os/exec"
|
|
|
+ "path/filepath"
|
|
|
+ "testing"
|
|
|
+
|
|
|
+ "github.com/stretchr/testify/assert"
|
|
|
+ "github.com/stretchr/testify/require"
|
|
|
+ "golang.org/x/sys/unix"
|
|
|
+)
|
|
|
+
|
|
|
+// 10MB
|
|
|
+const testQuotaSize = 10 * 1024 * 1024
|
|
|
+const imageSize = 64 * 1024 * 1024
|
|
|
+
|
|
|
+func TestBlockDev(t *testing.T) {
|
|
|
+ mkfs, err := exec.LookPath("mkfs.xfs")
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal("mkfs.xfs not installed")
|
|
|
+ }
|
|
|
+
|
|
|
+ // create a sparse image
|
|
|
+ imageFile, err := ioutil.TempFile("", "xfs-image")
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ imageFileName := imageFile.Name()
|
|
|
+ defer os.Remove(imageFileName)
|
|
|
+ if _, err = imageFile.Seek(imageSize-1, 0); err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ if _, err = imageFile.Write([]byte{0}); err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ if err = imageFile.Close(); err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ // The reason for disabling these options is sometimes people run with a newer userspace
|
|
|
+ // than kernelspace
|
|
|
+ out, err := exec.Command(mkfs, "-m", "crc=0,finobt=0", imageFileName).CombinedOutput()
|
|
|
+ if len(out) > 0 {
|
|
|
+ t.Log(string(out))
|
|
|
+ }
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+
|
|
|
+ runTest(t, "testBlockDevQuotaDisabled", wrapMountTest(imageFileName, false, testBlockDevQuotaDisabled))
|
|
|
+ runTest(t, "testBlockDevQuotaEnabled", wrapMountTest(imageFileName, true, testBlockDevQuotaEnabled))
|
|
|
+ runTest(t, "testSmallerThanQuota", wrapMountTest(imageFileName, true, wrapQuotaTest(testSmallerThanQuota)))
|
|
|
+ runTest(t, "testBiggerThanQuota", wrapMountTest(imageFileName, true, wrapQuotaTest(testBiggerThanQuota)))
|
|
|
+ runTest(t, "testRetrieveQuota", wrapMountTest(imageFileName, true, wrapQuotaTest(testRetrieveQuota)))
|
|
|
+}
|
|
|
+
|
|
|
+func runTest(t *testing.T, testName string, testFunc func(*testing.T)) {
|
|
|
+ if success := t.Run(testName, testFunc); !success {
|
|
|
+ out, _ := exec.Command("dmesg").CombinedOutput()
|
|
|
+ t.Log(string(out))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func wrapMountTest(imageFileName string, enableQuota bool, testFunc func(t *testing.T, mountPoint, backingFsDev string)) func(*testing.T) {
|
|
|
+ return func(t *testing.T) {
|
|
|
+ mountOptions := "loop"
|
|
|
+
|
|
|
+ if enableQuota {
|
|
|
+ mountOptions = mountOptions + ",prjquota"
|
|
|
+ }
|
|
|
+
|
|
|
+ // create a mountPoint
|
|
|
+ mountPoint, err := ioutil.TempDir("", "xfs-mountPoint")
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ defer os.RemoveAll(mountPoint)
|
|
|
+
|
|
|
+ out, err := exec.Command("mount", "-o", mountOptions, imageFileName, mountPoint).CombinedOutput()
|
|
|
+ if len(out) > 0 {
|
|
|
+ t.Log(string(out))
|
|
|
+ }
|
|
|
+ if err != nil {
|
|
|
+ t.Fatal("mount failed")
|
|
|
+ }
|
|
|
+
|
|
|
+ defer func() {
|
|
|
+ if err := unix.Unmount(mountPoint, 0); err != nil {
|
|
|
+ t.Fatal(err)
|
|
|
+ }
|
|
|
+ }()
|
|
|
+
|
|
|
+ backingFsDev, err := makeBackingFsDev(mountPoint)
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ testFunc(t, mountPoint, backingFsDev)
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func testBlockDevQuotaDisabled(t *testing.T, mountPoint, backingFsDev string) {
|
|
|
+ hasSupport, err := hasQuotaSupport(backingFsDev)
|
|
|
+ require.NoError(t, err)
|
|
|
+ assert.False(t, hasSupport)
|
|
|
+}
|
|
|
+
|
|
|
+func testBlockDevQuotaEnabled(t *testing.T, mountPoint, backingFsDev string) {
|
|
|
+ hasSupport, err := hasQuotaSupport(backingFsDev)
|
|
|
+ require.NoError(t, err)
|
|
|
+ assert.True(t, hasSupport)
|
|
|
+}
|
|
|
+
|
|
|
+func wrapQuotaTest(testFunc func(t *testing.T, ctrl *Control, mountPoint, testDir, testSubDir string)) func(t *testing.T, mountPoint, backingFsDev string) {
|
|
|
+ return func(t *testing.T, mountPoint, backingFsDev string) {
|
|
|
+ testDir, err := ioutil.TempDir(mountPoint, "per-test")
|
|
|
+ require.NoError(t, err)
|
|
|
+ defer os.RemoveAll(testDir)
|
|
|
+
|
|
|
+ ctrl, err := NewControl(testDir)
|
|
|
+ require.NoError(t, err)
|
|
|
+
|
|
|
+ testSubDir, err := ioutil.TempDir(testDir, "quota-test")
|
|
|
+ require.NoError(t, err)
|
|
|
+ testFunc(t, ctrl, mountPoint, testDir, testSubDir)
|
|
|
+ }
|
|
|
+
|
|
|
+}
|
|
|
+
|
|
|
+func testSmallerThanQuota(t *testing.T, ctrl *Control, homeDir, testDir, testSubDir string) {
|
|
|
+ require.NoError(t, ctrl.SetQuota(testSubDir, Quota{testQuotaSize}))
|
|
|
+ smallerThanQuotaFile := filepath.Join(testSubDir, "smaller-than-quota")
|
|
|
+ require.NoError(t, ioutil.WriteFile(smallerThanQuotaFile, make([]byte, testQuotaSize/2), 0644))
|
|
|
+ require.NoError(t, os.Remove(smallerThanQuotaFile))
|
|
|
+}
|
|
|
+
|
|
|
+func testBiggerThanQuota(t *testing.T, ctrl *Control, homeDir, testDir, testSubDir string) {
|
|
|
+ // Make sure the quota is being enforced
|
|
|
+ // TODO: When we implement this under EXT4, we need to shed CAP_SYS_RESOURCE, otherwise
|
|
|
+ // we're able to violate quota without issue
|
|
|
+ require.NoError(t, ctrl.SetQuota(testSubDir, Quota{testQuotaSize}))
|
|
|
+
|
|
|
+ biggerThanQuotaFile := filepath.Join(testSubDir, "bigger-than-quota")
|
|
|
+ err := ioutil.WriteFile(biggerThanQuotaFile, make([]byte, testQuotaSize+1), 0644)
|
|
|
+ require.Error(t, err)
|
|
|
+ if err == io.ErrShortWrite {
|
|
|
+ require.NoError(t, os.Remove(biggerThanQuotaFile))
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+func testRetrieveQuota(t *testing.T, ctrl *Control, homeDir, testDir, testSubDir string) {
|
|
|
+ // Validate that we can retrieve quota
|
|
|
+ require.NoError(t, ctrl.SetQuota(testSubDir, Quota{testQuotaSize}))
|
|
|
+
|
|
|
+ var q Quota
|
|
|
+ require.NoError(t, ctrl.GetQuota(testSubDir, &q))
|
|
|
+ assert.EqualValues(t, testQuotaSize, q.Size)
|
|
|
+}
|