Skip to content

Commit

Permalink
Add inode support to quota
Browse files Browse the repository at this point in the history
quota for overlay also supports setting the maximum number of
inodes. OpenShift would like to be able to set this to control the
number of inodes added to an image or to a volume.

Signed-off-by: Daniel J Walsh <[email protected]>
  • Loading branch information
rhatdan committed Jul 26, 2021
1 parent 0477c8f commit 0c7d877
Show file tree
Hide file tree
Showing 5 changed files with 59 additions and 17 deletions.
5 changes: 4 additions & 1 deletion docs/containers-storage.conf.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,9 @@ The `storage.options.overlay` table supports the following options:
**ignore_chown_errors** = "false"
ignore_chown_errors can be set to allow a non privileged user running with a single UID within a user namespace to run containers. The user can pull and use any image even those with multiple uids. Note multiple UIDs will be squashed down to the default uid in the container. These images will have no separation between the users in the container. (default: false)

**inodes**=""
Maximum inodes in a read/write layer. This flag can be used to set a quota on the inodes allocated for a read/write layer of a container image.

**force_mask** = "0000|shared|private"
ForceMask specifies the permissions mask that is used for new files and
directories.
Expand Down Expand Up @@ -220,7 +223,7 @@ based file systems.
Comma separated list of default options to be used to mount container images. Suggested value "nodev". Mount options are documented in the mount(8) man page.

**size**=""
Maximum size of a container image. This flag can be used to set quota on the size of container images. (format: <number>[<unit>], where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))
Maximum size of a read/write layer. This flag can be used to set quota on the size of a read/write layer of a container image. (format: <number>[<unit>], where unit = b (bytes), k (kilobytes), m (megabytes), or g (gigabytes))

### STORAGE OPTIONS FOR VFS TABLE

Expand Down
35 changes: 30 additions & 5 deletions drivers/overlay/overlay.go
Original file line number Diff line number Diff line change
Expand Up @@ -364,12 +364,12 @@ func Init(home string, options graphdriver.Options) (graphdriver.Driver, error)
// Try to enable project quota support over xfs.
if d.quotaCtl, err = quota.NewControl(home); err == nil {
projectQuotaSupported = true
} else if opts.quota.Size > 0 {
return nil, fmt.Errorf("Storage option overlay.size not supported. Filesystem does not support Project Quota: %v", err)
} else if opts.quota.Size > 0 || opts.quota.Inodes > 0 {
return nil, fmt.Errorf("Storage options overlay.size and overlay.inodes not supported. Filesystem does not support Project Quota: %v", err)
}
} else if opts.quota.Size > 0 {
} else if opts.quota.Size > 0 || opts.quota.Inodes > 0 {
// if xfs is not the backing fs then error out if the storage-opt overlay.size is used.
return nil, fmt.Errorf("Storage option overlay.size only supported for backingFS XFS. Found %v", backingFs)
return nil, fmt.Errorf("Storage option overlay.size and overlay.inodes only supported for backingFS XFS. Found %v", backingFs)
}

logrus.Debugf("backingFs=%s, projectQuotaSupported=%v, useNativeDiff=%v, usingMetacopy=%v", backingFs, projectQuotaSupported, !d.useNaiveDiff(), d.usingMetacopy)
Expand Down Expand Up @@ -400,6 +400,13 @@ func parseOptions(options []string) (*overlayOptions, error) {
return nil, err
}
o.quota.Size = uint64(size)
case "inodes":
logrus.Debugf("overlay: inodes=%s", val)
inodes, err := strconv.ParseUint(val, 10, 64)
if err != nil {
return nil, err
}
o.quota.Inodes = uint64(inodes)
case "imagestore", "additionalimagestore":
logrus.Debugf("overlay: imagestore=%s", val)
// Additional read only image stores to use for lower paths
Expand Down Expand Up @@ -788,6 +795,13 @@ func (d *Driver) CreateReadWrite(id, parent string, opts *graphdriver.CreateOpts
opts.StorageOpt["size"] = strconv.FormatUint(d.options.quota.Size, 10)
}

if _, ok := opts.StorageOpt["inodes"]; !ok {
if opts.StorageOpt == nil {
opts.StorageOpt = map[string]string{}
}
opts.StorageOpt["inodes"] = strconv.FormatUint(d.options.quota.Inodes, 10)
}

return d.create(id, parent, opts)
}

Expand All @@ -798,6 +812,9 @@ func (d *Driver) Create(id, parent string, opts *graphdriver.CreateOpts) (retErr
if _, ok := opts.StorageOpt["size"]; ok {
return fmt.Errorf("--storage-opt size is only supported for ReadWrite Layers")
}
if _, ok := opts.StorageOpt["inodes"]; ok {
return fmt.Errorf("--storage-opt inodes is only supported for ReadWrite Layers")
}
}

return d.create(id, parent, opts)
Expand Down Expand Up @@ -854,7 +871,9 @@ func (d *Driver) create(id, parent string, opts *graphdriver.CreateOpts) (retErr
if driver.options.quota.Size > 0 {
quota.Size = driver.options.quota.Size
}

if driver.options.quota.Inodes > 0 {
quota.Inodes = driver.options.quota.Inodes
}
}
// Set container disk quota limit
// If it is set to 0, we will track the disk usage, but not enforce a limit
Expand Down Expand Up @@ -926,6 +945,12 @@ func (d *Driver) parseStorageOpt(storageOpt map[string]string, driver *Driver) e
return err
}
driver.options.quota.Size = uint64(size)
case "inodes":
inodes, err := strconv.ParseUint(val, 10, 64)
if err != nil {
return err
}
driver.options.quota.Inodes = uint64(inodes)
default:
return fmt.Errorf("Unknown option %s", key)
}
Expand Down
30 changes: 20 additions & 10 deletions drivers/quota/projectquota.go
Original file line number Diff line number Diff line change
Expand Up @@ -38,8 +38,8 @@ struct fsxattr {
#ifndef PRJQUOTA
#define PRJQUOTA 2
#endif
#ifndef XFS_PROJ_QUOTA
#define XFS_PROJ_QUOTA 2
#ifndef FS_PROJ_QUOTA
#define FS_PROJ_QUOTA 2
#endif
#ifndef Q_XSETPQLIM
#define Q_XSETPQLIM QCMD(Q_XSETQLIM, PRJQUOTA)
Expand All @@ -61,9 +61,10 @@ import (
"golang.org/x/sys/unix"
)

// Quota limit params - currently we only control blocks hard limit
// Quota limit params - currently we only control blocks hard limit and inodes
type Quota struct {
Size uint64
Size uint64
Inodes uint64
}

// Control - Context to be used by storage driver (e.g. overlay)
Expand Down Expand Up @@ -119,7 +120,8 @@ func NewControl(basePath string) (*Control, error) {
// a quota on the first available project id
//
quota := Quota{
Size: 0,
Size: 0,
Inodes: 0,
}
if err := setProjectQuota(backingFsBlockDev, minProjectID, quota); err != nil {
return nil, err
Expand Down Expand Up @@ -166,7 +168,7 @@ func (q *Control) SetQuota(targetPath string, quota Quota) error {
//
// set the quota limit for the container's project id
//
logrus.Debugf("SetQuota(%s, %d): projectID=%d", targetPath, quota.Size, projectID)
logrus.Debugf("SetQuota path=%s, size=%d, inodes=%d, projectID=%d", targetPath, quota.Size, quota.Inodes, projectID)
return setProjectQuota(q.backingFsBlockDev, projectID, quota)
}

Expand All @@ -175,11 +177,18 @@ func setProjectQuota(backingFsBlockDev string, projectID uint32, quota Quota) er
var d C.fs_disk_quota_t
d.d_version = C.FS_DQUOT_VERSION
d.d_id = C.__u32(projectID)
d.d_flags = C.XFS_PROJ_QUOTA
d.d_flags = C.FS_PROJ_QUOTA

d.d_fieldmask = C.FS_DQ_BHARD | C.FS_DQ_BSOFT
d.d_blk_hardlimit = C.__u64(quota.Size / 512)
d.d_blk_softlimit = d.d_blk_hardlimit
if quota.Size > 0 {
d.d_fieldmask = C.FS_DQ_BHARD | C.FS_DQ_BSOFT
d.d_blk_hardlimit = C.__u64(quota.Size / 512)
d.d_blk_softlimit = d.d_blk_hardlimit
}
if quota.Inodes > 0 {
d.d_fieldmask = C.FS_DQ_IHARD | C.FS_DQ_ISOFT
d.d_ino_hardlimit = C.__u64(quota.Inodes)
d.d_ino_softlimit = d.d_ino_hardlimit
}

var cs = C.CString(backingFsBlockDev)
defer C.free(unsafe.Pointer(cs))
Expand All @@ -202,6 +211,7 @@ func (q *Control) GetQuota(targetPath string, quota *Quota) error {
return err
}
quota.Size = uint64(d.d_blk_hardlimit) * 512
quota.Inodes = uint64(d.d_ino_hardlimit)
return nil
}

Expand Down
3 changes: 2 additions & 1 deletion drivers/quota/projectquota_unsupported.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import (

// Quota limit params - currently we only control blocks hard limit
type Quota struct {
Size uint64
Size uint64
Inodes uint64
}

// Control - Context to be used by storage driver (e.g. overlay)
Expand Down
3 changes: 3 additions & 0 deletions storage.conf
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,9 @@ additionalimagestores = [
# and vfs drivers.
#ignore_chown_errors = "false"

# Inodes is used to set a maximum inodes of the container image.
# inodes = ""

# Path to an helper program to use for mounting the file system instead of mounting it
# directly.
#mount_program = "/usr/bin/fuse-overlayfs"
Expand Down

0 comments on commit 0c7d877

Please sign in to comment.