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