Signed-off-by: Zhu Guihua <zhugh.fnst@cn.fujitsu.com>
| ... | ... |
@@ -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 |