Browse code

Add disk quota support for btrfs

Signed-off-by: Zhu Guihua <zhugh.fnst@cn.fujitsu.com>

Zhu Guihua authored on 2016/03/24 10:14:38
Showing 5 changed files
... ...
@@ -19,12 +19,15 @@ import (
19 19
 	"os"
20 20
 	"path"
21 21
 	"path/filepath"
22
+	"strings"
22 23
 	"syscall"
23 24
 	"unsafe"
24 25
 
25 26
 	"github.com/docker/docker/daemon/graphdriver"
26 27
 	"github.com/docker/docker/pkg/idtools"
27 28
 	"github.com/docker/docker/pkg/mount"
29
+	"github.com/docker/docker/pkg/parsers"
30
+	"github.com/docker/go-units"
28 31
 	"github.com/opencontainers/runc/libcontainer/label"
29 32
 )
30 33
 
... ...
@@ -32,6 +35,16 @@ func init() {
32 32
 	graphdriver.Register("btrfs", Init)
33 33
 }
34 34
 
35
+var (
36
+	quotaEnabled  = false
37
+	userDiskQuota = false
38
+)
39
+
40
+type btrfsOptions struct {
41
+	minSpace uint64
42
+	size     uint64
43
+}
44
+
35 45
 // Init returns a new BTRFS driver.
36 46
 // An error is returned if BTRFS is not supported.
37 47
 func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (graphdriver.Driver, error) {
... ...
@@ -57,21 +70,58 @@ func Init(home string, options []string, uidMaps, gidMaps []idtools.IDMap) (grap
57 57
 		return nil, err
58 58
 	}
59 59
 
60
+	opt, err := parseOptions(options)
61
+	if err != nil {
62
+		return nil, err
63
+	}
64
+
65
+	if userDiskQuota {
66
+		if err := subvolEnableQuota(home); err != nil {
67
+			return nil, err
68
+		}
69
+		quotaEnabled = true
70
+	}
71
+
60 72
 	driver := &Driver{
61 73
 		home:    home,
62 74
 		uidMaps: uidMaps,
63 75
 		gidMaps: gidMaps,
76
+		options: opt,
64 77
 	}
65 78
 
66 79
 	return graphdriver.NewNaiveDiffDriver(driver, uidMaps, gidMaps), nil
67 80
 }
68 81
 
82
+func parseOptions(opt []string) (btrfsOptions, error) {
83
+	var options btrfsOptions
84
+	for _, option := range opt {
85
+		key, val, err := parsers.ParseKeyValueOpt(option)
86
+		if err != nil {
87
+			return options, err
88
+		}
89
+		key = strings.ToLower(key)
90
+		switch key {
91
+		case "btrfs.min_space":
92
+			minSpace, err := units.RAMInBytes(val)
93
+			if err != nil {
94
+				return options, err
95
+			}
96
+			userDiskQuota = true
97
+			options.minSpace = uint64(minSpace)
98
+		default:
99
+			return options, fmt.Errorf("Unknown option %s", key)
100
+		}
101
+	}
102
+	return options, nil
103
+}
104
+
69 105
 // Driver contains information about the filesystem mounted.
70 106
 type Driver struct {
71 107
 	//root of the file system
72 108
 	home    string
73 109
 	uidMaps []idtools.IDMap
74 110
 	gidMaps []idtools.IDMap
111
+	options btrfsOptions
75 112
 }
76 113
 
77 114
 // String prints the name of the driver (btrfs).
... ...
@@ -100,6 +150,12 @@ func (d *Driver) GetMetadata(id string) (map[string]string, error) {
100 100
 
101 101
 // Cleanup unmounts the home directory.
102 102
 func (d *Driver) Cleanup() error {
103
+	if quotaEnabled {
104
+		if err := subvolDisableQuota(d.home); err != nil {
105
+			return err
106
+		}
107
+	}
108
+
103 109
 	return mount.Unmount(d.home)
104 110
 }
105 111
 
... ...
@@ -238,6 +294,78 @@ func subvolDelete(dirpath, name string) error {
238 238
 	return nil
239 239
 }
240 240
 
241
+func subvolEnableQuota(path string) error {
242
+	dir, err := openDir(path)
243
+	if err != nil {
244
+		return err
245
+	}
246
+	defer closeDir(dir)
247
+
248
+	var args C.struct_btrfs_ioctl_quota_ctl_args
249
+	args.cmd = C.BTRFS_QUOTA_CTL_ENABLE
250
+	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_CTL,
251
+		uintptr(unsafe.Pointer(&args)))
252
+	if errno != 0 {
253
+		return fmt.Errorf("Failed to enable btrfs quota for %s: %v", dir, errno.Error())
254
+	}
255
+
256
+	return nil
257
+}
258
+
259
+func subvolDisableQuota(path string) error {
260
+	dir, err := openDir(path)
261
+	if err != nil {
262
+		return err
263
+	}
264
+	defer closeDir(dir)
265
+
266
+	var args C.struct_btrfs_ioctl_quota_ctl_args
267
+	args.cmd = C.BTRFS_QUOTA_CTL_DISABLE
268
+	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_CTL,
269
+		uintptr(unsafe.Pointer(&args)))
270
+	if errno != 0 {
271
+		return fmt.Errorf("Failed to disable btrfs quota for %s: %v", dir, errno.Error())
272
+	}
273
+
274
+	return nil
275
+}
276
+
277
+func subvolRescanQuota(path string) error {
278
+	dir, err := openDir(path)
279
+	if err != nil {
280
+		return err
281
+	}
282
+	defer closeDir(dir)
283
+
284
+	var args C.struct_btrfs_ioctl_quota_rescan_args
285
+	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QUOTA_RESCAN_WAIT,
286
+		uintptr(unsafe.Pointer(&args)))
287
+	if errno != 0 {
288
+		return fmt.Errorf("Failed to rescan btrfs quota for %s: %v", dir, errno.Error())
289
+	}
290
+
291
+	return nil
292
+}
293
+
294
+func subvolLimitQgroup(path string, size uint64) error {
295
+	dir, err := openDir(path)
296
+	if err != nil {
297
+		return err
298
+	}
299
+	defer closeDir(dir)
300
+
301
+	var args C.struct_btrfs_ioctl_qgroup_limit_args
302
+	args.lim.max_referenced = C.__u64(size)
303
+	args.lim.flags = C.BTRFS_QGROUP_LIMIT_MAX_RFER
304
+	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_LIMIT,
305
+		uintptr(unsafe.Pointer(&args)))
306
+	if errno != 0 {
307
+		return fmt.Errorf("Failed to limit qgroup for %s: %v", dir, errno.Error())
308
+	}
309
+
310
+	return nil
311
+}
312
+
241 313
 func (d *Driver) subvolumesDir() string {
242 314
 	return path.Join(d.home, "subvolumes")
243 315
 }
... ...
@@ -254,11 +382,6 @@ func (d *Driver) CreateReadWrite(id, parent, mountLabel string, storageOpt map[s
254 254
 
255 255
 // Create the filesystem with given id.
256 256
 func (d *Driver) Create(id, parent, mountLabel string, storageOpt map[string]string) error {
257
-
258
-	if len(storageOpt) != 0 {
259
-		return fmt.Errorf("--storage-opt is not supported for btrfs")
260
-	}
261
-
262 257
 	subvolumes := path.Join(d.home, "subvolumes")
263 258
 	rootUID, rootGID, err := idtools.GetRootUIDGID(d.uidMaps, d.gidMaps)
264 259
 	if err != nil {
... ...
@@ -285,6 +408,16 @@ func (d *Driver) Create(id, parent, mountLabel string, storageOpt map[string]str
285 285
 		}
286 286
 	}
287 287
 
288
+	if _, ok := storageOpt["size"]; ok {
289
+		driver := &Driver{}
290
+		if err := d.parseStorageOpt(storageOpt, driver); err != nil {
291
+			return err
292
+		}
293
+		if err := d.setStorageSize(path.Join(subvolumes, id), driver); err != nil {
294
+			return err
295
+		}
296
+	}
297
+
288 298
 	// if we have a remapped root (user namespaces enabled), change the created snapshot
289 299
 	// dir ownership to match
290 300
 	if rootUID != 0 || rootGID != 0 {
... ...
@@ -296,6 +429,49 @@ func (d *Driver) Create(id, parent, mountLabel string, storageOpt map[string]str
296 296
 	return label.Relabel(path.Join(subvolumes, id), mountLabel, false)
297 297
 }
298 298
 
299
+// Parse btrfs storage options
300
+func (d *Driver) parseStorageOpt(storageOpt map[string]string, driver *Driver) error {
301
+	// Read size to change the subvolume disk quota per container
302
+	for key, val := range storageOpt {
303
+		key := strings.ToLower(key)
304
+		switch key {
305
+		case "size":
306
+			size, err := units.RAMInBytes(val)
307
+			if err != nil {
308
+				return err
309
+			}
310
+			driver.options.size = uint64(size)
311
+		default:
312
+			return fmt.Errorf("Unknown option %s", key)
313
+		}
314
+	}
315
+
316
+	return nil
317
+}
318
+
319
+// Set btrfs storage size
320
+func (d *Driver) setStorageSize(dir string, driver *Driver) error {
321
+	if driver.options.size <= 0 {
322
+		return fmt.Errorf("btrfs: invalid storage size: %s", units.HumanSize(float64(driver.options.size)))
323
+	}
324
+	if d.options.minSpace > 0 && driver.options.size < d.options.minSpace {
325
+		return fmt.Errorf("btrfs: storage size cannot be less than %s", units.HumanSize(float64(d.options.minSpace)))
326
+	}
327
+
328
+	if !quotaEnabled {
329
+		if err := subvolEnableQuota(d.home); err != nil {
330
+			return err
331
+		}
332
+		quotaEnabled = true
333
+	}
334
+
335
+	if err := subvolLimitQgroup(dir, driver.options.size); err != nil {
336
+		return err
337
+	}
338
+
339
+	return nil
340
+}
341
+
299 342
 // Remove the filesystem with given id.
300 343
 func (d *Driver) Remove(id string) error {
301 344
 	dir := d.subvolumesDirID(id)
... ...
@@ -308,6 +484,9 @@ func (d *Driver) Remove(id string) error {
308 308
 	if err := os.RemoveAll(dir); err != nil && !os.IsNotExist(err) {
309 309
 		return err
310 310
 	}
311
+	if err := subvolRescanQuota(d.home); err != nil {
312
+		return err
313
+	}
311 314
 	return nil
312 315
 }
313 316
 
... ...
@@ -185,8 +185,10 @@ Linux kernel as of [3.18.0](https://lkml.org/lkml/2014/10/26/137). Call
185 185
 ### Storage driver options
186 186
 
187 187
 Particular storage-driver can be configured with options specified with
188
-`--storage-opt` flags. Options for `devicemapper` are prefixed with `dm` and
189
-options for `zfs` start with `zfs`.
188
+`--storage-opt` flags. Options for `devicemapper` are prefixed with `dm`,
189
+options for `zfs` start with `zfs` and options for `btrfs` start with `btrfs`.
190
+
191
+#### Devicemapper options
190 192
 
191 193
 *  `dm.thinpooldev`
192 194
 
... ...
@@ -470,7 +472,7 @@ options for `zfs` start with `zfs`.
470 470
     $ dockerd --storage-opt dm.min_free_space=10%
471 471
     ```
472 472
 
473
-Currently supported options of `zfs`:
473
+#### ZFS options
474 474
 
475 475
 * `zfs.fsname`
476 476
 
... ...
@@ -482,6 +484,18 @@ Currently supported options of `zfs`:
482 482
 
483 483
         $ dockerd -s zfs --storage-opt zfs.fsname=zroot/docker
484 484
 
485
+#### Btrfs options
486
+
487
+* `btrfs.min_space`
488
+
489
+    Specifies the mininum size to use when creating the subvolume which is used
490
+    for containers. If user uses disk quota for btrfs when creating or running
491
+    a container with **--storage-opt size** option, docker should ensure the
492
+    **size** cannot be smaller than **btrfs.min_space**.
493
+
494
+    Example use:
495
+        $ docker daemon -s btrfs --storage-opt btrfs.min_space=10G
496
+
485 497
 ## Docker runtime execution options
486 498
 
487 499
 The Docker daemon relies on a
... ...
@@ -333,6 +333,7 @@ unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap.
333 333
    $ docker create -it --storage-opt size=120G fedora /bin/bash
334 334
 
335 335
    This (size) will allow to set the container rootfs size to 120G at creation time. User cannot pass a size less than the Default BaseFS Size.
336
+   This option is only available for the `devicemapper`, `btrfs` and `zfs` graphrivers.
336 337
   
337 338
 **--stop-signal**=*SIGTERM*
338 339
   Signal to stop a container. Default is SIGTERM.
... ...
@@ -484,7 +484,8 @@ its root filesystem mounted as read only prohibiting any writes.
484 484
    $ docker run -it --storage-opt size=120G fedora /bin/bash
485 485
 
486 486
    This (size) will allow to set the container rootfs size to 120G at creation time. User cannot pass a size less than the Default BaseFS Size.
487
-  
487
+   This option is only available for the `devicemapper`, `btrfs` and `zfs` graphrivers.
488
+
488 489
 **--stop-signal**=*SIGTERM*
489 490
   Signal to stop a container. Default is SIGTERM.
490 491
 
... ...
@@ -247,9 +247,9 @@ backends use operating system level technologies and can be
247 247
 configured.
248 248
 
249 249
 Specify options to the storage backend with **--storage-opt** flags. The
250
-backends that currently take options are *devicemapper* and *zfs*.
251
-Options for *devicemapper* are prefixed with *dm* and options for *zfs*
252
-start with *zfs*.
250
+backends that currently take options are *devicemapper*, *zfs* and *btrfs*.
251
+Options for *devicemapper* are prefixed with *dm*, options for *zfs*
252
+start with *zfs* and options for *btrfs* start with *btrfs*.
253 253
 
254 254
 Specifically for devicemapper, the default is a "loopback" model which
255 255
 requires no pre-configuration, but is extremely inefficient.  Do not
... ...
@@ -511,6 +511,17 @@ By default docker will pick up the zfs filesystem where docker graph
511 511
 
512 512
 Example use: `dockerd -s zfs --storage-opt zfs.fsname=zroot/docker`
513 513
 
514
+## Btrfs options
515
+
516
+#### btrfs.min_space
517
+
518
+Specifies the mininum size to use when creating the subvolume which is used
519
+for containers. If user uses disk quota for btrfs when creating or running
520
+a container with **--storage-opt size** option, docker should ensure the
521
+**size** cannot be smaller than **btrfs.min_space**.
522
+
523
+Example use: `docker daemon -s btrfs --storage-opt btrfs.min_space=10G`
524
+
514 525
 # CLUSTER STORE OPTIONS
515 526
 
516 527
 The daemon uses libkv to advertise