github.com/docker/docker@v299999999.0.0-20200612211812-aaf470eca7b5+incompatible/daemon/graphdriver/quota/projectquota_test.go (about)

     1  // +build linux
     2  
     3  package quota // import "github.com/docker/docker/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  }