c473d14d45
Signed-off-by: Tonis Tiigi <tonistiigi@gmail.com>
(cherry picked from commit 9be0fb45c2
)
Signed-off-by: Tibor Vass <tibor@docker.com>
243 lines
6 KiB
Go
243 lines
6 KiB
Go
package libcontainerd
|
|
|
|
import (
|
|
"encoding/json"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"path/filepath"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/Sirupsen/logrus"
|
|
containerd "github.com/docker/containerd/api/grpc/types"
|
|
"github.com/docker/docker/restartmanager"
|
|
"github.com/opencontainers/specs/specs-go"
|
|
"golang.org/x/net/context"
|
|
)
|
|
|
|
type container struct {
|
|
containerCommon
|
|
|
|
// Platform specific fields are below here.
|
|
pauseMonitor
|
|
oom bool
|
|
runtime string
|
|
runtimeArgs []string
|
|
}
|
|
|
|
type runtime struct {
|
|
path string
|
|
args []string
|
|
}
|
|
|
|
// WithRuntime sets the runtime to be used for the created container
|
|
func WithRuntime(path string, args []string) CreateOption {
|
|
return runtime{path, args}
|
|
}
|
|
|
|
func (rt runtime) Apply(p interface{}) error {
|
|
if pr, ok := p.(*container); ok {
|
|
pr.runtime = rt.path
|
|
pr.runtimeArgs = rt.args
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func (ctr *container) clean() error {
|
|
if os.Getenv("LIBCONTAINERD_NOCLEAN") == "1" {
|
|
return nil
|
|
}
|
|
if _, err := os.Lstat(ctr.dir); err != nil {
|
|
if os.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
|
|
if err := os.RemoveAll(ctr.dir); err != nil {
|
|
return err
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// cleanProcess removes the fifos used by an additional process.
|
|
// Caller needs to lock container ID before calling this method.
|
|
func (ctr *container) cleanProcess(id string) {
|
|
if p, ok := ctr.processes[id]; ok {
|
|
for _, i := range []int{syscall.Stdin, syscall.Stdout, syscall.Stderr} {
|
|
if err := os.Remove(p.fifo(i)); err != nil {
|
|
logrus.Warnf("libcontainerd: failed to remove %v for process %v: %v", p.fifo(i), id, err)
|
|
}
|
|
}
|
|
}
|
|
delete(ctr.processes, id)
|
|
}
|
|
|
|
func (ctr *container) spec() (*specs.Spec, error) {
|
|
var spec specs.Spec
|
|
dt, err := ioutil.ReadFile(filepath.Join(ctr.dir, configFilename))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := json.Unmarshal(dt, &spec); err != nil {
|
|
return nil, err
|
|
}
|
|
return &spec, nil
|
|
}
|
|
|
|
func (ctr *container) start() error {
|
|
spec, err := ctr.spec()
|
|
if err != nil {
|
|
return nil
|
|
}
|
|
iopipe, err := ctr.openFifos(spec.Process.Terminal)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
r := &containerd.CreateContainerRequest{
|
|
Id: ctr.containerID,
|
|
BundlePath: ctr.dir,
|
|
Stdin: ctr.fifo(syscall.Stdin),
|
|
Stdout: ctr.fifo(syscall.Stdout),
|
|
Stderr: ctr.fifo(syscall.Stderr),
|
|
// check to see if we are running in ramdisk to disable pivot root
|
|
NoPivotRoot: os.Getenv("DOCKER_RAMDISK") != "",
|
|
Runtime: ctr.runtime,
|
|
RuntimeArgs: ctr.runtimeArgs,
|
|
}
|
|
ctr.client.appendContainer(ctr)
|
|
|
|
resp, err := ctr.client.remote.apiClient.CreateContainer(context.Background(), r)
|
|
if err != nil {
|
|
ctr.closeFifos(iopipe)
|
|
return err
|
|
}
|
|
ctr.startedAt = time.Now()
|
|
|
|
if err := ctr.client.backend.AttachStreams(ctr.containerID, *iopipe); err != nil {
|
|
return err
|
|
}
|
|
ctr.systemPid = systemPid(resp.Container)
|
|
|
|
return ctr.client.backend.StateChanged(ctr.containerID, StateInfo{
|
|
CommonStateInfo: CommonStateInfo{
|
|
State: StateStart,
|
|
Pid: ctr.systemPid,
|
|
}})
|
|
}
|
|
|
|
func (ctr *container) newProcess(friendlyName string) *process {
|
|
return &process{
|
|
dir: ctr.dir,
|
|
processCommon: processCommon{
|
|
containerID: ctr.containerID,
|
|
friendlyName: friendlyName,
|
|
client: ctr.client,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (ctr *container) handleEvent(e *containerd.Event) error {
|
|
ctr.client.lock(ctr.containerID)
|
|
defer ctr.client.unlock(ctr.containerID)
|
|
switch e.Type {
|
|
case StateExit, StatePause, StateResume, StateOOM:
|
|
var waitRestart chan error
|
|
st := StateInfo{
|
|
CommonStateInfo: CommonStateInfo{
|
|
State: e.Type,
|
|
ExitCode: e.Status,
|
|
},
|
|
OOMKilled: e.Type == StateExit && ctr.oom,
|
|
}
|
|
if e.Type == StateOOM {
|
|
ctr.oom = true
|
|
}
|
|
if e.Type == StateExit && e.Pid != InitFriendlyName {
|
|
st.ProcessID = e.Pid
|
|
st.State = StateExitProcess
|
|
}
|
|
if st.State == StateExit && ctr.restartManager != nil {
|
|
restart, wait, err := ctr.restartManager.ShouldRestart(e.Status, false, time.Since(ctr.startedAt))
|
|
if err != nil {
|
|
logrus.Warnf("libcontainerd: container %s %v", ctr.containerID, err)
|
|
} else if restart {
|
|
st.State = StateRestart
|
|
ctr.restarting = true
|
|
ctr.client.deleteContainer(e.Id)
|
|
waitRestart = wait
|
|
}
|
|
}
|
|
|
|
// Remove process from list if we have exited
|
|
// We need to do so here in case the Message Handler decides to restart it.
|
|
switch st.State {
|
|
case StateExit:
|
|
ctr.clean()
|
|
ctr.client.deleteContainer(e.Id)
|
|
case StateExitProcess:
|
|
ctr.cleanProcess(st.ProcessID)
|
|
}
|
|
ctr.client.q.append(e.Id, func() {
|
|
if err := ctr.client.backend.StateChanged(e.Id, st); err != nil {
|
|
logrus.Errorf("libcontainerd: backend.StateChanged(): %v", err)
|
|
}
|
|
if st.State == StateRestart {
|
|
go func() {
|
|
err := <-waitRestart
|
|
ctr.client.lock(ctr.containerID)
|
|
defer ctr.client.unlock(ctr.containerID)
|
|
ctr.restarting = false
|
|
if err == nil {
|
|
if err = ctr.start(); err != nil {
|
|
logrus.Errorf("libcontainerd: error restarting %v", err)
|
|
}
|
|
}
|
|
if err != nil {
|
|
st.State = StateExit
|
|
ctr.clean()
|
|
ctr.client.q.append(e.Id, func() {
|
|
if err := ctr.client.backend.StateChanged(e.Id, st); err != nil {
|
|
logrus.Errorf("libcontainerd: %v", err)
|
|
}
|
|
})
|
|
if err != restartmanager.ErrRestartCanceled {
|
|
logrus.Errorf("libcontainerd: %v", err)
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
if e.Type == StatePause || e.Type == StateResume {
|
|
ctr.pauseMonitor.handle(e.Type)
|
|
}
|
|
if e.Type == StateExit {
|
|
if en := ctr.client.getExitNotifier(e.Id); en != nil {
|
|
en.close()
|
|
}
|
|
}
|
|
})
|
|
|
|
default:
|
|
logrus.Debugf("libcontainerd: event unhandled: %+v", e)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// discardFifos attempts to fully read the container fifos to unblock processes
|
|
// that may be blocked on the writer side.
|
|
func (ctr *container) discardFifos() {
|
|
for _, i := range []int{syscall.Stdout, syscall.Stderr} {
|
|
f := ctr.fifo(i)
|
|
c := make(chan struct{})
|
|
go func() {
|
|
r := openReaderFromFifo(f)
|
|
close(c) // this channel is used to not close the writer too early, before readonly open has been called.
|
|
io.Copy(ioutil.Discard, r)
|
|
}()
|
|
<-c
|
|
closeReaderFifo(f) // avoid blocking permanently on open if there is no writer side
|
|
}
|
|
}
|