Skip to content

Commit

Permalink
rm: add ability to evict containers
Browse files Browse the repository at this point in the history
Add ability to evict a container when it becomes unusable. This may
happen when the host setup changes after a container creation, making it
impossible for that container to be used or removed.

Signed-off-by: Marco Vedovati <mvedovati@suse.com>
  • Loading branch information
marcov committed Jul 15, 2019
1 parent c1700d5 commit 0c39d07
Show file tree
Hide file tree
Showing 4 changed files with 155 additions and 6 deletions.
7 changes: 4 additions & 3 deletions cmd/podman/rm.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ var (
rmCommand cliconfig.RmValues
rmDescription = fmt.Sprintf(`Removes one or more containers from the host. The container name or ID can be used.
Command does not remove images. Running containers will not be removed without the -f option.`)
Command does not remove images. Running containers will not be removed without the -f option. Containers with an invalid runtime configuration can be removed with the -ff option.`)
_rmCommand = &cobra.Command{
Use: "rm [flags] CONTAINER [CONTAINER...]",
Short: "Remove one or more containers",
Expand All @@ -30,7 +30,8 @@ var (
},
Example: `podman rm imageID
podman rm mywebserver myflaskserver 860a4b23
podman rm --force --all`,
podman rm --force --all
podman rm -ff c684f0d469f2`,
}
)

Expand All @@ -40,7 +41,7 @@ func init() {
rmCommand.SetUsageTemplate(UsageTemplate())
flags := rmCommand.Flags()
flags.BoolVarP(&rmCommand.All, "all", "a", false, "Remove all containers")
flags.CountVarP(&rmCommand.Force, "force", "f", "Force removal of a running container")
flags.CountVarP(&rmCommand.Force, "force", "f", "Force removal of a running container. Used twice to force removal of a container with an invalid runtime configuration")
flags.BoolVarP(&rmCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
flags.BoolVar(&rmCommand.Storage, "storage", false, "Remove container from storage library")
flags.BoolVarP(&rmCommand.Volumes, "volumes", "v", false, "Remove the volumes associated with the container")
Expand Down
6 changes: 5 additions & 1 deletion docs/podman-rm.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ podman\-container\-rm (podman\-rm) - Remove one or more containers
**podman rm** [*options*] *container*

## DESCRIPTION
**podman rm** will remove one or more containers from the host. The container name or ID can be used. This does not remove images. Running containers will not be removed without the `-f` option
**podman rm** will remove one or more containers from the host. The container name or ID can be used. This does not remove images. Running containers will not be removed without the `-f` option. Containers with an invalid runtime configuration can be removed with the `-ff` option

## OPTIONS

Expand All @@ -23,6 +23,10 @@ Force the removal of running and paused containers. Forcing a containers remova
removes containers from container storage even if the container is not known to podman.
Containers could have been created by a different container engine.

When specified twice, force the removal of containers with an invalid runtime configuration.
This may happen when the OCI runtime associated to a container becomes unavailable
(for example when the runtime is removed from the host).

**--latest**, **-l**

Instead of providing the container name or ID, use the last created container. If you use methods other than Podman
Expand Down
117 changes: 117 additions & 0 deletions libpod/runtime_ctr.go
Original file line number Diff line number Diff line change
Expand Up @@ -513,6 +513,111 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool,
return cleanupErr
}

// EvictContainer removes the given container ID. This should be used to remove
// a container when obtaining a Container struct pointer has failed.
// Running container will not be stopped.
// If removeVolume is specified, named volumes used by the container will
// be removed also if and only if the container is the sole user
func (r *Runtime) EvictContainer(ctx context.Context, id []byte, removeVolume bool) error {
r.lock.RLock()
defer r.lock.RUnlock()

return r.evictContainer(ctx, id, removeVolume)
}

// Internal function to evict a container.
// This does not lock the runtime nor the container.
// removePod is used only when removing pods. It instructs Podman to ignore
// infra container protections, and *not* remove from the database (as pod
// remove will handle that).
func (r *Runtime) evictContainer(ctx context.Context, id []byte, removeVolume bool) error {
var err error

// Re-create a container struct for removal purposes
c := new(Container)
c.config, err = r.state.GetContainerConfig(id)
if err != nil {
return errors.Wrapf(err, "failed to retrieve config for ctr ID %q", id)
}
c.state = new(ContainerState)

// We need to lock the pod before we lock the container.
// To avoid races around removing a container and the pod it is in.
// Don't need to do this in pod removal case - we're evicting the entire
// pod.
var pod *Pod
if c.config.Pod != "" {
pod, err = r.state.Pod(c.config.Pod)
if err != nil {
return errors.Wrapf(err, "container %s is in pod %s, but pod cannot be retrieved", c.ID(), pod.ID())
}

// Lock the pod while we're removing container
pod.lock.Lock()
defer pod.lock.Unlock()
if err := pod.updatePod(); err != nil {
return err
}

infraID := pod.state.InfraContainerID
if c.ID() == infraID {
return errors.Errorf("container %s is the infra container of pod %s and cannot be removed without removing the pod", c.ID(), pod.ID())
}
}

var cleanupErr error
// Remove the container from the state
if c.config.Pod != "" {
// If we're removing the pod, the container will be evicted
// from the state elsewhere
if err := r.state.RemoveContainerFromPod(pod, c); err != nil {
if cleanupErr == nil {
cleanupErr = err
} else {
logrus.Errorf("removing container from pod: %v", err)
}
}
} else {
if err := r.state.RemoveContainer(c); err != nil {
if cleanupErr == nil {
cleanupErr = err
} else {
logrus.Errorf("removing container: %v", err)
}
}
}

// Unmount container mount points
for _, mount := range c.config.Mounts {
Unmount(mount)
}

// Remove container from c/storage
if err := r.removeStorageContainer(string(id), true); err != nil {
if cleanupErr == nil {
cleanupErr = err
}
return errors.Wrapf(err, "Could not remove container from storage")
}

if !removeVolume {
return nil
}

for _, v := range c.config.NamedVolumes {
if volume, err := r.state.Volume(v.Name); err == nil {
if !volume.IsCtrSpecific() {
continue
}
if err := r.removeVolume(ctx, volume, false); err != nil && err != config2.ErrNoSuchVolume && err != config2.ErrVolumeBeingUsed {
logrus.Errorf("cleanup volume (%s): %v", v, err)
}
}
}

return cleanupErr
}

// GetContainer retrieves a container by its ID
func (r *Runtime) GetContainer(id string) (*Container, error) {
r.lock.RLock()
Expand Down Expand Up @@ -549,6 +654,18 @@ func (r *Runtime) LookupContainer(idOrName string) (*Container, error) {
return r.state.LookupContainer(idOrName)
}

// LookupContainerID looks up a container by its name or a partial ID
// If a partial ID is not unique, an error will be returned
func (r *Runtime) LookupContainerID(idOrName string) ([]byte, error) {
r.lock.RLock()
defer r.lock.RUnlock()

if !r.valid {
return nil, config2.ErrRuntimeStopped
}
return r.state.LookupContainerID(idOrName)
}

// GetContainers retrieves all containers from the state
// Filters can be provided which will determine what containers are included in
// the output. Multiple filters are handled by ANDing their output, so only
Expand Down
31 changes: 29 additions & 2 deletions pkg/adapter/containers.go
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,7 @@ func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmVa
ok = []string{}
failures = map[string]error{}
force = (cli.Force > 0)
evict = (cli.Force > 1)
)

maxWorkers := shared.DefaultPoolSize("rm")
Expand All @@ -196,7 +197,7 @@ func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmVa

if cli.Storage {
for _, ctr := range cli.InputArgs {
if err := r.RemoveStorageContainer(ctr, cli.Force); err != nil {
if err := r.RemoveStorageContainer(ctr, force); err != nil {
failures[ctr] = err
}
ok = append(ok, ctr)
Expand All @@ -206,6 +207,25 @@ func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmVa

ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime)
if err != nil {
if !evict {
return ok, failures, err
}
err = nil
for _, ctr := range cli.InputArgs {
// Retrieve the container ID
id, err := r.Runtime.LookupContainerID(ctr)
if err != nil {
failures[ctr] = errors.Wrapf(err, "Failed to find container %q in state", ctr)
continue
}
logrus.Debugf("Container %q full ID for eviction: %q\n", ctr, id)
err = r.EvictContainer(ctx, id, cli.Volumes)
if err != nil {
failures[ctr] = errors.Wrapf(err, "Failed to evict container: %q", id)
continue
}
ok = append(ok, string(id))
}
return ok, failures, err
}

Expand All @@ -218,7 +238,14 @@ func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmVa
Fn: func() error {
err := r.RemoveContainer(ctx, c, force, cli.Volumes)
if err != nil {
logrus.Debugf("Failed to remove container %s: %s", c.ID(), err.Error())
if evict {
err = r.EvictContainer(ctx, []byte(c.ID()), cli.Volumes)
if err != nil {
logrus.Debugf("Failed to evict container %s: %s", c.ID(), err.Error())
}
} else {
logrus.Debugf("Failed to remove container %s: %s", c.ID(), err.Error())
}
}
return err
},
Expand Down

0 comments on commit 0c39d07

Please sign in to comment.