projectquota_test.go 4.6 KB

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