From 43413887c0d8a20095e454d1046df46a42810e75 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 18 May 2020 16:22:56 -0400 Subject: Add backend code for detached exec As part of the massive exec rework, I stubbed out a function for non-detached exec, which is implemented here. It's largely similar to the existing exec functions, but missing a few pieces. This also involves implemented a new OCI runtime call for detached exec. Again, very similar to the other functions, but with a few missing pieces. Signed-off-by: Matthew Heon --- libpod/container_exec.go | 49 +++++++++++++++++++++++++++++++++++++---- libpod/oci.go | 12 ++++++---- libpod/oci_conmon_exec_linux.go | 43 ++++++++++++++++++++++++++++++++++++ libpod/oci_missing.go | 5 +++++ 4 files changed, 101 insertions(+), 8 deletions(-) (limited to 'libpod') diff --git a/libpod/container_exec.go b/libpod/container_exec.go index 088bd0fd5..a38f22488 100644 --- a/libpod/container_exec.go +++ b/libpod/container_exec.go @@ -214,11 +214,52 @@ func (c *Container) ExecCreate(config *ExecConfig) (string, error) { } // ExecStart starts an exec session in the container, but does not attach to it. -// Returns immediately upon starting the exec session. +// Returns immediately upon starting the exec session, unlike other ExecStart +// functions, which will only return when the exec session exits. func (c *Container) ExecStart(sessionID string) error { - // Will be implemented in part 2, migrating Start and implementing - // detached Start. - return define.ErrNotImplemented + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return err + } + } + + // Verify that we are in a good state to continue + if !c.ensureState(define.ContainerStateRunning) { + return errors.Wrapf(define.ErrCtrStateInvalid, "can only start exec sessions when their container is running") + } + + session, ok := c.state.ExecSessions[sessionID] + if !ok { + return errors.Wrapf(define.ErrNoSuchExecSession, "container %s has no exec session with ID %s", c.ID(), sessionID) + } + + if session.State != define.ExecStateCreated { + return errors.Wrapf(define.ErrExecSessionStateInvalid, "can only start created exec sessions, while container %s session %s state is %q", c.ID(), session.ID(), session.State.String()) + } + + logrus.Infof("Going to start container %s exec session %s and attach to it", c.ID(), session.ID()) + + opts, err := prepareForExec(c, session) + if err != nil { + return err + } + + pid, err := c.ociRuntime.ExecContainerDetached(c, session.ID(), opts, session.Config.AttachStdin) + if err != nil { + return err + } + + c.newContainerEvent(events.Exec) + logrus.Debugf("Successfully started exec session %s in container %s", session.ID(), c.ID()) + + // Update and save session to reflect PID/running + session.PID = pid + session.State = define.ExecStateRunning + + return c.save() } // ExecStartAndAttach starts and attaches to an exec session in a container. diff --git a/libpod/oci.go b/libpod/oci.go index da4030c14..7c5218319 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -68,10 +68,10 @@ type OCIRuntime interface { AttachResize(ctr *Container, newSize remotecommand.TerminalSize) error // ExecContainer executes a command in a running container. - // Returns an int (exit code), error channel (errors from attach), and - // error (errors that occurred attempting to start the exec session). - // This returns once the exec session is running - not once it has - // completed, as one might expect. The attach session will remain + // Returns an int (PID of exec session), error channel (errors from + // attach), and error (errors that occurred attempting to start the exec + // session). This returns once the exec session is running - not once it + // has completed, as one might expect. The attach session will remain // running, in a goroutine that will return via the chan error in the // return signature. ExecContainer(ctr *Container, sessionID string, options *ExecOptions, streams *define.AttachStreams) (int, chan error, error) @@ -81,6 +81,10 @@ type OCIRuntime interface { // start, with a goroutine running in the background to handle attach). // The HTTP attach itself maintains the same invariants as HTTPAttach. ExecContainerHTTP(ctr *Container, sessionID string, options *ExecOptions, httpConn net.Conn, httpBuf *bufio.ReadWriter, streams *HTTPAttachStreams, cancel <-chan bool) (int, chan error, error) + // ExecContainerDetached executes a command in a running container, but + // does not attach to it. Returns the PID of the exec session and an + // error (if starting the exec session failed) + ExecContainerDetached(ctr *Container, sessionID string, options *ExecOptions, stdin bool) (int, error) // ExecAttachResize resizes the terminal of a running exec session. Only // allowed with sessions that were created with a TTY. ExecAttachResize(ctr *Container, sessionID string, newSize remotecommand.TerminalSize) error diff --git a/libpod/oci_conmon_exec_linux.go b/libpod/oci_conmon_exec_linux.go index 3fc866796..51819f90a 100644 --- a/libpod/oci_conmon_exec_linux.go +++ b/libpod/oci_conmon_exec_linux.go @@ -143,6 +143,49 @@ func (r *ConmonOCIRuntime) ExecContainerHTTP(ctr *Container, sessionID string, o return pid, attachChan, err } +// ExecContainerDetached executes a command in a running container, but does +// not attach to it. +func (r *ConmonOCIRuntime) ExecContainerDetached(ctr *Container, sessionID string, options *ExecOptions, stdin bool) (int, error) { + if options == nil { + return -1, errors.Wrapf(define.ErrInvalidArg, "must provide exec options to ExecContainerHTTP") + } + + var ociLog string + if logrus.GetLevel() != logrus.DebugLevel && r.supportsJSON { + ociLog = ctr.execOCILog(sessionID) + } + + execCmd, pipes, err := r.startExec(ctr, sessionID, options, stdin, ociLog) + if err != nil { + return -1, err + } + + defer func() { + pipes.cleanup() + }() + + // Wait for Conmon to tell us we're ready to attach. + // We aren't actually *going* to attach, but this means that we're good + // to proceed. + if _, err := readConmonPipeData(pipes.attachPipe, ""); err != nil { + return -1, err + } + + // Start the exec session + if err := writeConmonPipeData(pipes.startPipe); err != nil { + return -1, err + } + + // Wait for conmon to succeed, when return. + if err := execCmd.Wait(); err != nil { + return -1, errors.Wrapf(err, "cannot run conmon") + } + + pid, err := readConmonPipeData(pipes.syncPipe, ociLog) + + return pid, err +} + // ExecAttachResize resizes the TTY of the given exec session. func (r *ConmonOCIRuntime) ExecAttachResize(ctr *Container, sessionID string, newSize remotecommand.TerminalSize) error { controlFile, err := openControlFile(ctr, ctr.execBundlePath(sessionID)) diff --git a/libpod/oci_missing.go b/libpod/oci_missing.go index 626740f72..4da16876c 100644 --- a/libpod/oci_missing.go +++ b/libpod/oci_missing.go @@ -130,6 +130,11 @@ func (r *MissingRuntime) ExecContainerHTTP(ctr *Container, sessionID string, opt return -1, nil, r.printError() } +// ExecContainerDetached is not available as the runtime is missing +func (r *MissingRuntime) ExecContainerDetached(ctr *Container, sessionID string, options *ExecOptions, stdin bool) (int, error) { + return -1, r.printError() +} + // ExecAttachResize is not available as the runtime is missing. func (r *MissingRuntime) ExecAttachResize(ctr *Container, sessionID string, newSize remotecommand.TerminalSize) error { return r.printError() -- cgit v1.2.3-54-g00ecf