瀏覽代碼

Remove btrfs quota groups after containers destroyed

This fix tries to address the issue raised in 29325 where
btrfs quota groups are not clean up even after containers
have been destroyed.

The reason for the issue is that btrfs quota groups have
to be explicitly destroyed. This fix fixes this issue.

This fix is tested manually in Ubuntu 16.04,
with steps specified in 29325.

This fix fixes 29325.

Signed-off-by: Yong Tang <yong.tang.github@outlook.com>
Yong Tang 8 年之前
父節點
當前提交
e907c6418a
共有 1 個文件被更改,包括 79 次插入20 次删除
  1. 79 20
      daemon/graphdriver/btrfs/btrfs.go

+ 79 - 20
daemon/graphdriver/btrfs/btrfs.go

@@ -16,13 +16,16 @@ import "C"
 
 
 import (
 import (
 	"fmt"
 	"fmt"
+	"math"
 	"os"
 	"os"
 	"path"
 	"path"
 	"path/filepath"
 	"path/filepath"
 	"strings"
 	"strings"
+	"sync"
 	"syscall"
 	"syscall"
 	"unsafe"
 	"unsafe"
 
 
+	"github.com/Sirupsen/logrus"
 	"github.com/docker/docker/daemon/graphdriver"
 	"github.com/docker/docker/daemon/graphdriver"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/idtools"
 	"github.com/docker/docker/pkg/mount"
 	"github.com/docker/docker/pkg/mount"
@@ -119,6 +122,7 @@ type Driver struct {
 	gidMaps      []idtools.IDMap
 	gidMaps      []idtools.IDMap
 	options      btrfsOptions
 	options      btrfsOptions
 	quotaEnabled bool
 	quotaEnabled bool
+	once         sync.Once
 }
 }
 
 
 // String prints the name of the driver (btrfs).
 // String prints the name of the driver (btrfs).
@@ -237,7 +241,7 @@ func isSubvolume(p string) (bool, error) {
 	return bufStat.Ino == C.BTRFS_FIRST_FREE_OBJECTID, nil
 	return bufStat.Ino == C.BTRFS_FIRST_FREE_OBJECTID, nil
 }
 }
 
 
-func subvolDelete(dirpath, name string) error {
+func subvolDelete(dirpath, name string, quotaEnabled bool) error {
 	dir, err := openDir(dirpath)
 	dir, err := openDir(dirpath)
 	if err != nil {
 	if err != nil {
 		return err
 		return err
@@ -265,7 +269,7 @@ func subvolDelete(dirpath, name string) error {
 				return fmt.Errorf("Failed to test if %s is a btrfs subvolume: %v", p, err)
 				return fmt.Errorf("Failed to test if %s is a btrfs subvolume: %v", p, err)
 			}
 			}
 			if sv {
 			if sv {
-				if err := subvolDelete(path.Dir(p), f.Name()); err != nil {
+				if err := subvolDelete(path.Dir(p), f.Name(), quotaEnabled); err != nil {
 					return fmt.Errorf("Failed to destroy btrfs child subvolume (%s) of parent (%s): %v", p, dirpath, err)
 					return fmt.Errorf("Failed to destroy btrfs child subvolume (%s) of parent (%s): %v", p, dirpath, err)
 				}
 				}
 			}
 			}
@@ -276,6 +280,21 @@ func subvolDelete(dirpath, name string) error {
 		return fmt.Errorf("Recursively walking subvolumes for %s failed: %v", dirpath, err)
 		return fmt.Errorf("Recursively walking subvolumes for %s failed: %v", dirpath, err)
 	}
 	}
 
 
+	if quotaEnabled {
+		if qgroupid, err := subvolLookupQgroup(fullPath); err == nil {
+			var args C.struct_btrfs_ioctl_qgroup_create_args
+			args.qgroupid = C.__u64(qgroupid)
+
+			_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_QGROUP_CREATE,
+				uintptr(unsafe.Pointer(&args)))
+			if errno != 0 {
+				logrus.Errorf("Failed to delete btrfs qgroup %v for %s: %v", qgroupid, fullPath, errno.Error())
+			}
+		} else {
+			logrus.Errorf("Failed to lookup btrfs qgroup for %s: %v", fullPath, err.Error())
+		}
+	}
+
 	// all subvolumes have been removed
 	// all subvolumes have been removed
 	// now remove the one originally passed in
 	// now remove the one originally passed in
 	for i, c := range []byte(name) {
 	for i, c := range []byte(name) {
@@ -289,15 +308,25 @@ func subvolDelete(dirpath, name string) error {
 	return nil
 	return nil
 }
 }
 
 
+func (d *Driver) updateQuotaStatus() {
+	d.once.Do(func() {
+		if !d.quotaEnabled {
+			// In case quotaEnabled is not set, check qgroup and update quotaEnabled as needed
+			if err := subvolQgroupStatus(d.home); err != nil {
+				// quota is still not enabled
+				return
+			}
+			d.quotaEnabled = true
+		}
+	})
+}
+
 func (d *Driver) subvolEnableQuota() error {
 func (d *Driver) subvolEnableQuota() error {
+	d.updateQuotaStatus()
+
 	if d.quotaEnabled {
 	if d.quotaEnabled {
 		return nil
 		return nil
 	}
 	}
-	// In case quotaEnabled is not set, check qgroup and update quotaEnabled as needed
-	if _, err := subvolLookupQgroup(d.home); err == nil {
-		d.quotaEnabled = true
-		return nil
-	}
 
 
 	dir, err := openDir(d.home)
 	dir, err := openDir(d.home)
 	if err != nil {
 	if err != nil {
@@ -319,13 +348,10 @@ func (d *Driver) subvolEnableQuota() error {
 }
 }
 
 
 func (d *Driver) subvolDisableQuota() error {
 func (d *Driver) subvolDisableQuota() error {
+	d.updateQuotaStatus()
+
 	if !d.quotaEnabled {
 	if !d.quotaEnabled {
-		// In case quotaEnabled is not set, check qgroup and update quotaEnabled as needed
-		if _, err := subvolLookupQgroup(d.home); err != nil {
-			// quota is still not enabled
-			return nil
-		}
-		d.quotaEnabled = true
+		return nil
 	}
 	}
 
 
 	dir, err := openDir(d.home)
 	dir, err := openDir(d.home)
@@ -348,13 +374,10 @@ func (d *Driver) subvolDisableQuota() error {
 }
 }
 
 
 func (d *Driver) subvolRescanQuota() error {
 func (d *Driver) subvolRescanQuota() error {
+	d.updateQuotaStatus()
+
 	if !d.quotaEnabled {
 	if !d.quotaEnabled {
-		// In case quotaEnabled is not set, check qgroup and update quotaEnabled as needed
-		if _, err := subvolLookupQgroup(d.home); err != nil {
-			// quota is still not enabled
-			return nil
-		}
-		d.quotaEnabled = true
+		return nil
 	}
 	}
 
 
 	dir, err := openDir(d.home)
 	dir, err := openDir(d.home)
@@ -392,6 +415,38 @@ func subvolLimitQgroup(path string, size uint64) error {
 	return nil
 	return nil
 }
 }
 
 
+// subvolQgroupStatus performs a BTRFS_IOC_TREE_SEARCH on the root path
+// with search key of BTRFS_QGROUP_STATUS_KEY.
+// In case qgroup is enabled, the retuned key type will match BTRFS_QGROUP_STATUS_KEY.
+// For more details please see https://github.com/kdave/btrfs-progs/blob/v4.9/qgroup.c#L1035
+func subvolQgroupStatus(path string) error {
+	dir, err := openDir(path)
+	if err != nil {
+		return err
+	}
+	defer closeDir(dir)
+
+	var args C.struct_btrfs_ioctl_search_args
+	args.key.tree_id = C.BTRFS_QUOTA_TREE_OBJECTID
+	args.key.min_type = C.BTRFS_QGROUP_STATUS_KEY
+	args.key.max_type = C.BTRFS_QGROUP_STATUS_KEY
+	args.key.max_objectid = C.__u64(math.MaxUint64)
+	args.key.max_offset = C.__u64(math.MaxUint64)
+	args.key.max_transid = C.__u64(math.MaxUint64)
+	args.key.nr_items = 4096
+
+	_, _, errno := syscall.Syscall(syscall.SYS_IOCTL, getDirFd(dir), C.BTRFS_IOC_TREE_SEARCH,
+		uintptr(unsafe.Pointer(&args)))
+	if errno != 0 {
+		return fmt.Errorf("Failed to search qgroup for %s: %v", path, errno.Error())
+	}
+	sh := (*C.struct_btrfs_ioctl_search_header)(unsafe.Pointer(&args.buf))
+	if sh._type != C.BTRFS_QGROUP_STATUS_KEY {
+		return fmt.Errorf("Invalid qgroup search header type for %s: %v", path, sh._type)
+	}
+	return nil
+}
+
 func subvolLookupQgroup(path string) (uint64, error) {
 func subvolLookupQgroup(path string) (uint64, error) {
 	dir, err := openDir(path)
 	dir, err := openDir(path)
 	if err != nil {
 	if err != nil {
@@ -533,7 +588,11 @@ func (d *Driver) Remove(id string) error {
 	if _, err := os.Stat(dir); err != nil {
 	if _, err := os.Stat(dir); err != nil {
 		return err
 		return err
 	}
 	}
-	if err := subvolDelete(d.subvolumesDir(), id); err != nil {
+
+	// Call updateQuotaStatus() to invoke status update
+	d.updateQuotaStatus()
+
+	if err := subvolDelete(d.subvolumesDir(), id, d.quotaEnabled); err != nil {
 		return err
 		return err
 	}
 	}
 	if err := system.EnsureRemoveAll(dir); err != nil {
 	if err := system.EnsureRemoveAll(dir); err != nil {