Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature: add disk quota for container metadata directory #2102

Merged
merged 1 commit into from
Aug 21, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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 @@ -1193,15 +1169,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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

rootfs will be set diskquota twice, will it be a problem? just a question

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It is not a problem...

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 @@ -167,33 +167,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
}