Skip to content

Commit

Permalink
Fix device limitations in podman-remote update on remote systems
Browse files Browse the repository at this point in the history
  • Loading branch information
Honny1 committed Dec 9, 2024
1 parent 992b804 commit 2f31a61
Show file tree
Hide file tree
Showing 12 changed files with 277 additions and 50 deletions.
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
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

0 comments on commit 2f31a61

Please sign in to comment.