github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/daemon/graphdriver/quota/projectquota_test.go (about) 1 // +build linux 2 3 package quota // import "github.com/demonoid81/moby/daemon/graphdriver/quota" 4 5 import ( 6 "io" 7 "io/ioutil" 8 "os" 9 "os/exec" 10 "path/filepath" 11 "testing" 12 13 "golang.org/x/sys/unix" 14 "gotest.tools/v3/assert" 15 is "gotest.tools/v3/assert/cmp" 16 "gotest.tools/v3/fs" 17 ) 18 19 // 10MB 20 const testQuotaSize = 10 * 1024 * 1024 21 const imageSize = 64 * 1024 * 1024 22 23 func TestBlockDev(t *testing.T) { 24 mkfs, err := exec.LookPath("mkfs.xfs") 25 if err != nil { 26 t.Skip("mkfs.xfs not found in PATH") 27 } 28 29 // create a sparse image 30 imageFile, err := ioutil.TempFile("", "xfs-image") 31 if err != nil { 32 t.Fatal(err) 33 } 34 imageFileName := imageFile.Name() 35 defer os.Remove(imageFileName) 36 if _, err = imageFile.Seek(imageSize-1, 0); err != nil { 37 t.Fatal(err) 38 } 39 if _, err = imageFile.Write([]byte{0}); err != nil { 40 t.Fatal(err) 41 } 42 if err = imageFile.Close(); err != nil { 43 t.Fatal(err) 44 } 45 46 // The reason for disabling these options is sometimes people run with a newer userspace 47 // than kernelspace 48 out, err := exec.Command(mkfs, "-m", "crc=0,finobt=0", imageFileName).CombinedOutput() 49 if len(out) > 0 { 50 t.Log(string(out)) 51 } 52 if err != nil { 53 t.Fatal(err) 54 } 55 56 t.Run("testBlockDevQuotaDisabled", wrapMountTest(imageFileName, false, testBlockDevQuotaDisabled)) 57 t.Run("testBlockDevQuotaEnabled", wrapMountTest(imageFileName, true, testBlockDevQuotaEnabled)) 58 t.Run("testSmallerThanQuota", wrapMountTest(imageFileName, true, wrapQuotaTest(testSmallerThanQuota))) 59 t.Run("testBiggerThanQuota", wrapMountTest(imageFileName, true, wrapQuotaTest(testBiggerThanQuota))) 60 t.Run("testRetrieveQuota", wrapMountTest(imageFileName, true, wrapQuotaTest(testRetrieveQuota))) 61 } 62 63 func wrapMountTest(imageFileName string, enableQuota bool, testFunc func(t *testing.T, mountPoint, backingFsDev string)) func(*testing.T) { 64 return func(t *testing.T) { 65 mountOptions := "loop" 66 67 if enableQuota { 68 mountOptions = mountOptions + ",prjquota" 69 } 70 71 mountPointDir := fs.NewDir(t, "xfs-mountPoint") 72 defer mountPointDir.Remove() 73 mountPoint := mountPointDir.Path() 74 75 out, err := exec.Command("mount", "-o", mountOptions, imageFileName, mountPoint).CombinedOutput() 76 if err != nil { 77 _, err := os.Stat("/proc/fs/xfs") 78 if os.IsNotExist(err) { 79 t.Skip("no /proc/fs/xfs") 80 } 81 } 82 83 assert.NilError(t, err, "mount failed: %s", out) 84 85 defer func() { 86 assert.NilError(t, unix.Unmount(mountPoint, 0)) 87 }() 88 89 backingFsDev, err := makeBackingFsDev(mountPoint) 90 assert.NilError(t, err) 91 92 testFunc(t, mountPoint, backingFsDev) 93 } 94 } 95 96 func testBlockDevQuotaDisabled(t *testing.T, mountPoint, backingFsDev string) { 97 hasSupport, err := hasQuotaSupport(backingFsDev) 98 assert.NilError(t, err) 99 assert.Check(t, !hasSupport) 100 } 101 102 func testBlockDevQuotaEnabled(t *testing.T, mountPoint, backingFsDev string) { 103 hasSupport, err := hasQuotaSupport(backingFsDev) 104 assert.NilError(t, err) 105 assert.Check(t, hasSupport) 106 } 107 108 func wrapQuotaTest(testFunc func(t *testing.T, ctrl *Control, mountPoint, testDir, testSubDir string)) func(t *testing.T, mountPoint, backingFsDev string) { 109 return func(t *testing.T, mountPoint, backingFsDev string) { 110 testDir, err := ioutil.TempDir(mountPoint, "per-test") 111 assert.NilError(t, err) 112 defer os.RemoveAll(testDir) 113 114 ctrl, err := NewControl(testDir) 115 assert.NilError(t, err) 116 117 testSubDir, err := ioutil.TempDir(testDir, "quota-test") 118 assert.NilError(t, err) 119 testFunc(t, ctrl, mountPoint, testDir, testSubDir) 120 } 121 122 } 123 124 func testSmallerThanQuota(t *testing.T, ctrl *Control, homeDir, testDir, testSubDir string) { 125 assert.NilError(t, ctrl.SetQuota(testSubDir, Quota{testQuotaSize})) 126 smallerThanQuotaFile := filepath.Join(testSubDir, "smaller-than-quota") 127 assert.NilError(t, ioutil.WriteFile(smallerThanQuotaFile, make([]byte, testQuotaSize/2), 0644)) 128 assert.NilError(t, os.Remove(smallerThanQuotaFile)) 129 } 130 131 func testBiggerThanQuota(t *testing.T, ctrl *Control, homeDir, testDir, testSubDir string) { 132 // Make sure the quota is being enforced 133 // TODO: When we implement this under EXT4, we need to shed CAP_SYS_RESOURCE, otherwise 134 // we're able to violate quota without issue 135 assert.NilError(t, ctrl.SetQuota(testSubDir, Quota{testQuotaSize})) 136 137 biggerThanQuotaFile := filepath.Join(testSubDir, "bigger-than-quota") 138 err := ioutil.WriteFile(biggerThanQuotaFile, make([]byte, testQuotaSize+1), 0644) 139 assert.Assert(t, is.ErrorContains(err, "")) 140 if err == io.ErrShortWrite { 141 assert.NilError(t, os.Remove(biggerThanQuotaFile)) 142 } 143 } 144 145 func testRetrieveQuota(t *testing.T, ctrl *Control, homeDir, testDir, testSubDir string) { 146 // Validate that we can retrieve quota 147 assert.NilError(t, ctrl.SetQuota(testSubDir, Quota{testQuotaSize})) 148 149 var q Quota 150 assert.NilError(t, ctrl.GetQuota(testSubDir, &q)) 151 assert.Check(t, is.Equal(uint64(testQuotaSize), q.Size)) 152 }