From 797445a041165927b952d96dc6af83df9cb33cf3 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: this command is also now supported in the libpod api via the /libpod/containers//update endpoint where the resource limits are passed inthe request body and follow the OCI resource spec format –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 | 12 ++ .../markdown/options/device-read-bps.md | 6 + .../markdown/options/device-read-iops.md | 5 + .../markdown/options/device-write-bps.md | 5 + .../markdown/options/device-write-iops.md | 5 + .../markdown/options/memory-reservation.md | 11 ++ docs/source/markdown/options/memory-swap.md | 12 ++ docs/source/markdown/options/memory.md | 11 ++ .../markdown/podman-container-clone.1.md.in | 62 +----- docs/source/markdown/podman-container.1.md | 1 + docs/source/markdown/podman-create.1.md.in | 77 +------- 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 | 54 +---- 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_common.go | 46 +++++ libpod/oci_missing.go | 6 + pkg/api/handlers/libpod/containers.go | 25 +++ pkg/api/handlers/swagger/responses.go | 5 + pkg/api/handlers/types.go | 7 + pkg/api/server/register_containers.go | 28 +++ pkg/bindings/containers/update.go | 31 +++ 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 | 9 + pkg/specgen/generate/container.go | 70 ------- pkg/specgen/generate/container_create.go | 2 +- pkg/specgen/generate/pod_create.go | 2 +- pkg/specgen/utils.go | 9 + pkg/specgen/utils_linux.go | 80 ++++++++ pkg/specgenutil/specgen.go | 85 ++++---- test/apiv2/20-containers.at | 10 + .../python/rest_api/test_v2_0_0_container.py | 2 - test/e2e/update_test.go | 187 ++++++++++++++++++ 49 files changed, 821 insertions(+), 326 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 pkg/bindings/containers/update.go create mode 100644 pkg/specgen/utils.go create mode 100644 pkg/specgen/utils_linux.go 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 2bdcce197e33..4dc79d7171da 100644 --- a/docs/source/markdown/.gitignore +++ b/docs/source/markdown/.gitignore @@ -23,3 +23,4 @@ podman-run.1.md podman-search.1.md podman-stop.1.md podman-unpause.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..a7f5d053a66e --- /dev/null +++ b/docs/source/markdown/options/cpus.md @@ -0,0 +1,12 @@ +#### **--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 + + +This option is not supported on cgroups V1 rootless systems. 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..691ffbe716c5 --- /dev/null +++ b/docs/source/markdown/options/device-read-bps.md @@ -0,0 +1,6 @@ +#### **--device-read-bps**=*path* + +Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb). + + +This option is not supported on cgroups V1 rootless systems. 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..9cd0f9030bd9 --- /dev/null +++ b/docs/source/markdown/options/device-read-iops.md @@ -0,0 +1,5 @@ +#### **--device-read-iops**=*path:rate* + +Limit read rate (in IO operations per second) from a device (e.g. **--device-read-iops=/dev/sda:1000**). + +This option is not supported on cgroups V1 rootless systems. 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..3dacc4515ab3 --- /dev/null +++ b/docs/source/markdown/options/device-write-bps.md @@ -0,0 +1,5 @@ +#### **--device-write-bps**=*path:rate* + +Limit write rate (in bytes per second) to a device (e.g. **--device-write-bps=/dev/sda:1mb**). + +This option is not supported on cgroups V1 rootless systems. 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..ead94c92e593 --- /dev/null +++ b/docs/source/markdown/options/device-write-iops.md @@ -0,0 +1,5 @@ +#### **--device-write-iops**=*path* + +Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000) + +This option is not supported on cgroups V1 rootless systems. diff --git a/docs/source/markdown/options/memory-reservation.md b/docs/source/markdown/options/memory-reservation.md new file mode 100644 index 000000000000..c963b51bf3c1 --- /dev/null +++ b/docs/source/markdown/options/memory-reservation.md @@ -0,0 +1,11 @@ +#### **--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. + +This option is not supported on cgroups V1 rootless systems. diff --git a/docs/source/markdown/options/memory-swap.md b/docs/source/markdown/options/memory-swap.md new file mode 100644 index 000000000000..c4ae1e7ec0d4 --- /dev/null +++ b/docs/source/markdown/options/memory-swap.md @@ -0,0 +1,12 @@ +#### **--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. + +This option is not supported on cgroups V1 rootless systems. diff --git a/docs/source/markdown/options/memory.md b/docs/source/markdown/options/memory.md new file mode 100644 index 000000000000..e0513b3ddcab --- /dev/null +++ b/docs/source/markdown/options/memory.md @@ -0,0 +1,11 @@ +#### **--memory**, **-m**=*limit* + +Memory limit (format: `[]`, where unit = b (bytes), k (kibibytes), m (mebibytes), or g (gibibytes)) + +Constrains 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). + +This option is not supported on cgroups V1 rootless systems. diff --git a/docs/source/markdown/podman-container-clone.1.md.in b/docs/source/markdown/podman-container-clone.1.md.in index 26f414b620db..f436e1151051 100644 --- a/docs/source/markdown/podman-container-clone.1.md.in +++ b/docs/source/markdown/podman-container-clone.1.md.in @@ -31,16 +31,7 @@ If none is specified, the original container's CPU runtime period is used. @@option cpu-shares -If none are specified, the original container's CPU shares are used. - -#### **--cpus** - -Set a number of CPUs for the container that overrides the original containers CPU limits. If none are specified, the original container's Nano CPUs are used. - -This is shorthand -for **--cpu-period** and **--cpu-quota**, so only **--cpus** or either both the **--cpu-period** and **--cpu-quota** options can be set. - -This option is not supported on cgroups V1 rootless systems. +@@option cpus @@option cpuset-cpus @@ -52,60 +43,19 @@ If none are specified, the original container's CPU memory nodes are used. @@option destroy -#### **--device-read-bps**=*path* - -Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb). +@@option device-read-bps -This option is not supported on cgroups V1 rootless systems. - -#### **--device-write-bps**=*path* - -Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb) - -This option is not supported on cgroups V1 rootless systems. +@@option device-write-bps #### **--force**, **-f** Force removal of the original container that we are cloning. Can only be used in conjunction with **--destroy**. -#### **--memory**, **-m**=*limit* - -Memory limit (format: `[]`, where unit = b (bytes), k (kibibytes), m (mebibytes), or g (gibibytes)) - -Allows the memory available to a container to be constrained. 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). - -If no memory limits are specified, the original container's will be used. - -This option is not supported on cgroups V1 rootless systems. - -#### **--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 from the container being cloned. - -This option is not supported on cgroups V1 rootless systems. - -#### **--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 if specified. Otherwise, the container being cloned will be used to derive the swap value. +@@option 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. +@@option memory-reservation -This option is not supported on cgroups V1 rootless systems. +@@option memory-swap @@option memory-swappiness 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 2fecdb256b09..ddee984b6c26 100644 --- a/docs/source/markdown/podman-create.1.md.in +++ b/docs/source/markdown/podman-create.1.md.in @@ -87,9 +87,7 @@ each of stdin, stdout, and stderr. @@option blkio-weight -#### **--blkio-weight-device**=*device:weight* - -Block IO relative device weight. +@@option blkio-weight-device @@option cap-add @@ -119,17 +117,7 @@ Block IO relative device weight. @@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-resource-limits-fails-with-a-permissions-error - -This option is not supported on cgroups V1 rootless systems. +@@option cpus @@option cpuset-cpus @@ -161,29 +149,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) - -This option is not supported on cgroups V1 rootless systems. - -#### **--device-read-iops**=*path* - -Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000) - -This option is not supported on cgroups V1 rootless systems. - -#### **--device-write-bps**=*path* - -Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb) +@@option device-read-bps -This option is not supported on cgroups V1 rootless systems. +@@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) - -This option is not supported on cgroups V1 rootless systems. +@@option device-write-iops #### **--disable-content-trust** @@ -364,42 +336,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). - -This option is not supported on cgroups V1 rootless systems. - -#### **--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. - -This option is not supported on cgroups V1 rootless systems. - -#### **--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. +@@option 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. +@@option memory-reservation -This option is not supported on cgroups V1 rootless systems. +@@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 c12f296b4202..9ee780be47ca 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 40e05c06cb2e..ab6704dca421 100644 --- a/docs/source/markdown/podman-run.1.md.in +++ b/docs/source/markdown/podman-run.1.md.in @@ -134,17 +134,7 @@ each of **stdin**, **stdout**, and **stderr**. @@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-resource-limits-fails-with-a-permissions-error - -This option is not supported on cgroups V1 rootless systems. +@@option cpus @@option cpuset-cpus @@ -192,29 +182,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* - -Limit read rate (in bytes per second) from a device (e.g. **--device-read-bps=/dev/sda:1mb**). - -This option is not supported on cgroups V1 rootless systems. - -#### **--device-read-iops**=*path:rate* - -Limit read rate (in IO operations per second) from a device (e.g. **--device-read-iops=/dev/sda:1000**). - -This option is not supported on cgroups V1 rootless systems. - -#### **--device-write-bps**=*path:rate* +@@option device-read-bps -Limit write rate (in bytes per second) to a device (e.g. **--device-write-bps=/dev/sda:1mb**). +@@option device-read-iops -This option is not supported on cgroups V1 rootless systems. +@@option device-write-bps -#### **--device-write-iops**=*path:rate* - -Limit write rate (in IO operations per second) to a device (e.g. **--device-write-iops=/dev/sda:1000**). - -This option is not supported on cgroups V1 rootless systems. +@@option device-write-iops #### **--disable-content-trust** @@ -385,8 +359,6 @@ 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). -This option is not supported on cgroups V1 rootless systems. - #### **--memory-reservation**=*number[unit]* Memory soft limit. A _unit_ can be **b** (bytes), **k** (kibibytes), **m** (mebibytes), or **g** (gibibytes). @@ -397,21 +369,7 @@ reservation. So you should always set the value below **--memory**, otherwise th hard limit will take precedence. By default, memory reservation will be the same as memory limit. -This option is not supported on cgroups V1 rootless systems. - -#### **--memory-swap**=*number[unit]* - -A limit value equal to memory plus swap. -A _unit_ can be **b** (bytes), **k** (kibibytes), **m** (mebibytes), or **g** (gibibytes). - -Must be used with the **-m** (**--memory**) flag. -The argument value should always be larger than that of - **-m** (**--memory**) By default, it is set to double -the value of **--memory**. - -Set _number_ to **-1** to enable unlimited swap. - -This option is not supported on cgroups V1 rootless systems. +@@option memory-swap @@option memory-swappiness 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..476657ced641 --- /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 configuration 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 60fb29607b7d..0d905db43d37 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -2343,3 +2343,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_common.go b/libpod/oci_conmon_common.go index c3725cdb4678..dd99465e2fba 100644 --- a/libpod/oci_conmon_common.go +++ b/libpod/oci_conmon_common.go @@ -316,6 +316,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/api/handlers/libpod/containers.go b/pkg/api/handlers/libpod/containers.go index 5d85d40090ab..d1460569f335 100644 --- a/pkg/api/handlers/libpod/containers.go +++ b/pkg/api/handlers/libpod/containers.go @@ -1,6 +1,7 @@ package libpod import ( + "encoding/json" "errors" "fmt" "io/ioutil" @@ -10,6 +11,7 @@ import ( "github.com/containers/podman/v4/libpod" "github.com/containers/podman/v4/libpod/define" + "github.com/containers/podman/v4/pkg/api/handlers" "github.com/containers/podman/v4/pkg/api/handlers/compat" "github.com/containers/podman/v4/pkg/api/handlers/utils" api "github.com/containers/podman/v4/pkg/api/types" @@ -17,6 +19,7 @@ import ( "github.com/containers/podman/v4/pkg/domain/infra/abi" "github.com/containers/podman/v4/pkg/util" "github.com/gorilla/schema" + "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" ) @@ -391,6 +394,28 @@ func InitContainer(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusNoContent, "") } +func UpdateContainer(w http.ResponseWriter, r *http.Request) { + name := utils.GetName(r) + runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) + ctr, err := runtime.LookupContainer(name) + if err != nil { + utils.ContainerNotFound(w, name, err) + return + } + + options := &handlers.UpdateEntities{Resources: &specs.LinuxResources{}} + if err := json.NewDecoder(r.Body).Decode(&options.Resources); err != nil { + utils.Error(w, http.StatusInternalServerError, fmt.Errorf("decode(): %w", err)) + return + } + err = ctr.Update(options.Resources) + if err != nil { + utils.InternalServerError(w, err) + return + } + utils.WriteResponse(w, http.StatusCreated, ctr.ID()) +} + func ShouldRestart(w http.ResponseWriter, r *http.Request) { runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) // Now use the ABI implementation to prevent us from having duplicate diff --git a/pkg/api/handlers/swagger/responses.go b/pkg/api/handlers/swagger/responses.go index 93a508b393bd..3de9b06e99f9 100644 --- a/pkg/api/handlers/swagger/responses.go +++ b/pkg/api/handlers/swagger/responses.go @@ -313,6 +313,11 @@ type containerCreateResponse struct { Body entities.ContainerCreateResponse } +type containerUpdateResponse struct { + // in:body + ID string +} + // Wait container // swagger:response type containerWaitResponse struct { diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index b533e131ce8e..52dc941e1d50 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -11,6 +11,7 @@ import ( dockerContainer "github.com/docker/docker/api/types/container" dockerNetwork "github.com/docker/docker/api/types/network" "github.com/docker/go-connections/nat" + "github.com/opencontainers/runtime-spec/specs-go" ) type AuthConfig struct { @@ -64,6 +65,12 @@ type LibpodContainersRmReport struct { RmError string `json:"Err,omitempty"` } +// UpdateEntities used to wrap the oci resource spec in a swagger model +// swagger:model +type UpdateEntities struct { + Resources *specs.LinuxResources +} + type Info struct { docker.Info BuildahVersion string diff --git a/pkg/api/server/register_containers.go b/pkg/api/server/register_containers.go index 8aba4ea05b3f..41baf5418030 100644 --- a/pkg/api/server/register_containers.go +++ b/pkg/api/server/register_containers.go @@ -1626,5 +1626,33 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { // 500: // $ref: "#/responses/internalError" r.HandleFunc(VersionedPath("/libpod/containers/{name}/rename"), s.APIHandler(compat.RenameContainer)).Methods(http.MethodPost) + // swagger:operation POST /libpod/containers/{name}/update libpod ContainerUpdateLibpod + // --- + // tags: + // - containers + // summary: Update an existing containers cgroup configuration + // description: Update an existing containers cgroup configuration. + // parameters: + // - in: path + // name: name + // type: string + // required: true + // description: Full or partial ID or full name of the container to update + // - in: body + // name: resources + // description: attributes for updating the container + // schema: + // $ref: "#/definitions/UpdateEntities" + // produces: + // - application/json + // responses: + // responses: + // 201: + // $ref: "#/responses/containerUpdateResponse" + // 404: + // $ref: "#/responses/containerNotFound" + // 500: + // $ref: "#/responses/internalError" + r.HandleFunc(VersionedPath("/libpod/containers/{name}/update"), s.APIHandler(libpod.UpdateContainer)).Methods(http.MethodPost) return nil } diff --git a/pkg/bindings/containers/update.go b/pkg/bindings/containers/update.go new file mode 100644 index 000000000000..7cda7c3064ae --- /dev/null +++ b/pkg/bindings/containers/update.go @@ -0,0 +1,31 @@ +package containers + +import ( + "context" + "net/http" + "strings" + + "github.com/containers/podman/v4/pkg/bindings" + "github.com/containers/podman/v4/pkg/domain/entities" + jsoniter "github.com/json-iterator/go" +) + +func Update(ctx context.Context, options *entities.ContainerUpdateOptions) (string, error) { + conn, err := bindings.GetClient(ctx) + if err != nil { + return "", err + } + + resources, err := jsoniter.MarshalToString(options.Specgen.ResourceLimits) + if err != nil { + return "", err + } + stringReader := strings.NewReader(resources) + response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/containers/%s/update", nil, nil, options.NameOrID) + if err != nil { + return "", err + } + defer response.Body.Close() + + return options.NameOrID, response.Process(nil) +} diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 91ccdc2b28ff..47225f25c5b9 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -495,3 +495,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 08d845d70d1a..133be44105f3 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -1713,3 +1713,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 := specgen.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 046509140b93..21b24e20157a 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -1022,3 +1022,12 @@ 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) { + err := specgen.FinishThrottleDevices(updateOptions.Specgen) + if err != nil { + return "", err + } + return containers.Update(ic.ClientCtx, updateOptions) +} diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go index ec85f0f79bd0..7d3bc99af758 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -20,7 +20,6 @@ import ( "github.com/containers/podman/v4/pkg/specgen" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" ) func getImageFromSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerator) (*libimage.Image, string, *libimage.ImageData, error) { @@ -496,75 +495,6 @@ func mapSecurityConfig(c *libpod.ContainerConfig, s *specgen.SpecGenerator) { s.HostUsers = c.HostUsers } -// FinishThrottleDevices takes the temporary representation of the throttle -// devices in the specgen and looks up the major and major minors. it then -// sets the throttle devices proper in the specgen -func FinishThrottleDevices(s *specgen.SpecGenerator) error { - if s.ResourceLimits == nil { - s.ResourceLimits = &spec.LinuxResources{} - } - if bps := s.ThrottleReadBpsDevice; len(bps) > 0 { - if s.ResourceLimits.BlockIO == nil { - s.ResourceLimits.BlockIO = &spec.LinuxBlockIO{} - } - for k, v := range bps { - statT := unix.Stat_t{} - if err := unix.Stat(k, &statT); err != nil { - return fmt.Errorf("could not parse throttle device at %s: %w", k, err) - } - v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert - v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert - if s.ResourceLimits.BlockIO == nil { - s.ResourceLimits.BlockIO = new(spec.LinuxBlockIO) - } - s.ResourceLimits.BlockIO.ThrottleReadBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleReadBpsDevice, v) - } - } - if bps := s.ThrottleWriteBpsDevice; len(bps) > 0 { - if s.ResourceLimits.BlockIO == nil { - s.ResourceLimits.BlockIO = &spec.LinuxBlockIO{} - } - for k, v := range bps { - statT := unix.Stat_t{} - if err := unix.Stat(k, &statT); err != nil { - return fmt.Errorf("could not parse throttle device at %s: %w", k, err) - } - v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert - v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert - s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice, v) - } - } - if iops := s.ThrottleReadIOPSDevice; len(iops) > 0 { - if s.ResourceLimits.BlockIO == nil { - s.ResourceLimits.BlockIO = &spec.LinuxBlockIO{} - } - for k, v := range iops { - statT := unix.Stat_t{} - if err := unix.Stat(k, &statT); err != nil { - return fmt.Errorf("could not parse throttle device at %s: %w", k, err) - } - v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert - v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert - s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice, v) - } - } - if iops := s.ThrottleWriteIOPSDevice; len(iops) > 0 { - if s.ResourceLimits.BlockIO == nil { - s.ResourceLimits.BlockIO = &spec.LinuxBlockIO{} - } - for k, v := range iops { - statT := unix.Stat_t{} - if err := unix.Stat(k, &statT); err != nil { - return fmt.Errorf("could not parse throttle device at %s: %w", k, err) - } - v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert - v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert - s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice, v) - } - } - return nil -} - // Check name looks for existing containers/pods with the same name, and modifies the given string until a new name is found func CheckName(rt *libpod.Runtime, n string, kind bool) string { switch { diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go index e9cec2873969..6290e523b53e 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -56,7 +56,7 @@ func MakeContainer(ctx context.Context, rt *libpod.Runtime, s *specgen.SpecGener } } - if err := FinishThrottleDevices(s); err != nil { + if err := specgen.FinishThrottleDevices(s); err != nil { return nil, nil, nil, err } diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go index d6063b9a0ddc..8ecae581039f 100644 --- a/pkg/specgen/generate/pod_create.go +++ b/pkg/specgen/generate/pod_create.go @@ -45,7 +45,7 @@ func MakePod(p *entities.PodSpec, rt *libpod.Runtime) (*libpod.Pod, error) { } if !p.PodSpecGen.NoInfra { - err := FinishThrottleDevices(p.PodSpecGen.InfraContainerSpec) + err := specgen.FinishThrottleDevices(p.PodSpecGen.InfraContainerSpec) if err != nil { return nil, err } diff --git a/pkg/specgen/utils.go b/pkg/specgen/utils.go new file mode 100644 index 000000000000..f1cda88261ea --- /dev/null +++ b/pkg/specgen/utils.go @@ -0,0 +1,9 @@ +//go:build !linux +// +build !linux + +package specgen + +// FinishThrottleDevices cannot be called on non-linux OS' due to importing unix functions +func FinishThrottleDevices(s *SpecGenerator) error { + return nil +} diff --git a/pkg/specgen/utils_linux.go b/pkg/specgen/utils_linux.go new file mode 100644 index 000000000000..964914fff549 --- /dev/null +++ b/pkg/specgen/utils_linux.go @@ -0,0 +1,80 @@ +//go:build linux +// +build linux + +package specgen + +import ( + "fmt" + + spec "github.com/opencontainers/runtime-spec/specs-go" + "golang.org/x/sys/unix" +) + +// FinishThrottleDevices takes the temporary representation of the throttle +// devices in the specgen and looks up the major and major minors. it then +// sets the throttle devices proper in the specgen +func FinishThrottleDevices(s *SpecGenerator) error { + if s.ResourceLimits == nil { + s.ResourceLimits = &spec.LinuxResources{} + } + if bps := s.ThrottleReadBpsDevice; len(bps) > 0 { + if s.ResourceLimits.BlockIO == nil { + s.ResourceLimits.BlockIO = &spec.LinuxBlockIO{} + } + for k, v := range bps { + statT := unix.Stat_t{} + if err := unix.Stat(k, &statT); err != nil { + return fmt.Errorf("could not parse throttle device at %s: %w", k, err) + } + v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert + v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert + if s.ResourceLimits.BlockIO == nil { + s.ResourceLimits.BlockIO = new(spec.LinuxBlockIO) + } + s.ResourceLimits.BlockIO.ThrottleReadBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleReadBpsDevice, v) + } + } + if bps := s.ThrottleWriteBpsDevice; len(bps) > 0 { + if s.ResourceLimits.BlockIO == nil { + s.ResourceLimits.BlockIO = &spec.LinuxBlockIO{} + } + for k, v := range bps { + statT := unix.Stat_t{} + if err := unix.Stat(k, &statT); err != nil { + return fmt.Errorf("could not parse throttle device at %s: %w", k, err) + } + v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert + v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert + s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice, v) + } + } + if iops := s.ThrottleReadIOPSDevice; len(iops) > 0 { + if s.ResourceLimits.BlockIO == nil { + s.ResourceLimits.BlockIO = &spec.LinuxBlockIO{} + } + for k, v := range iops { + statT := unix.Stat_t{} + if err := unix.Stat(k, &statT); err != nil { + return fmt.Errorf("could not parse throttle device at %s: %w", k, err) + } + v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert + v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert + s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice, v) + } + } + if iops := s.ThrottleWriteIOPSDevice; len(iops) > 0 { + if s.ResourceLimits.BlockIO == nil { + s.ResourceLimits.BlockIO = &spec.LinuxBlockIO{} + } + for k, v := range iops { + statT := unix.Stat_t{} + if err := unix.Stat(k, &statT); err != nil { + return fmt.Errorf("could not parse throttle device at %s: %w", k, err) + } + v.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert + v.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert + s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice, v) + } + } + 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/apiv2/20-containers.at b/test/apiv2/20-containers.at index ac3626cf115b..a8df8dbb1266 100644 --- a/test/apiv2/20-containers.at +++ b/test/apiv2/20-containers.at @@ -545,6 +545,16 @@ if ! grep -q '201 Created' "${TMPD}/headers.txt"; then exit 1 fi +if root; then + podman run -dt --name=updateCtr alpine + t POST libpod/containers/updateCtr/update \ + Memory.Limit=1000 \ + CPU.Shares=123 \ + 201 \ + + podman rm -f updateCtr +fi + rm -rf $TMPD podman container rm -fa diff --git a/test/apiv2/python/rest_api/test_v2_0_0_container.py b/test/apiv2/python/rest_api/test_v2_0_0_container.py index a6cd93a1af4e..25596a9b7bbb 100644 --- a/test/apiv2/python/rest_api/test_v2_0_0_container.py +++ b/test/apiv2/python/rest_api/test_v2_0_0_container.py @@ -359,8 +359,6 @@ def test_memory(self): self.assertEqual(2000, out["HostConfig"]["MemorySwap"]) self.assertEqual(1000, out["HostConfig"]["Memory"]) - - def execute_process(cmd): return subprocess.run( cmd, diff --git a/test/e2e/update_test.go b/test/e2e/update_test.go new file mode 100644 index 000000000000..e8e62e63210c --- /dev/null +++ b/test/e2e/update_test.go @@ -0,0 +1,187 @@ +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 v1", func() { + SkipIfCgroupV2("testing flags that only work in cgroup v1") + SkipIfRootless("many of these handlers are not enabled 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", + "--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("2147483648")) + + 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")) + + }) + + It("podman update container all options v2", func() { + SkipIfCgroupV1("testing flags that only work in cgroup v2") + SkipIfRootless("many of these handlers are not enabled 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", + "--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 enabled 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")) + }) +})