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

Add vfs quota support
This commit is contained in:
Justin Cormack 2017-11-14 15:05:02 +00:00 committed by GitHub
commit 0defc69813
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 133 additions and 14 deletions

View File

@ -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)
}
}

View File

@ -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"
}

View File

@ -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

View File

@ -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)

View File

@ -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
}

View File

@ -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
}

View File

@ -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)
}

View File

@ -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) {