Skip to content

Commit

Permalink
Merge pull request #2102 from rudyfly/diskquota
Browse files Browse the repository at this point in the history
feature: add disk quota for container metadata directory
  • Loading branch information
HusterWan authored Aug 21, 2018
2 parents 231613a + 93622fa commit c4fd9bb
Show file tree
Hide file tree
Showing 4 changed files with 141 additions and 38 deletions.
57 changes: 27 additions & 30 deletions daemon/mgr/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -355,33 +355,9 @@ func (mgr *ContainerManager) Create(ctx context.Context, name string, config *ty
// after create options passed to containerd.
mgr.setBaseFS(ctx, container, id)

if err := mgr.Mount(ctx, container); err != nil {
return nil, errors.Wrapf(err, "failed to mount container: (%s) rootfs: (%s)", id, container.MountFS)
}

// parse volume config
if err := mgr.generateMountPoints(ctx, container); err != nil {
if err = mgr.Unmount(ctx, container); err != nil {
err = errors.Wrapf(err, "failed to umount container: (%s) rootfs: (%s)", id, container.MountFS)
}
return nil, errors.Wrap(err, "failed to parse volume argument")
}

// set mount point disk quota
if err := mgr.setMountPointDiskQuota(ctx, container); err != nil {
if err = mgr.Unmount(ctx, container); err != nil {
err = errors.Wrapf(err, "failed to umount container: (%s) rootfs: (%s)", id, container.MountFS)
}
return nil, errors.Wrap(err, "failed to set mount point disk quota")
}

// set rootfs disk quota
if err := mgr.setRootfsQuota(ctx, container); err != nil {
logrus.Warnf("failed to set rootfs disk quota, err: %v", err)
}

if err := mgr.Unmount(ctx, container); err != nil {
return nil, errors.Wrapf(err, "failed to umount container: (%s) rootfs: (%s)", id, container.MountFS)
// init container storage module, such as: set volumes, set diskquota, set /etc/mtab, copy image's data to volume.
if err := mgr.initContainerStorage(ctx, container); err != nil {
return nil, errors.Wrapf(err, "failed to init container storage, id: (%s)", container.ID)
}

// set network settings
Expand Down Expand Up @@ -1162,15 +1138,36 @@ func (mgr *ContainerManager) updateContainerDiskQuota(ctx context.Context, c *Co
}
// update container rootfs disk quota
// TODO: add lock for container?
rootfs := ""
if c.IsRunningOrPaused() && c.Snapshotter != nil {
basefs, ok := c.Snapshotter.Data["MergedDir"]
if !ok || basefs == "" {
return fmt.Errorf("Container is running, but MergedDir is missing")
}

if err := quota.SetRootfsDiskQuota(basefs, defaultQuota, qid); err != nil {
return errors.Wrapf(err, "failed to set container rootfs diskquota")
rootfs = basefs
} else {
if err := mgr.Mount(ctx, c); err != nil {
return errors.Wrapf(err, "failed to mount rootfs: (%s)", c.MountFS)
}
rootfs = c.MountFS

defer func() {
if err := mgr.Unmount(ctx, c); err != nil {
logrus.Errorf("failed to umount rootfs: (%s), err: (%v)", c.MountFS, err)
}
}()
}
newID, err := quota.SetRootfsDiskQuota(rootfs, defaultQuota, qid)
if err != nil {
return errors.Wrapf(err, "failed to set container rootfs diskquota")
}

// set container's metadata directory diskquota, for limit the size of container's logs
metaDir := mgr.Store.Path(c.ID)
err = quota.SetDiskQuota(metaDir, defaultQuota, newID)
if err != nil {
return errors.Wrapf(err, "failed to set container's log quota, dir: (%s), quota: (%s), quota id: (%d)",
metaDir, defaultQuota, newID)
}

return nil
Expand Down
44 changes: 43 additions & 1 deletion daemon/mgr/container_storage.go
Original file line number Diff line number Diff line change
Expand Up @@ -461,12 +461,21 @@ func (mgr *ContainerManager) setRootfsQuota(ctx context.Context, c *Container) e
return errors.Wrapf(err, "failed to change quota id: (%s) from string to int", qid)
}

err = quota.SetRootfsDiskQuota(c.MountFS, rootfsQuota, uint32(id))
// set rootfs quota
newID, err := quota.SetRootfsDiskQuota(c.MountFS, rootfsQuota, uint32(id))
if err != nil {
return errors.Wrapf(err, "failed to set rootfs quota, mountfs: (%s), quota: (%s), quota id: (%d)",
c.MountFS, rootfsQuota, id)
}

// set container's metadata directory diskquota, for limit the size of container's logs
metaDir := mgr.Store.Path(c.ID)
err = quota.SetDiskQuota(metaDir, rootfsQuota, newID)
if err != nil {
return errors.Wrapf(err, "failed to set container's log quota, dir: (%s), quota: (%s), quota id: (%d)",
metaDir, rootfsQuota, newID)
}

return nil
}

Expand Down Expand Up @@ -640,6 +649,39 @@ func (mgr *ContainerManager) Unmount(ctx context.Context, c *Container) error {
return os.RemoveAll(c.MountFS)
}

func (mgr *ContainerManager) initContainerStorage(ctx context.Context, c *Container) (err error) {
if err = mgr.Mount(ctx, c); err != nil {
return errors.Wrapf(err, "failed to mount rootfs: (%s)", c.MountFS)
}

defer func() {
if umountErr := mgr.Unmount(ctx, c); umountErr != nil {
if err != nil {
err = errors.Wrapf(err, "failed to umount rootfs: (%s), err: (%v)", c.MountFS, umountErr)
} else {
err = errors.Wrapf(umountErr, "failed to umount rootfs: (%s)", c.MountFS)
}
}
}()

// parse volume config
if err = mgr.generateMountPoints(ctx, c); err != nil {
return errors.Wrap(err, "failed to parse volume argument")
}

// set mount point disk quota
if err = mgr.setMountPointDiskQuota(ctx, c); err != nil {
return errors.Wrap(err, "failed to set mount point disk quota")
}

// set rootfs disk quota
if err = mgr.setRootfsQuota(ctx, c); err != nil {
logrus.Warnf("failed to set rootfs disk quota, err: %v", err)
}

return nil
}

func copyImageContent(source, destination string) error {
fi, err := os.Stat(source)
if err != nil {
Expand Down
14 changes: 7 additions & 7 deletions storage/quota/quota.go
Original file line number Diff line number Diff line change
Expand Up @@ -168,33 +168,33 @@ func GetDefaultQuota(quotas map[string]string) string {
}

// SetRootfsDiskQuota is to set container rootfs dir disk quota.
func SetRootfsDiskQuota(basefs, size string, quotaID uint32) error {
func SetRootfsDiskQuota(basefs, size string, quotaID uint32) (uint32, error) {
overlayMountInfo, err := getOverlayMountInfo(basefs)
if err != nil {
return fmt.Errorf("failed to get overlay mount info: %v", err)
return 0, fmt.Errorf("failed to get overlay mount info: %v", err)
}

for _, dir := range []string{overlayMountInfo.Upper, overlayMountInfo.Work} {
_, err = StartQuotaDriver(dir)
if err != nil {
return fmt.Errorf("failed to start quota driver: %v", err)
return 0, fmt.Errorf("failed to start quota driver: %v", err)
}

quotaID, err = SetSubtree(dir, quotaID)
if err != nil {
return fmt.Errorf("failed to set subtree: %v", err)
return 0, fmt.Errorf("failed to set subtree: %v", err)
}

if err := SetDiskQuota(dir, size, quotaID); err != nil {
return fmt.Errorf("failed to set disk quota: %v", err)
return 0, fmt.Errorf("failed to set disk quota: %v", err)
}

if err := setQuotaForDir(dir, quotaID); err != nil {
return fmt.Errorf("failed to set dir quota: %v", err)
return 0, fmt.Errorf("failed to set dir quota: %v", err)
}
}

return nil
return quotaID, nil
}

// setQuotaForDir sets file attribute
Expand Down
64 changes: 64 additions & 0 deletions test/cli_run_volume_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package main
import (
"encoding/json"
"fmt"
"path/filepath"
"runtime"
"strings"

Expand All @@ -12,6 +13,7 @@ import (

"github.com/go-check/check"
"github.com/gotestyourself/gotestyourself/icmd"
"github.com/pkg/errors"
)

// PouchRunVolumeSuite is the test suite for run CLI.
Expand Down Expand Up @@ -331,3 +333,65 @@ func (suite *PouchRunVolumeSuite) TestRunWithDiskQuota(c *check.C) {

c.Assert(found, check.Equals, true)
}

// TestRunWithDiskQuotaForLog tests limit the size of container's log.
func (suite *PouchRunVolumeSuite) TestRunWithDiskQuotaForLog(c *check.C) {
if !environment.IsDiskQuota() {
c.Skip("Host does not support disk quota")
}

cname := "TestRunWithDiskQuotaForLog"
command.PouchRun("run", "-d", "--disk-quota", "10m",
"--name", cname, busyboxImage, "top").Assert(c, icmd.Success)

defer DelContainerForceMultyTime(c, cname)

containerMetaDir, err := getContainerMetaDir(cname)
c.Assert(err, check.Equals, nil)

testFile := filepath.Join(containerMetaDir, "diskquota_testfile")

icmd.RunCommand("groupadd", "quota").Assert(c, icmd.Success)
defer icmd.RunCommand("groupdel", "quota").Assert(c, icmd.Success)

icmd.RunCommand("useradd", "-g", "quota", "quota").Assert(c, icmd.Success)
defer icmd.RunCommand("userdel", "quota").Assert(c, icmd.Success)

expct := icmd.Expected{
ExitCode: 1,
Err: "Disk quota exceeded",
}
err = icmd.RunCommand("dd", "if=/dev/zero", "of="+testFile, "bs=1M", "count=20", "oflag=direct").Compare(expct)
c.Assert(err, check.IsNil)
}

func getContainerMetaDir(name string) (string, error) {
ret := command.PouchRun("inspect", name)

mergedDir := ""
for _, line := range strings.Split(ret.Stdout(), "\n") {
if strings.Contains(line, "MergedDir") {
mergedDir = strings.Split(line, "\"")[3]
break
}
}

var (
graph string
cid string
)
if mergedDir == "" {
return "", errors.Errorf("failed to get container metadata directory")
}

parts := strings.Split(mergedDir, "/")
for i, part := range parts {
if part == "containerd" {
graph = "/" + filepath.Join(parts[:i]...)
cid = parts[i+4]
break
}
}

return filepath.Join(graph, "containers", cid), nil
}

0 comments on commit c4fd9bb

Please sign in to comment.