Add support for updating restart policy
This is something Docker does, and we did not do until now. Most difficult/annoying part was the REST API, where I did not really want to modify the struct being sent, so I made the new restart policy parameters query parameters instead. Testing was also a bit annoying, because testing restart policy always is. Signed-off-by: Matt Heon <mheon@redhat.com>
This commit is contained in:
		
							parent
							
								
									ddea30e40e
								
							
						
					
					
						commit
						482ef7bfcf
					
				|  | @ -643,7 +643,8 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, | ||||||
| 			`If a container with the same name exists, replace it`, | 			`If a container with the same name exists, replace it`, | ||||||
| 		) | 		) | ||||||
| 	} | 	} | ||||||
| 	if mode == entities.InfraMode || (mode == entities.CreateMode) { // infra container flags, create should also pick these up
 | 	// Restart is allowed for created, updated, and infra ctr
 | ||||||
|  | 	if mode == entities.InfraMode || mode == entities.CreateMode || mode == entities.UpdateMode { | ||||||
| 		restartFlagName := "restart" | 		restartFlagName := "restart" | ||||||
| 		createFlags.StringVar( | 		createFlags.StringVar( | ||||||
| 			&cf.Restart, | 			&cf.Restart, | ||||||
|  | @ -651,7 +652,8 @@ func DefineCreateFlags(cmd *cobra.Command, cf *entities.ContainerCreateOptions, | ||||||
| 			`Restart policy to apply when a container exits ("always"|"no"|"never"|"on-failure"|"unless-stopped")`, | 			`Restart policy to apply when a container exits ("always"|"no"|"never"|"on-failure"|"unless-stopped")`, | ||||||
| 		) | 		) | ||||||
| 		_ = cmd.RegisterFlagCompletionFunc(restartFlagName, AutocompleteRestartOption) | 		_ = cmd.RegisterFlagCompletionFunc(restartFlagName, AutocompleteRestartOption) | ||||||
| 
 | 	} | ||||||
|  | 	if mode == entities.InfraMode || (mode == entities.CreateMode) { // infra container flags, create should also pick these up
 | ||||||
| 		shmSizeFlagName := "shm-size" | 		shmSizeFlagName := "shm-size" | ||||||
| 		createFlags.String( | 		createFlags.String( | ||||||
| 			shmSizeFlagName, shmSize(), | 			shmSizeFlagName, shmSize(), | ||||||
|  |  | ||||||
|  | @ -7,9 +7,11 @@ import ( | ||||||
| 
 | 
 | ||||||
| 	"github.com/containers/podman/v5/cmd/podman/common" | 	"github.com/containers/podman/v5/cmd/podman/common" | ||||||
| 	"github.com/containers/podman/v5/cmd/podman/registry" | 	"github.com/containers/podman/v5/cmd/podman/registry" | ||||||
|  | 	"github.com/containers/podman/v5/libpod/define" | ||||||
| 	"github.com/containers/podman/v5/pkg/domain/entities" | 	"github.com/containers/podman/v5/pkg/domain/entities" | ||||||
| 	"github.com/containers/podman/v5/pkg/specgen" | 	"github.com/containers/podman/v5/pkg/specgen" | ||||||
| 	"github.com/containers/podman/v5/pkg/specgenutil" | 	"github.com/containers/podman/v5/pkg/specgenutil" | ||||||
|  | 	"github.com/containers/podman/v5/pkg/util" | ||||||
| 	"github.com/opencontainers/runtime-spec/specs-go" | 	"github.com/opencontainers/runtime-spec/specs-go" | ||||||
| 	"github.com/spf13/cobra" | 	"github.com/spf13/cobra" | ||||||
| ) | ) | ||||||
|  | @ -70,6 +72,17 @@ func update(cmd *cobra.Command, args []string) error { | ||||||
| 		return err | 		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.
 | 	// we need to pass the whole specgen since throttle devices are parsed later due to cross compat.
 | ||||||
| 	s.ResourceLimits, err = specgenutil.GetResources(s, &updateOpts) | 	s.ResourceLimits, err = specgenutil.GetResources(s, &updateOpts) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
|  |  | ||||||
|  | @ -1,5 +1,5 @@ | ||||||
| ####> This option file is used in: | ####> This option file is used in: | ||||||
| ####>   podman create, pod clone, pod create, run | ####>   podman create, pod clone, pod create, run, update | ||||||
| ####> If file is edited, make sure the changes | ####> If file is edited, make sure the changes | ||||||
| ####> are applicable to all of those. | ####> are applicable to all of those. | ||||||
| #### **--restart**=*policy* | #### **--restart**=*policy* | ||||||
|  |  | ||||||
|  | @ -53,6 +53,8 @@ The currently supported options are a subset of the podman create/run resource l | ||||||
| 
 | 
 | ||||||
| @@option pids-limit | @@option pids-limit | ||||||
| 
 | 
 | ||||||
|  | @@option restart | ||||||
|  | 
 | ||||||
| 
 | 
 | ||||||
| ## EXAMPLEs | ## EXAMPLEs | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -119,8 +119,10 @@ func (c *Container) Start(ctx context.Context, recursive bool) (finalErr error) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Update updates the given container.
 | // Update updates the given container.
 | ||||||
| // only the cgroup config can be updated and therefore only a linux resource spec is passed.
 | // Either resource limits or restart policy can be updated.
 | ||||||
| func (c *Container) Update(res *spec.LinuxResources) error { | // Either resourcs or restartPolicy must not be nil.
 | ||||||
|  | // If restartRetries is not nil, restartPolicy must be set and must be "on-failure".
 | ||||||
|  | func (c *Container) Update(resources *spec.LinuxResources, restartPolicy *string, restartRetries *uint) error { | ||||||
| 	if !c.batched { | 	if !c.batched { | ||||||
| 		c.lock.Lock() | 		c.lock.Lock() | ||||||
| 		defer c.lock.Unlock() | 		defer c.lock.Unlock() | ||||||
|  | @ -134,7 +136,7 @@ func (c *Container) Update(res *spec.LinuxResources) error { | ||||||
| 		return fmt.Errorf("container %s is being removed, cannot update: %w", c.ID(), define.ErrCtrStateInvalid) | 		return fmt.Errorf("container %s is being removed, cannot update: %w", c.ID(), define.ErrCtrStateInvalid) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return c.update(res) | 	return c.update(resources, restartPolicy, restartRetries) | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // StartAndAttach starts a container and attaches to it.
 | // StartAndAttach starts a container and attaches to it.
 | ||||||
|  |  | ||||||
|  | @ -467,6 +467,9 @@ func (c *Container) generateInspectContainerHostConfig(ctrSpec *spec.Spec, named | ||||||
| 
 | 
 | ||||||
| 	restartPolicy := new(define.InspectRestartPolicy) | 	restartPolicy := new(define.InspectRestartPolicy) | ||||||
| 	restartPolicy.Name = c.config.RestartPolicy | 	restartPolicy.Name = c.config.RestartPolicy | ||||||
|  | 	if restartPolicy.Name == "" { | ||||||
|  | 		restartPolicy.Name = define.RestartPolicyNo | ||||||
|  | 	} | ||||||
| 	restartPolicy.MaximumRetryCount = c.config.RestartRetries | 	restartPolicy.MaximumRetryCount = c.config.RestartRetries | ||||||
| 	hostConfig.RestartPolicy = restartPolicy | 	hostConfig.RestartPolicy = restartPolicy | ||||||
| 	if c.config.NoCgroups { | 	if c.config.NoCgroups { | ||||||
|  |  | ||||||
|  | @ -2514,22 +2514,55 @@ func (c *Container) extractSecretToCtrStorage(secr *ContainerSecret) error { | ||||||
| 	return nil | 	return nil | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // Update a container's resources after creation
 | // Update a container's resources or restart policy after creation.
 | ||||||
| func (c *Container) update(resources *spec.LinuxResources) error { | // At least one of resources or restartPolicy must not be nil.
 | ||||||
| 	oldResources := c.config.Spec.Linux.Resources | func (c *Container) update(resources *spec.LinuxResources, restartPolicy *string, restartRetries *uint) error { | ||||||
|  | 	if resources == nil && restartPolicy == nil { | ||||||
|  | 		return fmt.Errorf("must provide at least one of resources and restartPolicy to update a container: %w", define.ErrInvalidArg) | ||||||
|  | 	} | ||||||
|  | 	if restartRetries != nil && restartPolicy == nil { | ||||||
|  | 		return fmt.Errorf("must provide restart policy if updating restart retries: %w", define.ErrInvalidArg) | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
|  | 	oldResources := c.config.Spec.Linux.Resources | ||||||
|  | 	oldRestart := c.config.RestartPolicy | ||||||
|  | 	oldRetries := c.config.RestartRetries | ||||||
|  | 
 | ||||||
|  | 	if restartPolicy != nil { | ||||||
|  | 		if err := define.ValidateRestartPolicy(*restartPolicy); err != nil { | ||||||
|  | 			return err | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		if restartRetries != nil { | ||||||
|  | 			if *restartPolicy != define.RestartPolicyOnFailure { | ||||||
|  | 				return fmt.Errorf("cannot set restart policy retries unless policy is on-failure: %w", define.ErrInvalidArg) | ||||||
|  | 			} | ||||||
|  | 		} | ||||||
|  | 
 | ||||||
|  | 		c.config.RestartPolicy = *restartPolicy | ||||||
|  | 		if restartRetries != nil { | ||||||
|  | 			c.config.RestartRetries = *restartRetries | ||||||
|  | 		} else { | ||||||
|  | 			c.config.RestartRetries = 0 | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if resources != nil { | ||||||
| 		if c.config.Spec.Linux == nil { | 		if c.config.Spec.Linux == nil { | ||||||
| 			c.config.Spec.Linux = new(spec.Linux) | 			c.config.Spec.Linux = new(spec.Linux) | ||||||
| 		} | 		} | ||||||
| 		c.config.Spec.Linux.Resources = resources | 		c.config.Spec.Linux.Resources = resources | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil { | 	if err := c.runtime.state.SafeRewriteContainerConfig(c, "", "", c.config); err != nil { | ||||||
| 		// Assume DB write failed, revert to old resources block
 | 		// Assume DB write failed, revert to old resources block
 | ||||||
| 		c.config.Spec.Linux.Resources = oldResources | 		c.config.Spec.Linux.Resources = oldResources | ||||||
|  | 		c.config.RestartPolicy = oldRestart | ||||||
|  | 		c.config.RestartRetries = oldRetries | ||||||
| 		return err | 		return err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning, define.ContainerStatePaused) { | 	if c.ensureState(define.ContainerStateCreated, define.ContainerStateRunning, define.ContainerStatePaused) && resources != nil { | ||||||
| 		// So `podman inspect` on running containers sources its OCI spec from disk.
 | 		// So `podman inspect` on running containers sources its OCI spec from disk.
 | ||||||
| 		// To keep inspect accurate we need to update the on-disk OCI spec.
 | 		// To keep inspect accurate we need to update the on-disk OCI spec.
 | ||||||
| 		onDiskSpec, err := c.specFromState() | 		onDiskSpec, err := c.specFromState() | ||||||
|  |  | ||||||
|  | @ -1,5 +1,9 @@ | ||||||
| package define | package define | ||||||
| 
 | 
 | ||||||
|  | import ( | ||||||
|  | 	"fmt" | ||||||
|  | ) | ||||||
|  | 
 | ||||||
| // Valid restart policy types.
 | // Valid restart policy types.
 | ||||||
| const ( | const ( | ||||||
| 	// RestartPolicyNone indicates that no restart policy has been requested
 | 	// RestartPolicyNone indicates that no restart policy has been requested
 | ||||||
|  | @ -27,6 +31,16 @@ var RestartPolicyMap = map[string]string{ | ||||||
| 	RestartPolicyUnlessStopped: RestartPolicyUnlessStopped, | 	RestartPolicyUnlessStopped: RestartPolicyUnlessStopped, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Validate that the given string is a valid restart policy.
 | ||||||
|  | func ValidateRestartPolicy(policy string) error { | ||||||
|  | 	switch policy { | ||||||
|  | 	case RestartPolicyNone, RestartPolicyNo, RestartPolicyOnFailure, RestartPolicyAlways, RestartPolicyUnlessStopped: | ||||||
|  | 		return nil | ||||||
|  | 	default: | ||||||
|  | 		return fmt.Errorf("%q is not a valid restart policy: %w", policy, ErrInvalidArg) | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // InitContainerTypes
 | // InitContainerTypes
 | ||||||
| const ( | const ( | ||||||
| 	// AlwaysInitContainer is an init container that runs on each
 | 	// AlwaysInitContainer is an init container that runs on each
 | ||||||
|  |  | ||||||
|  | @ -1392,13 +1392,12 @@ func WithRestartPolicy(policy string) CtrCreateOption { | ||||||
| 			return define.ErrCtrFinalized | 			return define.ErrCtrFinalized | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		switch policy { | 		if err := define.ValidateRestartPolicy(policy); err != nil { | ||||||
| 		case define.RestartPolicyNone, define.RestartPolicyNo, define.RestartPolicyOnFailure, define.RestartPolicyAlways, define.RestartPolicyUnlessStopped: | 			return err | ||||||
| 			ctr.config.RestartPolicy = policy |  | ||||||
| 		default: |  | ||||||
| 			return fmt.Errorf("%q is not a valid restart policy: %w", policy, define.ErrInvalidArg) |  | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
|  | 		ctr.config.RestartPolicy = policy | ||||||
|  | 
 | ||||||
| 		return nil | 		return nil | ||||||
| 	} | 	} | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -774,8 +774,18 @@ func UpdateContainer(w http.ResponseWriter, r *http.Request) { | ||||||
| 		resources.BlockIO.Weight = &options.BlkioWeight | 		resources.BlockIO.Weight = &options.BlkioWeight | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err := ctr.Update(resources); err != nil { | 	// Restart policy
 | ||||||
| 		utils.Error(w, http.StatusInternalServerError, fmt.Errorf("updating resources: %w", err)) | 	localPolicy := string(options.RestartPolicy.Name) | ||||||
|  | 	restartPolicy := &localPolicy | ||||||
|  | 
 | ||||||
|  | 	var restartRetries *uint | ||||||
|  | 	if options.RestartPolicy.MaximumRetryCount != 0 { | ||||||
|  | 		localRetries := uint(options.RestartPolicy.MaximumRetryCount) | ||||||
|  | 		restartRetries = &localRetries | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := ctr.Update(resources, restartPolicy, restartRetries); err != nil { | ||||||
|  | 		utils.Error(w, http.StatusInternalServerError, fmt.Errorf("updating container: %w", err)) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -402,18 +402,46 @@ func InitContainer(w http.ResponseWriter, r *http.Request) { | ||||||
| func UpdateContainer(w http.ResponseWriter, r *http.Request) { | func UpdateContainer(w http.ResponseWriter, r *http.Request) { | ||||||
| 	name := utils.GetName(r) | 	name := utils.GetName(r) | ||||||
| 	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) | 	runtime := r.Context().Value(api.RuntimeKey).(*libpod.Runtime) | ||||||
|  | 	decoder := utils.GetDecoder(r) | ||||||
|  | 	query := struct { | ||||||
|  | 		RestartPolicy  string `schema:"restartPolicy"` | ||||||
|  | 		RestartRetries uint   `schema:"restartRetries"` | ||||||
|  | 	}{ | ||||||
|  | 		// override any golang type defaults
 | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err := decoder.Decode(&query, r.URL.Query()); err != nil { | ||||||
|  | 		utils.Error(w, http.StatusBadRequest, fmt.Errorf("failed to parse parameters for %s: %w", r.URL.String(), err)) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	ctr, err := runtime.LookupContainer(name) | 	ctr, err := runtime.LookupContainer(name) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		utils.ContainerNotFound(w, name, err) | 		utils.ContainerNotFound(w, name, err) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	var restartPolicy *string | ||||||
|  | 	var restartRetries *uint | ||||||
|  | 	if query.RestartPolicy != "" { | ||||||
|  | 		restartPolicy = &query.RestartPolicy | ||||||
|  | 		if query.RestartPolicy == define.RestartPolicyOnFailure { | ||||||
|  | 			restartRetries = &query.RestartRetries | ||||||
|  | 		} else if query.RestartRetries != 0 { | ||||||
|  | 			utils.Error(w, http.StatusBadRequest, errors.New("cannot set restart retries unless restart policy is on-failure")) | ||||||
|  | 			return | ||||||
|  | 		} | ||||||
|  | 	} else if query.RestartRetries != 0 { | ||||||
|  | 		utils.Error(w, http.StatusBadRequest, errors.New("cannot set restart retries unless restart policy is set")) | ||||||
|  | 		return | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	options := &handlers.UpdateEntities{Resources: &specs.LinuxResources{}} | 	options := &handlers.UpdateEntities{Resources: &specs.LinuxResources{}} | ||||||
| 	if err := json.NewDecoder(r.Body).Decode(&options.Resources); err != nil { | 	if err := json.NewDecoder(r.Body).Decode(&options.Resources); err != nil { | ||||||
| 		utils.Error(w, http.StatusInternalServerError, fmt.Errorf("decode(): %w", err)) | 		utils.Error(w, http.StatusInternalServerError, fmt.Errorf("decode(): %w", err)) | ||||||
| 		return | 		return | ||||||
| 	} | 	} | ||||||
| 	err = ctr.Update(options.Resources) | 	err = ctr.Update(options.Resources, restartPolicy, restartRetries) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		utils.InternalServerError(w, err) | 		utils.InternalServerError(w, err) | ||||||
| 		return | 		return | ||||||
|  |  | ||||||
|  | @ -4,6 +4,7 @@ package swagger | ||||||
| import ( | import ( | ||||||
| 	"github.com/containers/podman/v5/pkg/domain/entities" | 	"github.com/containers/podman/v5/pkg/domain/entities" | ||||||
| 	"github.com/docker/docker/api/types" | 	"github.com/docker/docker/api/types" | ||||||
|  | 	"github.com/docker/docker/api/types/container" | ||||||
| ) | ) | ||||||
| 
 | 
 | ||||||
| // Details for creating a volume
 | // Details for creating a volume
 | ||||||
|  | @ -48,3 +49,7 @@ type networkConnectRequestLibpod entities.NetworkConnectOptions | ||||||
| // Network update
 | // Network update
 | ||||||
| // swagger:model
 | // swagger:model
 | ||||||
| type networkUpdateRequestLibpod entities.NetworkUpdateOptions | type networkUpdateRequestLibpod entities.NetworkUpdateOptions | ||||||
|  | 
 | ||||||
|  | // Container update
 | ||||||
|  | // swagger:model
 | ||||||
|  | type containerUpdateRequest container.UpdateConfig | ||||||
|  |  | ||||||
|  | @ -689,9 +689,10 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { | ||||||
| 	//    description: Full or partial ID or full name of the container to rename
 | 	//    description: Full or partial ID or full name of the container to rename
 | ||||||
| 	//  - in: body
 | 	//  - in: body
 | ||||||
| 	//    name: resources
 | 	//    name: resources
 | ||||||
|  | 	//    required: false
 | ||||||
| 	//    description: attributes for updating the container
 | 	//    description: attributes for updating the container
 | ||||||
| 	//    schema:
 | 	//    schema:
 | ||||||
| 	//      $ref: "#/definitions/UpdateConfig"
 | 	//      $ref: "#/definitions/containerUpdateRequest"
 | ||||||
| 	// produces:
 | 	// produces:
 | ||||||
| 	// - application/json
 | 	// - application/json
 | ||||||
| 	// responses:
 | 	// responses:
 | ||||||
|  | @ -1783,8 +1784,18 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { | ||||||
| 	//    type: string
 | 	//    type: string
 | ||||||
| 	//    required: true
 | 	//    required: true
 | ||||||
| 	//    description: Full or partial ID or full name of the container to update
 | 	//    description: Full or partial ID or full name of the container to update
 | ||||||
|  | 	//  - in: query
 | ||||||
|  | 	//    name: restartPolicy
 | ||||||
|  | 	//    type: string
 | ||||||
|  | 	//    required: false
 | ||||||
|  | 	//    description: New restart policy for the container.
 | ||||||
|  | 	//  - in: query
 | ||||||
|  | 	//    name: restartRetries
 | ||||||
|  | 	//    type: integer
 | ||||||
|  | 	//    required: false
 | ||||||
|  | 	//    description: New amount of retries for the container's restart policy. Only allowed if restartPolicy is set to on-failure
 | ||||||
| 	//  - in: body
 | 	//  - in: body
 | ||||||
| 	//    name: resources
 | 	//    name: config
 | ||||||
| 	//    description: attributes for updating the container
 | 	//    description: attributes for updating the container
 | ||||||
| 	//    schema:
 | 	//    schema:
 | ||||||
| 	//      $ref: "#/definitions/UpdateEntities"
 | 	//      $ref: "#/definitions/UpdateEntities"
 | ||||||
|  | @ -1794,6 +1805,8 @@ func (s *APIServer) registerContainersHandlers(r *mux.Router) error { | ||||||
| 	//   responses:
 | 	//   responses:
 | ||||||
| 	//     201:
 | 	//     201:
 | ||||||
| 	//       $ref: "#/responses/containerUpdateResponse"
 | 	//       $ref: "#/responses/containerUpdateResponse"
 | ||||||
|  | 	//   400:
 | ||||||
|  | 	//     $ref: "#/responses/badParamError"
 | ||||||
| 	//   404:
 | 	//   404:
 | ||||||
| 	//     $ref: "#/responses/containerNotFound"
 | 	//     $ref: "#/responses/containerNotFound"
 | ||||||
| 	//   500:
 | 	//   500:
 | ||||||
|  |  | ||||||
|  | @ -3,6 +3,8 @@ package containers | ||||||
| import ( | import ( | ||||||
| 	"context" | 	"context" | ||||||
| 	"net/http" | 	"net/http" | ||||||
|  | 	"net/url" | ||||||
|  | 	"strconv" | ||||||
| 	"strings" | 	"strings" | ||||||
| 
 | 
 | ||||||
| 	"github.com/containers/podman/v5/pkg/bindings" | 	"github.com/containers/podman/v5/pkg/bindings" | ||||||
|  | @ -16,12 +18,20 @@ func Update(ctx context.Context, options *types.ContainerUpdateOptions) (string, | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	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))) | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
| 	resources, err := jsoniter.MarshalToString(options.Specgen.ResourceLimits) | 	resources, err := jsoniter.MarshalToString(options.Specgen.ResourceLimits) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
| 	stringReader := strings.NewReader(resources) | 	stringReader := strings.NewReader(resources) | ||||||
| 	response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/containers/%s/update", nil, nil, options.NameOrID) | 	response, err := conn.DoRequest(ctx, stringReader, http.MethodPost, "/containers/%s/update", params, nil, options.NameOrID) | ||||||
| 	if err != nil { | 	if err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -1767,7 +1767,12 @@ func (ic *ContainerEngine) ContainerUpdate(ctx context.Context, updateOptions *e | ||||||
| 		return "", fmt.Errorf("container not found") | 		return "", fmt.Errorf("container not found") | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	if err = containers[0].Update(updateOptions.Specgen.ResourceLimits); err != nil { | 	var restartPolicy *string | ||||||
|  | 	if updateOptions.Specgen.RestartPolicy != "" { | ||||||
|  | 		restartPolicy = &updateOptions.Specgen.RestartPolicy | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if err = containers[0].Update(updateOptions.Specgen.ResourceLimits, restartPolicy, updateOptions.Specgen.RestartRetries); err != nil { | ||||||
| 		return "", err | 		return "", err | ||||||
| 	} | 	} | ||||||
| 	return containers[0].ID(), nil | 	return containers[0].ID(), nil | ||||||
|  |  | ||||||
|  | @ -619,7 +619,12 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator, pod *l | ||||||
| 		} | 		} | ||||||
| 		restartPolicy = s.RestartPolicy | 		restartPolicy = s.RestartPolicy | ||||||
| 	} | 	} | ||||||
| 	options = append(options, libpod.WithRestartRetries(retries), libpod.WithRestartPolicy(restartPolicy)) | 	if restartPolicy != "" { | ||||||
|  | 		options = append(options, libpod.WithRestartPolicy(restartPolicy)) | ||||||
|  | 	} | ||||||
|  | 	if retries != 0 { | ||||||
|  | 		options = append(options, libpod.WithRestartRetries(retries)) | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	healthCheckSet := false | 	healthCheckSet := false | ||||||
| 	if s.ContainerHealthCheckConfig.HealthConfig != nil { | 	if s.ContainerHealthCheckConfig.HealthConfig != nil { | ||||||
|  |  | ||||||
|  | @ -454,6 +454,31 @@ func (p *PodmanTestIntegration) InspectContainer(name string) []define.InspectCo | ||||||
| 	return session.InspectContainerToJSON() | 	return session.InspectContainerToJSON() | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | // Pull a single field from a container using `podman inspect --format {{ field }}`,
 | ||||||
|  | // and verify it against the given expected value.
 | ||||||
|  | func (p *PodmanTestIntegration) CheckContainerSingleField(name, field, expected string) { | ||||||
|  | 	inspect := p.Podman([]string{"inspect", "--format", fmt.Sprintf("{{ %s }}", field), name}) | ||||||
|  | 	inspect.WaitWithDefaultTimeout() | ||||||
|  | 	ExpectWithOffset(1, inspect).Should(Exit(0)) | ||||||
|  | 	ExpectWithOffset(1, inspect.OutputToString()).To(Equal(expected)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Check that the contents of a single file in the given container matches the expected value.
 | ||||||
|  | func (p *PodmanTestIntegration) CheckFileInContainer(name, filepath, expected string) { | ||||||
|  | 	exec := p.Podman([]string{"exec", name, "cat", filepath}) | ||||||
|  | 	exec.WaitWithDefaultTimeout() | ||||||
|  | 	ExpectWithOffset(1, exec).Should(Exit(0)) | ||||||
|  | 	ExpectWithOffset(1, exec.OutputToString()).To(Equal(expected)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // Check that the contents of a single file in the given container containers the given value.
 | ||||||
|  | func (p *PodmanTestIntegration) CheckFileInContainerSubstring(name, filepath, expected string) { | ||||||
|  | 	exec := p.Podman([]string{"exec", name, "cat", filepath}) | ||||||
|  | 	exec.WaitWithDefaultTimeout() | ||||||
|  | 	ExpectWithOffset(1, exec).Should(Exit(0)) | ||||||
|  | 	ExpectWithOffset(1, exec.OutputToString()).To(ContainSubstring(expected)) | ||||||
|  | } | ||||||
|  | 
 | ||||||
| // StopContainer stops a container with no timeout, ensuring a fast test.
 | // StopContainer stops a container with no timeout, ensuring a fast test.
 | ||||||
| func (p *PodmanTestIntegration) StopContainer(nameOrID string) { | func (p *PodmanTestIntegration) StopContainer(nameOrID string) { | ||||||
| 	stop := p.Podman([]string{"stop", "-t0", nameOrID}) | 	stop := p.Podman([]string{"stop", "-t0", nameOrID}) | ||||||
|  |  | ||||||
|  | @ -35,47 +35,25 @@ var _ = Describe("Podman update", func() { | ||||||
| 		Expect(session).Should(ExitCleanly()) | 		Expect(session).Should(ExitCleanly()) | ||||||
| 
 | 
 | ||||||
| 		// checking cpu quota from --cpus
 | 		// checking cpu quota from --cpus
 | ||||||
| 		session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"}) | 		podmanTest.CheckFileInContainerSubstring(ctrID, "/sys/fs/cgroup/cpu/cpu.cfs_quota_us", "500000") | ||||||
| 		session.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(session).Should(ExitCleanly()) |  | ||||||
| 		Expect(session.OutputToString()).Should(ContainSubstring("500000")) |  | ||||||
| 
 | 
 | ||||||
| 		// checking cpuset-cpus
 | 		// checking cpuset-cpus
 | ||||||
| 		session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/cpuset/cpuset.cpus"}) | 		podmanTest.CheckFileInContainer(ctrID, "/sys/fs/cgroup/cpuset/cpuset.cpus", "0") | ||||||
| 		session.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(session).Should(ExitCleanly()) |  | ||||||
| 		Expect(session.OutputToString()).Should(Equal("0")) |  | ||||||
| 
 | 
 | ||||||
| 		// checking cpuset-mems
 | 		// checking cpuset-mems
 | ||||||
| 		session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/cpuset/cpuset.mems"}) | 		podmanTest.CheckFileInContainer(ctrID, "/sys/fs/cgroup/cpuset/cpuset.mems", "0") | ||||||
| 		session.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(session).Should(ExitCleanly()) |  | ||||||
| 		Expect(session.OutputToString()).Should(Equal("0")) |  | ||||||
| 
 | 
 | ||||||
| 		// checking memory limit
 | 		// checking memory limit
 | ||||||
| 		session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/memory/memory.limit_in_bytes"}) | 		podmanTest.CheckFileInContainerSubstring(ctrID, "/sys/fs/cgroup/memory/memory.limit_in_bytes", "1073741824") | ||||||
| 		session.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(session).Should(ExitCleanly()) |  | ||||||
| 		Expect(session.OutputToString()).Should(ContainSubstring("1073741824")) |  | ||||||
| 
 | 
 | ||||||
| 		// checking memory-swap
 | 		// checking memory-swap
 | ||||||
| 		session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes"}) | 		podmanTest.CheckFileInContainerSubstring(ctrID, "/sys/fs/cgroup/memory/memory.memsw.limit_in_bytes", "2147483648") | ||||||
| 		session.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(session).Should(ExitCleanly()) |  | ||||||
| 		Expect(session.OutputToString()).Should(ContainSubstring("2147483648")) |  | ||||||
| 
 | 
 | ||||||
| 		// checking cpu-shares
 | 		// checking cpu-shares
 | ||||||
| 		session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/cpu/cpu.shares"}) | 		podmanTest.CheckFileInContainerSubstring(ctrID, "/sys/fs/cgroup/cpu/cpu.shares", "123") | ||||||
| 		session.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(session).Should(ExitCleanly()) |  | ||||||
| 		Expect(session.OutputToString()).Should(ContainSubstring("123")) |  | ||||||
| 
 | 
 | ||||||
| 		// checking pids-limit
 | 		// checking pids-limit
 | ||||||
| 		session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/pids/pids.max"}) | 		podmanTest.CheckFileInContainerSubstring(ctrID, "/sys/fs/cgroup/pids/pids.max", "123") | ||||||
| 		session.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(session).Should(ExitCleanly()) |  | ||||||
| 		Expect(session.OutputToString()).Should(ContainSubstring("123")) |  | ||||||
| 
 |  | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	It("podman update container unspecified pid limit", func() { | 	It("podman update container unspecified pid limit", func() { | ||||||
|  | @ -99,10 +77,7 @@ var _ = Describe("Podman update", func() { | ||||||
| 		ctrID = session.OutputToString() | 		ctrID = session.OutputToString() | ||||||
| 
 | 
 | ||||||
| 		// checking pids-limit was not changed after update when not specified as an option
 | 		// checking pids-limit was not changed after update when not specified as an option
 | ||||||
| 		session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/pids.max"}) | 		podmanTest.CheckFileInContainerSubstring(ctrID, "/sys/fs/cgroup/pids.max", "max") | ||||||
| 		session.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(session).Should(ExitCleanly()) |  | ||||||
| 		Expect(session.OutputToString()).Should(ContainSubstring("max")) |  | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	It("podman update container all options v2", func() { | 	It("podman update container all options v2", func() { | ||||||
|  | @ -138,58 +113,31 @@ var _ = Describe("Podman update", func() { | ||||||
| 		ctrID = session.OutputToString() | 		ctrID = session.OutputToString() | ||||||
| 
 | 
 | ||||||
| 		// checking cpu quota and period
 | 		// checking cpu quota and period
 | ||||||
| 		session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/cpu.max"}) | 		podmanTest.CheckFileInContainerSubstring(ctrID, "/sys/fs/cgroup/cpu.max", "500000") | ||||||
| 		session.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(session).Should(ExitCleanly()) |  | ||||||
| 		Expect(session.OutputToString()).Should(ContainSubstring("500000")) |  | ||||||
| 
 | 
 | ||||||
| 		// checking blkio weight
 | 		// checking blkio weight
 | ||||||
| 		session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/io.bfq.weight"}) | 		podmanTest.CheckFileInContainerSubstring(ctrID, "/sys/fs/cgroup/io.bfq.weight", "123") | ||||||
| 		session.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(session).Should(ExitCleanly()) |  | ||||||
| 		Expect(session.OutputToString()).Should(ContainSubstring("123")) |  | ||||||
| 
 | 
 | ||||||
| 		// checking device-read/write-bps/iops
 | 		// checking device-read/write-bps/iops
 | ||||||
| 		session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/io.max"}) | 		podmanTest.CheckFileInContainerSubstring(ctrID, "/sys/fs/cgroup/io.max", "rbps=10485760 wbps=10485760 riops=1000 wiops=1000") | ||||||
| 		session.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(session).Should(ExitCleanly()) |  | ||||||
| 		Expect(session.OutputToString()).Should(ContainSubstring("rbps=10485760 wbps=10485760 riops=1000 wiops=1000")) |  | ||||||
| 
 | 
 | ||||||
| 		// checking cpuset-cpus
 | 		// checking cpuset-cpus
 | ||||||
| 		session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/cpuset.cpus"}) | 		podmanTest.CheckFileInContainer(ctrID, "/sys/fs/cgroup/cpuset.cpus", "0") | ||||||
| 		session.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(session).Should(ExitCleanly()) |  | ||||||
| 		Expect(session.OutputToString()).Should(Equal("0")) |  | ||||||
| 
 | 
 | ||||||
| 		// checking cpuset-mems
 | 		// checking cpuset-mems
 | ||||||
| 		session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/cpuset.mems"}) | 		podmanTest.CheckFileInContainer(ctrID, "/sys/fs/cgroup/cpuset.mems", "0") | ||||||
| 		session.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(session).Should(ExitCleanly()) |  | ||||||
| 		Expect(session.OutputToString()).Should(Equal("0")) |  | ||||||
| 
 | 
 | ||||||
| 		// checking memory limit
 | 		// checking memory limit
 | ||||||
| 		session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/memory.max"}) | 		podmanTest.CheckFileInContainerSubstring(ctrID, "/sys/fs/cgroup/memory.max", "1073741824") | ||||||
| 		session.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(session).Should(ExitCleanly()) |  | ||||||
| 		Expect(session.OutputToString()).Should(ContainSubstring("1073741824")) |  | ||||||
| 
 | 
 | ||||||
| 		// checking memory-swap
 | 		// checking memory-swap
 | ||||||
| 		session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/memory.swap.max"}) | 		podmanTest.CheckFileInContainerSubstring(ctrID, "/sys/fs/cgroup/memory.swap.max", "1073741824") | ||||||
| 		session.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(session).Should(ExitCleanly()) |  | ||||||
| 		Expect(session.OutputToString()).Should(ContainSubstring("1073741824")) |  | ||||||
| 
 | 
 | ||||||
| 		// checking cpu-shares
 | 		// checking cpu-shares
 | ||||||
| 		session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/cpu.weight"}) | 		podmanTest.CheckFileInContainerSubstring(ctrID, "/sys/fs/cgroup/cpu.weight", "5") | ||||||
| 		session.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(session).Should(ExitCleanly()) |  | ||||||
| 		Expect(session.OutputToString()).Should(ContainSubstring("5")) |  | ||||||
| 
 | 
 | ||||||
| 		// checking pids-limit
 | 		// checking pids-limit
 | ||||||
| 		session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/pids.max"}) | 		podmanTest.CheckFileInContainerSubstring(ctrID, "/sys/fs/cgroup/pids.max", "123") | ||||||
| 		session.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(session).Should(ExitCleanly()) |  | ||||||
| 		Expect(session.OutputToString()).Should(ContainSubstring("123")) |  | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	It("podman update keep original resources if not overridden", func() { | 	It("podman update keep original resources if not overridden", func() { | ||||||
|  | @ -209,62 +157,44 @@ var _ = Describe("Podman update", func() { | ||||||
| 
 | 
 | ||||||
| 		ctrID := session.OutputToString() | 		ctrID := session.OutputToString() | ||||||
| 
 | 
 | ||||||
|  | 		path := "/sys/fs/cgroup/cpu/cpu.cfs_quota_us" | ||||||
| 		if v2, _ := cgroupv2.Enabled(); v2 { | 		if v2, _ := cgroupv2.Enabled(); v2 { | ||||||
| 			session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/cpu.max"}) | 			path = "/sys/fs/cgroup/cpu.max" | ||||||
| 		} else { |  | ||||||
| 			session = podmanTest.Podman([]string{"exec", ctrID, "cat", "/sys/fs/cgroup/cpu/cpu.cfs_quota_us"}) |  | ||||||
| 		} | 		} | ||||||
| 		session.WaitWithDefaultTimeout() | 
 | ||||||
| 		Expect(session).Should(ExitCleanly()) | 		podmanTest.CheckFileInContainerSubstring(ctrID, path, "500000") | ||||||
| 		Expect(session.OutputToString()).Should(ContainSubstring("500000")) |  | ||||||
| 	}) | 	}) | ||||||
| 
 | 
 | ||||||
| 	It("podman update persists changes", func() { | 	It("podman update persists changes", func() { | ||||||
| 		SkipIfCgroupV1("testing flags that only work in cgroup v2") | 		SkipIfCgroupV1("testing flags that only work in cgroup v2") | ||||||
| 		SkipIfRootless("many of these handlers are not enabled while rootless in CI") | 		SkipIfRootless("many of these handlers are not enabled while rootless in CI") | ||||||
| 
 | 
 | ||||||
|  | 		memoryInspect := ".HostConfig.Memory" | ||||||
|  | 		memoryCgroup := "/sys/fs/cgroup/memory.max" | ||||||
|  | 		mem512m := "536870912" | ||||||
|  | 		mem256m := "268435456" | ||||||
|  | 
 | ||||||
| 		testCtr := "test-ctr-name" | 		testCtr := "test-ctr-name" | ||||||
| 		ctr1 := podmanTest.Podman([]string{"run", "-d", "--name", testCtr, "-m", "512m", ALPINE, "top"}) | 		ctr1 := podmanTest.Podman([]string{"run", "-d", "--name", testCtr, "-m", "512m", ALPINE, "top"}) | ||||||
| 		ctr1.WaitWithDefaultTimeout() | 		ctr1.WaitWithDefaultTimeout() | ||||||
| 		Expect(ctr1).Should(ExitCleanly()) | 		Expect(ctr1).Should(ExitCleanly()) | ||||||
| 
 | 
 | ||||||
| 		inspect1 := podmanTest.Podman([]string{"inspect", "--format", "{{ .HostConfig.Memory }}", testCtr}) | 		podmanTest.CheckContainerSingleField(testCtr, memoryInspect, mem512m) | ||||||
| 		inspect1.WaitWithDefaultTimeout() | 		podmanTest.CheckFileInContainer(testCtr, memoryCgroup, mem512m) | ||||||
| 		Expect(inspect1).Should(ExitCleanly()) |  | ||||||
| 		Expect(inspect1.OutputToString()).To(Equal("536870912")) |  | ||||||
| 
 |  | ||||||
| 		exec1 := podmanTest.Podman([]string{"exec", testCtr, "cat", "/sys/fs/cgroup/memory.max"}) |  | ||||||
| 		exec1.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(exec1).Should(ExitCleanly()) |  | ||||||
| 		Expect(exec1.OutputToString()).Should(ContainSubstring("536870912")) |  | ||||||
| 
 | 
 | ||||||
| 		update := podmanTest.Podman([]string{"update", "-m", "256m", testCtr}) | 		update := podmanTest.Podman([]string{"update", "-m", "256m", testCtr}) | ||||||
| 		update.WaitWithDefaultTimeout() | 		update.WaitWithDefaultTimeout() | ||||||
| 		Expect(update).Should(ExitCleanly()) | 		Expect(update).Should(ExitCleanly()) | ||||||
| 
 | 
 | ||||||
| 		inspect2 := podmanTest.Podman([]string{"inspect", "--format", "{{ .HostConfig.Memory }}", testCtr}) | 		podmanTest.CheckContainerSingleField(testCtr, memoryInspect, mem256m) | ||||||
| 		inspect2.WaitWithDefaultTimeout() | 		podmanTest.CheckFileInContainer(testCtr, memoryCgroup, mem256m) | ||||||
| 		Expect(inspect2).Should(ExitCleanly()) |  | ||||||
| 		Expect(inspect2.OutputToString()).To(Equal("268435456")) |  | ||||||
| 
 |  | ||||||
| 		exec2 := podmanTest.Podman([]string{"exec", testCtr, "cat", "/sys/fs/cgroup/memory.max"}) |  | ||||||
| 		exec2.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(exec2).Should(ExitCleanly()) |  | ||||||
| 		Expect(exec2.OutputToString()).Should(ContainSubstring("268435456")) |  | ||||||
| 
 | 
 | ||||||
| 		restart := podmanTest.Podman([]string{"restart", testCtr}) | 		restart := podmanTest.Podman([]string{"restart", testCtr}) | ||||||
| 		restart.WaitWithDefaultTimeout() | 		restart.WaitWithDefaultTimeout() | ||||||
| 		Expect(restart).Should(ExitCleanly()) | 		Expect(restart).Should(ExitCleanly()) | ||||||
| 
 | 
 | ||||||
| 		inspect3 := podmanTest.Podman([]string{"inspect", "--format", "{{ .HostConfig.Memory }}", testCtr}) | 		podmanTest.CheckContainerSingleField(testCtr, memoryInspect, mem256m) | ||||||
| 		inspect3.WaitWithDefaultTimeout() | 		podmanTest.CheckFileInContainer(testCtr, memoryCgroup, mem256m) | ||||||
| 		Expect(inspect3).Should(ExitCleanly()) |  | ||||||
| 		Expect(inspect3.OutputToString()).To(Equal("268435456")) |  | ||||||
| 
 |  | ||||||
| 		exec3 := podmanTest.Podman([]string{"exec", testCtr, "cat", "/sys/fs/cgroup/memory.max"}) |  | ||||||
| 		exec3.WaitWithDefaultTimeout() |  | ||||||
| 		Expect(exec3).Should(ExitCleanly()) |  | ||||||
| 		Expect(exec3.OutputToString()).Should(ContainSubstring("268435456")) |  | ||||||
| 
 | 
 | ||||||
| 		pause := podmanTest.Podman([]string{"pause", testCtr}) | 		pause := podmanTest.Podman([]string{"pause", testCtr}) | ||||||
| 		pause.WaitWithDefaultTimeout() | 		pause.WaitWithDefaultTimeout() | ||||||
|  | @ -278,14 +208,34 @@ var _ = Describe("Podman update", func() { | ||||||
| 		unpause.WaitWithDefaultTimeout() | 		unpause.WaitWithDefaultTimeout() | ||||||
| 		Expect(unpause).Should(ExitCleanly()) | 		Expect(unpause).Should(ExitCleanly()) | ||||||
| 
 | 
 | ||||||
| 		inspect4 := podmanTest.Podman([]string{"inspect", "--format", "{{ .HostConfig.Memory }}", testCtr}) | 		podmanTest.CheckContainerSingleField(testCtr, memoryInspect, mem512m) | ||||||
| 		inspect4.WaitWithDefaultTimeout() | 		podmanTest.CheckFileInContainer(testCtr, memoryCgroup, mem512m) | ||||||
| 		Expect(inspect4).Should(ExitCleanly()) | 	}) | ||||||
| 		Expect(inspect4.OutputToString()).To(Equal("536870912")) |  | ||||||
| 
 | 
 | ||||||
| 		exec4 := podmanTest.Podman([]string{"exec", testCtr, "cat", "/sys/fs/cgroup/memory.max"}) | 	It("podman update sets restart policy", func() { | ||||||
| 		exec4.WaitWithDefaultTimeout() | 		restartPolicyName := ".HostConfig.RestartPolicy.Name" | ||||||
| 		Expect(exec4).Should(ExitCleanly()) | 		restartPolicyRetries := ".HostConfig.RestartPolicy.MaximumRetryCount" | ||||||
| 		Expect(exec4.OutputToString()).Should(ContainSubstring("536870912")) | 
 | ||||||
|  | 		testCtr := "test-ctr-name" | ||||||
|  | 		ctr1 := podmanTest.Podman([]string{"run", "-dt", "--name", testCtr, ALPINE, "top"}) | ||||||
|  | 		ctr1.WaitWithDefaultTimeout() | ||||||
|  | 		Expect(ctr1).Should(ExitCleanly()) | ||||||
|  | 
 | ||||||
|  | 		podmanTest.CheckContainerSingleField(testCtr, restartPolicyName, "no") | ||||||
|  | 		podmanTest.CheckContainerSingleField(testCtr, restartPolicyRetries, "0") | ||||||
|  | 
 | ||||||
|  | 		update1 := podmanTest.Podman([]string{"update", "--restart", "on-failure:5", testCtr}) | ||||||
|  | 		update1.WaitWithDefaultTimeout() | ||||||
|  | 		Expect(update1).Should(ExitCleanly()) | ||||||
|  | 
 | ||||||
|  | 		podmanTest.CheckContainerSingleField(testCtr, restartPolicyName, "on-failure") | ||||||
|  | 		podmanTest.CheckContainerSingleField(testCtr, restartPolicyRetries, "5") | ||||||
|  | 
 | ||||||
|  | 		update2 := podmanTest.Podman([]string{"update", "--restart", "always", testCtr}) | ||||||
|  | 		update2.WaitWithDefaultTimeout() | ||||||
|  | 		Expect(update2).Should(ExitCleanly()) | ||||||
|  | 
 | ||||||
|  | 		podmanTest.CheckContainerSingleField(testCtr, restartPolicyName, "always") | ||||||
|  | 		podmanTest.CheckContainerSingleField(testCtr, restartPolicyRetries, "0") | ||||||
| 	}) | 	}) | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  | @ -127,4 +127,26 @@ device-write-iops   = /dev/zero:4000 | - | - | ||||||
|     fi |     fi | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @test "podman update - set restart policy" { | ||||||
|  |     touch ${PODMAN_TMPDIR}/sentinel | ||||||
|  |     run_podman run --security-opt label=disable --name testctr -v ${PODMAN_TMPDIR}:/testdir -d $IMAGE sh -c "touch /testdir/alive; while test -e /testdir/sentinel; do sleep 0.1; done;" | ||||||
|  | 
 | ||||||
|  |     run_podman container inspect testctr --format "{{ .HostConfig.RestartPolicy.Name }}" | ||||||
|  |     is "$output" "no" | ||||||
|  | 
 | ||||||
|  |     run_podman update --restart always testctr | ||||||
|  | 
 | ||||||
|  |     run_podman container inspect testctr --format "{{ .HostConfig.RestartPolicy.Name }}" | ||||||
|  |     is "$output" "always" | ||||||
|  | 
 | ||||||
|  |     # Ensure the container is alive | ||||||
|  |     wait_for_file ${PODMAN_TMPDIR}/alive | ||||||
|  | 
 | ||||||
|  |     rm -f ${PODMAN_TMPDIR}/alive | ||||||
|  |     rm -f ${PODMAN_TMPDIR}/sentinel | ||||||
|  | 
 | ||||||
|  |     # Restart should ensure that the container comes back up and recreates the file | ||||||
|  |     wait_for_file ${PODMAN_TMPDIR}/alive | ||||||
|  | } | ||||||
|  | 
 | ||||||
| # vim: filetype=sh | # vim: filetype=sh | ||||||
|  |  | ||||||
		Loading…
	
		Reference in New Issue