implement podman update

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/<CID>/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 <cdoern@redhat.com>
This commit is contained in:
Charlie Doern
2022-08-09 09:25:03 -04:00
parent 0085fbb488
commit 050f3291b9
48 changed files with 879 additions and 287 deletions

View File

@ -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,

View File

@ -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{

View File

@ -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)

View File

@ -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)

View File

@ -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
}

View File

@ -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

View File

@ -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")

View File

@ -27,3 +27,4 @@ podman-run.1.md
podman-search.1.md
podman-stop.1.md
podman-unpause.1.md
podman-update.1.md

View File

@ -0,0 +1 @@
.so man1/podman-update.1

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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.

View File

@ -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: `<number>[<unit>]`, 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: `<number>[<unit>]`, 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**

View File

@ -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

View File

@ -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)
@@option device-read-iops
This option is not supported on cgroups V1 rootless systems.
@@option device-write-bps
#### **--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*
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
@@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*
@@option memory
Memory limit (format: `<number>[<unit>]`, where unit = b (bytes), k (kibibytes), m (mebibytes), or g (gibibytes))
@@option memory-reservation
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: `<number>[<unit>]`, 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.
The format of `LIMIT` is `<number>[<unit>]`. 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.
@@option memory-swap
@@option memory-swappiness

View File

@ -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

View File

@ -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*

View File

@ -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.
@@option memory-reservation
#### **--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.
This option is not supported on cgroups V1 rootless systems.
@@option memory-swap
@@option memory-swappiness

View File

@ -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 <cdoern@redhat.com>

View File

@ -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. |

View File

@ -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

View File

@ -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
}

View File

@ -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

View File

@ -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.

View File

@ -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

View File

@ -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

View File

@ -313,6 +313,11 @@ type containerCreateResponse struct {
Body entities.ContainerCreateResponse
}
type containerUpdateResponse struct {
// in:body
ID string
}
// Wait container
// swagger:response
type containerWaitResponse struct {

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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
}

View File

@ -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

View File

@ -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

View File

@ -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
}

View File

@ -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)
}

View File

@ -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 {

View File

@ -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
}

View File

@ -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)

14
pkg/specgen/utils.go Normal file
View File

@ -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
}

103
pkg/specgen/utils_linux.go Normal file
View File

@ -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
}

View File

@ -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
}

View File

@ -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

View File

@ -359,8 +359,6 @@ class ContainerTestCase(APITestCase):
self.assertEqual(2000, out["HostConfig"]["MemorySwap"])
self.assertEqual(1000, out["HostConfig"]["Memory"])
def execute_process(cmd):
return subprocess.run(
cmd,

200
test/e2e/update_test.go Normal file
View File

@ -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"))
})
})