Merge pull request #35231 from sargun/add-vfs-quota-support
Add vfs quota support
This commit is contained in:
commit
0defc69813
8 changed files with 133 additions and 14 deletions
|
@ -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
daemon/graphdriver/quota/errors.go
Normal file
19
daemon/graphdriver/quota/errors.go
Normal 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"
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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
daemon/graphdriver/vfs/quota_linux.go
Normal file
27
daemon/graphdriver/vfs/quota_linux.go
Normal 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
|
||||
}
|
20
daemon/graphdriver/vfs/quota_unsupported.go
Normal file
20
daemon/graphdriver/vfs/quota_unsupported.go
Normal 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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Add table
Reference in a new issue