summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDaniel J Walsh <dwalsh@redhat.com>2021-08-02 10:19:58 -0400
committerDaniel J Walsh <dwalsh@redhat.com>2021-08-02 10:32:45 -0400
commitc0952c73341321b309174a4e83e2e74d509b98a8 (patch)
tree3fb791087398345300156da8fb3f42664422f605
parent0e2a7be0ec825449a1b95bd5df13e2519c67dcb4 (diff)
downloadpodman-c0952c73341321b309174a4e83e2e74d509b98a8.tar.gz
podman-c0952c73341321b309174a4e83e2e74d509b98a8.tar.bz2
podman-c0952c73341321b309174a4e83e2e74d509b98a8.zip
Support size and inode options on builtin volumes
[NO TESTS NEEDED] Since it is difficult to setup xfs quota Fixes: https://bugzilla.redhat.com/show_bug.cgi?id=1982164 Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
-rw-r--r--docs/source/markdown/podman-volume-create.1.md44
-rw-r--r--libpod/options.go26
-rw-r--r--libpod/runtime_volume_linux.go23
-rw-r--r--libpod/volume.go4
-rw-r--r--libpod/volume_internal.go3
-rw-r--r--libpod/volume_internal_linux.go2
-rw-r--r--pkg/domain/infra/abi/parse/parse.go21
7 files changed, 114 insertions, 9 deletions
diff --git a/docs/source/markdown/podman-volume-create.1.md b/docs/source/markdown/podman-volume-create.1.md
index a06411000..9bf5a3d81 100644
--- a/docs/source/markdown/podman-volume-create.1.md
+++ b/docs/source/markdown/podman-volume-create.1.md
@@ -17,7 +17,7 @@ driver options can be set using the **--opt** flag.
#### **--driver**=*driver*
-Specify the volume driver name (default **local**). Setting this to a value other than **local** Podman will attempt to create the volume using a volume plugin with the given name. Such plugins must be defined in the **volume_plugins** section of the **containers.conf**(5) configuration file.
+Specify the volume driver name (default **local**). Setting this to a value other than **local** Podman attempts to create the volume using a volume plugin with the given name. Such plugins must be defined in the **volume_plugins** section of the **containers.conf**(5) configuration file.
#### **--help**
@@ -34,10 +34,14 @@ For the default driver, **local**, this allows a volume to be configured to moun
For the `local` driver the following options are supported: `type`, `device`, and `o`.
The `type` option sets the type of the filesystem to be mounted, and is equivalent to the `-t` flag to **mount(8)**.
The `device` option sets the device to be mounted, and is equivalent to the `device` argument to **mount(8)**.
-The `o` option sets options for the mount, and is equivalent to the `-o` flag to **mount(8)** with two exceptions.
-The `o` option supports `uid` and `gid` options to set the UID and GID of the created volume that are not normally supported by **mount(8)**.
-Using volume options with the **local** driver requires root privileges.
-When not using the **local** driver, the given options will be passed directly to the volume plugin. In this case, supported options will be dictated by the plugin in question, not Podman.
+
+The `o` option sets options for the mount, and is equivalent to the `-o` flag to **mount(8)** with these exceptions:
+
+ - The `o` option supports `uid` and `gid` options to set the UID and GID of the created volume that are not normally supported by **mount(8)**.
+ - The `o` option supports the `size` option to set the maximum size of the created volume and the `inodes` option to set the maximum number of inodes for the volume. Currently these flags are only supported on "xfs" file system mounted with the `prjquota` flag described in the **xfs_quota(8)** man page.
+ - Using volume options other then the UID/GID options with the **local** driver requires root privileges.
+
+When not using the **local** driver, the given options are passed directly to the volume plugin. In this case, supported options are dictated by the plugin in question, not Podman.
## EXAMPLES
@@ -53,8 +57,36 @@ $ podman volume create --label foo=bar myvol
# podman volume create --opt device=tmpfs --opt type=tmpfs --opt o=uid=1000,gid=1000 testvol
```
+## QUOTAS
+
+podman volume create uses `XFS project quota controls` for controlling the size and the number of inodes of builtin volumes. The directory used to store the volumes must be an`XFS` file system and be mounted with the `pquota` option.
+
+Example /etc/fstab entry:
+```
+/dev/podman/podman-var /var xfs defaults,x-systemd.device-timeout=0,pquota 1 2
+```
+
+Podman generates project ids for each builtin volume, but these project ids need to be unique for the XFS file system. These project ids by default are generated randomly, with a potential for overlap with other quotas on the same file
+system.
+
+The xfs_quota tool can be used to assign a project id to the storage driver directory, e.g.:
+
+```
+echo 100000:/var/lib/containers/storage/overlay >> /etc/projects
+echo 200000:/var/lib/containers/storage/volumes >> /etc/projects
+echo storage:100000 >> /etc/projid
+echo volumes:200000 >> /etc/projid
+xfs_quota -x -c 'project -s storage volumes' /<xfs mount point>
+```
+
+In the example above we are configuring the overlay storage driver for newly
+created containers as well as volumes to use project ids with a **start offset**.
+All containers will be assigned larger project ids (e.g. >= 100000).
+All volume assigned project ids larger project ids starting with 200000.
+This prevents xfs_quota management conflicts with containers/storage.
+
## SEE ALSO
-**podman-volume**(1), **mount**(8), **containers.conf**(5)
+**podman-volume**(1), **mount**(8), **containers.conf**(5), **xfs_quota**(8), `xfs_quota(8)`, `projects(5)`, `projid(5)`
## HISTORY
January 2020, updated with information on volume plugins by Matthew Heon <mheon@redhat.com>
diff --git a/libpod/options.go b/libpod/options.go
index 17a36008d..b021b9f50 100644
--- a/libpod/options.go
+++ b/libpod/options.go
@@ -1645,6 +1645,32 @@ func WithVolumeUID(uid int) VolumeCreateOption {
}
}
+// WithVolumeSize sets the maximum size of the volume
+func WithVolumeSize(size uint64) VolumeCreateOption {
+ return func(volume *Volume) error {
+ if volume.valid {
+ return define.ErrVolumeFinalized
+ }
+
+ volume.config.Size = size
+
+ return nil
+ }
+}
+
+// WithVolumeInodes sets the maximum inodes of the volume
+func WithVolumeInodes(inodes uint64) VolumeCreateOption {
+ return func(volume *Volume) error {
+ if volume.valid {
+ return define.ErrVolumeFinalized
+ }
+
+ volume.config.Inodes = inodes
+
+ return nil
+ }
+}
+
// WithVolumeGID sets the GID that the volume will be created as.
func WithVolumeGID(gid int) VolumeCreateOption {
return func(volume *Volume) error {
diff --git a/libpod/runtime_volume_linux.go b/libpod/runtime_volume_linux.go
index 3d5bc8bb2..40df98d7c 100644
--- a/libpod/runtime_volume_linux.go
+++ b/libpod/runtime_volume_linux.go
@@ -12,6 +12,7 @@ import (
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/libpod/events"
volplugin "github.com/containers/podman/v3/libpod/plugin"
+ "github.com/containers/storage/drivers/quota"
"github.com/containers/storage/pkg/stringid"
pluginapi "github.com/docker/go-plugins-helpers/volume"
"github.com/pkg/errors"
@@ -68,7 +69,7 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption)
// Validate options
for key := range volume.config.Options {
switch key {
- case "device", "o", "type", "UID", "GID":
+ case "device", "o", "type", "UID", "GID", "SIZE", "INODES":
// Do nothing, valid keys
default:
return nil, errors.Wrapf(define.ErrInvalidArg, "invalid mount option %s for driver 'local'", key)
@@ -106,6 +107,26 @@ func (r *Runtime) newVolume(ctx context.Context, options ...VolumeCreateOption)
if err := LabelVolumePath(fullVolPath); err != nil {
return nil, err
}
+ projectQuotaSupported := false
+
+ q, err := quota.NewControl(r.config.Engine.VolumePath)
+ if err == nil {
+ projectQuotaSupported = true
+ }
+ quota := quota.Quota{}
+ if volume.config.Size > 0 || volume.config.Inodes > 0 {
+ if !projectQuotaSupported {
+ return nil, errors.New("Volume options size and inodes not supported. Filesystem does not support Project Quota")
+ }
+ quota.Size = volume.config.Size
+ quota.Inodes = volume.config.Inodes
+ }
+ if projectQuotaSupported {
+ if err := q.SetQuota(fullVolPath, quota); err != nil {
+ return nil, errors.Wrapf(err, "failed to set size quota size=%d inodes=%d for volume directory %q", volume.config.Size, volume.config.Inodes, fullVolPath)
+ }
+ }
+
volume.config.MountPoint = fullVolPath
}
diff --git a/libpod/volume.go b/libpod/volume.go
index 506c45b5a..8f3dc4fcc 100644
--- a/libpod/volume.go
+++ b/libpod/volume.go
@@ -49,6 +49,10 @@ type VolumeConfig struct {
UID int `json:"uid"`
// GID the volume will be created as.
GID int `json:"gid"`
+ // Size maximum of the volume.
+ Size uint64 `json:"size"`
+ // Inodes maximum of the volume.
+ Inodes uint64 `json:"inodes"`
}
// VolumeState holds the volume's mutable state.
diff --git a/libpod/volume_internal.go b/libpod/volume_internal.go
index 19008a253..f69f1c044 100644
--- a/libpod/volume_internal.go
+++ b/libpod/volume_internal.go
@@ -49,6 +49,9 @@ func (v *Volume) needsMount() bool {
if _, ok := v.config.Options["GID"]; ok {
index++
}
+ if _, ok := v.config.Options["SIZE"]; ok {
+ index++
+ }
// when uid or gid is set there is also the "o" option
// set so we have to ignore this one as well
if index > 0 {
diff --git a/libpod/volume_internal_linux.go b/libpod/volume_internal_linux.go
index 92391de1d..45cd22385 100644
--- a/libpod/volume_internal_linux.go
+++ b/libpod/volume_internal_linux.go
@@ -104,7 +104,7 @@ func (v *Volume) mount() error {
logrus.Debugf("Running mount command: %s %s", mountPath, strings.Join(mountArgs, " "))
if output, err := mountCmd.CombinedOutput(); err != nil {
- logrus.Debugf("Mount failed with %v", err)
+ logrus.Debugf("Mount %v failed with %v", mountCmd, err)
return errors.Wrapf(errors.Errorf(string(output)), "error mounting volume %s", v.Name())
}
diff --git a/pkg/domain/infra/abi/parse/parse.go b/pkg/domain/infra/abi/parse/parse.go
index 56c747711..5a75e1216 100644
--- a/pkg/domain/infra/abi/parse/parse.go
+++ b/pkg/domain/infra/abi/parse/parse.go
@@ -6,12 +6,13 @@ import (
"github.com/containers/podman/v3/libpod"
"github.com/containers/podman/v3/libpod/define"
+ units "github.com/docker/go-units"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// Handle volume options from CLI.
-// Parse "o" option to find UID, GID.
+// Parse "o" option to find UID, GID, Size.
func VolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error) {
libpodOptions := []libpod.VolumeCreateOption{}
volumeOptions := make(map[string]string)
@@ -28,6 +29,24 @@ func VolumeOptions(opts map[string]string) ([]libpod.VolumeCreateOption, error)
// "opt=value"
splitO := strings.SplitN(o, "=", 2)
switch strings.ToLower(splitO[0]) {
+ case "size":
+ size, err := units.FromHumanSize(splitO[1])
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot convert size %s to integer", splitO[1])
+ }
+ libpodOptions = append(libpodOptions, libpod.WithVolumeSize(uint64(size)))
+ finalVal = append(finalVal, o)
+ // set option "SIZE": "$size"
+ volumeOptions["SIZE"] = splitO[1]
+ case "inodes":
+ inodes, err := strconv.ParseUint(splitO[1], 10, 64)
+ if err != nil {
+ return nil, errors.Wrapf(err, "cannot convert inodes %s to integer", splitO[1])
+ }
+ libpodOptions = append(libpodOptions, libpod.WithVolumeInodes(uint64(inodes)))
+ finalVal = append(finalVal, o)
+ // set option "INODES": "$size"
+ volumeOptions["INODES"] = splitO[1]
case "uid":
if len(splitO) != 2 {
return nil, errors.Wrapf(define.ErrInvalidArg, "uid option must provide a UID")