Files
podman/pkg/adapter/containers.go
Matthew Heon 4004f646cd Add basic deadlock detection for container start/remove
We can easily tell if we're going to deadlock by comparing lock
IDs before actually taking the lock. Add a few checks for this in
common places where deadlocks might occur.

This does not yet cover pod operations, where detection is more
difficult (and costly) due to the number of locks being involved
being higher than 2.

Also, add some error wrapping on the Podman side, so we can tell
people to use `system renumber` when it occurs.

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
2020-02-24 09:29:34 -05:00

1362 lines
37 KiB
Go

// +build !remoteclient
package adapter
import (
"bufio"
"context"
"fmt"
"io"
"io/ioutil"
"os"
"strconv"
"strings"
"sync"
"syscall"
"time"
"github.com/containers/buildah"
"github.com/containers/image/v5/manifest"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/cmd/podman/shared/parse"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/libpod/logs"
"github.com/containers/libpod/pkg/adapter/shortcuts"
"github.com/containers/libpod/pkg/systemd/generate"
"github.com/containers/storage"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// GetLatestContainer gets the latest Container and wraps it in an adapter Container
func (r *LocalRuntime) GetLatestContainer() (*Container, error) {
Container := Container{}
c, err := r.Runtime.GetLatestContainer()
Container.Container = c
return &Container, err
}
// GetAllContainers gets all Containers and wraps each one in an adapter Container
func (r *LocalRuntime) GetAllContainers() ([]*Container, error) {
var containers []*Container
allContainers, err := r.Runtime.GetAllContainers()
if err != nil {
return nil, err
}
for _, c := range allContainers {
containers = append(containers, &Container{c})
}
return containers, nil
}
// LookupContainer gets a Container by name or id and wraps it in an adapter Container
func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) {
ctr, err := r.Runtime.LookupContainer(idOrName)
if err != nil {
return nil, err
}
return &Container{ctr}, nil
}
// StopContainers stops container(s) based on CLI inputs.
// Returns list of successful id(s), map of failed id(s) + error, or error not from container
func (r *LocalRuntime) StopContainers(ctx context.Context, cli *cliconfig.StopValues) ([]string, map[string]error, error) {
var timeout *uint
if cli.Flags().Changed("timeout") || cli.Flags().Changed("time") {
t := cli.Timeout
timeout = &t
}
maxWorkers := shared.DefaultPoolSize("stop")
if cli.GlobalIsSet("max-workers") {
maxWorkers = cli.GlobalFlags.MaxWorks
}
logrus.Debugf("Setting maximum stop workers to %d", maxWorkers)
names := cli.InputArgs
for _, cidFile := range cli.CIDFiles {
content, err := ioutil.ReadFile(cidFile)
if err != nil {
return nil, nil, errors.Wrap(err, "error reading CIDFile")
}
id := strings.Split(string(content), "\n")[0]
names = append(names, id)
}
ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, names, r.Runtime)
if err != nil && !(cli.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) {
return nil, nil, err
}
pool := shared.NewPool("stop", maxWorkers, len(ctrs))
for _, c := range ctrs {
c := c
if timeout == nil {
t := c.StopTimeout()
timeout = &t
logrus.Debugf("Set timeout to container %s default (%d)", c.ID(), *timeout)
}
pool.Add(shared.Job{
ID: c.ID(),
Fn: func() error {
err := c.StopWithTimeout(*timeout)
if err != nil {
if errors.Cause(err) == define.ErrCtrStopped {
logrus.Debugf("Container %s is already stopped", c.ID())
return nil
} else if cli.All && errors.Cause(err) == define.ErrCtrStateInvalid {
logrus.Debugf("Container %s is not running, could not stop", c.ID())
return nil
}
logrus.Debugf("Failed to stop container %s: %s", c.ID(), err.Error())
}
return err
},
})
}
return pool.Run()
}
// KillContainers sends signal to container(s) based on CLI inputs.
// Returns list of successful id(s), map of failed id(s) + error, or error not from container
func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillValues, signal syscall.Signal) ([]string, map[string]error, error) {
maxWorkers := shared.DefaultPoolSize("kill")
if cli.GlobalIsSet("max-workers") {
maxWorkers = cli.GlobalFlags.MaxWorks
}
logrus.Debugf("Setting maximum kill workers to %d", maxWorkers)
ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime)
if err != nil {
return nil, nil, err
}
pool := shared.NewPool("kill", maxWorkers, len(ctrs))
for _, c := range ctrs {
c := c
pool.Add(shared.Job{
ID: c.ID(),
Fn: func() error {
return c.Kill(uint(signal))
},
})
}
return pool.Run()
}
// InitContainers initializes container(s) based on CLI inputs.
// Returns list of successful id(s), map of failed id(s) to errors, or a general
// error not from the container.
func (r *LocalRuntime) InitContainers(ctx context.Context, cli *cliconfig.InitValues) ([]string, map[string]error, error) {
maxWorkers := shared.DefaultPoolSize("init")
if cli.GlobalIsSet("max-workers") {
maxWorkers = cli.GlobalFlags.MaxWorks
}
logrus.Debugf("Setting maximum init workers to %d", maxWorkers)
ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime)
if err != nil {
return nil, nil, err
}
pool := shared.NewPool("init", maxWorkers, len(ctrs))
for _, c := range ctrs {
ctr := c
pool.Add(shared.Job{
ID: ctr.ID(),
Fn: func() error {
err := ctr.Init(ctx)
if err != nil {
// If we're initializing all containers, ignore invalid state errors
if cli.All && errors.Cause(err) == define.ErrCtrStateInvalid {
return nil
}
return err
}
return nil
},
})
}
return pool.Run()
}
// RemoveContainers removes container(s) based on CLI inputs.
func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmValues) ([]string, map[string]error, error) {
var (
ok = []string{}
failures = map[string]error{}
)
maxWorkers := shared.DefaultPoolSize("rm")
if cli.GlobalIsSet("max-workers") {
maxWorkers = cli.GlobalFlags.MaxWorks
}
logrus.Debugf("Setting maximum rm workers to %d", maxWorkers)
if cli.Storage {
for _, ctr := range cli.InputArgs {
if err := r.RemoveStorageContainer(ctr, cli.Force); err != nil {
failures[ctr] = err
}
ok = append(ok, ctr)
}
return ok, failures, nil
}
names := cli.InputArgs
for _, cidFile := range cli.CIDFiles {
content, err := ioutil.ReadFile(cidFile)
if err != nil {
return nil, nil, errors.Wrap(err, "error reading CIDFile")
}
id := strings.Split(string(content), "\n")[0]
names = append(names, id)
}
ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, names, r.Runtime)
if err != nil && !(cli.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) {
// 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 {
if cli.Ignore && errors.Cause(err) == define.ErrNoSuchCtr {
logrus.Debugf("Ignoring error (--allow-missing): %v", err)
continue
}
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
pool.Add(shared.Job{
ID: c.ID(),
Fn: func() error {
err := r.RemoveContainer(ctx, c, cli.Force, cli.Volumes)
if err != nil {
if cli.Ignore && errors.Cause(err) == define.ErrNoSuchCtr {
logrus.Debugf("Ignoring error (--allow-missing): %v", err)
return nil
}
logrus.Debugf("Failed to remove container %s: %s", c.ID(), err.Error())
}
return err
},
})
}
return pool.Run()
}
// UmountRootFilesystems removes container(s) based on CLI inputs.
func (r *LocalRuntime) UmountRootFilesystems(ctx context.Context, cli *cliconfig.UmountValues) ([]string, map[string]error, error) {
var (
ok = []string{}
failures = map[string]error{}
)
ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime)
if err != nil {
return ok, failures, err
}
for _, ctr := range ctrs {
state, err := ctr.State()
if err != nil {
logrus.Debugf("Error umounting container %s state: %s", ctr.ID(), err.Error())
continue
}
if state == define.ContainerStateRunning {
logrus.Debugf("Error umounting container %s, is running", ctr.ID())
continue
}
if err := ctr.Unmount(cli.Force); err != nil {
if cli.All && errors.Cause(err) == storage.ErrLayerNotMounted {
logrus.Debugf("Error umounting container %s, storage.ErrLayerNotMounted", ctr.ID())
continue
}
failures[ctr.ID()] = errors.Wrapf(err, "error unmounting container %s", ctr.ID())
} else {
ok = append(ok, ctr.ID())
}
}
return ok, failures, nil
}
// WaitOnContainers waits for all given container(s) to stop
func (r *LocalRuntime) WaitOnContainers(ctx context.Context, cli *cliconfig.WaitValues, interval time.Duration) ([]string, map[string]error, error) {
var (
ok = []string{}
failures = map[string]error{}
)
ctrs, err := shortcuts.GetContainersByContext(false, cli.Latest, cli.InputArgs, r.Runtime)
if err != nil {
return ok, failures, err
}
for _, c := range ctrs {
if returnCode, err := c.WaitWithInterval(interval); err == nil {
ok = append(ok, strconv.Itoa(int(returnCode)))
} else {
failures[c.ID()] = err
}
}
return ok, failures, err
}
// Log logs one or more containers
func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *logs.LogOptions) error {
var wg sync.WaitGroup
options.WaitGroup = &wg
if len(c.InputArgs) > 1 {
options.Multi = true
}
tailLen := int(c.Tail)
if tailLen < 0 {
tailLen = 0
}
numContainers := len(c.InputArgs)
if numContainers == 0 {
numContainers = 1
}
logChannel := make(chan *logs.LogLine, tailLen*numContainers+1)
containers, err := shortcuts.GetContainersByContext(false, c.Latest, c.InputArgs, r.Runtime)
if err != nil {
return err
}
if err := r.Runtime.Log(containers, options, logChannel); err != nil {
return err
}
go func() {
wg.Wait()
close(logChannel)
}()
for line := range logChannel {
fmt.Println(line.String(options))
}
return nil
}
// CreateContainer creates a libpod container
func (r *LocalRuntime) CreateContainer(ctx context.Context, c *cliconfig.CreateValues) (string, error) {
results := shared.NewIntermediateLayer(&c.PodmanCommand, false)
ctr, _, err := shared.CreateContainer(ctx, &results, r.Runtime)
if err != nil {
return "", err
}
return ctr.ID(), nil
}
// Select the detach keys to use from user input flag, config file, or default value
func (r *LocalRuntime) selectDetachKeys(flagValue string) (string, error) {
if flagValue != "" {
return flagValue, nil
}
config, err := r.GetConfig()
if err != nil {
return "", errors.Wrapf(err, "unable to retrieve runtime config")
}
if config.DetachKeys != "" {
return config.DetachKeys, nil
}
return define.DefaultDetachKeys, nil
}
// Run a libpod container
func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode int) (int, error) {
results := shared.NewIntermediateLayer(&c.PodmanCommand, false)
ctr, createConfig, err := shared.CreateContainer(ctx, &results, r.Runtime)
if err != nil {
return exitCode, err
}
if logrus.GetLevel() == logrus.DebugLevel {
cgroupPath, err := ctr.CGroupPath()
if err == nil {
logrus.Debugf("container %q has CgroupParent %q", ctr.ID(), cgroupPath)
}
}
// Handle detached start
if createConfig.Detach {
// if the container was created as part of a pod, also start its dependencies, if any.
if err := ctr.Start(ctx, c.IsSet("pod")); err != nil {
// This means the command did not exist
return define.ExitCode(err), err
}
fmt.Printf("%s\n", ctr.ID())
exitCode = 0
return exitCode, nil
}
outputStream := os.Stdout
errorStream := os.Stderr
inputStream := os.Stdin
// If -i is not set, clear stdin
if !c.Bool("interactive") {
inputStream = nil
}
// If attach is set, clear stdin/stdout/stderr and only attach requested
if c.IsSet("attach") || c.IsSet("a") {
outputStream = nil
errorStream = nil
if !c.Bool("interactive") {
inputStream = nil
}
attachTo := c.StringSlice("attach")
for _, stream := range attachTo {
switch strings.ToLower(stream) {
case "stdout":
outputStream = os.Stdout
case "stderr":
errorStream = os.Stderr
case "stdin":
inputStream = os.Stdin
default:
return exitCode, errors.Wrapf(define.ErrInvalidArg, "invalid stream %q for --attach - must be one of stdin, stdout, or stderr", stream)
}
}
}
keys := c.String("detach-keys")
if !c.IsSet("detach-keys") {
keys, err = r.selectDetachKeys(keys)
if err != nil {
return exitCode, err
}
}
// if the container was created as part of a pod, also start its dependencies, if any.
if err := StartAttachCtr(ctx, ctr, outputStream, errorStream, inputStream, keys, c.Bool("sig-proxy"), true, c.IsSet("pod")); err != nil {
// We've manually detached from the container
// Do not perform cleanup, or wait for container exit code
// Just exit immediately
if errors.Cause(err) == define.ErrDetach {
return 0, nil
}
if c.IsSet("rm") {
if deleteError := r.Runtime.RemoveContainer(ctx, ctr, true, false); deleteError != nil {
logrus.Debugf("unable to remove container %s after failing to start and attach to it", ctr.ID())
}
}
if errors.Cause(err) == define.ErrWillDeadlock {
logrus.Debugf("Deadlock error: %v", err)
return define.ExitCode(err), errors.Errorf("attempting to start container %s would cause a deadlock; please run 'podman system renumber' to resolve", ctr.ID())
}
return define.ExitCode(err), err
}
if ecode, err := ctr.Wait(); err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr {
// Check events
event, err := r.Runtime.GetLastContainerEvent(ctr.ID(), events.Exited)
if err != nil {
logrus.Errorf("Cannot get exit code: %v", err)
exitCode = define.ExecErrorCodeNotFound
} else {
exitCode = event.ContainerExitCode
}
}
} else {
exitCode = int(ecode)
}
if c.IsSet("rm") {
if err := r.Runtime.RemoveContainer(ctx, ctr, false, true); err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr ||
errors.Cause(err) == define.ErrCtrRemoved {
logrus.Warnf("Container %s does not exist: %v", ctr.ID(), err)
} else {
logrus.Errorf("Error removing container %s: %v", ctr.ID(), err)
}
}
}
return exitCode, nil
}
// Ps ...
func (r *LocalRuntime) Ps(c *cliconfig.PsValues, opts shared.PsOptions) ([]shared.PsContainerOutput, error) {
maxWorkers := shared.Parallelize("ps")
if c.GlobalIsSet("max-workers") {
maxWorkers = c.GlobalFlags.MaxWorks
}
logrus.Debugf("Setting maximum workers to %d", maxWorkers)
return shared.GetPsContainerOutput(r.Runtime, opts, c.Filter, maxWorkers)
}
// Attach ...
func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) error {
var (
ctr *libpod.Container
err error
)
if c.Latest {
ctr, err = r.Runtime.GetLatestContainer()
} else {
ctr, err = r.Runtime.LookupContainer(c.InputArgs[0])
}
if err != nil {
return errors.Wrapf(err, "unable to exec into %s", c.InputArgs[0])
}
conState, err := ctr.State()
if err != nil {
return errors.Wrapf(err, "unable to determine state of %s", ctr.ID())
}
if conState != define.ContainerStateRunning {
return errors.Errorf("you can only attach to running containers")
}
inputStream := os.Stdin
if c.NoStdin {
inputStream = nil
}
keys := c.DetachKeys
if !c.IsSet("detach-keys") {
keys, err = r.selectDetachKeys(keys)
if err != nil {
return err
}
}
// If the container is in a pod, also set to recursively start dependencies
if err := StartAttachCtr(ctx, ctr, os.Stdout, os.Stderr, inputStream, keys, c.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != define.ErrDetach {
return errors.Wrapf(err, "error attaching to container %s", ctr.ID())
}
return nil
}
// Checkpoint one or more containers
func (r *LocalRuntime) Checkpoint(c *cliconfig.CheckpointValues) error {
var (
containers []*libpod.Container
err, lastError error
)
options := libpod.ContainerCheckpointOptions{
Keep: c.Keep,
KeepRunning: c.LeaveRunning,
TCPEstablished: c.TcpEstablished,
TargetFile: c.Export,
IgnoreRootfs: c.IgnoreRootfs,
}
if c.Export == "" && c.IgnoreRootfs {
return errors.Errorf("--ignore-rootfs can only be used with --export")
}
if c.All {
containers, err = r.Runtime.GetRunningContainers()
} else {
containers, err = shortcuts.GetContainersByContext(false, c.Latest, c.InputArgs, r.Runtime)
}
if err != nil {
return err
}
for _, ctr := range containers {
if err = ctr.Checkpoint(context.TODO(), options); err != nil {
if lastError != nil {
fmt.Fprintln(os.Stderr, lastError)
}
lastError = errors.Wrapf(err, "failed to checkpoint container %v", ctr.ID())
} else {
fmt.Println(ctr.ID())
}
}
return lastError
}
// Restore one or more containers
func (r *LocalRuntime) Restore(ctx context.Context, c *cliconfig.RestoreValues) error {
var (
containers []*libpod.Container
err, lastError error
filterFuncs []libpod.ContainerFilter
)
options := libpod.ContainerCheckpointOptions{
Keep: c.Keep,
TCPEstablished: c.TcpEstablished,
TargetFile: c.Import,
Name: c.Name,
IgnoreRootfs: c.IgnoreRootfs,
IgnoreStaticIP: c.IgnoreStaticIP,
IgnoreStaticMAC: c.IgnoreStaticMAC,
}
filterFuncs = append(filterFuncs, func(c *libpod.Container) bool {
state, _ := c.State()
return state == define.ContainerStateExited
})
switch {
case c.Import != "":
containers, err = crImportCheckpoint(ctx, r.Runtime, c.Import, c.Name)
case c.All:
containers, err = r.GetContainers(filterFuncs...)
default:
containers, err = shortcuts.GetContainersByContext(false, c.Latest, c.InputArgs, r.Runtime)
}
if err != nil {
return err
}
for _, ctr := range containers {
if err = ctr.Restore(context.TODO(), options); err != nil {
if lastError != nil {
fmt.Fprintln(os.Stderr, lastError)
}
lastError = errors.Wrapf(err, "failed to restore container %v", ctr.ID())
} else {
fmt.Println(ctr.ID())
}
}
return lastError
}
// Start will start a container
func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigProxy bool) (int, error) {
var (
exitCode = define.ExecErrorCodeGeneric
lastError error
)
args := c.InputArgs
if c.Latest {
lastCtr, err := r.GetLatestContainer()
if err != nil {
return 0, errors.Wrapf(err, "unable to get latest container")
}
args = append(args, lastCtr.ID())
}
for _, container := range args {
ctr, err := r.LookupContainer(container)
if err != nil {
if lastError != nil {
fmt.Fprintln(os.Stderr, lastError)
}
lastError = errors.Wrapf(err, "unable to find container %s", container)
continue
}
ctrState, err := ctr.State()
if err != nil {
return exitCode, errors.Wrapf(err, "unable to get container state")
}
ctrRunning := ctrState == define.ContainerStateRunning
if c.Attach {
inputStream := os.Stdin
if !c.Interactive {
if !ctr.Stdin() {
inputStream = nil
}
}
keys := c.DetachKeys
if !c.IsSet("detach-keys") {
keys, err = r.selectDetachKeys(keys)
if err != nil {
return exitCode, err
}
}
// attach to the container and also start it not already running
// If the container is in a pod, also set to recursively start dependencies
err = StartAttachCtr(ctx, ctr.Container, os.Stdout, os.Stderr, inputStream, keys, sigProxy, !ctrRunning, ctr.PodID() != "")
if errors.Cause(err) == define.ErrDetach {
// User manually detached
// Exit cleanly immediately
exitCode = 0
return exitCode, nil
}
if errors.Cause(err) == define.ErrWillDeadlock {
logrus.Debugf("Deadlock error: %v", err)
return define.ExitCode(err), errors.Errorf("attempting to start container %s would cause a deadlock; please run 'podman system renumber' to resolve", ctr.ID())
}
if ctrRunning {
return 0, err
}
if err != nil {
return exitCode, errors.Wrapf(err, "unable to start container %s", ctr.ID())
}
if ecode, err := ctr.Wait(); err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr {
// Check events
event, err := r.Runtime.GetLastContainerEvent(ctr.ID(), events.Exited)
if err != nil {
logrus.Errorf("Cannot get exit code: %v", err)
exitCode = define.ExecErrorCodeNotFound
} else {
exitCode = event.ContainerExitCode
}
}
} else {
exitCode = int(ecode)
}
return exitCode, nil
}
// Start the container if it's not running already.
if !ctrRunning {
// Handle non-attach start
// If the container is in a pod, also set to recursively start dependencies
if err := ctr.Start(ctx, ctr.PodID() != ""); err != nil {
if lastError != nil {
fmt.Fprintln(os.Stderr, lastError)
}
if errors.Cause(err) == define.ErrWillDeadlock {
lastError = errors.Wrapf(err, "please run 'podman system renumber' to resolve deadlocks")
continue
}
lastError = errors.Wrapf(err, "unable to start container %q", container)
continue
}
}
// Check if the container is referenced by ID or by name and print
// it accordingly.
if strings.HasPrefix(ctr.ID(), container) {
fmt.Println(ctr.ID())
} else {
fmt.Println(container)
}
}
return exitCode, lastError
}
// PauseContainers removes container(s) based on CLI inputs.
func (r *LocalRuntime) PauseContainers(ctx context.Context, cli *cliconfig.PauseValues) ([]string, map[string]error, error) {
var (
ok = []string{}
failures = map[string]error{}
ctrs []*libpod.Container
err error
)
maxWorkers := shared.DefaultPoolSize("pause")
if cli.GlobalIsSet("max-workers") {
maxWorkers = cli.GlobalFlags.MaxWorks
}
logrus.Debugf("Setting maximum rm workers to %d", maxWorkers)
if cli.All {
ctrs, err = r.GetRunningContainers()
} else {
ctrs, err = shortcuts.GetContainersByContext(false, false, cli.InputArgs, r.Runtime)
}
if err != nil {
return ok, failures, err
}
pool := shared.NewPool("pause", maxWorkers, len(ctrs))
for _, c := range ctrs {
ctr := c
pool.Add(shared.Job{
ID: ctr.ID(),
Fn: func() error {
err := ctr.Pause()
if err != nil {
logrus.Debugf("Failed to pause container %s: %s", ctr.ID(), err.Error())
}
return err
},
})
}
return pool.Run()
}
// UnpauseContainers removes container(s) based on CLI inputs.
func (r *LocalRuntime) UnpauseContainers(ctx context.Context, cli *cliconfig.UnpauseValues) ([]string, map[string]error, error) {
var (
ok = []string{}
failures = map[string]error{}
ctrs []*libpod.Container
err error
)
maxWorkers := shared.DefaultPoolSize("pause")
if cli.GlobalIsSet("max-workers") {
maxWorkers = cli.GlobalFlags.MaxWorks
}
logrus.Debugf("Setting maximum rm workers to %d", maxWorkers)
if cli.All {
var filterFuncs []libpod.ContainerFilter
filterFuncs = append(filterFuncs, func(c *libpod.Container) bool {
state, _ := c.State()
return state == define.ContainerStatePaused
})
ctrs, err = r.GetContainers(filterFuncs...)
} else {
ctrs, err = shortcuts.GetContainersByContext(false, false, cli.InputArgs, r.Runtime)
}
if err != nil {
return ok, failures, err
}
pool := shared.NewPool("pause", maxWorkers, len(ctrs))
for _, c := range ctrs {
ctr := c
pool.Add(shared.Job{
ID: ctr.ID(),
Fn: func() error {
err := ctr.Unpause()
if err != nil {
logrus.Debugf("Failed to unpause container %s: %s", ctr.ID(), err.Error())
}
return err
},
})
}
return pool.Run()
}
// Restart containers without or without a timeout
func (r *LocalRuntime) Restart(ctx context.Context, c *cliconfig.RestartValues) ([]string, map[string]error, error) {
var (
containers []*libpod.Container
restartContainers []*libpod.Container
err error
)
useTimeout := c.Flag("timeout").Changed || c.Flag("time").Changed
inputTimeout := c.Timeout
// Handle --latest
switch {
case c.Latest:
lastCtr, err := r.Runtime.GetLatestContainer()
if err != nil {
return nil, nil, errors.Wrapf(err, "unable to get latest container")
}
restartContainers = append(restartContainers, lastCtr)
case c.Running:
containers, err = r.GetRunningContainers()
if err != nil {
return nil, nil, err
}
restartContainers = append(restartContainers, containers...)
case c.All:
containers, err = r.Runtime.GetAllContainers()
if err != nil {
return nil, nil, err
}
restartContainers = append(restartContainers, containers...)
default:
for _, id := range c.InputArgs {
ctr, err := r.Runtime.LookupContainer(id)
if err != nil {
return nil, nil, err
}
restartContainers = append(restartContainers, ctr)
}
}
maxWorkers := shared.DefaultPoolSize("restart")
if c.GlobalIsSet("max-workers") {
maxWorkers = c.GlobalFlags.MaxWorks
}
logrus.Debugf("Setting maximum workers to %d", maxWorkers)
// We now have a slice of all the containers to be restarted. Iterate them to
// create restart Funcs with a timeout as needed
pool := shared.NewPool("restart", maxWorkers, len(restartContainers))
for _, c := range restartContainers {
ctr := c
timeout := ctr.StopTimeout()
if useTimeout {
timeout = inputTimeout
}
pool.Add(shared.Job{
ID: ctr.ID(),
Fn: func() error {
err := ctr.RestartWithTimeout(ctx, timeout)
if err != nil {
logrus.Debugf("Failed to restart container %s: %s", ctr.ID(), err.Error())
}
return err
},
})
}
return pool.Run()
}
// Top display the running processes of a container
func (r *LocalRuntime) Top(cli *cliconfig.TopValues) ([]string, error) {
var (
descriptors []string
container *libpod.Container
err error
)
if cli.Latest {
descriptors = cli.InputArgs
container, err = r.Runtime.GetLatestContainer()
} else {
descriptors = cli.InputArgs[1:]
container, err = r.Runtime.LookupContainer(cli.InputArgs[0])
}
if err != nil {
return nil, errors.Wrapf(err, "unable to lookup requested container")
}
return container.Top(descriptors)
}
// ExecContainer executes a command in the container
func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecValues) (int, error) {
var (
ctr *Container
err error
cmd []string
)
// default invalid command exit code
ec := define.ExecErrorCodeGeneric
if cli.Latest {
if ctr, err = r.GetLatestContainer(); err != nil {
return ec, err
}
cmd = cli.InputArgs[0:]
} else {
if ctr, err = r.LookupContainer(cli.InputArgs[0]); err != nil {
return ec, err
}
cmd = cli.InputArgs[1:]
}
if cli.PreserveFDs > 0 {
entries, err := ioutil.ReadDir("/proc/self/fd")
if err != nil {
return ec, errors.Wrapf(err, "unable to read /proc/self/fd")
}
m := make(map[int]bool)
for _, e := range entries {
i, err := strconv.Atoi(e.Name())
if err != nil {
return ec, errors.Wrapf(err, "cannot parse %s in /proc/self/fd", e.Name())
}
m[i] = true
}
for i := 3; i < 3+cli.PreserveFDs; i++ {
if _, found := m[i]; !found {
return ec, errors.New("invalid --preserve-fds=N specified. Not enough FDs available")
}
}
}
// Validate given environment variables
env := map[string]string{}
if err := parse.ReadKVStrings(env, cli.EnvFile, cli.Env); err != nil {
return ec, errors.Wrapf(err, "unable to process environment variables")
}
streams := new(libpod.AttachStreams)
streams.OutputStream = os.Stdout
streams.ErrorStream = os.Stderr
if cli.Interactive {
streams.InputStream = bufio.NewReader(os.Stdin)
streams.AttachInput = true
}
streams.AttachOutput = true
streams.AttachError = true
keys := cli.DetachKeys
if !cli.IsSet("detach-keys") {
keys, err = r.selectDetachKeys(keys)
if err != nil {
return ec, err
}
}
ec, err = ExecAttachCtr(ctx, ctr.Container, cli.Tty, cli.Privileged, env, cmd, cli.User, cli.Workdir, streams, uint(cli.PreserveFDs), keys)
return define.TranslateExecErrorToExitCode(ec, err), err
}
// Prune removes stopped containers
func (r *LocalRuntime) Prune(ctx context.Context, maxWorkers int, filters []string) ([]string, map[string]error, error) {
var (
ok = []string{}
failures = map[string]error{}
err error
filterFunc []libpod.ContainerFilter
)
logrus.Debugf("Setting maximum rm workers to %d", maxWorkers)
for _, filter := range filters {
filterSplit := strings.SplitN(filter, "=", 2)
if len(filterSplit) < 2 {
return ok, failures, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", filter)
}
f, err := shared.GenerateContainerFilterFuncs(filterSplit[0], filterSplit[1], r.Runtime)
if err != nil {
return ok, failures, err
}
filterFunc = append(filterFunc, f)
}
containerStateFilter := func(c *libpod.Container) bool {
state, err := c.State()
if err != nil {
logrus.Error(err)
return false
}
if c.PodID() != "" {
return false
}
if state == define.ContainerStateStopped || state == define.ContainerStateExited {
return true
}
return false
}
filterFunc = append(filterFunc, containerStateFilter)
delContainers, err := r.Runtime.GetContainers(filterFunc...)
if err != nil {
return ok, failures, err
}
if len(delContainers) < 1 {
return ok, failures, err
}
pool := shared.NewPool("prune", maxWorkers, len(delContainers))
for _, c := range delContainers {
ctr := c
pool.Add(shared.Job{
ID: ctr.ID(),
Fn: func() error {
err := r.Runtime.RemoveContainer(ctx, ctr, false, false)
if err != nil {
logrus.Debugf("Failed to prune container %s: %s", ctr.ID(), err.Error())
}
return err
},
})
}
return pool.Run()
}
// CleanupContainers any leftovers bits of stopped containers
func (r *LocalRuntime) CleanupContainers(ctx context.Context, cli *cliconfig.CleanupValues) ([]string, map[string]error, error) {
var (
ok = []string{}
failures = map[string]error{}
)
ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime)
if err != nil {
return ok, failures, err
}
for _, ctr := range ctrs {
if cli.Remove {
err = removeContainer(ctx, ctr, r)
} else {
err = cleanupContainer(ctx, ctr, r)
}
if err == nil {
ok = append(ok, ctr.ID())
} else {
failures[ctr.ID()] = err
}
}
return ok, failures, nil
}
// Only used when cleaning up containers
func removeContainer(ctx context.Context, ctr *libpod.Container, runtime *LocalRuntime) error {
if err := runtime.RemoveContainer(ctx, ctr, false, true); err != nil {
return errors.Wrapf(err, "failed to cleanup and remove container %v", ctr.ID())
}
return nil
}
func cleanupContainer(ctx context.Context, ctr *libpod.Container, runtime *LocalRuntime) error {
if err := ctr.Cleanup(ctx); err != nil {
return errors.Wrapf(err, "failed to cleanup container %v", ctr.ID())
}
return nil
}
// Port displays port information about existing containers
func (r *LocalRuntime) Port(c *cliconfig.PortValues) ([]*Container, error) {
var (
portContainers []*Container
containers []*libpod.Container
err error
)
if !c.All {
names := []string{}
if len(c.InputArgs) >= 1 {
names = []string{c.InputArgs[0]}
}
containers, err = shortcuts.GetContainersByContext(false, c.Latest, names, r.Runtime)
} else {
containers, err = r.Runtime.GetRunningContainers()
}
if err != nil {
return nil, err
}
//Convert libpod containers to adapter Containers
for _, con := range containers {
if state, _ := con.State(); state != define.ContainerStateRunning {
continue
}
portContainers = append(portContainers, &Container{con})
}
return portContainers, nil
}
// generateServiceName generates the container name and the service name for systemd service.
func generateServiceName(c *cliconfig.GenerateSystemdValues, ctr *libpod.Container, pod *libpod.Pod) (string, string) {
var kind, name, ctrName string
if pod == nil {
kind = "container"
name = ctr.ID()
if c.Name {
name = ctr.Name()
}
ctrName = name
} else {
kind = "pod"
name = pod.ID()
ctrName = ctr.ID()
if c.Name {
name = pod.Name()
ctrName = ctr.Name()
}
}
return ctrName, fmt.Sprintf("%s-%s", kind, name)
}
// generateSystemdgenContainerInfo is a helper to generate a
// systemdgen.ContainerInfo for `GenerateSystemd`.
func (r *LocalRuntime) generateSystemdgenContainerInfo(c *cliconfig.GenerateSystemdValues, nameOrID string, pod *libpod.Pod) (*generate.ContainerInfo, bool, error) {
ctr, err := r.Runtime.LookupContainer(nameOrID)
if err != nil {
return nil, false, err
}
timeout := int(ctr.StopTimeout())
if c.StopTimeout >= 0 {
timeout = c.StopTimeout
}
config := ctr.Config()
conmonPidFile := config.ConmonPidFile
if conmonPidFile == "" {
return nil, true, errors.Errorf("conmon PID file path is empty, try to recreate the container with --conmon-pidfile flag")
}
name, serviceName := generateServiceName(c, ctr, pod)
info := &generate.ContainerInfo{
ServiceName: serviceName,
ContainerName: name,
RestartPolicy: c.RestartPolicy,
PIDFile: conmonPidFile,
StopTimeout: timeout,
GenerateTimestamp: true,
CreateCommand: config.CreateCommand,
}
return info, true, nil
}
// GenerateSystemd creates a unit file for a container or pod.
func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (string, error) {
opts := generate.Options{
Files: c.Files,
New: c.New,
}
// First assume it's a container.
if info, found, err := r.generateSystemdgenContainerInfo(c, c.InputArgs[0], nil); found && err != nil {
return "", err
} else if found && err == nil {
return generate.CreateContainerSystemdUnit(info, opts)
}
// --new does not support pods.
if c.New {
return "", errors.Errorf("error generating systemd unit files: cannot generate generic files for a pod")
}
// We're either having a pod or garbage.
pod, err := r.Runtime.LookupPod(c.InputArgs[0])
if err != nil {
return "", err
}
// Error out if the pod has no infra container, which we require to be the
// main service.
if !pod.HasInfraContainer() {
return "", fmt.Errorf("error generating systemd unit files: Pod %q has no infra container", pod.Name())
}
// Generate a systemdgen.ContainerInfo for the infra container. This
// ContainerInfo acts as the main service of the pod.
infraID, err := pod.InfraContainerID()
if err != nil {
return "", nil
}
podInfo, _, err := r.generateSystemdgenContainerInfo(c, infraID, pod)
if err != nil {
return "", nil
}
// Compute the container-dependency graph for the Pod.
containers, err := pod.AllContainers()
if err != nil {
return "", err
}
if len(containers) == 0 {
return "", fmt.Errorf("error generating systemd unit files: Pod %q has no containers", pod.Name())
}
graph, err := libpod.BuildContainerGraph(containers)
if err != nil {
return "", err
}
// Traverse the dependency graph and create systemdgen.ContainerInfo's for
// each container.
containerInfos := []*generate.ContainerInfo{podInfo}
for ctr, dependencies := range graph.DependencyMap() {
// Skip the infra container as we already generated it.
if ctr.ID() == infraID {
continue
}
ctrInfo, _, err := r.generateSystemdgenContainerInfo(c, ctr.ID(), nil)
if err != nil {
return "", err
}
// Now add the container's dependencies and at the container as a
// required service of the infra container.
for _, dep := range dependencies {
if dep.ID() == infraID {
ctrInfo.BoundToServices = append(ctrInfo.BoundToServices, podInfo.ServiceName)
} else {
_, serviceName := generateServiceName(c, dep, nil)
ctrInfo.BoundToServices = append(ctrInfo.BoundToServices, serviceName)
}
}
podInfo.RequiredServices = append(podInfo.RequiredServices, ctrInfo.ServiceName)
containerInfos = append(containerInfos, ctrInfo)
}
// Now generate the systemd service for all containers.
builder := strings.Builder{}
for i, info := range containerInfos {
if i > 0 {
builder.WriteByte('\n')
}
out, err := generate.CreateContainerSystemdUnit(info, opts)
if err != nil {
return "", err
}
builder.WriteString(out)
}
return builder.String(), nil
}
// GetNamespaces returns namespace information about a container for PS
func (r *LocalRuntime) GetNamespaces(container shared.PsContainerOutput) *shared.Namespace {
return shared.GetNamespaces(container.Pid)
}
// Commit creates a local image from a container
func (r *LocalRuntime) Commit(ctx context.Context, c *cliconfig.CommitValues, container, imageName string) (string, error) {
var (
writer io.Writer
mimeType string
)
switch c.Format {
case "oci":
mimeType = buildah.OCIv1ImageManifest
if c.Flag("message").Changed {
return "", errors.Errorf("messages are only compatible with the docker image format (-f docker)")
}
case "docker":
mimeType = manifest.DockerV2Schema2MediaType
default:
return "", errors.Errorf("unrecognized image format %q", c.Format)
}
if !c.Quiet {
writer = os.Stderr
}
ctr, err := r.Runtime.LookupContainer(container)
if err != nil {
return "", errors.Wrapf(err, "error looking up container %q", container)
}
rtc, err := r.Runtime.GetConfig()
if err != nil {
return "", err
}
sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false)
coptions := buildah.CommitOptions{
SignaturePolicyPath: rtc.SignaturePolicyPath,
ReportWriter: writer,
SystemContext: sc,
PreferredManifestType: mimeType,
}
options := libpod.ContainerCommitOptions{
CommitOptions: coptions,
Pause: c.Pause,
IncludeVolumes: c.IncludeVolumes,
Message: c.Message,
Changes: c.Change,
Author: c.Author,
}
newImage, err := ctr.Commit(ctx, imageName, options)
if err != nil {
return "", err
}
return newImage.ID(), nil
}