rm: add containers eviction with rm --force

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.
Evicting a container is done using the `rm --force` command.

Signed-off-by: Marco Vedovati <mvedovati@suse.com>
This commit is contained in:
Marco Vedovati 2019-07-05 12:54:07 +02:00
parent 83b2348313
commit dacbc5beb2
17 changed files with 540 additions and 122 deletions

22
API.md
View file

@ -41,6 +41,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in
[func Diff(name: string) DiffInfo](#Diff)
[func EvictContainer(name: string, removeVolumes: bool) string](#EvictContainer)
[func ExecContainer(opts: ExecOpts) ](#ExecContainer)
[func ExportContainer(name: string, path: string) string](#ExportContainer)
@ -445,6 +447,22 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.DeleteUnusedImages
method Diff(name: [string](https://godoc.org/builtin#string)) [DiffInfo](#DiffInfo)</div>
Diff returns a diff between libpod objects
### <a name="EvictContainer"></a>func EvictContainer
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
method EvictContainer(name: [string](https://godoc.org/builtin#string), removeVolumes: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)</div>
EvictContainer requires the name or ID of a container as well as a boolean that
indicates to remove builtin volumes. Upon successful eviction of the container,
its ID is returned. If the container cannot be found by name or ID,
a [ContainerNotFound](#ContainerNotFound) error will be returned.
See also [RemoveContainer](RemoveContainer).
#### Example
~~~
$ varlink call -m unix:/run/podman/io.podman/io.podman.EvictContainer '{"name": "62f4fd98cb57"}'
{
"container": "62f4fd98cb57f529831e8f90610e54bba74bd6f02920ffb485e15376ed365c20"
}
~~~
### <a name="ExecContainer"></a>func ExecContainer
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
@ -953,10 +971,12 @@ ReceiveFile allows the host to send a remote client a file
<div style="background-color: #E8E8E8; padding: 15px; margin: 10px; border-radius: 10px;">
method RemoveContainer(name: [string](https://godoc.org/builtin#string), force: [bool](https://godoc.org/builtin#bool), removeVolumes: [bool](https://godoc.org/builtin#bool)) [string](https://godoc.org/builtin#string)</div>
RemoveContainer requires the name or ID of container as well a boolean representing whether a running container can be stopped and removed, and a boolean
RemoveContainer requires the name or ID of a container as well as a boolean that
indicates whether a container should be forcefully removed (e.g., by stopping it), and a boolean
indicating whether to remove builtin volumes. Upon successful removal of the
container, its ID is returned. If the
container cannot be found by name or ID, a [ContainerNotFound](#ContainerNotFound) error will be returned.
See also [EvictContainer](EvictContainer).
#### Example
~~~
$ varlink call -m unix:/run/podman/io.podman/io.podman.RemoveContainer '{"name": "62f4fd98cb57"}'

View file

@ -13,7 +13,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 or unusable containers will not be removed without the -f option.`)
_rmCommand = &cobra.Command{
Use: "rm [flags] CONTAINER [CONTAINER...]",
Short: "Remove one or more containers",
@ -29,7 +29,8 @@ var (
},
Example: `podman rm imageID
podman rm mywebserver myflaskserver 860a4b23
podman rm --force --all`,
podman rm --force --all
podman rm -f c684f0d469f2`,
}
)
@ -39,7 +40,7 @@ func init() {
rmCommand.SetUsageTemplate(UsageTemplate())
flags := rmCommand.Flags()
flags.BoolVarP(&rmCommand.All, "all", "a", false, "Remove all containers")
flags.BoolVarP(&rmCommand.Force, "force", "f", false, "Force removal of a running container. The default is false")
flags.BoolVarP(&rmCommand.Force, "force", "f", false, "Force removal of a running or unusable container. The default is false")
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")

View file

@ -727,10 +727,12 @@ method GetAttachSockets(name: string) -> (sockets: Sockets)
# or name, a [ContainerNotFound](#ContainerNotFound) error is returned.
method WaitContainer(name: string, interval: int) -> (exitcode: int)
# RemoveContainer requires the name or ID of container as well a boolean representing whether a running container can be stopped and removed, and a boolean
# RemoveContainer requires the name or ID of a container as well as a boolean that
# indicates whether a container should be forcefully removed (e.g., by stopping it), and a boolean
# indicating whether to remove builtin volumes. Upon successful removal of the
# container, its ID is returned. If the
# container cannot be found by name or ID, a [ContainerNotFound](#ContainerNotFound) error will be returned.
# See also [EvictContainer](EvictContainer).
# #### Example
# ~~~
# $ varlink call -m unix:/run/podman/io.podman/io.podman.RemoveContainer '{"name": "62f4fd98cb57"}'
@ -740,6 +742,20 @@ method WaitContainer(name: string, interval: int) -> (exitcode: int)
# ~~~
method RemoveContainer(name: string, force: bool, removeVolumes: bool) -> (container: string)
# EvictContainer requires the name or ID of a container as well as a boolean that
# indicates to remove builtin volumes. Upon successful eviction of the container,
# its ID is returned. If the container cannot be found by name or ID,
# a [ContainerNotFound](#ContainerNotFound) error will be returned.
# See also [RemoveContainer](RemoveContainer).
# #### Example
# ~~~
# $ varlink call -m unix:/run/podman/io.podman/io.podman.EvictContainer '{"name": "62f4fd98cb57"}'
# {
# "container": "62f4fd98cb57f529831e8f90610e54bba74bd6f02920ffb485e15376ed365c20"
# }
# ~~~
method EvictContainer(name: string, removeVolumes: bool) -> (container: string)
# DeleteStoppedContainers will delete all containers that are not running. It will return a list the deleted
# container IDs. See also [RemoveContainer](RemoveContainer).
# #### Example

View file

@ -9,7 +9,8 @@ podman\-rm - Remove one or more containers
**podman container 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 or unusable containers will not be removed without the `-f` option.
## OPTIONS
@ -19,9 +20,11 @@ Remove all containers. Can be used in conjunction with -f as well.
**--force**, **-f**
Force the removal of running and paused containers. Forcing a containers removal also
Force the removal of running and paused containers. Forcing a container removal also
removes containers from container storage even if the container is not known to podman.
Containers could have been created by a different container engine.
In addition, forcing can be used to remove unusable containers, e.g. containers
whose OCI runtime has become unavailable.
**--latest**, **-l**

View file

@ -407,6 +407,60 @@ func (s *BoltState) Container(id string) (*Container, error) {
return ctr, nil
}
// LookupContainerID retrieves a container ID from the state by full or unique
// partial ID or name
func (s *BoltState) LookupContainerID(idOrName string) (string, error) {
if idOrName == "" {
return "", define.ErrEmptyID
}
if !s.valid {
return "", define.ErrDBClosed
}
db, err := s.getDBCon()
if err != nil {
return "", err
}
defer s.deferredCloseDBCon(db)
var id []byte
err = db.View(func(tx *bolt.Tx) error {
ctrBucket, err := getCtrBucket(tx)
if err != nil {
return err
}
namesBucket, err := getNamesBucket(tx)
if err != nil {
return err
}
nsBucket, err := getNSBucket(tx)
if err != nil {
return err
}
fullID, err := s.lookupContainerID(idOrName, ctrBucket, namesBucket, nsBucket)
// Check if it is in our namespace
if s.namespaceBytes != nil {
ns := nsBucket.Get(fullID)
if !bytes.Equal(ns, s.namespaceBytes) {
return errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName)
}
}
id = fullID
return err
})
if err != nil {
return "", err
}
retID := string(id)
return retID, nil
}
// LookupContainer retrieves a container from the state by full or unique
// partial ID or name
func (s *BoltState) LookupContainer(idOrName string) (*Container, error) {
@ -444,67 +498,9 @@ func (s *BoltState) LookupContainer(idOrName string) (*Container, error) {
return err
}
// First, check if the ID given was the actual container ID
var id []byte
ctrExists := ctrBucket.Bucket([]byte(idOrName))
if ctrExists != nil {
// A full container ID was given.
// It might not be in our namespace, but
// getContainerFromDB() will handle that case.
id = []byte(idOrName)
return s.getContainerFromDB(id, ctr, ctrBucket)
}
// Next, check if the full name was given
isPod := false
fullID := namesBucket.Get([]byte(idOrName))
if fullID != nil {
// The name exists and maps to an ID.
// However, we are not yet certain the ID is a
// container.
ctrExists = ctrBucket.Bucket(fullID)
if ctrExists != nil {
// A container bucket matching the full ID was
// found.
return s.getContainerFromDB(fullID, ctr, ctrBucket)
}
// Don't error if we have a name match but it's not a
// container - there's a chance we have a container with
// an ID starting with those characters.
// However, so we can return a good error, note whether
// this is a pod.
isPod = true
}
// We were not given a full container ID or name.
// Search for partial ID matches.
exists := false
err = ctrBucket.ForEach(func(checkID, checkName []byte) error {
// If the container isn't in our namespace, we
// can't match it
if s.namespaceBytes != nil {
ns := nsBucket.Get(checkID)
if !bytes.Equal(ns, s.namespaceBytes) {
return nil
}
}
if strings.HasPrefix(string(checkID), idOrName) {
if exists {
return errors.Wrapf(define.ErrCtrExists, "more than one result for container ID %s", idOrName)
}
id = checkID
exists = true
}
return nil
})
id, err := s.lookupContainerID(idOrName, ctrBucket, namesBucket, nsBucket)
if err != nil {
return err
} else if !exists {
if isPod {
return errors.Wrapf(define.ErrNoSuchCtr, "%s is a pod, not a container", idOrName)
}
return errors.Wrapf(define.ErrNoSuchCtr, "no container with name or ID %s found", idOrName)
}
return s.getContainerFromDB(id, ctr, ctrBucket)
@ -860,6 +856,39 @@ func (s *BoltState) AllContainers() ([]*Container, error) {
return ctrs, nil
}
// GetContainerConfig returns a container config from the database by full ID
func (s *BoltState) GetContainerConfig(id string) (*ContainerConfig, error) {
if len(id) == 0 {
return nil, define.ErrEmptyID
}
if !s.valid {
return nil, define.ErrDBClosed
}
config := new(ContainerConfig)
db, err := s.getDBCon()
if err != nil {
return nil, err
}
defer s.deferredCloseDBCon(db)
err = db.View(func(tx *bolt.Tx) error {
ctrBucket, err := getCtrBucket(tx)
if err != nil {
return err
}
return s.getContainerConfigFromDB([]byte(id), config, ctrBucket)
})
if err != nil {
return nil, err
}
return config, nil
}
// RewriteContainerConfig rewrites a container's configuration.
// WARNING: This function is DANGEROUS. Do not use without reading the full
// comment on this function in state.go.

View file

@ -347,7 +347,7 @@ func getRuntimeConfigBucket(tx *bolt.Tx) (*bolt.Bucket, error) {
return bkt, nil
}
func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt.Bucket) error {
func (s *BoltState) getContainerConfigFromDB(id []byte, config *ContainerConfig, ctrsBkt *bolt.Bucket) error {
ctrBkt := ctrsBkt.Bucket(id)
if ctrBkt == nil {
return errors.Wrapf(define.ErrNoSuchCtr, "container %s not found in DB", string(id))
@ -365,10 +365,18 @@ func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt.
return errors.Wrapf(define.ErrInternal, "container %s missing config key in DB", string(id))
}
if err := json.Unmarshal(configBytes, ctr.config); err != nil {
if err := json.Unmarshal(configBytes, config); err != nil {
return errors.Wrapf(err, "error unmarshalling container %s config", string(id))
}
return nil
}
func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt.Bucket) error {
if err := s.getContainerConfigFromDB(id, ctr.config, ctrsBkt); err != nil {
return err
}
// Get the lock
lock, err := s.runtime.lockManager.RetrieveLock(ctr.config.LockID)
if err != nil {
@ -388,7 +396,7 @@ func (s *BoltState) getContainerFromDB(id []byte, ctr *Container, ctrsBkt *bolt.
ociRuntime, ok := s.runtime.ociRuntimes[runtimeName]
if !ok {
return errors.Wrapf(define.ErrInternal, "container %s was created with OCI runtime %s, but that runtime is not available in the current configuration", ctr.ID(), ctr.config.OCIRuntime)
return errors.Wrapf(define.ErrOCIRuntimeUnavailable, "cannot find OCI runtime %q for container %s", ctr.config.OCIRuntime, ctr.ID())
}
ctr.ociRuntime = ociRuntime
}
@ -862,3 +870,72 @@ func (s *BoltState) removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error
return nil
}
// lookupContainerID retrieves a container ID from the state by full or unique
// partial ID or name.
// NOTE: the retrieved container ID namespace may not match the state namespace.
func (s *BoltState) lookupContainerID(idOrName string, ctrBucket, namesBucket, nsBucket *bolt.Bucket) ([]byte, error) {
// First, check if the ID given was the actual container ID
ctrExists := ctrBucket.Bucket([]byte(idOrName))
if ctrExists != nil {
// A full container ID was given.
// It might not be in our namespace, but this will be handled
// the callers.
return []byte(idOrName), nil
}
// Next, check if the full name was given
isPod := false
fullID := namesBucket.Get([]byte(idOrName))
if fullID != nil {
// The name exists and maps to an ID.
// However, we are not yet certain the ID is a
// container.
ctrExists = ctrBucket.Bucket(fullID)
if ctrExists != nil {
// A container bucket matching the full ID was
// found.
return fullID, nil
}
// Don't error if we have a name match but it's not a
// container - there's a chance we have a container with
// an ID starting with those characters.
// However, so we can return a good error, note whether
// this is a pod.
isPod = true
}
var id []byte
// We were not given a full container ID or name.
// Search for partial ID matches.
exists := false
err := ctrBucket.ForEach(func(checkID, checkName []byte) error {
// If the container isn't in our namespace, we
// can't match it
if s.namespaceBytes != nil {
ns := nsBucket.Get(checkID)
if !bytes.Equal(ns, s.namespaceBytes) {
return nil
}
}
if strings.HasPrefix(string(checkID), idOrName) {
if exists {
return errors.Wrapf(define.ErrCtrExists, "more than one result for container ID %s", idOrName)
}
id = checkID
exists = true
}
return nil
})
if err != nil {
return nil, err
} else if !exists {
if isPod {
return nil, errors.Wrapf(define.ErrNoSuchCtr, "%s is a pod, not a container", idOrName)
}
return nil, errors.Wrapf(define.ErrNoSuchCtr, "no container with name or ID %s found", idOrName)
}
return id, nil
}

View file

@ -112,6 +112,10 @@ var (
// that was not found
ErrOCIRuntimeNotFound = errors.New("OCI runtime command not found error")
// ErrOCIRuntimeUnavailable indicates that the OCI runtime associated to a container
// could not be found in the configuration
ErrOCIRuntimeUnavailable = errors.New("OCI runtime not available in the current configuration")
// ErrConmonOutdated indicates the version of conmon found (whether via the configuration or $PATH)
// is out of date for the current podman version
ErrConmonOutdated = errors.New("outdated conmon version")

View file

@ -115,15 +115,16 @@ func (s *InMemoryState) Container(id string) (*Container, error) {
return ctr, nil
}
// LookupContainer retrieves a container by full ID, unique partial ID, or name
func (s *InMemoryState) LookupContainer(idOrName string) (*Container, error) {
// lookupID retrieves a container or pod ID by full ID, unique partial ID, or
// name
func (s *InMemoryState) lookupID(idOrName string) (string, error) {
var (
nameIndex *registrar.Registrar
idIndex *truncindex.TruncIndex
)
if idOrName == "" {
return nil, define.ErrEmptyID
return "", define.ErrEmptyID
}
if s.namespace != "" {
@ -131,7 +132,7 @@ func (s *InMemoryState) LookupContainer(idOrName string) (*Container, error) {
if !ok {
// We have no containers in the namespace
// Return false
return nil, errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName)
return "", define.ErrNoSuchCtr
}
nameIndex = nsIndex.nameIndex
idIndex = nsIndex.idIndex
@ -147,15 +148,55 @@ func (s *InMemoryState) LookupContainer(idOrName string) (*Container, error) {
fullID, err = idIndex.Get(idOrName)
if err != nil {
if err == truncindex.ErrNotExist {
return nil, errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName)
return "", define.ErrNoSuchCtr
}
return nil, errors.Wrapf(err, "error performing truncindex lookup for ID %s", idOrName)
return "", errors.Wrapf(err, "error performing truncindex lookup for ID %s", idOrName)
}
} else {
return nil, errors.Wrapf(err, "error performing registry lookup for ID %s", idOrName)
return "", errors.Wrapf(err, "error performing registry lookup for ID %s", idOrName)
}
}
return fullID, nil
}
// LookupContainerID retrieves a container ID by full ID, unique partial ID, or
// name
func (s *InMemoryState) LookupContainerID(idOrName string) (string, error) {
fullID, err := s.lookupID(idOrName)
switch err {
case nil:
_, ok := s.containers[fullID]
if !ok {
// It's a pod, not a container
return "", errors.Wrapf(define.ErrNoSuchCtr, "name or ID %s is a pod, not a container", idOrName)
}
case define.ErrNoSuchCtr:
return "", errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName)
default:
return "", err
}
return fullID, nil
}
// LookupContainer retrieves a container by full ID, unique partial ID, or name
func (s *InMemoryState) LookupContainer(idOrName string) (*Container, error) {
fullID, err := s.lookupID(idOrName)
switch err {
case nil:
case define.ErrNoSuchCtr:
return nil, errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName)
default:
return nil, err
}
ctr, ok := s.containers[fullID]
if !ok {
// It's a pod, not a container
@ -385,6 +426,16 @@ func (s *InMemoryState) AllContainers() ([]*Container, error) {
return ctrs, nil
}
// GetContainerConfig returns a container config from the database by full ID
func (s *InMemoryState) GetContainerConfig(id string) (*ContainerConfig, error) {
ctr, err := s.LookupContainer(id)
if err != nil {
return nil, err
}
return ctr.Config(), nil
}
// RewriteContainerConfig rewrites a container's configuration.
// This function is DANGEROUS, even with an in-memory state.
// Please read the full comment on it in state.go before using it.
@ -623,49 +674,22 @@ func (s *InMemoryState) Pod(id string) (*Pod, error) {
// LookupPod retrieves a pod from the state from a full or unique partial ID or
// a full name
func (s *InMemoryState) LookupPod(idOrName string) (*Pod, error) {
var (
nameIndex *registrar.Registrar
idIndex *truncindex.TruncIndex
)
fullID, err := s.lookupID(idOrName)
if idOrName == "" {
return nil, define.ErrEmptyID
}
switch err {
case nil:
if s.namespace != "" {
nsIndex, ok := s.namespaceIndexes[s.namespace]
if !ok {
// We have no containers in the namespace
// Return false
return nil, errors.Wrapf(define.ErrNoSuchCtr, "no container found with name or ID %s", idOrName)
}
nameIndex = nsIndex.nameIndex
idIndex = nsIndex.idIndex
} else {
nameIndex = s.nameIndex
idIndex = s.idIndex
}
fullID, err := nameIndex.Get(idOrName)
if err != nil {
if err == registrar.ErrNameNotReserved {
// What was passed is not a name, assume it's an ID
fullID, err = idIndex.Get(idOrName)
if err != nil {
if err == truncindex.ErrNotExist {
case define.ErrNoSuchCtr, define.ErrNoSuchPod:
return nil, errors.Wrapf(define.ErrNoSuchPod, "no pod found with name or ID %s", idOrName)
}
return nil, errors.Wrapf(err, "error performing truncindex lookup for ID %s", idOrName)
}
} else {
return nil, errors.Wrapf(err, "error performing registry lookup for ID %s", idOrName)
}
default:
return nil, err
}
pod, ok := s.pods[fullID]
if !ok {
// It's a container not a pod
return nil, errors.Wrapf(define.ErrNoSuchPod, "id or name %s is a container not a pod", idOrName)
return nil, errors.Wrapf(define.ErrNoSuchPod, "id or name %s is a container, not a pod", idOrName)
}
return pod, nil

View file

@ -60,6 +60,12 @@ func (r *Runtime) RemoveStorageContainer(idOrName string, force bool) error {
r.lock.Lock()
defer r.lock.Unlock()
return r.removeStorageContainer(idOrName, force)
}
// Internal function to remove the container storage without
// locking the runtime.
func (r *Runtime) removeStorageContainer(idOrName string, force bool) error {
targetID, err := r.store.Lookup(idOrName)
if err != nil {
if err == storage.ErrLayerUnknown {

View file

@ -550,6 +550,122 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool,
return cleanupErr
}
// EvictContainer removes the given container partial or full ID or name, and
// returns the full ID of the evicted container and any error encountered.
// It 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, idOrName string, removeVolume bool) (string, error) {
r.lock.RLock()
defer r.lock.RUnlock()
return r.evictContainer(ctx, idOrName, removeVolume)
}
// evictContainer is the internal function to handle container eviction based
// on its partial or full ID or name.
// It returns the full ID of the evicted container and any error encountered.
// 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, idOrName string, removeVolume bool) (string, error) {
var err error
if !r.valid {
return "", define.ErrRuntimeStopped
}
id, err := r.state.LookupContainerID(idOrName)
if err != nil {
return "", errors.Wrapf(err, "Failed to find container %q in state", idOrName)
}
// Error out if the container does not exist in libpod
exists, err := r.state.HasContainer(id)
if err != nil {
return id, err
}
if !exists {
return id, errors.Wrapf(err, "Failed to find container ID %q for eviction", id)
}
// Re-create a container struct for removal purposes
c := new(Container)
c.config, err = r.state.GetContainerConfig(id)
if err != nil {
return id, 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 id, 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 id, err
}
infraID := pod.state.InfraContainerID
if c.ID() == infraID {
return id, 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 {
cleanupErr = err
}
} else {
if err := r.state.RemoveContainer(c); err != nil {
cleanupErr = err
}
}
// Unmount container mount points
for _, mount := range c.config.Mounts {
Unmount(mount)
}
// Remove container from c/storage
if err := r.removeStorageContainer(id, true); err != nil {
if cleanupErr == nil {
cleanupErr = err
}
}
if !removeVolume {
return id, cleanupErr
}
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 != define.ErrNoSuchVolume && err != define.ErrVolumeBeingUsed {
logrus.Errorf("cleanup volume (%s): %v", v, err)
}
}
}
return id, cleanupErr
}
// GetContainer retrieves a container by its ID
func (r *Runtime) GetContainer(id string) (*Container, error) {
r.lock.RLock()

View file

@ -58,6 +58,9 @@ type State interface {
// If the container is not in the set namespace, an error will be
// returned.
Container(id string) (*Container, error)
// Return a container ID from the database by full or partial ID or full
// name.
LookupContainerID(idOrName string) (string, error)
// Return a container from the database by full or partial ID or full
// name.
// Containers not in the set namespace will be ignored.
@ -98,6 +101,9 @@ type State interface {
// returned.
AllContainers() ([]*Container, error)
// Return a container config from the database by full ID
GetContainerConfig(id string) (*ContainerConfig, error)
// PLEASE READ FULL DESCRIPTION BEFORE USING.
// Rewrite a container's configuration.
// This function breaks libpod's normal prohibition on a read-only

View file

@ -452,6 +452,9 @@ func TestLookupContainerWithEmptyIDFails(t *testing.T) {
runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) {
_, err := state.LookupContainer("")
assert.Error(t, err)
_, err = state.LookupContainerID("")
assert.Error(t, err)
})
}
@ -459,6 +462,9 @@ func TestLookupNonexistentContainerFails(t *testing.T) {
runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) {
_, err := state.LookupContainer("does not exist")
assert.Error(t, err)
_, err = state.LookupContainerID("does not exist")
assert.Error(t, err)
})
}
@ -472,8 +478,11 @@ func TestLookupContainerByFullID(t *testing.T) {
retrievedCtr, err := state.LookupContainer(testCtr.ID())
assert.NoError(t, err)
testContainersEqual(t, retrievedCtr, testCtr, true)
retrievedID, err := state.LookupContainerID(testCtr.ID())
assert.NoError(t, err)
assert.Equal(t, retrievedID, testCtr.ID())
})
}
@ -487,8 +496,11 @@ func TestLookupContainerByUniquePartialID(t *testing.T) {
retrievedCtr, err := state.LookupContainer(testCtr.ID()[0:8])
assert.NoError(t, err)
testContainersEqual(t, retrievedCtr, testCtr, true)
retrievedID, err := state.LookupContainerID(testCtr.ID()[0:8])
assert.NoError(t, err)
assert.Equal(t, retrievedID, testCtr.ID())
})
}
@ -507,6 +519,9 @@ func TestLookupContainerByNonUniquePartialIDFails(t *testing.T) {
_, err = state.LookupContainer(testCtr1.ID()[0:8])
assert.Error(t, err)
_, err = state.LookupContainerID(testCtr1.ID()[0:8])
assert.Error(t, err)
})
}
@ -520,8 +535,11 @@ func TestLookupContainerByName(t *testing.T) {
retrievedCtr, err := state.LookupContainer(testCtr.Name())
assert.NoError(t, err)
testContainersEqual(t, retrievedCtr, testCtr, true)
retrievedID, err := state.LookupContainerID(testCtr.Name())
assert.NoError(t, err)
assert.Equal(t, retrievedID, testCtr.ID())
})
}
@ -535,6 +553,9 @@ func TestLookupCtrByPodNameFails(t *testing.T) {
_, err = state.LookupContainer(testPod.Name())
assert.Error(t, err)
_, err = state.LookupContainerID(testPod.Name())
assert.Error(t, err)
})
}
@ -548,6 +569,9 @@ func TestLookupCtrByPodIDFails(t *testing.T) {
_, err = state.LookupContainer(testPod.ID())
assert.Error(t, err)
_, err = state.LookupContainerID(testPod.ID())
assert.Error(t, err)
})
}
@ -565,8 +589,11 @@ func TestLookupCtrInSameNamespaceSucceeds(t *testing.T) {
ctr, err := state.LookupContainer(testCtr.ID())
assert.NoError(t, err)
testContainersEqual(t, ctr, testCtr, true)
ctrID, err := state.LookupContainerID(testCtr.ID())
assert.NoError(t, err)
assert.Equal(t, ctrID, testCtr.ID())
})
}
@ -584,6 +611,9 @@ func TestLookupCtrInDifferentNamespaceFails(t *testing.T) {
_, err = state.LookupContainer(testCtr.ID())
assert.Error(t, err)
_, err = state.LookupContainerID(testCtr.ID())
assert.Error(t, err)
})
}
@ -606,8 +636,11 @@ func TestLookupContainerMatchInDifferentNamespaceSucceeds(t *testing.T) {
ctr, err := state.LookupContainer("000")
assert.NoError(t, err)
testContainersEqual(t, ctr, testCtr2, true)
ctrID, err := state.LookupContainerID("000")
assert.NoError(t, err)
assert.Equal(t, ctrID, testCtr2.ID())
})
}
@ -3599,3 +3632,30 @@ func TestSaveAndUpdatePodSameNamespace(t *testing.T) {
testPodsEqual(t, testPod, statePod, false)
})
}
func TestGetContainerConfigSucceeds(t *testing.T) {
runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) {
testCtr, err := getTestCtr1(manager)
assert.NoError(t, err)
err = state.AddContainer(testCtr)
assert.NoError(t, err)
ctrCfg, err := state.GetContainerConfig(testCtr.ID())
assert.NoError(t, err)
assert.Equal(t, ctrCfg, testCtr.Config())
})
}
func TestGetContainerConfigEmptyIDFails(t *testing.T) {
runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) {
_, err := state.GetContainerConfig("")
assert.Error(t, err)
})
}
func TestGetContainerConfigNonExistentIDFails(t *testing.T) {
runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) {
_, err := state.GetContainerConfig("does not exist")
assert.Error(t, err)
})
}

View file

@ -5,6 +5,7 @@ package libpod
import (
"fmt"
"strings"
"syscall"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/cgroups"
@ -12,6 +13,7 @@ import (
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
// systemdSliceFromPath makes a new systemd slice under the given parent with
@ -107,3 +109,14 @@ func LabelVolumePath(path string, shared bool) error {
}
return nil
}
// Unmount umounts a target directory
func Unmount(mount string) {
if err := unix.Unmount(mount, unix.MNT_DETACH); err != nil {
if err != syscall.EINVAL {
logrus.Warnf("failed to unmount %s : %v", mount, err)
} else {
logrus.Debugf("failed to unmount %s : %v", mount, err)
}
}
}

View file

@ -28,3 +28,7 @@ func assembleSystemdCgroupName(baseSlice, newSlice string) (string, error) {
func LabelVolumePath(path string, shared bool) error {
return define.ErrNotImplemented
}
func Unmount(mount string) error {
return define.ErrNotImplemented
}

View file

@ -205,9 +205,24 @@ 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 {
// Failed to get containers. If force is specified, get the containers ID
// and evict them
if !cli.Force {
return ok, failures, err
}
for _, ctr := range cli.InputArgs {
logrus.Debugf("Evicting container %q", ctr)
id, err := r.EvictContainer(ctx, ctr, cli.Volumes)
if err != nil {
failures[ctr] = errors.Wrapf(err, "Failed to evict container: %q", id)
continue
}
ok = append(ok, id)
}
return ok, failures, nil
}
pool := shared.NewPool("rm", maxWorkers, len(ctrs))
for _, c := range ctrs {
c := c

View file

@ -321,16 +321,31 @@ func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillVa
// RemoveContainer removes container(s) based on varlink inputs.
func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmValues) ([]string, map[string]error, error) {
ids, err := iopodman.GetContainersByContext().Call(r.Conn, cli.All, cli.Latest, cli.InputArgs)
if err != nil {
return nil, nil, TranslateError(err)
}
var (
ok = []string{}
failures = map[string]error{}
)
ids, err := iopodman.GetContainersByContext().Call(r.Conn, cli.All, cli.Latest, cli.InputArgs)
if err != nil {
// Failed to get containers. If force is specified, get the containers ID
// and evict them
if !cli.Force {
return nil, nil, TranslateError(err)
}
for _, ctr := range cli.InputArgs {
logrus.Debugf("Evicting container %q", ctr)
id, err := iopodman.EvictContainer().Call(r.Conn, ctr, 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, nil
}
for _, id := range ids {
_, err := iopodman.RemoveContainer().Call(r.Conn, id, cli.Force, cli.Volumes)
if err != nil {

View file

@ -508,7 +508,16 @@ func (i *LibpodAPI) RemoveContainer(call iopodman.VarlinkCall, name string, forc
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyRemoveContainer(ctr.ID())
}
// EvictContainer ...
func (i *LibpodAPI) EvictContainer(call iopodman.VarlinkCall, name string, removeVolumes bool) error {
ctx := getContext()
id, err := i.Runtime.EvictContainer(ctx, name, removeVolumes)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyEvictContainer(id)
}
// DeleteStoppedContainers ...