Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix device limitations in podman-remote update on remote systems #24760

Merged
merged 1 commit into from
Dec 18, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 28 additions & 16 deletions cmd/podman/containers/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -113,6 +113,16 @@ func GetChangedHealthCheckConfiguration(cmd *cobra.Command, vals *entities.Conta
return updateHealthCheckConfig
}

func GetChangedDeviceLimits(s *specgen.SpecGenerator) *define.UpdateContainerDevicesLimits {
updateDevicesLimits := define.UpdateContainerDevicesLimits{}
updateDevicesLimits.SetBlkIOWeightDevice(s.WeightDevice)
updateDevicesLimits.SetDeviceReadBPs(s.ThrottleReadBpsDevice)
updateDevicesLimits.SetDeviceWriteBPs(s.ThrottleWriteBpsDevice)
updateDevicesLimits.SetDeviceReadIOPs(s.ThrottleReadIOPSDevice)
updateDevicesLimits.SetDeviceWriteIOPs(s.ThrottleWriteIOPSDevice)
return &updateDevicesLimits
}

func update(cmd *cobra.Command, args []string) error {
var err error
// use a specgen since this is the easiest way to hold resource info
Expand All @@ -124,33 +134,35 @@ func update(cmd *cobra.Command, args []string) error {
return err
}

if updateOpts.Restart != "" {
policy, retries, err := util.ParseRestartPolicy(updateOpts.Restart)
if err != nil {
return err
}
s.RestartPolicy = policy
if policy == define.RestartPolicyOnFailure {
s.RestartRetries = &retries
}
}

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

healthCheckConfig := GetChangedHealthCheckConfiguration(cmd, &updateOpts)
if err != nil {
return err
if s.ResourceLimits == nil {
s.ResourceLimits = &specs.LinuxResources{}
}

healthCheckConfig := GetChangedHealthCheckConfiguration(cmd, &updateOpts)

opts := &entities.ContainerUpdateOptions{
NameOrID: strings.TrimPrefix(args[0], "/"),
Specgen: s,
Resources: s.ResourceLimits,
ChangedHealthCheckConfiguration: &healthCheckConfig,
DevicesLimits: GetChangedDeviceLimits(s),
}

if cmd.Flags().Changed("restart") {
policy, retries, err := util.ParseRestartPolicy(updateOpts.Restart)
if err != nil {
return err
}
opts.RestartPolicy = &policy
if policy == define.RestartPolicyOnFailure {
opts.RestartRetries = &retries
}
}

rep, err := registry.ContainerEngine().ContainerUpdate(context.Background(), opts)
if err != nil {
return err
Expand Down
110 changes: 110 additions & 0 deletions libpod/define/container.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package define

import (
"fmt"

"github.com/opencontainers/runtime-spec/specs-go"
)

// Valid restart policy types.
Expand Down Expand Up @@ -64,3 +66,111 @@ const (
// a Job kube yaml spec
K8sKindJob = "job"
)

type WeightDevice struct {
Path string
Weight uint16
}

type ThrottleDevice struct {
Path string
Rate uint64
}

type UpdateContainerDevicesLimits struct {
// Block IO weight (relative device weight) in the form:
// ```[{"Path": "device_path", "Weight": weight}]```
BlkIOWeightDevice []WeightDevice `json:",omitempty"`
// Limit read rate (bytes per second) from a device, in the form:
// ```[{"Path": "device_path", "Rate": rate}]```
DeviceReadBPs []ThrottleDevice `json:",omitempty"`
// Limit write rate (bytes per second) to a device, in the form:
// ```[{"Path": "device_path", "Rate": rate}]```
DeviceWriteBPs []ThrottleDevice `json:",omitempty"`
// Limit read rate (IO per second) from a device, in the form:
// ```[{"Path": "device_path", "Rate": rate}]```
DeviceReadIOPs []ThrottleDevice `json:",omitempty"`
// Limit write rate (IO per second) to a device, in the form:
// ```[{"Path": "device_path", "Rate": rate}]```
DeviceWriteIOPs []ThrottleDevice `json:",omitempty"`
}

func (d *WeightDevice) addToLinuxWeightDevice(wd map[string]specs.LinuxWeightDevice) {
wd[d.Path] = specs.LinuxWeightDevice{
Weight: &d.Weight,
LeafWeight: nil,
}
}

func LinuxWeightDeviceToWeightDevice(path string, d specs.LinuxWeightDevice) WeightDevice {
return WeightDevice{Path: path, Weight: *d.Weight}
}

func (d *ThrottleDevice) addToLinuxThrottleDevice(td map[string]specs.LinuxThrottleDevice) {
td[d.Path] = specs.LinuxThrottleDevice{Rate: d.Rate}
}

func LinuxThrottleDevicesToThrottleDevices(path string, d specs.LinuxThrottleDevice) ThrottleDevice {
return ThrottleDevice{Path: path, Rate: d.Rate}
}

func (u *UpdateContainerDevicesLimits) SetBlkIOWeightDevice(wd map[string]specs.LinuxWeightDevice) {
for path, dev := range wd {
u.BlkIOWeightDevice = append(u.BlkIOWeightDevice, LinuxWeightDeviceToWeightDevice(path, dev))
}
}

func copyLinuxThrottleDevicesFromMapToThrottleDevicesArray(source map[string]specs.LinuxThrottleDevice, dest []ThrottleDevice) []ThrottleDevice {
for path, dev := range source {
dest = append(dest, LinuxThrottleDevicesToThrottleDevices(path, dev))
}
return dest
}

func (u *UpdateContainerDevicesLimits) SetDeviceReadBPs(td map[string]specs.LinuxThrottleDevice) {
u.DeviceReadBPs = copyLinuxThrottleDevicesFromMapToThrottleDevicesArray(td, u.DeviceReadBPs)
}

func (u *UpdateContainerDevicesLimits) SetDeviceWriteBPs(td map[string]specs.LinuxThrottleDevice) {
u.DeviceWriteBPs = copyLinuxThrottleDevicesFromMapToThrottleDevicesArray(td, u.DeviceWriteBPs)
}

func (u *UpdateContainerDevicesLimits) SetDeviceReadIOPs(td map[string]specs.LinuxThrottleDevice) {
u.DeviceReadIOPs = copyLinuxThrottleDevicesFromMapToThrottleDevicesArray(td, u.DeviceReadIOPs)
}

func (u *UpdateContainerDevicesLimits) SetDeviceWriteIOPs(td map[string]specs.LinuxThrottleDevice) {
u.DeviceWriteIOPs = copyLinuxThrottleDevicesFromMapToThrottleDevicesArray(td, u.DeviceWriteIOPs)
}

func (u *UpdateContainerDevicesLimits) GetMapOfLinuxWeightDevice() map[string]specs.LinuxWeightDevice {
wd := make(map[string]specs.LinuxWeightDevice)
for _, dev := range u.BlkIOWeightDevice {
dev.addToLinuxWeightDevice(wd)
}
return wd
}

func getMapOfLinuxThrottleDevices(source []ThrottleDevice) map[string]specs.LinuxThrottleDevice {
td := make(map[string]specs.LinuxThrottleDevice)
for _, dev := range source {
dev.addToLinuxThrottleDevice(td)
}
return td
}

func (u *UpdateContainerDevicesLimits) GetMapOfDeviceReadBPs() map[string]specs.LinuxThrottleDevice {
return getMapOfLinuxThrottleDevices(u.DeviceReadBPs)
}

func (u *UpdateContainerDevicesLimits) GetMapOfDeviceWriteBPs() map[string]specs.LinuxThrottleDevice {
return getMapOfLinuxThrottleDevices(u.DeviceWriteBPs)
}

func (u *UpdateContainerDevicesLimits) GetMapOfDeviceReadIOPs() map[string]specs.LinuxThrottleDevice {
return getMapOfLinuxThrottleDevices(u.DeviceReadIOPs)
}

func (u *UpdateContainerDevicesLimits) GetMapOfDeviceWriteIOPs() map[string]specs.LinuxThrottleDevice {
return getMapOfLinuxThrottleDevices(u.DeviceWriteIOPs)
}
10 changes: 9 additions & 1 deletion pkg/api/handlers/libpod/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import (
api "github.com/containers/podman/v5/pkg/api/types"
"github.com/containers/podman/v5/pkg/domain/entities"
"github.com/containers/podman/v5/pkg/domain/infra/abi"
"github.com/containers/podman/v5/pkg/specgenutil"
"github.com/containers/podman/v5/pkg/util"
"github.com/gorilla/schema"
"github.com/sirupsen/logrus"
Expand Down Expand Up @@ -447,7 +448,14 @@ func UpdateContainer(w http.ResponseWriter, r *http.Request) {
utils.Error(w, http.StatusInternalServerError, fmt.Errorf("decode(): %w", err))
return
}
err = ctr.Update(&options.LinuxResources, restartPolicy, restartRetries, &options.UpdateHealthCheckConfig)

resourceLimits, err := specgenutil.UpdateMajorAndMinorNumbers(&options.LinuxResources, &options.UpdateContainerDevicesLimits)
if err != nil {
utils.InternalServerError(w, err)
return
}

err = ctr.Update(resourceLimits, restartPolicy, restartRetries, &options.UpdateHealthCheckConfig)
if err != nil {
utils.InternalServerError(w, err)
return
Expand Down
6 changes: 5 additions & 1 deletion pkg/api/handlers/swagger/responses.go
Original file line number Diff line number Diff line change
Expand Up @@ -324,9 +324,13 @@ type containerCreateResponse struct {
Body entities.ContainerCreateResponse
}

// Update container
// swagger:response
type containerUpdateResponse struct {
// in:body
ID string
Body struct {
ID string
}
}

// Wait container
Expand Down
1 change: 1 addition & 0 deletions pkg/api/handlers/types.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,7 @@ type LibpodContainersRmReport struct {
type UpdateEntities struct {
specs.LinuxResources
define.UpdateHealthCheckConfig
define.UpdateContainerDevicesLimits
}

type Info struct {
Expand Down
5 changes: 2 additions & 3 deletions pkg/api/server/register_containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1804,9 +1804,8 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error {
// produces:
// - application/json
// responses:
// responses:
// 201:
// $ref: "#/responses/containerUpdateResponse"
// 201:
// $ref: "#/responses/containerUpdateResponse"
// 400:
// $ref: "#/responses/badParamError"
// 404:
Expand Down
13 changes: 7 additions & 6 deletions pkg/bindings/containers/update.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,16 @@ func Update(ctx context.Context, options *types.ContainerUpdateOptions) (string,
}

params := url.Values{}
if options.Specgen.RestartPolicy != "" {
params.Set("restartPolicy", options.Specgen.RestartPolicy)
if options.Specgen.RestartRetries != nil {
params.Set("restartRetries", strconv.Itoa(int(*options.Specgen.RestartRetries)))
if options.RestartPolicy != nil {
params.Set("restartPolicy", *options.RestartPolicy)
if options.RestartRetries != nil {
params.Set("restartRetries", strconv.Itoa(int(*options.RestartRetries)))
}
}
updateEntities := &handlers.UpdateEntities{
LinuxResources: *options.Specgen.ResourceLimits,
UpdateHealthCheckConfig: *options.ChangedHealthCheckConfiguration,
LinuxResources: *options.Resources,
UpdateHealthCheckConfig: *options.ChangedHealthCheckConfiguration,
UpdateContainerDevicesLimits: *options.DevicesLimits,
}
requestData, err := jsoniter.MarshalToString(updateEntities)
if err != nil {
Expand Down
46 changes: 45 additions & 1 deletion pkg/domain/entities/types/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package types
import (
"github.com/containers/podman/v5/libpod/define"
"github.com/containers/podman/v5/pkg/specgen"
"github.com/opencontainers/runtime-spec/specs-go"
)

type ContainerCopyFunc func() error
Expand Down Expand Up @@ -36,7 +37,50 @@ type ContainerStatsReport struct {
}

type ContainerUpdateOptions struct {
NameOrID string
NameOrID string
// This individual items of Specgen are used to update container configuration:
// - ResourceLimits
// - RestartPolicy
// - RestartRetries
//
// Deprecated: Specgen should not be used to change the container configuration.
// Specgen is processed first, so values will be overwritten by values from other fields.
//
// To change configuration use other fields in ContainerUpdateOptions struct:
// - Resources to change resource configuration
// - DevicesLimits to Limit device
// - RestartPolicy to change restart policy
// - RestartRetries to change restart retries
Specgen *specgen.SpecGenerator
Resources *specs.LinuxResources
DevicesLimits *define.UpdateContainerDevicesLimits
ChangedHealthCheckConfiguration *define.UpdateHealthCheckConfig
RestartPolicy *string
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to define this *string? It seems pretty clear to me that string should work fine too. Just leave the string empty if it should not be changed sounds easy enough. I don't see what the extra pointer offers here? Allowing user to set the restart policy to an empty string seems pointless as this is not valid?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm sure an empty string would work, but the intent was to be consistent in the representation of the unset value throughout the structure.

RestartRetries *uint
}

func (u *ContainerUpdateOptions) ProcessSpecgen() {
if u.Specgen == nil {
return
}

if u.Resources == nil {
u.Resources = u.Specgen.ResourceLimits
}

if u.DevicesLimits == nil {
u.DevicesLimits = new(define.UpdateContainerDevicesLimits)
u.DevicesLimits.SetBlkIOWeightDevice(u.Specgen.WeightDevice)
u.DevicesLimits.SetDeviceReadBPs(u.Specgen.ThrottleReadBpsDevice)
u.DevicesLimits.SetDeviceWriteBPs(u.Specgen.ThrottleWriteBpsDevice)
u.DevicesLimits.SetDeviceReadIOPs(u.Specgen.ThrottleReadIOPSDevice)
u.DevicesLimits.SetDeviceWriteIOPs(u.Specgen.ThrottleWriteIOPSDevice)
}

if u.RestartPolicy == nil {
u.RestartPolicy = &u.Specgen.RestartPolicy
}
if u.RestartRetries == nil {
u.RestartRetries = u.Specgen.RestartRetries
}
}
17 changes: 5 additions & 12 deletions pkg/domain/infra/abi/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1792,14 +1792,7 @@ func (ic *ContainerEngine) ContainerClone(ctx context.Context, ctrCloneOpts enti

// 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
}
updateOptions.ProcessSpecgen()
containers, err := getContainers(ic.Libpod, getContainersOptions{names: []string{updateOptions.NameOrID}})
if err != nil {
return "", err
Expand All @@ -1809,12 +1802,12 @@ func (ic *ContainerEngine) ContainerUpdate(ctx context.Context, updateOptions *e
}
container := containers[0].Container

var restartPolicy *string
if updateOptions.Specgen.RestartPolicy != "" {
restartPolicy = &updateOptions.Specgen.RestartPolicy
resourceLimits, err := specgenutil.UpdateMajorAndMinorNumbers(updateOptions.Resources, updateOptions.DevicesLimits)
if err != nil {
return "", err
}

if err = container.Update(updateOptions.Specgen.ResourceLimits, restartPolicy, updateOptions.Specgen.RestartRetries, updateOptions.ChangedHealthCheckConfiguration); err != nil {
if err = container.Update(resourceLimits, updateOptions.RestartPolicy, updateOptions.RestartRetries, updateOptions.ChangedHealthCheckConfiguration); err != nil {
return "", err
}
return containers[0].ID(), nil
Expand Down
9 changes: 1 addition & 8 deletions pkg/domain/infra/tunnel/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -1061,13 +1061,6 @@ func (ic *ContainerEngine) ContainerClone(ctx context.Context, ctrCloneOpts enti

// 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
}
updateOptions.ProcessSpecgen()
return containers.Update(ic.ClientCtx, updateOptions)
}
Loading
Loading