Revert "exec: get the exit code from sync pipe instead of file"

This reverts commit 4b72f9e4013411208751df2a92ab9f322d4da5b2.

Continues what began with revert of
d3d97a25e8c87cf741b2e24ac01ef84962137106 in previous commit.

Signed-off-by: Matthew Heon <matthew.heon@pm.me>
This commit is contained in:
Matthew Heon
2020-03-09 09:49:27 -04:00
parent ffce869daa
commit 521ff14d83
6 changed files with 63 additions and 83 deletions

View File

@ -282,16 +282,7 @@ func (c *Container) Exec(tty, privileged bool, env map[string]string, cmd []stri
opts.Resize = resize opts.Resize = resize
opts.DetachKeys = detachKeys opts.DetachKeys = detachKeys
pid := 0 pid, attachChan, err := c.ociRuntime.ExecContainer(c, sessionID, opts)
pipeDataChan, attachChan, err := c.ociRuntime.ExecContainer(c, sessionID, opts)
// if pipeDataChan isn't nil, we should set the err
if pipeDataChan != nil {
pidData := <-pipeDataChan
if pidData.err != nil {
err = pidData.err
}
pid = pidData.data
}
if err != nil { if err != nil {
ec := define.ExecErrorCodeGeneric ec := define.ExecErrorCodeGeneric
// Conmon will pass a non-zero exit code from the runtime as a pid here. // Conmon will pass a non-zero exit code from the runtime as a pid here.
@ -327,18 +318,18 @@ func (c *Container) Exec(tty, privileged bool, env map[string]string, cmd []stri
lastErr := <-attachChan lastErr := <-attachChan
exitCodeData := <-pipeDataChan exitCode, err := c.readExecExitCode(sessionID)
if exitCodeData.err != nil { if err != nil {
if lastErr != nil { if lastErr != nil {
logrus.Errorf(lastErr.Error()) logrus.Errorf(lastErr.Error())
} }
lastErr = exitCodeData.err lastErr = err
} }
if exitCodeData.data != 0 { if exitCode != 0 {
if lastErr != nil { if lastErr != nil {
logrus.Errorf(lastErr.Error()) logrus.Errorf(lastErr.Error())
} }
lastErr = errors.Wrapf(define.ErrOCIRuntime, "non zero exit code: %d", exitCodeData.data) lastErr = errors.Wrapf(define.ErrOCIRuntime, "non zero exit code: %d", exitCode)
} }
// Lock again // Lock again
@ -349,7 +340,7 @@ func (c *Container) Exec(tty, privileged bool, env map[string]string, cmd []stri
// Sync the container again to pick up changes in state // Sync the container again to pick up changes in state
if err := c.syncContainer(); err != nil { if err := c.syncContainer(); err != nil {
logrus.Errorf("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 exitCodeData.data, lastErr return exitCode, lastErr
} }
// Remove the exec session from state // Remove the exec session from state
@ -357,7 +348,7 @@ func (c *Container) Exec(tty, privileged bool, env map[string]string, cmd []stri
if err := c.save(); err != nil { if err := c.save(); err != nil {
logrus.Errorf("Error removing exec session %s from container %s state: %v", sessionID, c.ID(), err) logrus.Errorf("Error removing exec session %s from container %s state: %v", sessionID, c.ID(), err)
} }
return exitCodeData.data, lastErr return exitCode, lastErr
} }
// AttachStreams contains streams that will be attached to the container // AttachStreams contains streams that will be attached to the container

View File

@ -206,6 +206,28 @@ func (c *Container) execOCILog(sessionID string) string {
return filepath.Join(c.execBundlePath(sessionID), "oci-log") return filepath.Join(c.execBundlePath(sessionID), "oci-log")
} }
// readExecExitCode reads the exit file for an exec session and returns
// the exit code
func (c *Container) readExecExitCode(sessionID string) (int, error) {
exitFile := filepath.Join(c.execExitFileDir(sessionID), c.ID())
chWait := make(chan error)
defer close(chWait)
_, err := WaitForFile(exitFile, chWait, time.Second*5)
if err != nil {
return -1, err
}
ec, err := ioutil.ReadFile(exitFile)
if err != nil {
return -1, err
}
ecInt, err := strconv.Atoi(string(ec))
if err != nil {
return -1, err
}
return ecInt, nil
}
// Wait for the container's exit file to appear. // Wait for the container's exit file to appear.
// When it does, update our state based on it. // When it does, update our state based on it.
func (c *Container) waitForExitFileAndSync() error { func (c *Container) waitForExitFileAndSync() error {

View File

@ -70,7 +70,7 @@ type OCIRuntime interface {
// ExecContainer executes a command in a running container. // ExecContainer executes a command in a running container.
// Returns an int (exit code), error channel (errors from attach), and // Returns an int (exit code), error channel (errors from attach), and
// error (errors that occurred attempting to start the exec session). // error (errors that occurred attempting to start the exec session).
ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (chan DataAndErr, chan error, error) ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (int, chan error, error)
// ExecStopContainer stops a given exec session in a running container. // ExecStopContainer stops a given exec session in a running container.
// SIGTERM with be sent initially, then SIGKILL after the given timeout. // SIGTERM with be sent initially, then SIGKILL after the given timeout.
// If timeout is 0, SIGKILL will be sent immediately, and SIGTERM will // If timeout is 0, SIGKILL will be sent immediately, and SIGTERM will
@ -159,10 +159,3 @@ type HTTPAttachStreams struct {
Stdout bool Stdout bool
Stderr bool Stderr bool
} }
// DataAndErr is a generic structure for passing around an int and an error
// it is especially useful for getting information from conmon
type DataAndErr struct {
data int
err error
}

View File

@ -119,8 +119,8 @@ func (c *Container) attachToExec(streams *AttachStreams, keys string, resize <-c
socketPath := buildSocketPath(sockPath) socketPath := buildSocketPath(sockPath)
// 2: read from attachFd that the parent process has set up the console socket // 2: read from attachFd that the parent process has set up the console socket
if pipeData := readConmonPipeData(attachFd, ""); pipeData.err != nil { if _, err := readConmonPipeData(attachFd, ""); err != nil {
return pipeData.err return err
} }
// 2: then attach // 2: then attach

View File

@ -595,29 +595,31 @@ func (r *ConmonOCIRuntime) AttachResize(ctr *Container, newSize remotecommand.Te
// ExecContainer executes a command in a running container // ExecContainer executes a command in a running container
// TODO: Split into Create/Start/Attach/Wait // TODO: Split into Create/Start/Attach/Wait
func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options *ExecOptions) (chan DataAndErr, chan error, error) { func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options *ExecOptions) (int, chan error, error) {
if options == nil { if options == nil {
return nil, nil, errors.Wrapf(define.ErrInvalidArg, "must provide an ExecOptions struct to ExecContainer") return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide an ExecOptions struct to ExecContainer")
} }
if len(options.Cmd) == 0 { if len(options.Cmd) == 0 {
return nil, nil, errors.Wrapf(define.ErrInvalidArg, "must provide a command to execute") return -1, nil, errors.Wrapf(define.ErrInvalidArg, "must provide a command to execute")
} }
if sessionID == "" { if sessionID == "" {
return nil, nil, errors.Wrapf(define.ErrEmptyID, "must provide a session ID for exec") return -1, nil, errors.Wrapf(define.ErrEmptyID, "must provide a session ID for exec")
} }
// create sync pipe to receive the pid // create sync pipe to receive the pid
parentSyncPipe, childSyncPipe, err := newPipe() parentSyncPipe, childSyncPipe, err := newPipe()
if err != nil { if err != nil {
return nil, nil, errors.Wrapf(err, "error creating socket pair") return -1, nil, errors.Wrapf(err, "error creating socket pair")
} }
defer errorhandling.CloseQuiet(parentSyncPipe)
// create start pipe to set the cgroup before running // create start pipe to set the cgroup before running
// attachToExec is responsible for closing parentStartPipe // attachToExec is responsible for closing parentStartPipe
childStartPipe, parentStartPipe, err := newPipe() childStartPipe, parentStartPipe, err := newPipe()
if err != nil { if err != nil {
return nil, nil, errors.Wrapf(err, "error creating socket pair") return -1, nil, errors.Wrapf(err, "error creating socket pair")
} }
// We want to make sure we close the parent{Start,Attach}Pipes if we fail // We want to make sure we close the parent{Start,Attach}Pipes if we fail
@ -636,7 +638,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options
// attachToExec is responsible for closing parentAttachPipe // attachToExec is responsible for closing parentAttachPipe
parentAttachPipe, childAttachPipe, err := newPipe() parentAttachPipe, childAttachPipe, err := newPipe()
if err != nil { if err != nil {
return nil, nil, errors.Wrapf(err, "error creating socket pair") return -1, nil, errors.Wrapf(err, "error creating socket pair")
} }
defer func() { defer func() {
@ -656,7 +658,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options
runtimeDir, err := util.GetRuntimeDir() runtimeDir, err := util.GetRuntimeDir()
if err != nil { if err != nil {
return nil, nil, err return -1, nil, err
} }
finalEnv := make([]string, 0, len(options.Env)) finalEnv := make([]string, 0, len(options.Env))
@ -666,7 +668,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options
processFile, err := prepareProcessExec(c, options.Cmd, finalEnv, options.Terminal, options.Cwd, options.User, sessionID) processFile, err := prepareProcessExec(c, options.Cmd, finalEnv, options.Terminal, options.Cwd, options.User, sessionID)
if err != nil { if err != nil {
return nil, nil, err return -1, nil, err
} }
var ociLog string var ociLog string
@ -715,7 +717,7 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options
conmonEnv, extraFiles, err := r.configureConmonEnv(runtimeDir) conmonEnv, extraFiles, err := r.configureConmonEnv(runtimeDir)
if err != nil { if err != nil {
return nil, nil, err return -1, nil, err
} }
if options.PreserveFDs > 0 { if options.PreserveFDs > 0 {
@ -746,10 +748,10 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options
childrenClosed = true childrenClosed = true
if err != nil { if err != nil {
return nil, nil, errors.Wrapf(err, "cannot start container %s", c.ID()) return -1, nil, errors.Wrapf(err, "cannot start container %s", c.ID())
} }
if err := r.moveConmonToCgroupAndSignal(c, execCmd, parentStartPipe); err != nil { if err := r.moveConmonToCgroupAndSignal(c, execCmd, parentStartPipe); err != nil {
return nil, nil, err return -1, nil, err
} }
if options.PreserveFDs > 0 { if options.PreserveFDs > 0 {
@ -772,16 +774,9 @@ func (r *ConmonOCIRuntime) ExecContainer(c *Container, sessionID string, options
}() }()
attachToExecCalled = true attachToExecCalled = true
dataChan := make(chan DataAndErr) pid, err := readConmonPipeData(parentSyncPipe, ociLog)
go func() {
// read the exec pid
dataChan <- readConmonPipeData(parentSyncPipe, ociLog)
// read the exec exit code
dataChan <- readConmonPipeData(parentSyncPipe, ociLog)
errorhandling.CloseQuiet(parentSyncPipe)
}()
return dataChan, attachChan, err return pid, attachChan, err
} }
// ExecStopContainer stops a given exec session in a running container. // ExecStopContainer stops a given exec session in a running container.
@ -1211,14 +1206,14 @@ func (r *ConmonOCIRuntime) createOCIContainer(ctr *Container, restoreOptions *Co
return err return err
} }
pipeData := readConmonPipeData(parentSyncPipe, ociLog) pid, err := readConmonPipeData(parentSyncPipe, ociLog)
if pipeData.err != nil { if err != nil {
if err2 := r.DeleteContainer(ctr); err2 != nil { if err2 := r.DeleteContainer(ctr); err2 != nil {
logrus.Errorf("Error removing container %s from runtime after creation failed", ctr.ID()) logrus.Errorf("Error removing container %s from runtime after creation failed", ctr.ID())
} }
return pipeData.err return err
} }
ctr.state.PID = pipeData.data ctr.state.PID = pid
conmonPID, err := readConmonPidFile(ctr.config.ConmonPidFile) conmonPID, err := readConmonPidFile(ctr.config.ConmonPidFile)
if err != nil { if err != nil {
@ -1530,7 +1525,7 @@ func readConmonPidFile(pidFile string) (int, error) {
} }
// readConmonPipeData attempts to read a syncInfo struct from the pipe // readConmonPipeData attempts to read a syncInfo struct from the pipe
func readConmonPipeData(pipe *os.File, ociLog string) DataAndErr { func readConmonPipeData(pipe *os.File, ociLog string) (int, error) {
// syncInfo is used to return data from monitor process to daemon // syncInfo is used to return data from monitor process to daemon
type syncInfo struct { type syncInfo struct {
Data int `json:"data"` Data int `json:"data"`
@ -1566,17 +1561,11 @@ func readConmonPipeData(pipe *os.File, ociLog string) DataAndErr {
if err == nil { if err == nil {
var ociErr ociError var ociErr ociError
if err := json.Unmarshal(ociLogData, &ociErr); err == nil { if err := json.Unmarshal(ociLogData, &ociErr); err == nil {
return DataAndErr{ return -1, getOCIRuntimeError(ociErr.Msg)
data: -1,
err: getOCIRuntimeError(ociErr.Msg),
}
} }
} }
} }
return DataAndErr{ return -1, errors.Wrapf(ss.err, "container create failed (no logs from conmon)")
data: -1,
err: errors.Wrapf(ss.err, "container create failed (no logs from conmon)"),
}
} }
logrus.Debugf("Received: %d", ss.si.Data) logrus.Debugf("Received: %d", ss.si.Data)
if ss.si.Data < 0 { if ss.si.Data < 0 {
@ -1585,36 +1574,21 @@ func readConmonPipeData(pipe *os.File, ociLog string) DataAndErr {
if err == nil { if err == nil {
var ociErr ociError var ociErr ociError
if err := json.Unmarshal(ociLogData, &ociErr); err == nil { if err := json.Unmarshal(ociLogData, &ociErr); err == nil {
return DataAndErr{ return ss.si.Data, getOCIRuntimeError(ociErr.Msg)
data: ss.si.Data,
err: getOCIRuntimeError(ociErr.Msg),
}
} }
} }
} }
// If we failed to parse the JSON errors, then print the output as it is // If we failed to parse the JSON errors, then print the output as it is
if ss.si.Message != "" { if ss.si.Message != "" {
return DataAndErr{ return ss.si.Data, getOCIRuntimeError(ss.si.Message)
data: ss.si.Data,
err: getOCIRuntimeError(ss.si.Message),
}
}
return DataAndErr{
data: ss.si.Data,
err: errors.Wrapf(define.ErrInternal, "container create failed"),
} }
return ss.si.Data, errors.Wrapf(define.ErrInternal, "container create failed")
} }
data = ss.si.Data data = ss.si.Data
case <-time.After(define.ContainerCreateTimeout): case <-time.After(define.ContainerCreateTimeout):
return DataAndErr{ return -1, errors.Wrapf(define.ErrInternal, "container creation timeout")
data: -1,
err: errors.Wrapf(define.ErrInternal, "container creation timeout"),
}
}
return DataAndErr{
data: data,
err: nil,
} }
return data, nil
} }
// writeConmonPipeData writes nonse data to a pipe // writeConmonPipeData writes nonse data to a pipe

View File

@ -121,8 +121,8 @@ func (r *MissingRuntime) AttachResize(ctr *Container, newSize remotecommand.Term
} }
// ExecContainer is not available as the runtime is missing // ExecContainer is not available as the runtime is missing
func (r *MissingRuntime) ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (chan DataAndErr, chan error, error) { func (r *MissingRuntime) ExecContainer(ctr *Container, sessionID string, options *ExecOptions) (int, chan error, error) {
return nil, nil, r.printError() return -1, nil, r.printError()
} }
// ExecStopContainer is not available as the runtime is missing. // ExecStopContainer is not available as the runtime is missing.