From 5f757cbfbe183c0f8ceeb8a0b9eb494207381df4 Mon Sep 17 00:00:00 2001 From: Charlie Doern Date: Tue, 9 Aug 2022 09:25:03 -0400 Subject: [PATCH] implement podman update MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit podman update allows users to change the cgroup configuration of an existing container using the already defined resource limits flags from podman create/run. The supported flags in crun are: –memory –cpus –cpuset-cpus –cpuset-mems –memory-swap –memory-reservation –cpu-shares –cpu-quota –cpu-period –blkio-weight –cpu-rt-period –cpu-rt-runtime -device-read-bps -device-write-bps -device-read-iops -device-write-iops -memory-swappiness -blkio-weight-device resolves #15067 Signed-off-by: Charlie Doern --- cmd/podman/common/create.go | 52 +++--- cmd/podman/containers/clone.go | 2 +- cmd/podman/containers/create.go | 2 +- cmd/podman/containers/run.go | 2 +- cmd/podman/containers/update.go | 83 +++++++++ cmd/podman/pods/clone.go | 2 +- cmd/podman/pods/create.go | 2 +- docs/source/markdown/.gitignore | 1 + .../markdown/links/podman-container-update.1 | 1 + docs/source/markdown/options/cpus.md | 9 + .../markdown/options/device-read-bps.md | 3 + .../markdown/options/device-read-iops.md | 3 + .../markdown/options/device-write-bps.md | 3 + .../markdown/options/device-write-iops.md | 3 + .../markdown/options/memory-reservation.md | 9 + docs/source/markdown/options/memory-swap.md | 10 ++ docs/source/markdown/options/memory.md | 9 + .../markdown/podman-container-clone.1.md.in | 8 +- docs/source/markdown/podman-container.1.md | 1 + docs/source/markdown/podman-create.1.md.in | 61 +------ docs/source/markdown/podman-pod-clone.1.md.in | 8 +- .../source/markdown/podman-pod-create.1.md.in | 8 +- docs/source/markdown/podman-run.1.md.in | 16 +- docs/source/markdown/podman-update.1.md.in | 59 ++++++ docs/source/markdown/podman.1.md | 1 + libpod/container_api.go | 10 ++ libpod/container_internal.go | 9 + libpod/oci.go | 4 + libpod/oci_conmon_linux.go | 46 +++++ libpod/oci_missing.go | 6 + pkg/domain/entities/containers.go | 6 + pkg/domain/entities/engine_container.go | 1 + pkg/domain/entities/pods.go | 9 + pkg/domain/infra/abi/containers.go | 20 +++ pkg/domain/infra/tunnel/containers.go | 5 + pkg/specgenutil/specgen.go | 85 +++++---- test/e2e/update_test.go | 169 ++++++++++++++++++ 37 files changed, 578 insertions(+), 150 deletions(-) create mode 100644 cmd/podman/containers/update.go create mode 100644 docs/source/markdown/links/podman-container-update.1 create mode 100644 docs/source/markdown/options/cpus.md create mode 100644 docs/source/markdown/options/device-read-bps.md create mode 100644 docs/source/markdown/options/device-read-iops.md create mode 100644 docs/source/markdown/options/device-write-bps.md create mode 100644 docs/source/markdown/options/device-write-iops.md create mode 100644 docs/source/markdown/options/memory-reservation.md create mode 100644 docs/source/markdown/options/memory-swap.md create mode 100644 docs/source/markdown/options/memory.md create mode 100644 docs/source/markdown/podman-update.1.md.in create mode 100644 test/e2e/update_test.go diff --git a/cmd/podman/common/create.go b/cmd/podman/common/create.go index 00873b95b5cb..611d8744072f 100644 --- a/cmd/podman/common/create.go +++ b/cmd/podman/common/create.go @@ -28,10 +28,10 @@ func ContainerToPodOptions(containerCreate *entities.ContainerCreateOptions, pod } // DefineCreateFlags declares and instantiates the container create flags -func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, isInfra bool, clone bool) { +func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, mode entities.ContainerMode) { createFlags := cmd.Flags() - if !isInfra && !clone { // regular create flags + if mode == entities.CreateMode { // regular create flags annotationFlagName := "annotation" createFlags.StringSliceVar( &cf.Annotation, @@ -103,22 +103,6 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(deviceCgroupRuleFlagName, completion.AutocompleteNone) - deviceReadIopsFlagName := "device-read-iops" - createFlags.StringSliceVar( - &cf.DeviceReadIOPs, - deviceReadIopsFlagName, []string{}, - "Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000)", - ) - _ = cmd.RegisterFlagCompletionFunc(deviceReadIopsFlagName, completion.AutocompleteDefault) - - deviceWriteIopsFlagName := "device-write-iops" - createFlags.StringSliceVar( - &cf.DeviceWriteIOPs, - deviceWriteIopsFlagName, []string{}, - "Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)", - ) - _ = cmd.RegisterFlagCompletionFunc(deviceWriteIopsFlagName, completion.AutocompleteDefault) - createFlags.Bool( "disable-content-trust", false, "This is a Docker specific option and is a NOOP", @@ -589,7 +573,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, `If a container with the same name exists, replace it`, ) } - if isInfra || (!clone && !isInfra) { // infra container flags, create should also pick these up + if mode == entities.InfraMode || (mode == entities.CreateMode) { // infra container flags, create should also pick these up shmSizeFlagName := "shm-size" createFlags.String( shmSizeFlagName, shmSize(), @@ -669,7 +653,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(cgroupParentFlagName, completion.AutocompleteDefault) var conmonPidfileFlagName string - if !isInfra { + if mode == entities.CreateMode { conmonPidfileFlagName = "conmon-pidfile" } else { conmonPidfileFlagName = "infra-conmon-pidfile" @@ -682,7 +666,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, _ = cmd.RegisterFlagCompletionFunc(conmonPidfileFlagName, completion.AutocompleteDefault) var entrypointFlagName string - if !isInfra { + if mode == entities.CreateMode { entrypointFlagName = "entrypoint" } else { entrypointFlagName = "infra-command" @@ -717,7 +701,7 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(labelFileFlagName, completion.AutocompleteDefault) - if isInfra { + if mode == entities.InfraMode { nameFlagName := "infra-name" createFlags.StringVar( &cf.Name, @@ -767,7 +751,8 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(volumesFromFlagName, AutocompleteContainers) } - if clone || !isInfra { // clone and create only flags, we need this level of separation so clone does not pick up all of the flags + + if mode == entities.CloneMode || mode == entities.CreateMode { nameFlagName := "name" createFlags.StringVar( &cf.Name, @@ -783,7 +768,8 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, "Run container in an existing pod", ) _ = cmd.RegisterFlagCompletionFunc(podFlagName, AutocompletePods) - + } + if mode != entities.InfraMode { // clone create and update only flags, we need this level of separation so clone does not pick up all of the flags cpuPeriodFlagName := "cpu-period" createFlags.Uint64Var( &cf.CPUPeriod, @@ -832,8 +818,24 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, ) _ = cmd.RegisterFlagCompletionFunc(memorySwappinessFlagName, completion.AutocompleteNone) } - // anyone can use these + if mode == entities.CreateMode || mode == entities.UpdateMode { + deviceReadIopsFlagName := "device-read-iops" + createFlags.StringSliceVar( + &cf.DeviceReadIOPs, + deviceReadIopsFlagName, []string{}, + "Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000)", + ) + _ = cmd.RegisterFlagCompletionFunc(deviceReadIopsFlagName, completion.AutocompleteDefault) + deviceWriteIopsFlagName := "device-write-iops" + createFlags.StringSliceVar( + &cf.DeviceWriteIOPs, + deviceWriteIopsFlagName, []string{}, + "Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)", + ) + _ = cmd.RegisterFlagCompletionFunc(deviceWriteIopsFlagName, completion.AutocompleteDefault) + } + // anyone can use these cpusFlagName := "cpus" createFlags.Float64Var( &cf.CPUS, diff --git a/cmd/podman/containers/clone.go b/cmd/podman/containers/clone.go index 9881a791ca9b..62bc90a2cfe1 100644 --- a/cmd/podman/containers/clone.go +++ b/cmd/podman/containers/clone.go @@ -41,7 +41,7 @@ func cloneFlags(cmd *cobra.Command) { flags.BoolVarP(&ctrClone.Force, forceFlagName, "f", false, "force the existing container to be destroyed") common.DefineCreateDefaults(&ctrClone.CreateOpts) - common.DefineCreateFlags(cmd, &ctrClone.CreateOpts, false, true) + common.DefineCreateFlags(cmd, &ctrClone.CreateOpts, entities.CloneMode) } func init() { registry.Commands = append(registry.Commands, registry.CliCommand{ diff --git a/cmd/podman/containers/create.go b/cmd/podman/containers/create.go index 455127fd79f2..b854ff4b28fb 100644 --- a/cmd/podman/containers/create.go +++ b/cmd/podman/containers/create.go @@ -72,7 +72,7 @@ func createFlags(cmd *cobra.Command) { flags.SetInterspersed(false) common.DefineCreateDefaults(&cliVals) - common.DefineCreateFlags(cmd, &cliVals, false, false) + common.DefineCreateFlags(cmd, &cliVals, entities.CreateMode) common.DefineNetFlags(cmd) flags.SetNormalizeFunc(utils.AliasFlags) diff --git a/cmd/podman/containers/run.go b/cmd/podman/containers/run.go index ef13ea95ea06..f66d4d4d3450 100644 --- a/cmd/podman/containers/run.go +++ b/cmd/podman/containers/run.go @@ -61,7 +61,7 @@ func runFlags(cmd *cobra.Command) { flags.SetInterspersed(false) common.DefineCreateDefaults(&cliVals) - common.DefineCreateFlags(cmd, &cliVals, false, false) + common.DefineCreateFlags(cmd, &cliVals, entities.CreateMode) common.DefineNetFlags(cmd) flags.SetNormalizeFunc(utils.AliasFlags) diff --git a/cmd/podman/containers/update.go b/cmd/podman/containers/update.go new file mode 100644 index 000000000000..33aa25f8e5eb --- /dev/null +++ b/cmd/podman/containers/update.go @@ -0,0 +1,83 @@ +package containers + +import ( + "context" + "fmt" + + "github.com/containers/podman/v4/cmd/podman/common" + "github.com/containers/podman/v4/cmd/podman/registry" + "github.com/containers/podman/v4/pkg/domain/entities" + "github.com/containers/podman/v4/pkg/specgen" + "github.com/containers/podman/v4/pkg/specgenutil" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/spf13/cobra" +) + +var ( + updateDescription = `Updates the cgroup configuration of a given container` + + updateCommand = &cobra.Command{ + Use: "update [options] CONTAINER", + Short: "update an existing container", + Long: updateDescription, + RunE: update, + Args: cobra.ExactArgs(1), + ValidArgsFunction: common.AutocompleteContainers, + Example: `podman update --cpus=5 foobar_container`, + } + + containerUpdateCommand = &cobra.Command{ + Args: updateCommand.Args, + Use: updateCommand.Use, + Short: updateCommand.Short, + Long: updateCommand.Long, + RunE: updateCommand.RunE, + ValidArgsFunction: updateCommand.ValidArgsFunction, + Example: `podman container update --cpus=5 foobar_container`, + } +) +var ( + updateOpts entities.ContainerCreateOptions +) + +func updateFlags(cmd *cobra.Command) { + common.DefineCreateDefaults(&updateOpts) + common.DefineCreateFlags(cmd, &updateOpts, entities.UpdateMode) +} + +func init() { + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: updateCommand, + }) + updateFlags(updateCommand) + + registry.Commands = append(registry.Commands, registry.CliCommand{ + Command: containerUpdateCommand, + Parent: containerCmd, + }) + updateFlags(containerUpdateCommand) +} + +func update(cmd *cobra.Command, args []string) error { + var err error + // use a specgen since this is the easiest way to hold resource info + s := &specgen.SpecGenerator{} + s.ResourceLimits = &specs.LinuxResources{} + + // we need to pass the whole specgen since throttle devices are parsed later due to cross compat. + s.ResourceLimits, err = specgenutil.GetResources(s, &updateOpts) + if err != nil { + return err + } + + opts := &entities.ContainerUpdateOptions{ + NameOrID: args[0], + Specgen: s, + } + rep, err := registry.ContainerEngine().ContainerUpdate(context.Background(), opts) + if err != nil { + return err + } + fmt.Println(rep) + return nil +} diff --git a/cmd/podman/pods/clone.go b/cmd/podman/pods/clone.go index 9558c6aedaf8..bf7d10c4473c 100644 --- a/cmd/podman/pods/clone.go +++ b/cmd/podman/pods/clone.go @@ -44,7 +44,7 @@ func cloneFlags(cmd *cobra.Command) { _ = podCloneCommand.RegisterFlagCompletionFunc(nameFlagName, completion.AutocompleteNone) common.DefineCreateDefaults(&podClone.InfraOptions) - common.DefineCreateFlags(cmd, &podClone.InfraOptions, true, false) + common.DefineCreateFlags(cmd, &podClone.InfraOptions, entities.InfraMode) podClone.InfraOptions.MemorySwappiness = -1 // this is not implemented for pods yet, need to set -1 default manually diff --git a/cmd/podman/pods/create.go b/cmd/podman/pods/create.go index 4f1f66ad6fd4..d30f4782a312 100644 --- a/cmd/podman/pods/create.go +++ b/cmd/podman/pods/create.go @@ -65,7 +65,7 @@ func init() { flags := createCommand.Flags() flags.SetInterspersed(false) common.DefineCreateDefaults(&infraOptions) - common.DefineCreateFlags(createCommand, &infraOptions, true, false) + common.DefineCreateFlags(createCommand, &infraOptions, entities.InfraMode) common.DefineNetFlags(createCommand) flags.BoolVar(&createOptions.Infra, "infra", true, "Create an infra container associated with the pod to share namespaces with") diff --git a/docs/source/markdown/.gitignore b/docs/source/markdown/.gitignore index 6689b5b718d5..10f028afd22d 100644 --- a/docs/source/markdown/.gitignore +++ b/docs/source/markdown/.gitignore @@ -6,3 +6,4 @@ podman-pod-clone.1.md podman-pod-create.1.md podman-pull.1.md podman-run.1.md +podman-update.1.md diff --git a/docs/source/markdown/links/podman-container-update.1 b/docs/source/markdown/links/podman-container-update.1 new file mode 100644 index 000000000000..e903b5c06ba4 --- /dev/null +++ b/docs/source/markdown/links/podman-container-update.1 @@ -0,0 +1 @@ +.so man1/podman-update.1 diff --git a/docs/source/markdown/options/cpus.md b/docs/source/markdown/options/cpus.md new file mode 100644 index 000000000000..e5af4c3736ca --- /dev/null +++ b/docs/source/markdown/options/cpus.md @@ -0,0 +1,9 @@ +#### **--cpus**=*number* + +Number of CPUs. The default is *0.0* which means no limit. This is shorthand +for **--cpu-period** and **--cpu-quota**, so you may only set either +**--cpus** or **--cpu-period** and **--cpu-quota**. + +On some systems, changing the CPU limits may not be allowed for non-root +users. For more details, see +https://github.com/containers/podman/blob/main/troubleshooting.md#26-running-containers-with-cpu-limits-fails-with-a-permissions-error diff --git a/docs/source/markdown/options/device-read-bps.md b/docs/source/markdown/options/device-read-bps.md new file mode 100644 index 000000000000..78a2c869ae48 --- /dev/null +++ b/docs/source/markdown/options/device-read-bps.md @@ -0,0 +1,3 @@ +#### **--device-read-bps**=*path* + +Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb). diff --git a/docs/source/markdown/options/device-read-iops.md b/docs/source/markdown/options/device-read-iops.md new file mode 100644 index 000000000000..589fdea834c2 --- /dev/null +++ b/docs/source/markdown/options/device-read-iops.md @@ -0,0 +1,3 @@ +#### **--device-read-iops**=*path:rate* + +Limit read rate (in IO operations per second) from a device (e.g. **--device-read-iops=/dev/sda:1000**). diff --git a/docs/source/markdown/options/device-write-bps.md b/docs/source/markdown/options/device-write-bps.md new file mode 100644 index 000000000000..4ab969a8a613 --- /dev/null +++ b/docs/source/markdown/options/device-write-bps.md @@ -0,0 +1,3 @@ +#### **--device-write-bps**=*path:rate* + +Limit write rate (in bytes per second) to a device (e.g. **--device-write-bps=/dev/sda:1mb**). diff --git a/docs/source/markdown/options/device-write-iops.md b/docs/source/markdown/options/device-write-iops.md new file mode 100644 index 000000000000..be0c0acff71b --- /dev/null +++ b/docs/source/markdown/options/device-write-iops.md @@ -0,0 +1,3 @@ +#### **--device-write-iops**=*path* + +Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000) diff --git a/docs/source/markdown/options/memory-reservation.md b/docs/source/markdown/options/memory-reservation.md new file mode 100644 index 000000000000..9de0f1dbcdc6 --- /dev/null +++ b/docs/source/markdown/options/memory-reservation.md @@ -0,0 +1,9 @@ +#### **--memory-reservation**=*limit* + +Memory soft limit (format: `[]`, where unit = b (bytes), k (kibibytes), m (mebibytes), or g (gibibytes)) + +After setting memory reservation, when the system detects memory contention +or low memory, containers are forced to restrict their consumption to their +reservation. So you should always set the value below **--memory**, otherwise the +hard limit will take precedence. By default, memory reservation will be the same +as memory limit. diff --git a/docs/source/markdown/options/memory-swap.md b/docs/source/markdown/options/memory-swap.md new file mode 100644 index 000000000000..46abaa01a20e --- /dev/null +++ b/docs/source/markdown/options/memory-swap.md @@ -0,0 +1,10 @@ +#### **--memory-swap**=*limit* + +A limit value equal to memory plus swap. Must be used with the **-m** +(**--memory**) flag. The swap `LIMIT` should always be larger than **-m** +(**--memory**) value. By default, the swap `LIMIT` will be set to double +the value of --memory. + +The format of `LIMIT` is `[]`. Unit can be `b` (bytes), +`k` (kibibytes), `m` (mebibytes), or `g` (gibibytes). If you don't specify a +unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap. diff --git a/docs/source/markdown/options/memory.md b/docs/source/markdown/options/memory.md new file mode 100644 index 000000000000..e97697532e8c --- /dev/null +++ b/docs/source/markdown/options/memory.md @@ -0,0 +1,9 @@ +#### **--memory**, **-m**=*limit* + +Memory limit (format: `[]`, where unit = b (bytes), k (kibibytes), m (mebibytes), or g (gibibytes)) + +Allows you to constrain the memory available to a container. If the host +supports swap memory, then the **-m** memory setting can be larger than physical +RAM. If a limit of 0 is specified (not using **-m**), the container's memory is +not limited. The actual limit may be rounded up to a multiple of the operating +system's page size (the value would be very large, that's millions of trillions). diff --git a/docs/source/markdown/podman-container-clone.1.md.in b/docs/source/markdown/podman-container-clone.1.md.in index cf760d7a2b23..f720622ae8b1 100644 --- a/docs/source/markdown/podman-container-clone.1.md.in +++ b/docs/source/markdown/podman-container-clone.1.md.in @@ -50,13 +50,9 @@ If none are specified, the original container's CPU memory nodes are used. @@option destroy -#### **--device-read-bps**=*path* +@@option device-read-bps -Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb). - -#### **--device-write-bps**=*path* - -Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb) +@@option device-write-bps #### **--force**, **-f** diff --git a/docs/source/markdown/podman-container.1.md b/docs/source/markdown/podman-container.1.md index a66e2789d480..593cd3c422d0 100644 --- a/docs/source/markdown/podman-container.1.md +++ b/docs/source/markdown/podman-container.1.md @@ -46,6 +46,7 @@ The container command allows you to manage containers | top | [podman-top(1)](podman-top.1.md) | Display the running processes of a container. | | unmount | [podman-unmount(1)](podman-unmount.1.md) | Unmount a working container's root filesystem.(Alias unmount) | | unpause | [podman-unpause(1)](podman-unpause.1.md) | Unpause one or more containers. | +| update | [podman-update(1)](podman-update.1.md) | Updates the cgroup configuration of a given container. | | wait | [podman-wait(1)](podman-wait.1.md) | Wait on one or more containers to stop and print their exit codes. | ## SEE ALSO diff --git a/docs/source/markdown/podman-create.1.md.in b/docs/source/markdown/podman-create.1.md.in index 5bb1dceca532..21c4c05dbe72 100644 --- a/docs/source/markdown/podman-create.1.md.in +++ b/docs/source/markdown/podman-create.1.md.in @@ -96,9 +96,7 @@ environment variable. `export REGISTRY_AUTH_FILE=path` @@option blkio-weight -#### **--blkio-weight-device**=*device:weight* - -Block IO relative device weight. +@@option blkio-weight-device @@option cap-add @@ -130,15 +128,7 @@ Write the container ID to the file @@option cpu-shares -#### **--cpus**=*number* - -Number of CPUs. The default is *0.0* which means no limit. This is shorthand -for **--cpu-period** and **--cpu-quota**, so you may only set either -**--cpus** or **--cpu-period** and **--cpu-quota**. - -On some systems, changing the CPU limits may not be allowed for non-root -users. For more details, see -https://github.com/containers/podman/blob/main/troubleshooting.md#26-running-containers-with-cpu-limits-fails-with-a-permissions-error +@@option cpus @@option cpuset-cpus @@ -170,21 +160,13 @@ Add a rule to the cgroup allowed devices list. The rule is expected to be in the - major and minor: either a number, or * for all; - mode: a composition of r (read), w (write), and m (mknod(2)). -#### **--device-read-bps**=*path* - -Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb) - -#### **--device-read-iops**=*path* - -Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000) - -#### **--device-write-bps**=*path* +@@option device-read-bps -Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb) +@@option device-read-iops -#### **--device-write-iops**=*path* +@@option device-write-bps -Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000) +@@option device-write-iops #### **--disable-content-trust** @@ -365,36 +347,11 @@ This option is currently supported only by the **journald** log driver. @@option mac-address -#### **--memory**, **-m**=*limit* - -Memory limit (format: `[]`, where unit = b (bytes), k (kibibytes), m (mebibytes), or g (gibibytes)) - -Allows you to constrain the memory available to a container. If the host -supports swap memory, then the **-m** memory setting can be larger than physical -RAM. If a limit of 0 is specified (not using **-m**), the container's memory is -not limited. The actual limit may be rounded up to a multiple of the operating -system's page size (the value would be very large, that's millions of trillions). - -#### **--memory-reservation**=*limit* - -Memory soft limit (format: `[]`, where unit = b (bytes), k (kibibytes), m (mebibytes), or g (gibibytes)) - -After setting memory reservation, when the system detects memory contention -or low memory, containers are forced to restrict their consumption to their -reservation. So you should always set the value below **--memory**, otherwise the -hard limit will take precedence. By default, memory reservation will be the same -as memory limit. - -#### **--memory-swap**=*limit* +@@option memory -A limit value equal to memory plus swap. Must be used with the **-m** -(**--memory**) flag. The swap `LIMIT` should always be larger than **-m** -(**--memory**) value. By default, the swap `LIMIT` will be set to double -the value of --memory. +@@option memory-reservation -The format of `LIMIT` is `[]`. Unit can be `b` (bytes), -`k` (kibibytes), `m` (mebibytes), or `g` (gibibytes). If you don't specify a -unit, `b` is used. Set LIMIT to `-1` to enable unlimited swap. +@@option memory-swap @@option memory-swappiness diff --git a/docs/source/markdown/podman-pod-clone.1.md.in b/docs/source/markdown/podman-pod-clone.1.md.in index a5746fd84a1d..2809618c4740 100644 --- a/docs/source/markdown/podman-pod-clone.1.md.in +++ b/docs/source/markdown/podman-pod-clone.1.md.in @@ -48,13 +48,9 @@ Podman may load kernel modules required for using the specified device. The devices that Podman will load modules for when necessary are: /dev/fuse. -#### **--device-read-bps**=*path* +@@option device-read-bps -Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb). - -#### **--device-write-bps**=*path* - -Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb) +@@option device-write-bps #### **--gidmap**=*pod_gid:host_gid:amount* diff --git a/docs/source/markdown/podman-pod-create.1.md.in b/docs/source/markdown/podman-pod-create.1.md.in index 73b634548dd6..ccfdd1e523e8 100644 --- a/docs/source/markdown/podman-pod-create.1.md.in +++ b/docs/source/markdown/podman-pod-create.1.md.in @@ -65,13 +65,9 @@ Podman may load kernel modules required for using the specified device. The devices that Podman will load modules for when necessary are: /dev/fuse. -#### **--device-read-bps**=*path* +@@option device-read-bps -Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb) - -#### **--device-write-bps**=*path* - -Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb) +@@option device-write-bps #### **--dns**=*ipaddr* diff --git a/docs/source/markdown/podman-run.1.md.in b/docs/source/markdown/podman-run.1.md.in index 21ce566cee97..ebf2f96d8759 100644 --- a/docs/source/markdown/podman-run.1.md.in +++ b/docs/source/markdown/podman-run.1.md.in @@ -201,21 +201,13 @@ device. The devices that Podman will load modules when necessary are: Add a rule to the cgroup allowed devices list -#### **--device-read-bps**=*path:rate* +@@option device-read-bps -Limit read rate (in bytes per second) from a device (e.g. **--device-read-bps=/dev/sda:1mb**). +@@option device-read-iops -#### **--device-read-iops**=*path:rate* +@@option device-write-bps -Limit read rate (in IO operations per second) from a device (e.g. **--device-read-iops=/dev/sda:1000**). - -#### **--device-write-bps**=*path:rate* - -Limit write rate (in bytes per second) to a device (e.g. **--device-write-bps=/dev/sda:1mb**). - -#### **--device-write-iops**=*path:rate* - -Limit write rate (in IO operations per second) to a device (e.g. **--device-write-iops=/dev/sda:1000**). +@@option device-write-iops #### **--disable-content-trust** diff --git a/docs/source/markdown/podman-update.1.md.in b/docs/source/markdown/podman-update.1.md.in new file mode 100644 index 000000000000..3dbfe09e1387 --- /dev/null +++ b/docs/source/markdown/podman-update.1.md.in @@ -0,0 +1,59 @@ +% podman-update(1) + +## NAME +podman\-update - Updates the cgroup configuration of a given container + +## SYNOPSIS +**podman update** [*options*] *container* + +**podman container update** [*options*] *container* + +## DESCRIPTION + +Updates the cgroup configuration of an already existing container. The currently supported options are a susbet of the +podman create/run resource limits options. These new options are non-persistent and only last for the current execution of the container; the config will be honored on its next run. This command takes one argument, a container name or ID, alongside the resource flags to modify the cgroup. + +## OPTIONS + +@@option blkio-weight + +@@option blkio-weight-device + +@@option cpu-period + +@@option cpu-quota + +@@option cpu-rt-period + +@@option cpu-rt-runtime + +@@option cpu-shares + +@@option cpus + +@@option cpuset-cpus + +@@option cpuset-mems + +@@option device-read-bps + +@@option device-read-iops + +@@option device-write-bps + +@@option device-write-iops + +@@option memory + +@@option memory-reservation + +@@option memory-swap + +@@option memory-swappiness + + +## SEE ALSO +**[podman(1)](podman.1.md)**, **[podman-create(1)](podman-create.1.md)**, **[podman-run(1)](podman-run.1.md)** + +## HISTORY +August 2022, Originally written by Charlie Doern diff --git a/docs/source/markdown/podman.1.md b/docs/source/markdown/podman.1.md index d1192b6d225b..8c3af25610bb 100644 --- a/docs/source/markdown/podman.1.md +++ b/docs/source/markdown/podman.1.md @@ -355,6 +355,7 @@ the exit codes follow the `chroot` standard, see below: | [podman-unpause(1)](podman-unpause.1.md) | Unpause one or more containers. | | [podman-unshare(1)](podman-unshare.1.md) | Run a command inside of a modified user namespace. | | [podman-untag(1)](podman-untag.1.md) | Removes one or more names from a locally-stored image. | +| [podman-update(1)](podman-update.1.md) | Updates the cgroup configuration of a given container. | | [podman-version(1)](podman-version.1.md) | Display the Podman version information. | | [podman-volume(1)](podman-volume.1.md) | Simple management tool for volumes. | | [podman-wait(1)](podman-wait.1.md) | Wait on one or more containers to stop and print their exit codes. | diff --git a/libpod/container_api.go b/libpod/container_api.go index 2ff4bfe0818b..f88e38ce14bf 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -16,6 +16,7 @@ import ( "github.com/containers/podman/v4/libpod/events" "github.com/containers/podman/v4/pkg/signal" "github.com/containers/storage/pkg/archive" + spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" ) @@ -98,6 +99,15 @@ func (c *Container) Start(ctx context.Context, recursive bool) error { return c.start() } +// Update updates the given container. +// only the cgroup config can be updated and therefore only a linux resource spec is passed. +func (c *Container) Update(res *spec.LinuxResources) error { + if err := c.syncContainer(); err != nil { + return err + } + return c.update(res) +} + // StartAndAttach starts a container and attaches to it. // This acts as a combination of the Start and Attach APIs, ensuring proper // ordering of the two such that no output from the container is lost (e.g. the diff --git a/libpod/container_internal.go b/libpod/container_internal.go index bad68991bb40..55ec82448ab2 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -2354,3 +2354,12 @@ func (c *Container) extractSecretToCtrStorage(secr *ContainerSecret) error { } return nil } + +// update calls the ociRuntime update function to modify a cgroup config after container creation +func (c *Container) update(resources *spec.LinuxResources) error { + if err := c.ociRuntime.UpdateContainer(c, resources); err != nil { + return err + } + logrus.Debugf("updated container %s", c.ID()) + return nil +} diff --git a/libpod/oci.go b/libpod/oci.go index 70053db1b2f7..e5b9a0dcd492 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -5,6 +5,7 @@ import ( "github.com/containers/common/pkg/resize" "github.com/containers/podman/v4/libpod/define" + "github.com/opencontainers/runtime-spec/specs-go" ) // OCIRuntime is an implementation of an OCI runtime. @@ -148,6 +149,9 @@ type OCIRuntime interface { // RuntimeInfo returns verbose information about the runtime. RuntimeInfo() (*define.ConmonInfo, *define.OCIRuntimeInfo, error) + + // UpdateContainer updates the given container's cgroup configuration. + UpdateContainer(ctr *Container, res *specs.LinuxResources) error } // AttachOptions are options used when attached to a container or an exec diff --git a/libpod/oci_conmon_linux.go b/libpod/oci_conmon_linux.go index cb76de72c2e7..9299e6322f2b 100644 --- a/libpod/oci_conmon_linux.go +++ b/libpod/oci_conmon_linux.go @@ -376,6 +376,52 @@ func (r *ConmonOCIRuntime) StartContainer(ctr *Container) error { return nil } +// UpdateContainer updates the given container's cgroup configuration +func (r *ConmonOCIRuntime) UpdateContainer(ctr *Container, resources *spec.LinuxResources) error { + runtimeDir, err := util.GetRuntimeDir() + if err != nil { + return err + } + env := []string{fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)} + if path, ok := os.LookupEnv("PATH"); ok { + env = append(env, fmt.Sprintf("PATH=%s", path)) + } + args := r.runtimeFlags + args = append(args, "update") + tempFile, additionalArgs, err := generateResourceFile(resources) + if err != nil { + return err + } + defer os.Remove(tempFile) + + args = append(args, additionalArgs...) + return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, append(args, ctr.ID())...) +} + +func generateResourceFile(res *spec.LinuxResources) (string, []string, error) { + flags := []string{} + if res == nil { + return "", flags, nil + } + + f, err := ioutil.TempFile("", "podman") + if err != nil { + return "", nil, err + } + + j, err := json.Marshal(res) + if err != nil { + return "", nil, err + } + _, err = f.WriteString(string(j)) + if err != nil { + return "", nil, err + } + + flags = append(flags, "--resources="+f.Name()) + return f.Name(), flags, nil +} + // KillContainer sends the given signal to the given container. // If all is set, send to all PIDs in the container. // All is only supported if the container created cgroups. diff --git a/libpod/oci_missing.go b/libpod/oci_missing.go index 2ab2b4577b03..bbf2957ff729 100644 --- a/libpod/oci_missing.go +++ b/libpod/oci_missing.go @@ -8,6 +8,7 @@ import ( "github.com/containers/common/pkg/resize" "github.com/containers/podman/v4/libpod/define" + spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" ) @@ -80,6 +81,11 @@ func (r *MissingRuntime) StartContainer(ctr *Container) error { return r.printError() } +// UpdateContainer is not available as the runtime is missing +func (r *MissingRuntime) UpdateContainer(ctr *Container, resources *spec.LinuxResources) error { + return r.printError() +} + // KillContainer is not available as the runtime is missing // TODO: We could attempt to unix.Kill() the PID as recorded in the state if we // really want to smooth things out? Won't be perfect, but if the container has diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 3ba5077508de..4567661c9272 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -493,3 +493,9 @@ type ContainerCloneOptions struct { Run bool Force bool } + +// ContainerUpdateOptions containers options for updating an existing containers cgroup configuration +type ContainerUpdateOptions struct { + NameOrID string + Specgen *specgen.SpecGenerator +} diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 6a766eb84456..69adc97325bc 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -51,6 +51,7 @@ type ContainerEngine interface { ContainerTop(ctx context.Context, options TopOptions) (*StringSliceReport, error) ContainerUnmount(ctx context.Context, nameOrIDs []string, options ContainerUnmountOptions) ([]*ContainerUnmountReport, error) ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) + ContainerUpdate(ctx context.Context, options *ContainerUpdateOptions) (string, error) ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error) Diff(ctx context.Context, namesOrIds []string, options DiffOptions) (*DiffReport, error) Events(ctx context.Context, opts EventsOptions) error diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index 14ce370c1f1d..a931c54683af 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -164,6 +164,15 @@ type PodCloneOptions struct { Start bool } +type ContainerMode string + +const ( + InfraMode = ContainerMode("infra") + CloneMode = ContainerMode("clone") + UpdateMode = ContainerMode("update") + CreateMode = ContainerMode("create") +) + type ContainerCreateOptions struct { Annotation []string Attach []string diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index 5b5bc665ebc6..35b805aa49a1 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -1702,3 +1702,23 @@ func (ic *ContainerEngine) ContainerClone(ctx context.Context, ctrCloneOpts enti return &entities.ContainerCreateReport{Id: ctr.ID()}, nil } + +// ContainerUpdate finds and updates the given container's cgroup config with the specified options +func (ic *ContainerEngine) ContainerUpdate(ctx context.Context, updateOptions *entities.ContainerUpdateOptions) (string, error) { + err := generate.FinishThrottleDevices(updateOptions.Specgen) + if err != nil { + return "", err + } + ctrs, err := getContainersByContext(false, false, []string{updateOptions.NameOrID}, ic.Libpod) + if err != nil { + return "", err + } + if len(ctrs) != 1 { + return "", fmt.Errorf("container not found") + } + + if err = ctrs[0].Update(updateOptions.Specgen.ResourceLimits); err != nil { + return "", err + } + return ctrs[0].ID(), nil +} diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index d49f029d53d3..8e8ec126acde 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -1017,3 +1017,8 @@ func (ic *ContainerEngine) ContainerRename(ctx context.Context, nameOrID string, func (ic *ContainerEngine) ContainerClone(ctx context.Context, ctrCloneOpts entities.ContainerCloneOptions) (*entities.ContainerCreateReport, error) { return nil, errors.New("cloning a container is not supported on the remote client") } + +// ContainerUpdate finds and updates the given container's cgroup config with the specified options +func (ic *ContainerEngine) ContainerUpdate(ctx context.Context, updateOptions *entities.ContainerUpdateOptions) (string, error) { + return "", nil +} diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go index 7392e7b44849..6cd8ada2e054 100644 --- a/pkg/specgenutil/specgen.go +++ b/pkg/specgenutil/specgen.go @@ -510,44 +510,9 @@ func FillOutSpecGen(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions s.ResourceLimits = &specs.LinuxResources{} } - if s.ResourceLimits.Memory == nil || (len(c.Memory) != 0 || len(c.MemoryReservation) != 0 || len(c.MemorySwap) != 0 || c.MemorySwappiness != 0) { - s.ResourceLimits.Memory, err = getMemoryLimits(c) - if err != nil { - return err - } - } - if s.ResourceLimits.BlockIO == nil || (len(c.BlkIOWeight) != 0 || len(c.BlkIOWeightDevice) != 0 || len(c.DeviceReadBPs) != 0 || len(c.DeviceWriteBPs) != 0) { - s.ResourceLimits.BlockIO, err = getIOLimits(s, c) - if err != nil { - return err - } - } - if c.PIDsLimit != nil { - pids := specs.LinuxPids{ - Limit: *c.PIDsLimit, - } - - s.ResourceLimits.Pids = &pids - } - - if s.ResourceLimits.CPU == nil || (c.CPUPeriod != 0 || c.CPUQuota != 0 || c.CPURTPeriod != 0 || c.CPURTRuntime != 0 || c.CPUS != 0 || len(c.CPUSetCPUs) != 0 || len(c.CPUSetMems) != 0 || c.CPUShares != 0) { - s.ResourceLimits.CPU = getCPULimits(c) - } - - unifieds := make(map[string]string) - for _, unified := range c.CgroupConf { - splitUnified := strings.SplitN(unified, "=", 2) - if len(splitUnified) < 2 { - return errors.New("--cgroup-conf must be formatted KEY=VALUE") - } - unifieds[splitUnified[0]] = splitUnified[1] - } - if len(unifieds) > 0 { - s.ResourceLimits.Unified = unifieds - } - - if s.ResourceLimits.CPU == nil && s.ResourceLimits.Pids == nil && s.ResourceLimits.BlockIO == nil && s.ResourceLimits.Memory == nil && s.ResourceLimits.Unified == nil { - s.ResourceLimits = nil + s.ResourceLimits, err = GetResources(s, c) + if err != nil { + return err } if s.LogConfiguration == nil { @@ -1171,3 +1136,47 @@ func parseLinuxResourcesDeviceAccess(device string) (specs.LinuxDeviceCgroup, er Access: access, }, nil } + +func GetResources(s *specgen.SpecGenerator, c *entities.ContainerCreateOptions) (*specs.LinuxResources, error) { + var err error + if s.ResourceLimits.Memory == nil || (len(c.Memory) != 0 || len(c.MemoryReservation) != 0 || len(c.MemorySwap) != 0 || c.MemorySwappiness != 0) { + s.ResourceLimits.Memory, err = getMemoryLimits(c) + if err != nil { + return nil, err + } + } + if s.ResourceLimits.BlockIO == nil || (len(c.BlkIOWeight) != 0 || len(c.BlkIOWeightDevice) != 0 || len(c.DeviceReadBPs) != 0 || len(c.DeviceWriteBPs) != 0) { + s.ResourceLimits.BlockIO, err = getIOLimits(s, c) + if err != nil { + return nil, err + } + } + if c.PIDsLimit != nil { + pids := specs.LinuxPids{ + Limit: *c.PIDsLimit, + } + + s.ResourceLimits.Pids = &pids + } + + if s.ResourceLimits.CPU == nil || (c.CPUPeriod != 0 || c.CPUQuota != 0 || c.CPURTPeriod != 0 || c.CPURTRuntime != 0 || c.CPUS != 0 || len(c.CPUSetCPUs) != 0 || len(c.CPUSetMems) != 0 || c.CPUShares != 0) { + s.ResourceLimits.CPU = getCPULimits(c) + } + + unifieds := make(map[string]string) + for _, unified := range c.CgroupConf { + splitUnified := strings.SplitN(unified, "=", 2) + if len(splitUnified) < 2 { + return nil, errors.New("--cgroup-conf must be formatted KEY=VALUE") + } + unifieds[splitUnified[0]] = splitUnified[1] + } + if len(unifieds) > 0 { + s.ResourceLimits.Unified = unifieds + } + + if s.ResourceLimits.CPU == nil && s.ResourceLimits.Pids == nil && s.ResourceLimits.BlockIO == nil && s.ResourceLimits.Memory == nil && s.ResourceLimits.Unified == nil { + s.ResourceLimits = nil + } + return s.ResourceLimits, nil +} diff --git a/test/e2e/update_test.go b/test/e2e/update_test.go new file mode 100644 index 000000000000..b23164b97594 --- /dev/null +++ b/test/e2e/update_test.go @@ -0,0 +1,169 @@ +package integration + +import ( + "github.com/containers/common/pkg/cgroupv2" + . "github.com/containers/podman/v4/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" + . "github.com/onsi/gomega/gexec" +) + +var _ = Describe("Podman update", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + Expect(err).To(BeNil()) + podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + processTestResult(f) + + }) + + It("podman update container all options", func() { + SkipIfRootless("many of these handlers are not enables while rootless in CI") + session := podmanTest.Podman([]string{"run", "-dt", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + ctrID := session.OutputToString() + + commonArgs := []string{ + "update", + "--cpus", "5", + "--cpuset-cpus", "0", + "--cpu-shares", "123", + "--cpuset-mems", "0", + "--memory", "1G", + "--memory-swap", "2G", + "--memory-reservation", "2G", + } + + if v2, _ := cgroupv2.Enabled(); !v2 { + commonArgs = append(commonArgs, []string{"--memory-swappiness", "50", ctrID}...) + session = podmanTest.Podman(commonArgs) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("500000")) + + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpuset/cpuset.cpus"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("0")) + + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpuset/cpuset.mems"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("0")) + + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/memory/memory.limit_in_bytes"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("1073741824")) + + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("1073741824")) + + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpu/cpu.shares"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("123")) + } else { + commonArgs = append(commonArgs, []string{"--blkio-weight-device", "/dev/zero:123", + "--blkio-weight", "123", + "--device-read-bps", "/dev/zero:10mb", + "--device-write-bps", "/dev/zero:10mb", + "--device-read-iops", "/dev/zero:1000", + "--device-write-iops", "/dev/zero:1000", + ctrID}...) + session = podmanTest.Podman(commonArgs) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + ctrID := session.OutputToString() + + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpu.max"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("500000")) + + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/io.bfq.weight"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("123")) + + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/io.max"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("rbps=10485760 wbps=10485760 riops=1000 wiops=1000")) + + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpuset.cpus"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("0")) + + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpuset.mems"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("0")) + + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/memory.max"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("1073741824")) + + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/memory.swap.max"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("1073741824")) + + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpu.weight"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("5")) + } + + }) + + It("podman update keep original resources if not overridden", func() { + SkipIfRootless("many of these handlers are not enables while rootless in CI") + session := podmanTest.Podman([]string{"run", "-dt", "--cpus", "5", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + session = podmanTest.Podman([]string{ + "update", + "--memory", "1G", + session.OutputToString(), + }) + + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + + ctrID := session.OutputToString() + + if v2, _ := cgroupv2.Enabled(); v2 { + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpu.max"}) + } else { + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"}) + } + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(ContainSubstring("500000")) + }) +})