Kaynağa Gözat

Merge pull request #35231 from sargun/add-vfs-quota-support

Add vfs quota support
Justin Cormack 7 yıl önce
ebeveyn
işleme
0defc69813

+ 22 - 6
daemon/graphdriver/graphtest/graphtest_unix.go

@@ -13,6 +13,7 @@ import (
 	"unsafe"
 
 	"github.com/docker/docker/daemon/graphdriver"
+	"github.com/docker/docker/daemon/graphdriver/quota"
 	"github.com/docker/docker/pkg/stringid"
 	"github.com/docker/go-units"
 	"github.com/stretchr/testify/assert"
@@ -310,7 +311,7 @@ func writeRandomFile(path string, size uint64) error {
 }
 
 // DriverTestSetQuota Create a driver and test setting quota.
-func DriverTestSetQuota(t *testing.T, drivername string) {
+func DriverTestSetQuota(t *testing.T, drivername string, required bool) {
 	driver := GetDriver(t, drivername)
 	defer PutDriver(t)
 
@@ -318,19 +319,34 @@ func DriverTestSetQuota(t *testing.T, drivername string) {
 	createOpts := &graphdriver.CreateOpts{}
 	createOpts.StorageOpt = make(map[string]string, 1)
 	createOpts.StorageOpt["size"] = "50M"
-	if err := driver.Create("zfsTest", "Base", createOpts); err != nil {
+	layerName := drivername + "Test"
+	if err := driver.CreateReadWrite(layerName, "Base", createOpts); err == quota.ErrQuotaNotSupported && !required {
+		t.Skipf("Quota not supported on underlying filesystem: %v", err)
+	} else if err != nil {
 		t.Fatal(err)
 	}
 
-	mountPath, err := driver.Get("zfsTest", "")
+	mountPath, err := driver.Get(layerName, "")
 	if err != nil {
 		t.Fatal(err)
 	}
 
 	quota := uint64(50 * units.MiB)
 
-	err = writeRandomFile(path.Join(mountPath.Path(), "file"), quota*2)
-	if pathError, ok := err.(*os.PathError); ok && pathError.Err != unix.EDQUOT {
-		t.Fatalf("expect write() to fail with %v, got %v", unix.EDQUOT, err)
+	// Try to write a file smaller than quota, and ensure it works
+	err = writeRandomFile(path.Join(mountPath.Path(), "smallfile"), quota/2)
+	if err != nil {
+		t.Fatal(err)
+	}
+	defer os.Remove(path.Join(mountPath.Path(), "smallfile"))
+
+	// Try to write a file bigger than quota. We've already filled up half the quota, so hitting the limit should be easy
+	err = writeRandomFile(path.Join(mountPath.Path(), "bigfile"), quota)
+	if err == nil {
+		t.Fatalf("expected write to fail(), instead had success")
+	}
+	if pathError, ok := err.(*os.PathError); ok && pathError.Err != unix.EDQUOT && pathError.Err != unix.ENOSPC {
+		os.Remove(path.Join(mountPath.Path(), "bigfile"))
+		t.Fatalf("expect write() to fail with %v or %v, got %v", unix.EDQUOT, unix.ENOSPC, pathError.Err)
 	}
 }

+ 19 - 0
daemon/graphdriver/quota/errors.go

@@ -0,0 +1,19 @@
+package quota
+
+import "github.com/docker/docker/api/errdefs"
+
+var (
+	_ errdefs.ErrNotImplemented = (*errQuotaNotSupported)(nil)
+)
+
+// ErrQuotaNotSupported indicates if were found the FS didn't have projects quotas available
+var ErrQuotaNotSupported = errQuotaNotSupported{}
+
+type errQuotaNotSupported struct {
+}
+
+func (e errQuotaNotSupported) NotImplemented() {}
+
+func (e errQuotaNotSupported) Error() string {
+	return "Filesystem does not support, or has not enabled quotas"
+}

+ 0 - 5
daemon/graphdriver/quota/projectquota.go

@@ -58,15 +58,10 @@ import (
 	"path/filepath"
 	"unsafe"
 
-	"errors"
-
 	"github.com/sirupsen/logrus"
 	"golang.org/x/sys/unix"
 )
 
-// ErrQuotaNotSupported indicates if were found the FS does not have projects quotas available
-var ErrQuotaNotSupported = errors.New("Filesystem does not support or has not enabled quotas")
-
 // Quota limit params - currently we only control blocks hard limit
 type Quota struct {
 	Size uint64

+ 40 - 2
daemon/graphdriver/vfs/driver.go

@@ -6,10 +6,12 @@ import (
 	"path/filepath"
 
 	"github.com/docker/docker/daemon/graphdriver"
+	"github.com/docker/docker/daemon/graphdriver/quota"
 	"github.com/docker/docker/pkg/chrootarchive"
 	"github.com/docker/docker/pkg/containerfs"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/system"
+	units "github.com/docker/go-units"
 	"github.com/opencontainers/selinux/go-selinux/label"
 )
 
@@ -33,6 +35,11 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 	if err := idtools.MkdirAllAndChown(home, 0700, rootIDs); err != nil {
 		return nil, err
 	}
+
+	if err := setupDriverQuota(d); err != nil {
+		return nil, err
+	}
+
 	return graphdriver.NewNaiveDiffDriver(d, uidMaps, gidMaps), nil
 }
 
@@ -41,6 +48,7 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
 // In order to support layering, files are copied from the parent layer into the new layer. There is no copy-on-write support.
 // Driver must be wrapped in NaiveDiffDriver to be used as a graphdriver.Driver
 type Driver struct {
+	driverQuota
 	home       string
 	idMappings *idtools.IDMappings
 }
@@ -67,15 +75,38 @@ func (d *Driver) Cleanup() error {
 // CreateReadWrite creates a layer that is writable for use as a container
 // file system.
 func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts) error {
-	return d.Create(id, parent, opts)
+	var err error
+	var size int64
+
+	if opts != nil {
+		for key, val := range opts.StorageOpt {
+			switch key {
+			case "size":
+				if !d.quotaSupported() {
+					return quota.ErrQuotaNotSupported
+				}
+				if size, err = units.RAMInBytes(val); err != nil {
+					return err
+				}
+			default:
+				return fmt.Errorf("Storage opt %s not supported", key)
+			}
+		}
+	}
+
+	return d.create(id, parent, uint64(size))
 }
 
 // Create prepares the filesystem for the VFS driver and copies the directory for the given id under the parent.
 func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
 	if opts != nil && len(opts.StorageOpt) != 0 {
-		return fmt.Errorf("--storage-opt is not supported for vfs")
+		return fmt.Errorf("--storage-opt is not supported for vfs on read-only layers")
 	}
 
+	return d.create(id, parent, 0)
+}
+
+func (d *Driver) create(id, parent string, size uint64) error {
 	dir := d.dir(id)
 	rootIDs := d.idMappings.RootPair()
 	if err := idtools.MkdirAllAndChown(filepath.Dir(dir), 0700, rootIDs); err != nil {
@@ -84,6 +115,13 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) error {
 	if err := idtools.MkdirAndChown(dir, 0755, rootIDs); err != nil {
 		return err
 	}
+
+	if size != 0 {
+		if err := d.setupQuota(dir, size); err != nil {
+			return err
+		}
+	}
+
 	labelOpts := []string{"level:s0"}
 	if _, mountLabel, err := label.InitLabels(labelOpts); err == nil {
 		label.SetFileLabel(dir, mountLabel)

+ 27 - 0
daemon/graphdriver/vfs/quota_linux.go

@@ -0,0 +1,27 @@
+// +build linux
+
+package vfs
+
+import "github.com/docker/docker/daemon/graphdriver/quota"
+
+type driverQuota struct {
+	quotaCtl *quota.Control
+}
+
+func setupDriverQuota(driver *Driver) error {
+	if quotaCtl, err := quota.NewControl(driver.home); err == nil {
+		driver.quotaCtl = quotaCtl
+	} else if err != quota.ErrQuotaNotSupported {
+		return err
+	}
+
+	return nil
+}
+
+func (d *Driver) setupQuota(dir string, size uint64) error {
+	return d.quotaCtl.SetQuota(dir, quota.Quota{Size: size})
+}
+
+func (d *Driver) quotaSupported() bool {
+	return d.quotaCtl != nil
+}

+ 20 - 0
daemon/graphdriver/vfs/quota_unsupported.go

@@ -0,0 +1,20 @@
+// +build !linux
+
+package vfs
+
+import "github.com/docker/docker/daemon/graphdriver/quota"
+
+type driverQuota struct {
+}
+
+func setupDriverQuota(driver *Driver) error {
+	return nil
+}
+
+func (d *Driver) setupQuota(dir string, size uint64) error {
+	return quota.ErrQuotaNotSupported
+}
+
+func (d *Driver) quotaSupported() bool {
+	return false
+}

+ 4 - 0
daemon/graphdriver/vfs/vfs_test.go

@@ -32,6 +32,10 @@ func TestVfsCreateSnap(t *testing.T) {
 	graphtest.DriverTestCreateSnap(t, "vfs")
 }
 
+func TestVfsSetQuota(t *testing.T) {
+	graphtest.DriverTestSetQuota(t, "vfs", false)
+}
+
 func TestVfsTeardown(t *testing.T) {
 	graphtest.PutDriver(t)
 }

+ 1 - 1
daemon/graphdriver/zfs/zfs_test.go

@@ -27,7 +27,7 @@ func TestZfsCreateSnap(t *testing.T) {
 }
 
 func TestZfsSetQuota(t *testing.T) {
-	graphtest.DriverTestSetQuota(t, "zfs")
+	graphtest.DriverTestSetQuota(t, "zfs", true)
 }
 
 func TestZfsTeardown(t *testing.T) {