From 11d44b0dc0f0bec5e94605d979cf2550bc108661 Mon Sep 17 00:00:00 2001 From: Ken Herner Date: Mon, 11 Apr 2016 11:51:40 -0400 Subject: [PATCH 1/3] Add support for setting storage size on zfs containers Now supports setting a containers storage size when using zfs as the storage engine. By passing in `--storage-opt size=`, the created container's storage size will be limited to the given size. Note that the way zfs works, the given specified storage size will be given in addition to the base container size. Example: The node image reports a size of `671M` from `df -h` when started. Setting `--storage-opt size=2G` will result in a drive the size of `671M` + `2G`, `2.7G` in total. Available space will be `2.0G`. The storage size is achieved by setting the zfs option `quota` to the given size on the zfs volume. Signed-off-by: Ken Herner Upstream-commit: e91834043100a6ae3f80c21fd15daccc2f451528 Component: engine --- .../engine/daemon/graphdriver/zfs/zfs.go | 51 +++++++++++++++---- 1 file changed, 40 insertions(+), 11 deletions(-) diff --git a/components/engine/daemon/graphdriver/zfs/zfs.go b/components/engine/daemon/graphdriver/zfs/zfs.go index ffde8a545f..8bfe0584fc 100644 --- a/components/engine/daemon/graphdriver/zfs/zfs.go +++ b/components/engine/daemon/graphdriver/zfs/zfs.go @@ -250,11 +250,7 @@ func (d *Driver) CreateReadWrite(id, parent, mountLabel string, storageOpt map[s // Create prepares the dataset and filesystem for the ZFS driver for the given id under the parent. func (d *Driver) Create(id string, parent string, mountLabel string, storageOpt map[string]string) error { - if len(storageOpt) != 0 { - return fmt.Errorf("--storage-opt is not supported for zfs") - } - - err := d.create(id, parent) + err := d.create(id, parent, storageOpt) if err == nil { return nil } @@ -273,22 +269,55 @@ func (d *Driver) Create(id string, parent string, mountLabel string, storageOpt } // retry - return d.create(id, parent) + return d.create(id, parent, storageOpt) } -func (d *Driver) create(id, parent string) error { +func (d *Driver) create(id, parent string, storageOpt map[string]string) error { name := d.zfsPath(id) + quota, err := parseStorageOpt(storageOpt) if parent == "" { mountoptions := map[string]string{"mountpoint": "legacy"} fs, err := zfs.CreateFilesystem(name, mountoptions) if err == nil { - d.Lock() - d.filesystemsCache[fs.Name] = true - d.Unlock() + err = setQuota(name, quota) + if err == nil { + d.Lock() + d.filesystemsCache[fs.Name] = true + d.Unlock() + } } return err } - return d.cloneFilesystem(name, d.zfsPath(parent)) + err = d.cloneFilesystem(name, d.zfsPath(parent)) + if err == nil { + err = setQuota(name, quota) + } + return err +} + +func parseStorageOpt(storageOpt map[string]string) (string, error) { + // Read size to change the disk quota per container + for k, v := range storageOpt { + key := strings.ToLower(k) + switch key { + case "size": + return v, nil + default: + return "0", fmt.Errorf("Unknown option %s", key) + } + } + return "0", nil +} + +func setQuota(name string, quota string) error { + if quota == "0" { + return nil + } + fs, err := zfs.GetDataset(name) + if err != nil { + return err + } + return fs.SetProperty("quota", quota) } // Remove deletes the dataset, filesystem and the cache for the given id. From 74bbceaf490b6b3ee7e5974b0d8c20cdd7274bf9 Mon Sep 17 00:00:00 2001 From: Ken Herner Date: Thu, 12 May 2016 17:21:38 -0400 Subject: [PATCH 2/3] Add error check after parseStorageOpt Signed-off-by: Ken Herner Upstream-commit: 373654f43e87a2e0bd5388ca4ab1852fd51a7199 Component: engine --- components/engine/daemon/graphdriver/zfs/zfs.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/components/engine/daemon/graphdriver/zfs/zfs.go b/components/engine/daemon/graphdriver/zfs/zfs.go index 8bfe0584fc..056586d4ba 100644 --- a/components/engine/daemon/graphdriver/zfs/zfs.go +++ b/components/engine/daemon/graphdriver/zfs/zfs.go @@ -275,6 +275,9 @@ func (d *Driver) Create(id string, parent string, mountLabel string, storageOpt func (d *Driver) create(id, parent string, storageOpt map[string]string) error { name := d.zfsPath(id) quota, err := parseStorageOpt(storageOpt) + if err != nil { + return err + } if parent == "" { mountoptions := map[string]string{"mountpoint": "legacy"} fs, err := zfs.CreateFilesystem(name, mountoptions) From a8712b3f5da1c195d4ce17f5bd3776e1c93f54e4 Mon Sep 17 00:00:00 2001 From: Ken Herner Date: Thu, 19 May 2016 14:47:33 -0400 Subject: [PATCH 3/3] Add test to ZFS for disk quota Signed-off-by: Ken Herner Upstream-commit: 04b4e3e6d80d5d734edba90e55f41863b50ee45b Component: engine --- .../graphdriver/graphtest/graphtest_unix.go | 46 +++++++++++++++++++ .../engine/daemon/graphdriver/zfs/zfs_test.go | 4 ++ 2 files changed, 50 insertions(+) diff --git a/components/engine/daemon/graphdriver/graphtest/graphtest_unix.go b/components/engine/daemon/graphdriver/graphtest/graphtest_unix.go index 488be174f6..22ac025bcb 100644 --- a/components/engine/daemon/graphdriver/graphtest/graphtest_unix.go +++ b/components/engine/daemon/graphdriver/graphtest/graphtest_unix.go @@ -5,12 +5,16 @@ package graphtest import ( "fmt" "io/ioutil" + "math/rand" "os" "path" + "reflect" "syscall" "testing" + "unsafe" "github.com/docker/docker/daemon/graphdriver" + "github.com/docker/go-units" ) var ( @@ -297,3 +301,45 @@ func DriverTestCreateSnap(t *testing.T, drivername string) { t.Fatal(err) } } + +func writeRandomFile(path string, size uint64) error { + buf := make([]int64, size/8) + + r := rand.NewSource(0) + for i := range buf { + buf[i] = r.Int63() + } + + // Cast to []byte + header := *(*reflect.SliceHeader)(unsafe.Pointer(&buf)) + header.Len *= 8 + header.Cap *= 8 + data := *(*[]byte)(unsafe.Pointer(&header)) + + return ioutil.WriteFile(path, data, 0700) +} + +// DriverTestSetQuota Create a driver and test setting quota. +func DriverTestSetQuota(t *testing.T, drivername string) { + driver := GetDriver(t, drivername) + defer PutDriver(t) + + createBase(t, driver, "Base") + storageOpt := make(map[string]string, 1) + storageOpt["size"] = "50M" + if err := driver.Create("zfsTest", "Base", "", storageOpt); err != nil { + t.Fatal(err) + } + + mountPath, err := driver.Get("zfsTest", "") + if err != nil { + t.Fatal(err) + } + + quota := uint64(50 * units.MiB) + err = writeRandomFile(path.Join(mountPath, "file"), quota*2) + if pathError, ok := err.(*os.PathError); ok && pathError.Err != syscall.EDQUOT { + t.Fatalf("expect write() to fail with %v, got %v", syscall.EDQUOT, err) + } + +} diff --git a/components/engine/daemon/graphdriver/zfs/zfs_test.go b/components/engine/daemon/graphdriver/zfs/zfs_test.go index 0e7937eccb..3e22928438 100644 --- a/components/engine/daemon/graphdriver/zfs/zfs_test.go +++ b/components/engine/daemon/graphdriver/zfs/zfs_test.go @@ -26,6 +26,10 @@ func TestZfsCreateSnap(t *testing.T) { graphtest.DriverTestCreateSnap(t, "zfs") } +func TestZfsSetQuota(t *testing.T) { + graphtest.DriverTestSetQuota(t, "zfs") +} + func TestZfsTeardown(t *testing.T) { graphtest.PutDriver(t) }