4a4897bbfe
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
261 lines
6.7 KiB
Go
261 lines
6.7 KiB
Go
package oci
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"path/filepath"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
"github.com/kubernetes-incubator/ocid/utils"
|
|
"github.com/opencontainers/runtime-spec/specs-go"
|
|
)
|
|
|
|
// New creates a new Runtime with options provided
|
|
func New(runtimePath string, containerDir string) (*Runtime, error) {
|
|
r := &Runtime{
|
|
name: filepath.Base(runtimePath),
|
|
path: runtimePath,
|
|
containerDir: containerDir,
|
|
}
|
|
return r, nil
|
|
}
|
|
|
|
// Runtime stores the information about a oci runtime
|
|
type Runtime struct {
|
|
name string
|
|
path string
|
|
containerDir string
|
|
}
|
|
|
|
// syncInfo is used to return data from monitor process to daemon
|
|
type syncInfo struct {
|
|
Pid int `"json:pid"`
|
|
}
|
|
|
|
// Name returns the name of the OCI Runtime
|
|
func (r *Runtime) Name() string {
|
|
return r.name
|
|
}
|
|
|
|
// Path returns the full path the OCI Runtime executable
|
|
func (r *Runtime) Path() string {
|
|
return r.path
|
|
}
|
|
|
|
// ContainerDir returns the path to the base directory for storing container configurations
|
|
func (r *Runtime) ContainerDir() string {
|
|
return r.containerDir
|
|
}
|
|
|
|
// Version returns the version of the OCI Runtime
|
|
func (r *Runtime) Version() (string, error) {
|
|
runtimeVersion, err := getOCIVersion(r.path, "-v")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return runtimeVersion, nil
|
|
}
|
|
|
|
func getOCIVersion(name string, args ...string) (string, error) {
|
|
out, err := utils.ExecCmd(name, args...)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
firstLine := out[:strings.Index(out, "\n")]
|
|
v := firstLine[strings.LastIndex(firstLine, " ")+1:]
|
|
return v, nil
|
|
}
|
|
|
|
// CreateContainer creates a container.
|
|
func (r *Runtime) CreateContainer(c *Container) error {
|
|
parentPipe, childPipe, err := newPipe()
|
|
if err != nil {
|
|
return fmt.Errorf("error creating socket pair: %v", err)
|
|
}
|
|
defer parentPipe.Close()
|
|
|
|
args := []string{"-c", c.name}
|
|
args = append(args, "-r", r.path)
|
|
if c.terminal {
|
|
args = append(args, "-t")
|
|
}
|
|
|
|
cmd := exec.Command("conmon", args...)
|
|
cmd.Dir = c.bundlePath
|
|
cmd.SysProcAttr = &syscall.SysProcAttr{
|
|
Setpgid: true,
|
|
}
|
|
cmd.Stdin = os.Stdin
|
|
cmd.Stdout = os.Stdout
|
|
cmd.Stderr = os.Stderr
|
|
cmd.ExtraFiles = append(cmd.ExtraFiles, childPipe)
|
|
// 0, 1 and 2 are stdin, stdout and stderr
|
|
cmd.Env = append(cmd.Env, fmt.Sprintf("_OCI_SYNCPIPE=%d", 3))
|
|
|
|
err = cmd.Start()
|
|
if err != nil {
|
|
childPipe.Close()
|
|
return err
|
|
}
|
|
|
|
// We don't need childPipe on the parent side
|
|
childPipe.Close()
|
|
|
|
// Wait to get container pid from conmon
|
|
// TODO(mrunalp): Add a timeout here
|
|
var si *syncInfo
|
|
if err := json.NewDecoder(parentPipe).Decode(&si); err != nil {
|
|
return fmt.Errorf("reading pid from init pipe: %v", err)
|
|
}
|
|
logrus.Infof("Received container pid: %v", si.Pid)
|
|
return nil
|
|
}
|
|
|
|
// StartContainer starts a container.
|
|
func (r *Runtime) StartContainer(c *Container) error {
|
|
if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, r.path, "start", c.name); err != nil {
|
|
return err
|
|
}
|
|
c.state.Started = time.Now()
|
|
return nil
|
|
}
|
|
|
|
// StopContainer stops a container.
|
|
func (r *Runtime) StopContainer(c *Container) error {
|
|
// TODO: Check if it is still running after some time and send SIGKILL
|
|
return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, r.path, "kill", c.name)
|
|
}
|
|
|
|
// DeleteContainer deletes a container.
|
|
func (r *Runtime) DeleteContainer(c *Container) error {
|
|
return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, r.path, "delete", c.name)
|
|
}
|
|
|
|
// updateStatus refreshes the status of the container.
|
|
func (r *Runtime) UpdateStatus(c *Container) error {
|
|
c.stateLock.Lock()
|
|
defer c.stateLock.Unlock()
|
|
out, err := exec.Command(r.path, "state", c.name).Output()
|
|
if err != nil {
|
|
return fmt.Errorf("error getting container state for %s: %s", c.name, err)
|
|
}
|
|
stateReader := bytes.NewReader(out)
|
|
if err := json.NewDecoder(stateReader).Decode(&c.state); err != nil {
|
|
return fmt.Errorf("failed to decode container status for %s: %s", c.name, err)
|
|
}
|
|
|
|
if c.state.Status == "stopped" {
|
|
exitFilePath := filepath.Join(c.bundlePath, "exit")
|
|
fi, err := os.Stat(exitFilePath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to find container exit file: %v", err)
|
|
}
|
|
st := fi.Sys().(*syscall.Stat_t)
|
|
c.state.Finished = time.Unix(int64(st.Ctim.Sec), int64(st.Ctim.Nsec))
|
|
|
|
statusCodeStr, err := ioutil.ReadFile(exitFilePath)
|
|
if err != nil {
|
|
return fmt.Errorf("failed to read exit file: %v", err)
|
|
}
|
|
statusCode, err := strconv.Atoi(string(statusCodeStr))
|
|
if err != nil {
|
|
return fmt.Errorf("status code conversion failed: %v", err)
|
|
}
|
|
c.state.ExitCode = int32(statusCode)
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// ContainerStatus returns the state of a container.
|
|
func (r *Runtime) ContainerStatus(c *Container) *ContainerState {
|
|
c.stateLock.Lock()
|
|
defer c.stateLock.Unlock()
|
|
return c.state
|
|
}
|
|
|
|
// Container respresents a runtime container.
|
|
type Container struct {
|
|
name string
|
|
bundlePath string
|
|
logPath string
|
|
labels map[string]string
|
|
sandbox string
|
|
terminal bool
|
|
state *ContainerState
|
|
stateLock sync.Mutex
|
|
}
|
|
|
|
// ContainerStatus represents the status of a container.
|
|
type ContainerState struct {
|
|
specs.State
|
|
Created time.Time `json:"created"`
|
|
Started time.Time `json:"started"`
|
|
Finished time.Time `json:"finished"`
|
|
ExitCode int32 `json:"exitCode"`
|
|
}
|
|
|
|
// NewContainer creates a container object.
|
|
func NewContainer(name string, bundlePath string, logPath string, labels map[string]string, sandbox string, terminal bool) (*Container, error) {
|
|
c := &Container{
|
|
name: name,
|
|
bundlePath: bundlePath,
|
|
logPath: logPath,
|
|
labels: labels,
|
|
sandbox: sandbox,
|
|
terminal: terminal,
|
|
}
|
|
return c, nil
|
|
}
|
|
|
|
// Name returns the name of the container.
|
|
func (c *Container) Name() string {
|
|
return c.name
|
|
}
|
|
|
|
// BundlePath returns the bundlePath of the container.
|
|
func (c *Container) BundlePath() string {
|
|
return c.bundlePath
|
|
}
|
|
|
|
// LogPath returns the log path of the container.
|
|
func (c *Container) LogPath() string {
|
|
return c.logPath
|
|
}
|
|
|
|
// Labels returns the labels of the container.
|
|
func (c *Container) Labels() map[string]string {
|
|
return c.labels
|
|
}
|
|
|
|
// Sandbox returns the sandbox name of the container.
|
|
func (c *Container) Sandbox() string {
|
|
return c.sandbox
|
|
}
|
|
|
|
// NetNsPath returns the path to the network namespace of the container.
|
|
func (c *Container) NetNsPath() (string, error) {
|
|
if c.state == nil {
|
|
return "", fmt.Errorf("container state is not populated")
|
|
}
|
|
return fmt.Sprintf("/proc/%d/ns/net", c.state.Pid), nil
|
|
}
|
|
|
|
// newPipe creates a unix socket pair for communication
|
|
func newPipe() (parent *os.File, child *os.File, err error) {
|
|
fds, err := syscall.Socketpair(syscall.AF_LOCAL, syscall.SOCK_STREAM|syscall.SOCK_CLOEXEC, 0)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
return os.NewFile(uintptr(fds[1]), "parent"), os.NewFile(uintptr(fds[0]), "child"), nil
|
|
}
|