// +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) }