summaryrefslogtreecommitdiff
path: root/libpod/container_api.go
diff options
context:
space:
mode:
authorPeter Hunt <pehunt@redhat.com>2019-07-01 13:55:03 -0400
committerPeter Hunt <pehunt@redhat.com>2019-07-22 15:57:23 -0400
commita1a79c08b72793cf2f75490d8ffc844c3d16bd4a (patch)
tree0ba4dd73229399a4c57e9d073327886fa3640707 /libpod/container_api.go
parentcf9efa90e5dcf89e10408eae5229c4ce904d9fc7 (diff)
downloadpodman-a1a79c08b72793cf2f75490d8ffc844c3d16bd4a.tar.gz
podman-a1a79c08b72793cf2f75490d8ffc844c3d16bd4a.tar.bz2
podman-a1a79c08b72793cf2f75490d8ffc844c3d16bd4a.zip
Implement conmon exec
This includes: Implement exec -i and fix some typos in description of -i docs pass failed runtime status to caller Add resize handling for a terminal connection Customize exec systemd-cgroup slice fix healthcheck fix top add --detach-keys Implement podman-remote exec (jhonce) * Cleanup some orphaned code (jhonce) adapt remote exec for conmon exec (pehunt) Fix healthcheck and exec to match docs Introduce two new OCIRuntime errors to more comprehensively describe situations in which the runtime can error Use these different errors in branching for exit code in healthcheck and exec Set conmon to use new api version Signed-off-by: Jhon Honce <jhonce@redhat.com> Signed-off-by: Peter Hunt <pehunt@redhat.com>
Diffstat (limited to 'libpod/container_api.go')
-rw-r--r--libpod/container_api.go130
1 files changed, 49 insertions, 81 deletions
diff --git a/libpod/container_api.go b/libpod/container_api.go
index 50fa1759d..0cce6ca22 100644
--- a/libpod/container_api.go
+++ b/libpod/container_api.go
@@ -2,16 +2,13 @@ package libpod
import (
"context"
- "fmt"
"io"
"io/ioutil"
"os"
- "strconv"
"time"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/events"
- "github.com/containers/libpod/pkg/lookup"
"github.com/containers/storage/pkg/stringid"
"github.com/docker/docker/oci/caps"
"github.com/opentracing/opentracing-go"
@@ -21,6 +18,11 @@ import (
"k8s.io/client-go/tools/remotecommand"
)
+const (
+ defaultExecExitCode = 125
+ defaultExecExitCodeCannotInvoke = 126
+)
+
// Init creates a container in the OCI runtime
func (c *Container) Init(ctx context.Context) (err error) {
span, _ := opentracing.StartSpanFromContext(ctx, "containerInit")
@@ -220,23 +222,19 @@ func (c *Container) Kill(signal uint) error {
}
// Exec starts a new process inside the container
+// Returns an exit code and an error. If Exec was not able to exec in the container before a failure, an exit code of 126 is returned.
+// If another generic error happens, an exit code of 125 is returned.
+// Sometimes, the $RUNTIME exec call errors, and if that is the case, the exit code is the exit code of the call.
+// Otherwise, the exit code will be the exit code of the executed call inside of the container.
// TODO investigate allowing exec without attaching
-func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir string, streams *AttachStreams, preserveFDs int) error {
+func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir string, streams *AttachStreams, preserveFDs int, resize chan remotecommand.TerminalSize, detachKeys string) (int, error) {
var capList []string
-
- locked := false
if !c.batched {
- locked = true
-
c.lock.Lock()
- defer func() {
- if locked {
- c.lock.Unlock()
- }
- }()
+ defer c.lock.Unlock()
if err := c.syncContainer(); err != nil {
- return err
+ return defaultExecExitCodeCannotInvoke, err
}
}
@@ -244,25 +242,13 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir
// TODO can probably relax this once we track exec sessions
if conState != define.ContainerStateRunning {
- return errors.Wrapf(define.ErrCtrStateInvalid, "cannot exec into container that is not running")
+ return defaultExecExitCodeCannotInvoke, errors.Wrapf(define.ErrCtrStateInvalid, "cannot exec into container that is not running")
}
+
if privileged || c.config.Privileged {
capList = caps.GetAllCapabilities()
}
- // If user was set, look it up in the container to get a UID to use on
- // the host
- hostUser := ""
- if user != "" {
- execUser, err := lookup.GetUserGroupInfo(c.state.Mountpoint, user, nil)
- if err != nil {
- return err
- }
-
- // runc expects user formatted as uid:gid
- hostUser = fmt.Sprintf("%d:%d", execUser.Uid, execUser.Gid)
- }
-
// Generate exec session ID
// Ensure we don't conflict with an existing session ID
sessionID := stringid.GenerateNonCryptoID()
@@ -282,55 +268,27 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir
}
logrus.Debugf("Creating new exec session in container %s with session id %s", c.ID(), sessionID)
-
- execCmd, err := c.ociRuntime.execContainer(c, cmd, capList, env, tty, workDir, hostUser, sessionID, streams, preserveFDs)
- if err != nil {
- return errors.Wrapf(err, "error exec %s", c.ID())
+ if err := c.createExecBundle(sessionID); err != nil {
+ return defaultExecExitCodeCannotInvoke, err
}
- chWait := make(chan error)
- go func() {
- chWait <- execCmd.Wait()
- close(chWait)
- }()
- pidFile := c.execPidPath(sessionID)
- // 60 second seems a reasonable time to wait
- // https://github.com/containers/libpod/issues/1495
- // https://github.com/containers/libpod/issues/1816
- const pidWaitTimeout = 60000
-
- // Wait until the runtime makes the pidfile
- exited, err := WaitForFile(pidFile, chWait, pidWaitTimeout*time.Millisecond)
- if err != nil {
- if exited {
- // If the runtime exited, propagate the error we got from the process.
- // We need to remove PID files to ensure no memory leaks
- if err2 := os.Remove(pidFile); err2 != nil {
- logrus.Errorf("Error removing exit file for container %s exec session %s: %v", c.ID(), sessionID, err2)
- }
-
- return err
+ defer func() {
+ // cleanup exec bundle
+ if err := c.cleanupExecBundle(sessionID); err != nil {
+ logrus.Errorf("Error removing exec session %s bundle path for container %s: %v", sessionID, c.ID(), err)
}
- return errors.Wrapf(err, "timed out waiting for runtime to create pidfile for exec session in container %s", c.ID())
- }
+ }()
- // Pidfile exists, read it
- contents, err := ioutil.ReadFile(pidFile)
- // We need to remove PID files to ensure no memory leaks
- if err2 := os.Remove(pidFile); err2 != nil {
- logrus.Errorf("Error removing exit file for container %s exec session %s: %v", c.ID(), sessionID, err2)
- }
- if err != nil {
- // We don't know the PID of the exec session
- // However, it may still be alive
- // TODO handle this better
- return errors.Wrapf(err, "could not read pidfile for exec session %s in container %s", sessionID, c.ID())
- }
- pid, err := strconv.ParseInt(string(contents), 10, 32)
+ pid, attachChan, err := c.ociRuntime.execContainer(c, cmd, capList, env, tty, workDir, user, sessionID, streams, preserveFDs, resize, detachKeys)
if err != nil {
- // As above, we don't have a valid PID, but the exec session is likely still alive
- // TODO handle this better
- return errors.Wrapf(err, "error parsing PID of exec session %s in container %s", sessionID, c.ID())
+ ec := defaultExecExitCode
+ // Conmon will pass a non-zero exit code from the runtime as a pid here.
+ // we differentiate a pid with an exit code by sending it as negative, so reverse
+ // that change and return the exit code the runtime failed with.
+ if pid < 0 {
+ ec = -1 * pid
+ }
+ return ec, err
}
// We have the PID, add it to state
@@ -340,12 +298,12 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir
session := new(ExecSession)
session.ID = sessionID
session.Command = cmd
- session.PID = int(pid)
+ session.PID = pid
c.state.ExecSessions[sessionID] = session
if err := c.save(); err != nil {
// Now we have a PID but we can't save it in the DB
// TODO handle this better
- return errors.Wrapf(err, "error saving exec sessions %s for container %s", sessionID, c.ID())
+ return defaultExecExitCode, errors.Wrapf(err, "error saving exec sessions %s for container %s", sessionID, c.ID())
}
c.newContainerEvent(events.Exec)
logrus.Debugf("Successfully started exec session %s in container %s", sessionID, c.ID())
@@ -353,23 +311,33 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir
// Unlock so other processes can use the container
if !c.batched {
c.lock.Unlock()
- locked = false
}
- var waitErr error
- if !exited {
- waitErr = <-chWait
+ lastErr := <-attachChan
+
+ exitCode, err := c.readExecExitCode(sessionID)
+ if err != nil {
+ if lastErr != nil {
+ logrus.Errorf(lastErr.Error())
+ }
+ lastErr = err
+ }
+ if exitCode != 0 {
+ if lastErr != nil {
+ logrus.Errorf(lastErr.Error())
+ }
+ lastErr = errors.Wrapf(define.ErrOCIRuntime, "non zero exit code: %d", exitCode)
}
// Lock again
if !c.batched {
- locked = true
c.lock.Lock()
}
// Sync the container again to pick up changes in state
if err := c.syncContainer(); err != nil {
- return errors.Wrapf(err, "error syncing container %s state to remove exec session %s", c.ID(), sessionID)
+ logrus.Errorf("error syncing container %s state to remove exec session %s", c.ID(), sessionID)
+ return exitCode, lastErr
}
// Remove the exec session from state
@@ -377,7 +345,7 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir
if err := c.save(); err != nil {
logrus.Errorf("Error removing exec session %s from container %s state: %v", sessionID, c.ID(), err)
}
- return waitErr
+ return exitCode, lastErr
}
// AttachStreams contains streams that will be attached to the container