From a031b83a09a8628435317a03f199cdc18b78262f Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Wed, 1 Nov 2017 11:24:59 -0400 Subject: Initial checkin from CRI-O repo Signed-off-by: Matthew Heon --- oci/container.go | 260 ++++++++++++++++++ oci/finished.go | 14 + oci/finished_32.go | 14 + oci/history.go | 31 +++ oci/memory_store.go | 92 +++++++ oci/oci.go | 748 ++++++++++++++++++++++++++++++++++++++++++++++++++++ oci/store.go | 27 ++ 7 files changed, 1186 insertions(+) create mode 100644 oci/container.go create mode 100644 oci/finished.go create mode 100644 oci/finished_32.go create mode 100644 oci/history.go create mode 100644 oci/memory_store.go create mode 100644 oci/oci.go create mode 100644 oci/store.go (limited to 'oci') diff --git a/oci/container.go b/oci/container.go new file mode 100644 index 000000000..c0eff2fd1 --- /dev/null +++ b/oci/container.go @@ -0,0 +1,260 @@ +package oci + +import ( + "encoding/json" + "fmt" + "os" + "path/filepath" + "strings" + "sync" + "time" + + "github.com/containernetworking/plugins/pkg/ns" + "github.com/docker/docker/pkg/signal" + specs "github.com/opencontainers/runtime-spec/specs-go" + "k8s.io/apimachinery/pkg/fields" + pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" +) + +const ( + defaultStopSignal = "TERM" +) + +// Container represents a runtime container. +type Container struct { + id string + name string + logPath string + labels fields.Set + annotations fields.Set + crioAnnotations fields.Set + image string + sandbox string + netns ns.NetNS + terminal bool + stdin bool + stdinOnce bool + privileged bool + trusted bool + state *ContainerState + metadata *pb.ContainerMetadata + opLock sync.Locker + // this is the /var/run/storage/... directory, erased on reboot + bundlePath string + // this is the /var/lib/storage/... directory + dir string + stopSignal string + imageName string + imageRef string + volumes []ContainerVolume + mountPoint string + spec *specs.Spec +} + +// ContainerVolume is a bind mount for the container. +type ContainerVolume struct { + ContainerPath string `json:"container_path"` + HostPath string `json:"host_path"` + Readonly bool `json:"readonly"` +} + +// ContainerState represents the status of a container. +type ContainerState struct { + specs.State + Created time.Time `json:"created"` + Started time.Time `json:"started,omitempty"` + Finished time.Time `json:"finished,omitempty"` + ExitCode int32 `json:"exitCode,omitempty"` + OOMKilled bool `json:"oomKilled,omitempty"` + Error string `json:"error,omitempty"` +} + +// NewContainer creates a container object. +func NewContainer(id string, name string, bundlePath string, logPath string, netns ns.NetNS, labels map[string]string, crioAnnotations map[string]string, annotations map[string]string, image string, imageName string, imageRef string, metadata *pb.ContainerMetadata, sandbox string, terminal bool, stdin bool, stdinOnce bool, privileged bool, trusted bool, dir string, created time.Time, stopSignal string) (*Container, error) { + state := &ContainerState{} + state.Created = created + c := &Container{ + id: id, + name: name, + bundlePath: bundlePath, + logPath: logPath, + labels: labels, + sandbox: sandbox, + netns: netns, + terminal: terminal, + stdin: stdin, + stdinOnce: stdinOnce, + privileged: privileged, + trusted: trusted, + metadata: metadata, + annotations: annotations, + crioAnnotations: crioAnnotations, + image: image, + imageName: imageName, + imageRef: imageRef, + dir: dir, + state: state, + stopSignal: stopSignal, + opLock: new(sync.Mutex), + } + return c, nil +} + +// SetSpec loads the OCI spec in the container struct +func (c *Container) SetSpec(s *specs.Spec) { + c.spec = s +} + +// Spec returns a copy of the spec for the container +func (c *Container) Spec() specs.Spec { + return *c.spec +} + +// GetStopSignal returns the container's own stop signal configured from the +// image configuration or the default one. +func (c *Container) GetStopSignal() string { + if c.stopSignal == "" { + return defaultStopSignal + } + cleanSignal := strings.TrimPrefix(strings.ToUpper(c.stopSignal), "SIG") + _, ok := signal.SignalMap[cleanSignal] + if !ok { + return defaultStopSignal + } + return cleanSignal +} + +// FromDisk restores container's state from disk +func (c *Container) FromDisk() error { + jsonSource, err := os.Open(c.StatePath()) + if err != nil { + return err + } + defer jsonSource.Close() + + dec := json.NewDecoder(jsonSource) + return dec.Decode(c.state) +} + +// StatePath returns the containers state.json path +func (c *Container) StatePath() string { + return filepath.Join(c.dir, "state.json") +} + +// CreatedAt returns the container creation time +func (c *Container) CreatedAt() time.Time { + return c.state.Created +} + +// Name returns the name of the container. +func (c *Container) Name() string { + return c.name +} + +// ID returns the id of the container. +func (c *Container) ID() string { + return c.id +} + +// 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 +} + +// Annotations returns the annotations of the container. +func (c *Container) Annotations() map[string]string { + return c.annotations +} + +// CrioAnnotations returns the crio annotations of the container. +func (c *Container) CrioAnnotations() map[string]string { + return c.crioAnnotations +} + +// Image returns the image of the container. +func (c *Container) Image() string { + return c.image +} + +// ImageName returns the image name of the container. +func (c *Container) ImageName() string { + return c.imageName +} + +// ImageRef returns the image ref of the container. +func (c *Container) ImageRef() string { + return c.imageRef +} + +// Sandbox returns the sandbox name of the container. +func (c *Container) Sandbox() string { + return c.sandbox +} + +// Dir returns the the dir of the container +func (c *Container) Dir() string { + return c.dir +} + +// 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") + } + + if c.netns == nil { + return fmt.Sprintf("/proc/%d/ns/net", c.state.Pid), nil + } + + return c.netns.Path(), nil +} + +// Metadata returns the metadata of the container. +func (c *Container) Metadata() *pb.ContainerMetadata { + return c.metadata +} + +// State returns the state of the running container +func (c *Container) State() *ContainerState { + c.opLock.Lock() + defer c.opLock.Unlock() + return c.state +} + +// AddVolume adds a volume to list of container volumes. +func (c *Container) AddVolume(v ContainerVolume) { + c.volumes = append(c.volumes, v) +} + +// Volumes returns the list of container volumes. +func (c *Container) Volumes() []ContainerVolume { + return c.volumes + +} + +// SetMountPoint sets the container mount point +func (c *Container) SetMountPoint(mp string) { + c.mountPoint = mp +} + +// MountPoint returns the container mount point +func (c *Container) MountPoint() string { + return c.mountPoint +} + +// SetState sets the conainer state +// +// XXX: DO NOT EVER USE THIS, THIS IS JUST USEFUL FOR MOCKING!!! +func (c *Container) SetState(state *ContainerState) { + c.state = state +} diff --git a/oci/finished.go b/oci/finished.go new file mode 100644 index 000000000..9fedbfb7a --- /dev/null +++ b/oci/finished.go @@ -0,0 +1,14 @@ +// +build !arm,!386 + +package oci + +import ( + "os" + "syscall" + "time" +) + +func getFinishedTime(fi os.FileInfo) time.Time { + st := fi.Sys().(*syscall.Stat_t) + return time.Unix(st.Ctim.Sec, st.Ctim.Nsec) +} diff --git a/oci/finished_32.go b/oci/finished_32.go new file mode 100644 index 000000000..3f24b1baa --- /dev/null +++ b/oci/finished_32.go @@ -0,0 +1,14 @@ +// +build arm 386 + +package oci + +import ( + "os" + "syscall" + "time" +) + +func getFinishedTime(fi os.FileInfo) time.Time { + st := fi.Sys().(*syscall.Stat_t) + return time.Unix(int64(st.Ctim.Sec), int64(st.Ctim.Nsec)) +} diff --git a/oci/history.go b/oci/history.go new file mode 100644 index 000000000..2ced41d61 --- /dev/null +++ b/oci/history.go @@ -0,0 +1,31 @@ +package oci + +import "sort" + +// History is a convenience type for storing a list of containers, +// sorted by creation date in descendant order. +type History []*Container + +// Len returns the number of containers in the history. +func (history *History) Len() int { + return len(*history) +} + +// Less compares two containers and returns true if the second one +// was created before the first one. +func (history *History) Less(i, j int) bool { + containers := *history + // FIXME: state access should be serialized + return containers[j].state.Created.Before(containers[i].state.Created) +} + +// Swap switches containers i and j positions in the history. +func (history *History) Swap(i, j int) { + containers := *history + containers[i], containers[j] = containers[j], containers[i] +} + +// sort orders the history by creation date in descendant order. +func (history *History) sort() { + sort.Sort(history) +} diff --git a/oci/memory_store.go b/oci/memory_store.go new file mode 100644 index 000000000..6223ce7f0 --- /dev/null +++ b/oci/memory_store.go @@ -0,0 +1,92 @@ +package oci + +import "sync" + +// memoryStore implements a Store in memory. +type memoryStore struct { + s map[string]*Container + sync.RWMutex +} + +// NewMemoryStore initializes a new memory store. +func NewMemoryStore() ContainerStorer { + return &memoryStore{ + s: make(map[string]*Container), + } +} + +// Add appends a new container to the memory store. +// It overrides the id if it existed before. +func (c *memoryStore) Add(id string, cont *Container) { + c.Lock() + c.s[id] = cont + c.Unlock() +} + +// Get returns a container from the store by id. +func (c *memoryStore) Get(id string) *Container { + c.RLock() + res := c.s[id] + c.RUnlock() + return res +} + +// Delete removes a container from the store by id. +func (c *memoryStore) Delete(id string) { + c.Lock() + delete(c.s, id) + c.Unlock() +} + +// List returns a sorted list of containers from the store. +// The containers are ordered by creation date. +func (c *memoryStore) List() []*Container { + containers := History(c.all()) + containers.sort() + return containers +} + +// Size returns the number of containers in the store. +func (c *memoryStore) Size() int { + c.RLock() + defer c.RUnlock() + return len(c.s) +} + +// First returns the first container found in the store by a given filter. +func (c *memoryStore) First(filter StoreFilter) *Container { + for _, cont := range c.all() { + if filter(cont) { + return cont + } + } + return nil +} + +// ApplyAll calls the reducer function with every container in the store. +// This operation is asynchronous in the memory store. +// NOTE: Modifications to the store MUST NOT be done by the StoreReducer. +func (c *memoryStore) ApplyAll(apply StoreReducer) { + wg := new(sync.WaitGroup) + for _, cont := range c.all() { + wg.Add(1) + go func(container *Container) { + apply(container) + wg.Done() + }(cont) + } + + wg.Wait() +} + +func (c *memoryStore) all() []*Container { + c.RLock() + containers := make([]*Container, 0, len(c.s)) + for _, cont := range c.s { + containers = append(containers, cont) + } + c.RUnlock() + return containers +} + +var _ ContainerStorer = &memoryStore{} diff --git a/oci/oci.go b/oci/oci.go new file mode 100644 index 000000000..756be44bf --- /dev/null +++ b/oci/oci.go @@ -0,0 +1,748 @@ +package oci + +import ( + "bytes" + "encoding/json" + "fmt" + "io/ioutil" + "os" + "os/exec" + "path/filepath" + "strconv" + "strings" + "syscall" + "time" + + "github.com/containerd/cgroups" + "github.com/kubernetes-incubator/cri-o/utils" + rspec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/sirupsen/logrus" + "golang.org/x/net/context" + "golang.org/x/sys/unix" + kwait "k8s.io/apimachinery/pkg/util/wait" +) + +const ( + // ContainerStateCreated represents the created state of a container + ContainerStateCreated = "created" + // ContainerStatePaused represents the paused state of a container + ContainerStatePaused = "paused" + // ContainerStateRunning represents the running state of a container + ContainerStateRunning = "running" + // ContainerStateStopped represents the stopped state of a container + ContainerStateStopped = "stopped" + // ContainerCreateTimeout represents the value of container creating timeout + ContainerCreateTimeout = 240 * time.Second + + // CgroupfsCgroupsManager represents cgroupfs native cgroup manager + CgroupfsCgroupsManager = "cgroupfs" + // SystemdCgroupsManager represents systemd native cgroup manager + SystemdCgroupsManager = "systemd" + // ContainerExitsDir is the location of container exit dirs + ContainerExitsDir = "/var/run/crio/exits" + // ContainerAttachSocketDir is the location for container attach sockets + ContainerAttachSocketDir = "/var/run/crio" + + // killContainerTimeout is the timeout that we wait for the container to + // be SIGKILLed. + killContainerTimeout = 2 * time.Minute +) + +// New creates a new Runtime with options provided +func New(runtimeTrustedPath string, + runtimeUntrustedPath string, + trustLevel string, + conmonPath string, + conmonEnv []string, + cgroupManager string, + containerExitsDir string, + logSizeMax int64, + noPivot bool) (*Runtime, error) { + r := &Runtime{ + name: filepath.Base(runtimeTrustedPath), + trustedPath: runtimeTrustedPath, + untrustedPath: runtimeUntrustedPath, + trustLevel: trustLevel, + conmonPath: conmonPath, + conmonEnv: conmonEnv, + cgroupManager: cgroupManager, + containerExitsDir: containerExitsDir, + logSizeMax: logSizeMax, + noPivot: noPivot, + } + return r, nil +} + +// Runtime stores the information about a oci runtime +type Runtime struct { + name string + trustedPath string + untrustedPath string + trustLevel string + conmonPath string + conmonEnv []string + cgroupManager string + containerExitsDir string + logSizeMax int64 + noPivot bool +} + +// syncInfo is used to return data from monitor process to daemon +type syncInfo struct { + Pid int `json:"pid"` + Message string `json:"message,omitempty"` +} + +// exitCodeInfo is used to return the monitored process exit code to the daemon +type exitCodeInfo struct { + ExitCode int32 `json:"exit_code"` + Message string `json:"message,omitempty"` +} + +// 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. +// Depending if the container is privileged and/or trusted, +// this will return either the trusted or untrusted runtime path. +func (r *Runtime) Path(c *Container) string { + if !c.trusted { + // We have an explicitly untrusted container. + if c.privileged { + logrus.Warnf("Running an untrusted but privileged container") + return r.trustedPath + } + + if r.untrustedPath != "" { + return r.untrustedPath + } + + return r.trustedPath + } + + // Our container is trusted. Let's look at the configured trust level. + if r.trustLevel == "trusted" { + return r.trustedPath + } + + // Our container is trusted, but we are running untrusted. + // We will use the untrusted container runtime if it's set + // and if it's not a privileged container. + if c.privileged || r.untrustedPath == "" { + return r.trustedPath + } + + return r.untrustedPath +} + +// Version returns the version of the OCI Runtime +func (r *Runtime) Version() (string, error) { + runtimeVersion, err := getOCIVersion(r.trustedPath, "-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, cgroupParent string) (err error) { + var stderrBuf bytes.Buffer + parentPipe, childPipe, err := newPipe() + childStartPipe, parentStartPipe, err := newPipe() + if err != nil { + return fmt.Errorf("error creating socket pair: %v", err) + } + defer parentPipe.Close() + defer parentStartPipe.Close() + + var args []string + if r.cgroupManager == SystemdCgroupsManager { + args = append(args, "-s") + } + args = append(args, "-c", c.id) + args = append(args, "-u", c.id) + args = append(args, "-r", r.Path(c)) + args = append(args, "-b", c.bundlePath) + args = append(args, "-p", filepath.Join(c.bundlePath, "pidfile")) + args = append(args, "-l", c.logPath) + args = append(args, "--exit-dir", r.containerExitsDir) + args = append(args, "--socket-dir-path", ContainerAttachSocketDir) + if r.logSizeMax >= 0 { + args = append(args, "--log-size-max", fmt.Sprintf("%v", r.logSizeMax)) + } + if r.noPivot { + args = append(args, "--no-pivot") + } + if c.terminal { + args = append(args, "-t") + } else if c.stdin { + args = append(args, "-i") + } + logrus.WithFields(logrus.Fields{ + "args": args, + }).Debugf("running conmon: %s", r.conmonPath) + + cmd := exec.Command(r.conmonPath, args...) + cmd.Dir = c.bundlePath + cmd.SysProcAttr = &syscall.SysProcAttr{ + Setpgid: true, + } + cmd.Stdin = os.Stdin + cmd.Stdout = os.Stdout + cmd.Stderr = os.Stderr + if c.terminal { + cmd.Stderr = &stderrBuf + } + cmd.ExtraFiles = append(cmd.ExtraFiles, childPipe, childStartPipe) + // 0, 1 and 2 are stdin, stdout and stderr + cmd.Env = append(r.conmonEnv, fmt.Sprintf("_OCI_SYNCPIPE=%d", 3)) + cmd.Env = append(cmd.Env, fmt.Sprintf("_OCI_STARTPIPE=%d", 4)) + + err = cmd.Start() + if err != nil { + childPipe.Close() + return err + } + + // We don't need childPipe on the parent side + childPipe.Close() + childStartPipe.Close() + + // Move conmon to specified cgroup + if r.cgroupManager == SystemdCgroupsManager { + logrus.Infof("Running conmon under slice %s and unitName %s", cgroupParent, createUnitName("crio-conmon", c.id)) + if err = utils.RunUnderSystemdScope(cmd.Process.Pid, cgroupParent, createUnitName("crio-conmon", c.id)); err != nil { + logrus.Warnf("Failed to add conmon to systemd sandbox cgroup: %v", err) + } + } else { + control, err := cgroups.New(cgroups.V1, cgroups.StaticPath(filepath.Join(cgroupParent, "/crio-conmon-"+c.id)), &rspec.LinuxResources{}) + if err != nil { + logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) + } else { + // Here we should defer a crio-connmon- cgroup hierarchy deletion, but it will + // always fail as conmon's pid is still there. + // Fortunately, kubelet takes care of deleting this for us, so the leak will + // only happens in corner case where one does a manual deletion of the container + // through e.g. runc. This should be handled by implementing a conmon monitoring + // routine that does the cgroup cleanup once conmon is terminated. + if err := control.Add(cgroups.Process{Pid: cmd.Process.Pid}); err != nil { + logrus.Warnf("Failed to add conmon to cgroupfs sandbox cgroup: %v", err) + } + } + } + + /* We set the cgroup, now the child can start creating children */ + someData := []byte{0} + _, err = parentStartPipe.Write(someData) + if err != nil { + return err + } + + /* Wait for initial setup and fork, and reap child */ + err = cmd.Wait() + if err != nil { + return err + } + + // We will delete all container resources if creation fails + defer func() { + if err != nil { + r.DeleteContainer(c) + } + }() + + // Wait to get container pid from conmon + type syncStruct struct { + si *syncInfo + err error + } + ch := make(chan syncStruct) + go func() { + var si *syncInfo + if err = json.NewDecoder(parentPipe).Decode(&si); err != nil { + ch <- syncStruct{err: err} + return + } + ch <- syncStruct{si: si} + }() + + select { + case ss := <-ch: + if ss.err != nil { + return fmt.Errorf("error reading container (probably exited) json message: %v", ss.err) + } + logrus.Debugf("Received container pid: %d", ss.si.Pid) + if ss.si.Pid == -1 { + if ss.si.Message != "" { + logrus.Errorf("Container creation error: %s", ss.si.Message) + return fmt.Errorf("container create failed: %s", ss.si.Message) + } + logrus.Errorf("Container creation failed") + return fmt.Errorf("container create failed") + } + case <-time.After(ContainerCreateTimeout): + logrus.Errorf("Container creation timeout (%v)", ContainerCreateTimeout) + return fmt.Errorf("create container timeout") + } + return nil +} + +func createUnitName(prefix string, name string) string { + return fmt.Sprintf("%s-%s.scope", prefix, name) +} + +// StartContainer starts a container. +func (r *Runtime) StartContainer(c *Container) error { + c.opLock.Lock() + defer c.opLock.Unlock() + if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, r.Path(c), "start", c.id); err != nil { + return err + } + c.state.Started = time.Now() + return nil +} + +// ExecSyncResponse is returned from ExecSync. +type ExecSyncResponse struct { + Stdout []byte + Stderr []byte + ExitCode int32 +} + +// ExecSyncError wraps command's streams, exit code and error on ExecSync error. +type ExecSyncError struct { + Stdout bytes.Buffer + Stderr bytes.Buffer + ExitCode int32 + Err error +} + +func (e ExecSyncError) Error() string { + return fmt.Sprintf("command error: %+v, stdout: %s, stderr: %s, exit code %d", e.Err, e.Stdout.Bytes(), e.Stderr.Bytes(), e.ExitCode) +} + +func prepareExec() (pidFile, parentPipe, childPipe *os.File, err error) { + parentPipe, childPipe, err = os.Pipe() + if err != nil { + return nil, nil, nil, err + } + + pidFile, err = ioutil.TempFile("", "pidfile") + if err != nil { + parentPipe.Close() + childPipe.Close() + return nil, nil, nil, err + } + + return +} + +func parseLog(log []byte) (stdout, stderr []byte) { + // Split the log on newlines, which is what separates entries. + lines := bytes.SplitAfter(log, []byte{'\n'}) + for _, line := range lines { + // Ignore empty lines. + if len(line) == 0 { + continue + } + + // The format of log lines is "DATE pipe REST". + parts := bytes.SplitN(line, []byte{' '}, 3) + if len(parts) < 3 { + // Ignore the line if it's formatted incorrectly, but complain + // about it so it can be debugged. + logrus.Warnf("hit invalid log format: %q", string(line)) + continue + } + + pipe := string(parts[1]) + content := parts[2] + + switch pipe { + case "stdout": + stdout = append(stdout, content...) + case "stderr": + stderr = append(stderr, content...) + default: + // Complain about unknown pipes. + logrus.Warnf("hit invalid log format [unknown pipe %s]: %q", pipe, string(line)) + continue + } + } + + return stdout, stderr +} + +// ExecSync execs a command in a container and returns it's stdout, stderr and return code. +func (r *Runtime) ExecSync(c *Container, command []string, timeout int64) (resp *ExecSyncResponse, err error) { + pidFile, parentPipe, childPipe, err := prepareExec() + if err != nil { + return nil, ExecSyncError{ + ExitCode: -1, + Err: err, + } + } + defer parentPipe.Close() + defer func() { + if e := os.Remove(pidFile.Name()); e != nil { + logrus.Warnf("could not remove temporary PID file %s", pidFile.Name()) + } + }() + + logFile, err := ioutil.TempFile("", "crio-log-"+c.id) + if err != nil { + return nil, ExecSyncError{ + ExitCode: -1, + Err: err, + } + } + logPath := logFile.Name() + defer func() { + logFile.Close() + os.RemoveAll(logPath) + }() + + f, err := ioutil.TempFile("", "exec-process") + if err != nil { + return nil, ExecSyncError{ + ExitCode: -1, + Err: err, + } + } + defer os.RemoveAll(f.Name()) + + var args []string + args = append(args, "-c", c.id) + args = append(args, "-r", r.Path(c)) + args = append(args, "-p", pidFile.Name()) + args = append(args, "-e") + if c.terminal { + args = append(args, "-t") + } + if timeout > 0 { + args = append(args, "-T") + args = append(args, fmt.Sprintf("%d", timeout)) + } + args = append(args, "-l", logPath) + args = append(args, "--socket-dir-path", ContainerAttachSocketDir) + + pspec := c.Spec().Process + pspec.Env = append(pspec.Env, r.conmonEnv...) + pspec.Args = command + processJSON, err := json.Marshal(pspec) + if err != nil { + return nil, ExecSyncError{ + ExitCode: -1, + Err: err, + } + } + + if err := ioutil.WriteFile(f.Name(), processJSON, 0644); err != nil { + return nil, ExecSyncError{ + ExitCode: -1, + Err: err, + } + } + + args = append(args, "--exec-process-spec", f.Name()) + + cmd := exec.Command(r.conmonPath, args...) + + var stdoutBuf, stderrBuf bytes.Buffer + cmd.Stdout = &stdoutBuf + cmd.Stderr = &stderrBuf + cmd.ExtraFiles = append(cmd.ExtraFiles, childPipe) + // 0, 1 and 2 are stdin, stdout and stderr + cmd.Env = append(r.conmonEnv, fmt.Sprintf("_OCI_SYNCPIPE=%d", 3)) + + err = cmd.Start() + if err != nil { + childPipe.Close() + return nil, ExecSyncError{ + Stdout: stdoutBuf, + Stderr: stderrBuf, + ExitCode: -1, + Err: err, + } + } + + // We don't need childPipe on the parent side + childPipe.Close() + + err = cmd.Wait() + if err != nil { + if exitErr, ok := err.(*exec.ExitError); ok { + if status, ok := exitErr.Sys().(unix.WaitStatus); ok { + return nil, ExecSyncError{ + Stdout: stdoutBuf, + Stderr: stderrBuf, + ExitCode: int32(status.ExitStatus()), + Err: err, + } + } + } else { + return nil, ExecSyncError{ + Stdout: stdoutBuf, + Stderr: stderrBuf, + ExitCode: -1, + Err: err, + } + } + } + + var ec *exitCodeInfo + if err := json.NewDecoder(parentPipe).Decode(&ec); err != nil { + return nil, ExecSyncError{ + Stdout: stdoutBuf, + Stderr: stderrBuf, + ExitCode: -1, + Err: err, + } + } + + logrus.Infof("Received container exit code: %v, message: %s", ec.ExitCode, ec.Message) + + if ec.ExitCode == -1 { + return nil, ExecSyncError{ + Stdout: stdoutBuf, + Stderr: stderrBuf, + ExitCode: -1, + Err: fmt.Errorf(ec.Message), + } + } + + // The actual logged output is not the same as stdoutBuf and stderrBuf, + // which are used for getting error information. For the actual + // ExecSyncResponse we have to read the logfile. + // XXX: Currently runC dups the same console over both stdout and stderr, + // so we can't differentiate between the two. + + logBytes, err := ioutil.ReadFile(logPath) + if err != nil { + return nil, ExecSyncError{ + Stdout: stdoutBuf, + Stderr: stderrBuf, + ExitCode: -1, + Err: err, + } + } + + // We have to parse the log output into {stdout, stderr} buffers. + stdoutBytes, stderrBytes := parseLog(logBytes) + return &ExecSyncResponse{ + Stdout: stdoutBytes, + Stderr: stderrBytes, + ExitCode: ec.ExitCode, + }, nil +} + +func waitContainerStop(ctx context.Context, c *Container, timeout time.Duration) error { + done := make(chan struct{}) + // we could potentially re-use "done" channel to exit the loop on timeout + // but we use another channel "chControl" so that we won't never incur in the + // case the "done" channel is closed in the "default" select case and we also + // reach the timeout in the select below. If that happens we could raise + // a panic closing a closed channel so better be safe and use another new + // channel just to control the loop. + chControl := make(chan struct{}) + go func() { + for { + select { + case <-chControl: + return + default: + // Check if the process is still around + err := unix.Kill(c.state.Pid, 0) + if err == unix.ESRCH { + close(done) + return + } + time.Sleep(100 * time.Millisecond) + } + } + }() + select { + case <-done: + return nil + case <-ctx.Done(): + close(chControl) + return ctx.Err() + case <-time.After(timeout): + close(chControl) + err := unix.Kill(c.state.Pid, unix.SIGKILL) + if err != nil && err != unix.ESRCH { + return fmt.Errorf("failed to kill process: %v", err) + } + } + + c.state.Finished = time.Now() + return nil +} + +// StopContainer stops a container. Timeout is given in seconds. +func (r *Runtime) StopContainer(ctx context.Context, c *Container, timeout int64) error { + c.opLock.Lock() + defer c.opLock.Unlock() + + // Check if the process is around before sending a signal + err := unix.Kill(c.state.Pid, 0) + if err == unix.ESRCH { + c.state.Finished = time.Now() + return nil + } + + if timeout > 0 { + if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, r.Path(c), "kill", c.id, c.GetStopSignal()); err != nil { + return fmt.Errorf("failed to stop container %s, %v", c.id, err) + } + err = waitContainerStop(ctx, c, time.Duration(timeout)*time.Second) + if err == nil { + return nil + } + logrus.Warnf("Stop container %q timed out: %v", c.ID(), err) + } + + if err := utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, r.Path(c), "kill", "--all", c.id, "KILL"); err != nil { + return fmt.Errorf("failed to stop container %s, %v", c.id, err) + } + + return waitContainerStop(ctx, c, killContainerTimeout) +} + +// DeleteContainer deletes a container. +func (r *Runtime) DeleteContainer(c *Container) error { + c.opLock.Lock() + defer c.opLock.Unlock() + _, err := utils.ExecCmd(r.Path(c), "delete", "--force", c.id) + return err +} + +// SetStartFailed sets the container state appropriately after a start failure +func (r *Runtime) SetStartFailed(c *Container, err error) { + c.opLock.Lock() + defer c.opLock.Unlock() + // adjust finished and started times + c.state.Finished, c.state.Started = c.state.Created, c.state.Created + c.state.Error = err.Error() +} + +// UpdateStatus refreshes the status of the container. +func (r *Runtime) UpdateStatus(c *Container) error { + c.opLock.Lock() + defer c.opLock.Unlock() + out, err := exec.Command(r.Path(c), "state", c.id).CombinedOutput() + if err != nil { + // there are many code paths that could lead to have a bad state in the + // underlying runtime. + // On any error like a container went away or we rebooted and containers + // went away we do not error out stopping kubernetes to recover. + // We always populate the fields below so kube can restart/reschedule + // containers failing. + c.state.Status = ContainerStateStopped + c.state.Finished = time.Now() + c.state.ExitCode = 255 + return nil + } + if err := json.NewDecoder(bytes.NewBuffer(out)).Decode(&c.state); err != nil { + return fmt.Errorf("failed to decode container status for %s: %s", c.id, err) + } + + if c.state.Status == ContainerStateStopped { + exitFilePath := filepath.Join(r.containerExitsDir, c.id) + var fi os.FileInfo + err = kwait.ExponentialBackoff( + kwait.Backoff{ + Duration: 500 * time.Millisecond, + Factor: 1.2, + Steps: 6, + }, + func() (bool, error) { + var err error + fi, err = os.Stat(exitFilePath) + if err != nil { + // wait longer + return false, nil + } + return true, nil + }) + if err != nil { + logrus.Warnf("failed to find container exit file: %v", err) + c.state.ExitCode = -1 + } else { + c.state.Finished = getFinishedTime(fi) + 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) + } + + oomFilePath := filepath.Join(c.bundlePath, "oom") + if _, err = os.Stat(oomFilePath); err == nil { + c.state.OOMKilled = true + } + } + + return nil +} + +// ContainerStatus returns the state of a container. +func (r *Runtime) ContainerStatus(c *Container) *ContainerState { + c.opLock.Lock() + defer c.opLock.Unlock() + return c.state +} + +// newPipe creates a unix socket pair for communication +func newPipe() (parent *os.File, child *os.File, err error) { + fds, err := unix.Socketpair(unix.AF_LOCAL, unix.SOCK_STREAM|unix.SOCK_CLOEXEC, 0) + if err != nil { + return nil, nil, err + } + return os.NewFile(uintptr(fds[1]), "parent"), os.NewFile(uintptr(fds[0]), "child"), nil +} + +// RuntimeReady checks if the runtime is up and ready to accept +// basic containers e.g. container only needs host network. +func (r *Runtime) RuntimeReady() (bool, error) { + return true, nil +} + +// NetworkReady checks if the runtime network is up and ready to +// accept containers which require container network. +func (r *Runtime) NetworkReady() (bool, error) { + return true, nil +} + +// PauseContainer pauses a container. +func (r *Runtime) PauseContainer(c *Container) error { + c.opLock.Lock() + defer c.opLock.Unlock() + _, err := utils.ExecCmd(r.Path(c), "pause", c.id) + return err +} + +// UnpauseContainer unpauses a container. +func (r *Runtime) UnpauseContainer(c *Container) error { + c.opLock.Lock() + defer c.opLock.Unlock() + _, err := utils.ExecCmd(r.Path(c), "resume", c.id) + return err +} diff --git a/oci/store.go b/oci/store.go new file mode 100644 index 000000000..1d27a0f9e --- /dev/null +++ b/oci/store.go @@ -0,0 +1,27 @@ +package oci + +// StoreFilter defines a function to filter +// container in the store. +type StoreFilter func(*Container) bool + +// StoreReducer defines a function to +// manipulate containers in the store +type StoreReducer func(*Container) + +// ContainerStorer defines an interface that any container store must implement. +type ContainerStorer interface { + // Add appends a new container to the store. + Add(string, *Container) + // Get returns a container from the store by the identifier it was stored with. + Get(string) *Container + // Delete removes a container from the store by the identifier it was stored with. + Delete(string) + // List returns a list of containers from the store. + List() []*Container + // Size returns the number of containers in the store. + Size() int + // First returns the first container found in the store by a given filter. + First(StoreFilter) *Container + // ApplyAll calls the reducer function with every container in the store. + ApplyAll(StoreReducer) +} -- cgit v1.2.3-54-g00ecf From c13f61798aa7bcf7b4de7ee31aa30148a3b08d97 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Wed, 1 Nov 2017 13:22:04 -0400 Subject: Prune Server package. Convert to new github location. Signed-off-by: Matthew Heon --- CONTRIBUTING.md | 8 +- Dockerfile | 4 +- Makefile | 4 +- cmd/kpod/common.go | 11 +- cmd/kpod/diff.go | 2 +- cmd/kpod/history.go | 2 +- cmd/kpod/images.go | 6 +- cmd/kpod/info.go | 2 +- cmd/kpod/inspect.go | 6 +- cmd/kpod/kill.go | 2 +- cmd/kpod/load.go | 2 +- cmd/kpod/login.go | 2 +- cmd/kpod/logout.go | 2 +- cmd/kpod/logs.go | 2 +- cmd/kpod/mount.go | 2 +- cmd/kpod/pause.go | 2 +- cmd/kpod/ps.go | 6 +- cmd/kpod/pull.go | 4 +- cmd/kpod/push.go | 4 +- cmd/kpod/rename.go | 2 +- cmd/kpod/rm.go | 2 +- cmd/kpod/save.go | 2 +- cmd/kpod/stats.go | 4 +- cmd/kpod/stop.go | 2 +- cmd/kpod/tag.go | 2 +- cmd/kpod/unpause.go | 2 +- cmd/kpod/wait.go | 2 +- contrib/rpm/Makefile | 14 - contrib/rpm/crio.spec | 76 -- contrib/systemd/crio-shutdown.service | 14 - contrib/systemd/crio.service | 24 - contrib/test/integration/README.md | 21 - contrib/test/integration/ansible.cfg | 359 ------ contrib/test/integration/build/bats.yml | 17 - contrib/test/integration/build/cri-o.yml | 79 -- contrib/test/integration/build/cri-tools.yml | 16 - contrib/test/integration/build/kubernetes.yml | 63 - contrib/test/integration/build/plugins.yml | 50 - contrib/test/integration/build/runc.yml | 23 - .../test/integration/callback_plugins/default.py | 156 --- contrib/test/integration/e2e.yml | 57 - contrib/test/integration/golang.yml | 51 - contrib/test/integration/main.yml | 58 - contrib/test/integration/results.yml | 62 - contrib/test/integration/system.yml | 117 -- contrib/test/integration/test.yml | 25 - contrib/test/integration/vars.yml | 8 - contrib/test/requirements.txt | 54 - contrib/test/venv-ansible-playbook.sh | 106 -- kubernetes.md | 105 -- libkpod/config.go | 2 +- libkpod/container.go | 6 +- libkpod/container_data.go | 6 +- libkpod/container_server.go | 10 +- libkpod/kill.go | 4 +- libkpod/pause.go | 2 +- libkpod/remove.go | 2 +- libkpod/rename.go | 4 +- libkpod/sandbox/sandbox.go | 2 +- libkpod/stats.go | 2 +- libkpod/stop.go | 2 +- libkpod/wait.go | 2 +- libpod/container.go | 2 +- libpod/diff.go | 2 +- libpod/images/image_data.go | 2 +- libpod/in_memory_state.go | 2 +- libpod/oci.go | 2 +- libpod/runtime_img.go | 2 +- oci/oci.go | 2 +- server/apparmor/aaparser.go | 89 -- server/apparmor/apparmor_common.go | 14 - server/apparmor/apparmor_supported.go | 145 --- server/apparmor/apparmor_unsupported.go | 18 - server/apparmor/template.go | 45 - server/config.go | 112 -- server/container_attach.go | 147 --- server/container_create.go | 1215 -------------------- server/container_exec.go | 108 -- server/container_execsync.go | 46 - server/container_list.go | 112 -- server/container_portforward.go | 91 -- server/container_remove.go | 20 - server/container_start.go | 43 - server/container_stats.go | 14 - server/container_stats_list.go | 13 - server/container_status.go | 102 -- server/container_stop.go | 19 - server/container_updateruntimeconfig.go | 11 - server/image_fs_info.go | 13 - server/image_list.go | 41 - server/image_pull.go | 108 -- server/image_remove.go | 52 - server/image_status.go | 53 - server/inspect.go | 105 -- server/inspect_test.go | 235 ---- server/naming.go | 86 -- server/runtime_status.go | 41 - server/sandbox_list.go | 94 -- server/sandbox_network.go | 70 -- server/sandbox_remove.go | 98 -- server/sandbox_run.go | 615 ---------- server/sandbox_status.go | 41 - server/sandbox_stop.go | 114 -- server/seccomp/seccomp.go | 165 --- server/seccomp/seccomp_unsupported.go | 20 - server/seccomp/types.go | 93 -- server/secrets.go | 162 --- server/server.go | 423 ------- server/utils.go | 183 --- server/version.go | 27 - tutorial.md | 425 ------- 111 files changed, 75 insertions(+), 7257 deletions(-) delete mode 100644 contrib/rpm/Makefile delete mode 100644 contrib/rpm/crio.spec delete mode 100644 contrib/systemd/crio-shutdown.service delete mode 100644 contrib/systemd/crio.service delete mode 100644 contrib/test/integration/README.md delete mode 100644 contrib/test/integration/ansible.cfg delete mode 100644 contrib/test/integration/build/bats.yml delete mode 100644 contrib/test/integration/build/cri-o.yml delete mode 100644 contrib/test/integration/build/cri-tools.yml delete mode 100644 contrib/test/integration/build/kubernetes.yml delete mode 100644 contrib/test/integration/build/plugins.yml delete mode 100644 contrib/test/integration/build/runc.yml delete mode 100644 contrib/test/integration/callback_plugins/default.py delete mode 100644 contrib/test/integration/e2e.yml delete mode 100644 contrib/test/integration/golang.yml delete mode 100644 contrib/test/integration/main.yml delete mode 100644 contrib/test/integration/results.yml delete mode 100644 contrib/test/integration/system.yml delete mode 100644 contrib/test/integration/test.yml delete mode 100644 contrib/test/integration/vars.yml delete mode 100644 contrib/test/requirements.txt delete mode 100755 contrib/test/venv-ansible-playbook.sh delete mode 100644 kubernetes.md delete mode 100644 server/apparmor/aaparser.go delete mode 100644 server/apparmor/apparmor_common.go delete mode 100644 server/apparmor/apparmor_supported.go delete mode 100644 server/apparmor/apparmor_unsupported.go delete mode 100644 server/apparmor/template.go delete mode 100644 server/config.go delete mode 100644 server/container_attach.go delete mode 100644 server/container_create.go delete mode 100644 server/container_exec.go delete mode 100644 server/container_execsync.go delete mode 100644 server/container_list.go delete mode 100644 server/container_portforward.go delete mode 100644 server/container_remove.go delete mode 100644 server/container_start.go delete mode 100644 server/container_stats.go delete mode 100644 server/container_stats_list.go delete mode 100644 server/container_status.go delete mode 100644 server/container_stop.go delete mode 100644 server/container_updateruntimeconfig.go delete mode 100644 server/image_fs_info.go delete mode 100644 server/image_list.go delete mode 100644 server/image_pull.go delete mode 100644 server/image_remove.go delete mode 100644 server/image_status.go delete mode 100644 server/inspect.go delete mode 100644 server/inspect_test.go delete mode 100644 server/naming.go delete mode 100644 server/runtime_status.go delete mode 100644 server/sandbox_list.go delete mode 100644 server/sandbox_network.go delete mode 100644 server/sandbox_remove.go delete mode 100644 server/sandbox_run.go delete mode 100644 server/sandbox_status.go delete mode 100644 server/sandbox_stop.go delete mode 100644 server/seccomp/seccomp.go delete mode 100644 server/seccomp/seccomp_unsupported.go delete mode 100644 server/seccomp/types.go delete mode 100644 server/secrets.go delete mode 100644 server/server.go delete mode 100644 server/utils.go delete mode 100644 server/version.go delete mode 100644 tutorial.md (limited to 'oci') diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cc549116d..c121ac416 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,4 +1,4 @@ -# Contributing to CRI-O +# Contributing to Libpod We'd love to have you join the community! Below summarizes the processes that we follow. @@ -13,7 +13,7 @@ that we follow. ## Reporting Issues Before reporting an issue, check our backlog of -[open issues](https://github.com/kubernetes-incubator/cri-o/issues) +[open issues](https://github.com/projectatomic/libpod/issues) to see if someone else has already reported it. If so, feel free to add your scenario, or additional information, to the discussion. Or simply "subscribe" to it to be notified when it is updated. @@ -120,9 +120,9 @@ IRC group on `irc.freenode.net` called `cri-o` that has been setup. For discussions around issues/bugs and features, you can use the github -[issues](https://github.com/kubernetes-incubator/cri-o/issues) +[issues](https://github.com/projectatomic/libpod/issues) and -[PRs](https://github.com/kubernetes-incubator/cri-o/pulls) +[PRs](https://github.com/projectatomic/libpod/pulls) tracking system.