From 050f3291b9ef8788510b78c543055638275eb20f 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 + .../markdown/options/device-read-bps.md | 5 + .../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 | 13 ++ docs/source/markdown/options/memory.md | 11 + .../markdown/podman-container-clone.1.md.in | 26 +-- docs/source/markdown/podman-container.1.md | 1 + docs/source/markdown/podman-create.1.md.in | 63 +----- 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 | 52 +---- docs/source/markdown/podman-update.1.md.in | 78 +++++++ 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 | 24 +++ pkg/domain/infra/tunnel/containers.go | 13 ++ pkg/specgen/generate/container.go | 70 ------ pkg/specgen/generate/container_create.go | 2 +- pkg/specgen/generate/pod_create.go | 12 +- pkg/specgen/utils.go | 14 ++ pkg/specgen/utils_linux.go | 103 +++++++++ pkg/specgenutil/specgen.go | 85 ++++---- test/apiv2/20-containers.at | 15 ++ .../python/rest_api/test_v2_0_0_container.py | 2 - test/e2e/update_test.go | 200 ++++++++++++++++++ 48 files changed, 879 insertions(+), 287 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/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 1e573cc2d1..a2bc45b9ec 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", @@ -597,7 +581,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(), @@ -677,7 +661,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" @@ -690,7 +674,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" @@ -725,7 +709,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, @@ -775,7 +759,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, @@ -791,7 +776,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, @@ -840,8 +826,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 9881a791ca..62bc90a2cf 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 455127fd79..b854ff4b28 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 ef13ea95ea..f66d4d4d34 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 0000000000..33aa25f8e5 --- /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 9558c6aeda..bf7d10c447 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 4f1f66ad6f..d30f4782a3 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 26509612d5..74e7fc075e 100644 --- a/docs/source/markdown/.gitignore +++ b/docs/source/markdown/.gitignore @@ -27,3 +27,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 0000000000..e903b5c06b --- /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/device-read-bps.md b/docs/source/markdown/options/device-read-bps.md new file mode 100644 index 0000000000..e0c610061d --- /dev/null +++ b/docs/source/markdown/options/device-read-bps.md @@ -0,0 +1,5 @@ +#### **--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. 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 0000000000..9cd0f9030b --- /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 0000000000..3dacc4515a --- /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 0000000000..cf5ce3859f --- /dev/null +++ b/docs/source/markdown/options/device-write-iops.md @@ -0,0 +1,5 @@ +#### **--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. diff --git a/docs/source/markdown/options/memory-reservation.md b/docs/source/markdown/options/memory-reservation.md new file mode 100644 index 0000000000..410f1dd7cc --- /dev/null +++ b/docs/source/markdown/options/memory-reservation.md @@ -0,0 +1,11 @@ +#### **--memory-reservation**=*number[unit]* + +Memory soft limit. A _unit_ can be **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 0000000000..08ee8b1a02 --- /dev/null +++ b/docs/source/markdown/options/memory-swap.md @@ -0,0 +1,13 @@ +#### **--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. diff --git a/docs/source/markdown/options/memory.md b/docs/source/markdown/options/memory.md new file mode 100644 index 0000000000..1be9159c3c --- /dev/null +++ b/docs/source/markdown/options/memory.md @@ -0,0 +1,11 @@ +#### **--memory**, **-m**=*number[unit]* + +Memory limit. A _unit_ can be **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). + +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 26f414b620..3e31389d2e 100644 --- a/docs/source/markdown/podman-container-clone.1.md.in +++ b/docs/source/markdown/podman-container-clone.1.md.in @@ -52,36 +52,18 @@ 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). - -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). +@@option memory 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)) @@ -92,8 +74,6 @@ 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 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** diff --git a/docs/source/markdown/podman-container.1.md b/docs/source/markdown/podman-container.1.md index a66e2789d4..593cd3c422 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 2fad2deb1b..10f0f7f56c 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 @@ -148,27 +146,11 @@ device. The devices that podman will load modules when necessary are: #### **--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) - -This option is not supported on cgroups V1 rootless systems. - -#### **--device-write-iops**=*path* +@@option device-read-iops -Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000) +@@option device-write-bps -This option is not supported on cgroups V1 rootless systems. +@@option device-write-iops @@option disable-content-trust @@ -307,42 +289,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 999297f5e1..24edc44ece 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 @@option gidmap.pod diff --git a/docs/source/markdown/podman-pod-create.1.md.in b/docs/source/markdown/podman-pod-create.1.md.in index 2f8bcc31c8..35d06fa004 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 c4df88e3ba..b63c37e6f8 100644 --- a/docs/source/markdown/podman-run.1.md.in +++ b/docs/source/markdown/podman-run.1.md.in @@ -180,29 +180,13 @@ device. The devices that Podman will load modules when necessary are: @@option device-cgroup-rule -#### **--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 -This option is not supported on cgroups V1 rootless systems. +@@option device-write-bps -#### **--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* - -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. - -#### **--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 @@option disable-content-trust @@ -335,33 +319,9 @@ 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). - -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**=*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. +@@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-update.1.md.in b/docs/source/markdown/podman-update.1.md.in new file mode 100644 index 0000000000..2928379f33 --- /dev/null +++ b/docs/source/markdown/podman-update.1.md.in @@ -0,0 +1,78 @@ +% 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 subset 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 means that this command can only be executed on an already running container and the changes made will be erased the next time the container is stopped and restarted, this is to ensure immutability. +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.container + +@@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 + + +## EXAMPLEs + +update a container with a new cpu quota and period +``` +podman update --cpus=5 myCtr +``` + +update a container with all available options for cgroups v2 +``` +podman 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 +``` + +update a container with all available options for cgroups v1 +``` +podman update --cpus 5 --cpuset-cpus 0 --cpu-shares 123 --cpuset-mems 0 --memory 1G --memory-swap 2G --memory-reservation 2G --memory-swappiness 50 ctrID +``` + +## 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 d1192b6d22..8c3af25610 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 2ff4bfe081..f88e38ce14 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 7d390ec211..32674235a8 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -2352,3 +2352,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 70053db1b2..e5b9a0dcd4 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 b96f92d3aa..cc65e12610 100644 --- a/libpod/oci_conmon_common.go +++ b/libpod/oci_conmon_common.go @@ -307,6 +307,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 2ab2b4577b..bbf2957ff7 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 5d85d40090..d1460569f3 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 93a508b393..3de9b06e99 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 aab905878e..bb416d9f41 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 8aba4ea05b..41baf54180 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 0000000000..7cda7c3064 --- /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 91ccdc2b28..47225f25c5 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 6a766eb844..69adc97325 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 33ca2c8073..b672434d8e 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 0a8e5bc2fc..dfa3c5ba04 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -1715,3 +1715,27 @@ 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.WeightDevices(updateOptions.Specgen) + if err != nil { + return "", err + } + 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 023bee4306..68ca788b8f 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -1024,3 +1024,16 @@ 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.WeightDevices(updateOptions.Specgen) + if err != nil { + return "", err + } + 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 d57efa0d1b..46b7a2dc22 100644 --- a/pkg/specgen/generate/container.go +++ b/pkg/specgen/generate/container.go @@ -21,7 +21,6 @@ import ( spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/openshift/imagebuilder" "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) { @@ -518,75 +517,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 819800176e..4d5ac22adb 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 d6063b9a0d..14d390e49f 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 } @@ -53,17 +53,11 @@ func MakePod(p *entities.PodSpec, rt *libpod.Runtime) (*libpod.Pod, error) { p.PodSpecGen.ResourceLimits.BlockIO = p.PodSpecGen.InfraContainerSpec.ResourceLimits.BlockIO } - weightDevices, err := WeightDevices(p.PodSpecGen.InfraContainerSpec.WeightDevice) + err = specgen.WeightDevices(p.PodSpecGen.InfraContainerSpec) if err != nil { return nil, err } - - if p.PodSpecGen.ResourceLimits != nil && len(weightDevices) > 0 { - if p.PodSpecGen.ResourceLimits.BlockIO == nil { - p.PodSpecGen.ResourceLimits.BlockIO = &specs.LinuxBlockIO{} - } - p.PodSpecGen.ResourceLimits.BlockIO.WeightDevice = weightDevices - } + p.PodSpecGen.ResourceLimits = p.PodSpecGen.InfraContainerSpec.ResourceLimits } options, err := createPodOptions(&p.PodSpecGen) diff --git a/pkg/specgen/utils.go b/pkg/specgen/utils.go new file mode 100644 index 0000000000..dc9127bb31 --- /dev/null +++ b/pkg/specgen/utils.go @@ -0,0 +1,14 @@ +//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 +} + +// WeightDevices cannot be called on non-linux OS' due to importing unix functions +func WeightDevices(s *SpecGenerator) error { + return nil +} diff --git a/pkg/specgen/utils_linux.go b/pkg/specgen/utils_linux.go new file mode 100644 index 0000000000..d8e4cbae37 --- /dev/null +++ b/pkg/specgen/utils_linux.go @@ -0,0 +1,103 @@ +//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 +} + +func WeightDevices(specgen *SpecGenerator) error { + devs := []spec.LinuxWeightDevice{} + if specgen.ResourceLimits == nil { + specgen.ResourceLimits = &spec.LinuxResources{} + } + for k, v := range specgen.WeightDevice { + statT := unix.Stat_t{} + if err := unix.Stat(k, &statT); err != nil { + return fmt.Errorf("failed to inspect '%s' in --blkio-weight-device: %w", k, err) + } + dev := new(spec.LinuxWeightDevice) + dev.Major = (int64(unix.Major(uint64(statT.Rdev)))) //nolint: unconvert + dev.Minor = (int64(unix.Minor(uint64(statT.Rdev)))) //nolint: unconvert + dev.Weight = v.Weight + devs = append(devs, *dev) + if specgen.ResourceLimits.BlockIO == nil { + specgen.ResourceLimits.BlockIO = &spec.LinuxBlockIO{} + } + specgen.ResourceLimits.BlockIO.WeightDevice = devs + } + return nil +} diff --git a/pkg/specgenutil/specgen.go b/pkg/specgenutil/specgen.go index d0e09fe725..439a13385b 100644 --- a/pkg/specgenutil/specgen.go +++ b/pkg/specgenutil/specgen.go @@ -507,44 +507,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 253bb2e454..9ace46b8b6 100644 --- a/test/apiv2/20-containers.at +++ b/test/apiv2/20-containers.at @@ -547,6 +547,21 @@ t GET libpod/containers/$cname/json 200 \ .ImageName=$IMAGE \ .Name=$cname +if root; then + podman run -dt --name=updateCtr alpine + echo '{"Memory":{"Limit":500000}, "CPU":{"Shares":123}}' >${TMPD}/update.json + t POST libpod/containers/updateCtr/update ${TMPD}/update.json 201 + + # Verify + echo '{ "AttachStdout":true,"Cmd":["cat","/sys/fs/cgroup/cpu.weight"]}' >${TMPD}/exec.json + t POST containers/updateCtr/exec ${TMPD}/exec.json 201 .Id~[0-9a-f]\\{64\\} + eid=$(jq -r '.Id' <<<"$output") + # 002 is the byte length + t POST exec/$eid/start 200 $'\001\0025' + + 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 a6cd93a1af..25596a9b7b 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 0000000000..97dadd04c6 --- /dev/null +++ b/test/e2e/update_test.go @@ -0,0 +1,200 @@ +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)) + + // checking cpu quota from --cpus + 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")) + + // checking cpuset-cpus + 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(Equal("0")) + + // checking cpuset-mems + 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(Equal("0")) + + // checking memory limit + 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")) + + // checking memory-swap + 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")) + + // checking cpu-shares + 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", "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() + + // checking cpu quota and period + 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")) + + // checking blkio weight + 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")) + + // checking device-read/write-bps/iops + 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")) + + // checking cpuset-cpus + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpuset.cpus"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(Equal("0")) + + // checking cpuset-mems + session = podmanTest.Podman([]string{"exec", "-it", ctrID, "cat", "/sys/fs/cgroup/cpuset.mems"}) + session.WaitWithDefaultTimeout() + Expect(session).Should(Exit(0)) + Expect(session.OutputToString()).Should(Equal("0")) + + // checking memory limit + 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")) + + // checking memory-swap + 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")) + + // checking cpu-shares + 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")) + }) +})