diff options
Diffstat (limited to 'libpod/oci.go')
-rw-r--r-- | libpod/oci.go | 200 |
1 files changed, 73 insertions, 127 deletions
diff --git a/libpod/oci.go b/libpod/oci.go index 7138108c5..193e66aaf 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -11,6 +11,7 @@ import ( "strings" "time" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/util" "github.com/cri-o/ocicni/pkg/ocicni" spec "github.com/opencontainers/runtime-spec/specs-go" @@ -58,32 +59,64 @@ type OCIRuntime struct { logSizeMax int64 noPivot bool reservePorts bool + supportsJSON bool } -// syncInfo is used to return data from monitor process to daemon -type syncInfo struct { - Pid int `json:"pid"` - Message string `json:"message,omitempty"` +// ociError is used to parse the OCI runtime JSON log. It is not part of the +// OCI runtime specifications, it follows what runc does +type ociError struct { + Level string `json:"level,omitempty"` + Time string `json:"time,omitempty"` + Msg string `json:"msg,omitempty"` } -// Make a new OCI runtime with provided options -func newOCIRuntime(oruntime OCIRuntimePath, conmonPath string, conmonEnv []string, cgroupManager string, tmpDir string, logSizeMax int64, noPivotRoot bool, reservePorts bool) (*OCIRuntime, error) { +// Make a new OCI runtime with provided options. +// The first path that points to a valid executable will be used. +func newOCIRuntime(name string, paths []string, conmonPath string, runtimeCfg *RuntimeConfig, supportsJSON bool) (*OCIRuntime, error) { + if name == "" { + return nil, errors.Wrapf(define.ErrInvalidArg, "the OCI runtime must be provided a non-empty name") + } + runtime := new(OCIRuntime) - runtime.name = oruntime.Name - runtime.path = oruntime.Paths[0] + runtime.name = name runtime.conmonPath = conmonPath - runtime.conmonEnv = conmonEnv - runtime.cgroupManager = cgroupManager - runtime.tmpDir = tmpDir - runtime.logSizeMax = logSizeMax - runtime.noPivot = noPivotRoot - runtime.reservePorts = reservePorts + + runtime.conmonEnv = runtimeCfg.ConmonEnvVars + runtime.cgroupManager = runtimeCfg.CgroupManager + runtime.tmpDir = runtimeCfg.TmpDir + runtime.logSizeMax = runtimeCfg.MaxLogSize + runtime.noPivot = runtimeCfg.NoPivotRoot + runtime.reservePorts = runtimeCfg.EnablePortReservation + + // TODO: probe OCI runtime for feature and enable automatically if + // available. + runtime.supportsJSON = supportsJSON + + foundPath := false + for _, path := range paths { + stat, err := os.Stat(path) + if err != nil { + if os.IsNotExist(err) { + continue + } + return nil, errors.Wrapf(err, "cannot stat %s", path) + } + if !stat.Mode().IsRegular() { + continue + } + foundPath = true + runtime.path = path + break + } + if !foundPath { + return nil, errors.Wrapf(define.ErrInvalidArg, "no valid executable found for OCI runtime %s", name) + } runtime.exitsDir = filepath.Join(runtime.tmpDir, "exits") runtime.socketsDir = filepath.Join(runtime.tmpDir, "socket") - if cgroupManager != CgroupfsCgroupsManager && cgroupManager != SystemdCgroupsManager { - return nil, errors.Wrapf(ErrInvalidArg, "invalid cgroup manager specified: %s", cgroupManager) + if runtime.cgroupManager != CgroupfsCgroupsManager && runtime.cgroupManager != SystemdCgroupsManager { + return nil, errors.Wrapf(define.ErrInvalidArg, "invalid cgroup manager specified: %s", runtime.cgroupManager) } // Create the exit files and attach sockets directories @@ -130,7 +163,6 @@ func bindPorts(ports []ocicni.PortMapping) ([]*os.File, error) { return nil, errors.Wrapf(err, "cannot get file for UDP socket") } files = append(files, f) - break case "tcp": addr, err := net.ResolveTCPAddr("tcp4", fmt.Sprintf("%s:%d", i.HostIP, i.HostPort)) @@ -147,13 +179,11 @@ func bindPorts(ports []ocicni.PortMapping) ([]*os.File, error) { return nil, errors.Wrapf(err, "cannot get file for TCP socket") } files = append(files, f) - break case "sctp": if !notifySCTP { notifySCTP = true logrus.Warnf("port reservation for SCTP is not supported") } - break default: return nil, fmt.Errorf("unknown protocol %s", i.Protocol) @@ -178,7 +208,7 @@ func (r *OCIRuntime) updateContainerStatus(ctr *Container, useRuntime bool) erro // If not using the OCI runtime, we don't need to do most of this. if !useRuntime { // If the container's not running, nothing to do. - if ctr.state.State != ContainerStateRunning && ctr.state.State != ContainerStatePaused { + if ctr.state.State != define.ContainerStateRunning && ctr.state.State != define.ContainerStatePaused { return nil } @@ -194,7 +224,9 @@ func (r *OCIRuntime) updateContainerStatus(ctr *Container, useRuntime bool) erro } // Alright, it exists. Transition to Stopped state. - ctr.state.State = ContainerStateStopped + ctr.state.State = define.ContainerStateStopped + ctr.state.PID = 0 + ctr.state.ConmonPID = 0 // Read the exit file to get our stopped time and exit code. return ctr.handleExitFile(exitFile, info) @@ -207,6 +239,7 @@ func (r *OCIRuntime) updateContainerStatus(ctr *Container, useRuntime bool) erro cmd := exec.Command(r.path, "state", ctr.ID()) cmd.Env = append(cmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)) + outPipe, err := cmd.StdoutPipe() if err != nil { return errors.Wrapf(err, "getting stdout pipe") @@ -222,17 +255,23 @@ func (r *OCIRuntime) updateContainerStatus(ctr *Container, useRuntime bool) erro return errors.Wrapf(err, "error getting container %s state", ctr.ID()) } if strings.Contains(string(out), "does not exist") { - ctr.removeConmonFiles() + if err := ctr.removeConmonFiles(); err != nil { + logrus.Debugf("unable to remove conmon files for container %s", ctr.ID()) + } ctr.state.ExitCode = -1 ctr.state.FinishedTime = time.Now() - ctr.state.State = ContainerStateExited + ctr.state.State = define.ContainerStateExited return nil } return errors.Wrapf(err, "error getting container %s state. stderr/out: %s", ctr.ID(), out) } - defer cmd.Wait() + defer func() { + _ = cmd.Wait() + }() - errPipe.Close() + if err := errPipe.Close(); err != nil { + return err + } out, err := ioutil.ReadAll(outPipe) if err != nil { return errors.Wrapf(err, "error reading stdout: %s", ctr.ID()) @@ -244,21 +283,21 @@ func (r *OCIRuntime) updateContainerStatus(ctr *Container, useRuntime bool) erro switch state.Status { case "created": - ctr.state.State = ContainerStateCreated + ctr.state.State = define.ContainerStateCreated case "paused": - ctr.state.State = ContainerStatePaused + ctr.state.State = define.ContainerStatePaused case "running": - ctr.state.State = ContainerStateRunning + ctr.state.State = define.ContainerStateRunning case "stopped": - ctr.state.State = ContainerStateStopped + ctr.state.State = define.ContainerStateStopped default: - return errors.Wrapf(ErrInternal, "unrecognized status returned by runtime for container %s: %s", + return errors.Wrapf(define.ErrInternal, "unrecognized status returned by runtime for container %s: %s", ctr.ID(), state.Status) } // Only grab exit status if we were not already stopped // If we were, it should already be in the database - if ctr.state.State == ContainerStateStopped && oldState != ContainerStateStopped { + if ctr.state.State == define.ContainerStateStopped && oldState != define.ContainerStateStopped { var fi os.FileInfo chWait := make(chan error) defer close(chWait) @@ -346,104 +385,11 @@ func (r *OCIRuntime) unpauseContainer(ctr *Container) error { return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, env, r.path, "resume", ctr.ID()) } -// execContainer executes a command in a running container -// TODO: Add --detach support -// TODO: Convert to use conmon -// TODO: add --pid-file and use that to generate exec session tracking -func (r *OCIRuntime) execContainer(c *Container, cmd, capAdd, env []string, tty bool, cwd, user, sessionID string, streams *AttachStreams, preserveFDs int) (*exec.Cmd, error) { - if len(cmd) == 0 { - return nil, errors.Wrapf(ErrInvalidArg, "must provide a command to execute") - } - - if sessionID == "" { - return nil, errors.Wrapf(ErrEmptyID, "must provide a session ID for exec") - } - - runtimeDir, err := util.GetRootlessRuntimeDir() - if err != nil { - return nil, err - } - - args := []string{} - - // TODO - should we maintain separate logpaths for exec sessions? - args = append(args, "exec") - - if cwd != "" { - args = append(args, "--cwd", cwd) - } - - args = append(args, "--pid-file", c.execPidPath(sessionID)) - - if tty { - args = append(args, "--tty") - } else { - args = append(args, "--tty=false") - } - - if user != "" { - args = append(args, "--user", user) - } - - if preserveFDs > 0 { - args = append(args, fmt.Sprintf("--preserve-fds=%d", preserveFDs)) - } - if c.config.Spec.Process.NoNewPrivileges { - args = append(args, "--no-new-privs") - } - - for _, cap := range capAdd { - args = append(args, "--cap", cap) - } - - for _, envVar := range env { - args = append(args, "--env", envVar) - } - - // Append container ID, name and command - args = append(args, c.ID()) - args = append(args, cmd...) - - logrus.Debugf("Starting runtime %s with following arguments: %v", r.path, args) - - execCmd := exec.Command(r.path, args...) - - if streams.AttachOutput { - execCmd.Stdout = streams.OutputStream - } - if streams.AttachInput { - execCmd.Stdin = streams.InputStream - } - if streams.AttachError { - execCmd.Stderr = streams.ErrorStream - } - - execCmd.Env = append(execCmd.Env, fmt.Sprintf("XDG_RUNTIME_DIR=%s", runtimeDir)) - - if preserveFDs > 0 { - for fd := 3; fd < 3+preserveFDs; fd++ { - execCmd.ExtraFiles = append(execCmd.ExtraFiles, os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd))) - } - } - - if err := execCmd.Start(); err != nil { - return nil, errors.Wrapf(err, "cannot start container %s", c.ID()) - } - - if preserveFDs > 0 { - for fd := 3; fd < 3+preserveFDs; fd++ { - // These fds were passed down to the runtime. Close them - // and not interfere - os.NewFile(uintptr(fd), fmt.Sprintf("fd-%d", fd)).Close() - } - } - - return execCmd, nil -} - // checkpointContainer checkpoints the given container func (r *OCIRuntime) checkpointContainer(ctr *Container, options ContainerCheckpointOptions) error { - label.SetSocketLabel(ctr.ProcessLabel()) + if err := label.SetSocketLabel(ctr.ProcessLabel()); err != nil { + return err + } // imagePath is used by CRIU to store the actual checkpoint files imagePath := ctr.CheckpointPath() // workPath will be used to store dump.log and stats-dump |