github.com/noxiouz/docker@v0.7.3-0.20160629055221-3d231c78e8c5/libcontainerd/container_linux.go (about)

     1  package libcontainerd
     2  
     3  import (
     4  	"encoding/json"
     5  	"io"
     6  	"io/ioutil"
     7  	"os"
     8  	"path/filepath"
     9  	"syscall"
    10  	"time"
    11  
    12  	"github.com/Sirupsen/logrus"
    13  	containerd "github.com/docker/containerd/api/grpc/types"
    14  	"github.com/docker/docker/restartmanager"
    15  	"github.com/opencontainers/specs/specs-go"
    16  	"golang.org/x/net/context"
    17  )
    18  
    19  type container struct {
    20  	containerCommon
    21  
    22  	// Platform specific fields are below here.
    23  	pauseMonitor
    24  	oom         bool
    25  	runtime     string
    26  	runtimeArgs []string
    27  }
    28  
    29  type runtime struct {
    30  	path string
    31  	args []string
    32  }
    33  
    34  // WithRuntime sets the runtime to be used for the created container
    35  func WithRuntime(path string, args []string) CreateOption {
    36  	return runtime{path, args}
    37  }
    38  
    39  func (rt runtime) Apply(p interface{}) error {
    40  	if pr, ok := p.(*container); ok {
    41  		pr.runtime = rt.path
    42  		pr.runtimeArgs = rt.args
    43  	}
    44  	return nil
    45  }
    46  
    47  func (ctr *container) clean() error {
    48  	if os.Getenv("LIBCONTAINERD_NOCLEAN") == "1" {
    49  		return nil
    50  	}
    51  	if _, err := os.Lstat(ctr.dir); err != nil {
    52  		if os.IsNotExist(err) {
    53  			return nil
    54  		}
    55  		return err
    56  	}
    57  
    58  	if err := os.RemoveAll(ctr.dir); err != nil {
    59  		return err
    60  	}
    61  	return nil
    62  }
    63  
    64  // cleanProcess removes the fifos used by an additional process.
    65  // Caller needs to lock container ID before calling this method.
    66  func (ctr *container) cleanProcess(id string) {
    67  	if p, ok := ctr.processes[id]; ok {
    68  		for _, i := range []int{syscall.Stdin, syscall.Stdout, syscall.Stderr} {
    69  			if err := os.Remove(p.fifo(i)); err != nil {
    70  				logrus.Warnf("failed to remove %v for process %v: %v", p.fifo(i), id, err)
    71  			}
    72  		}
    73  	}
    74  	delete(ctr.processes, id)
    75  }
    76  
    77  func (ctr *container) spec() (*specs.Spec, error) {
    78  	var spec specs.Spec
    79  	dt, err := ioutil.ReadFile(filepath.Join(ctr.dir, configFilename))
    80  	if err != nil {
    81  		return nil, err
    82  	}
    83  	if err := json.Unmarshal(dt, &spec); err != nil {
    84  		return nil, err
    85  	}
    86  	return &spec, nil
    87  }
    88  
    89  func (ctr *container) start() error {
    90  	spec, err := ctr.spec()
    91  	if err != nil {
    92  		return nil
    93  	}
    94  	iopipe, err := ctr.openFifos(spec.Process.Terminal)
    95  	if err != nil {
    96  		return err
    97  	}
    98  
    99  	r := &containerd.CreateContainerRequest{
   100  		Id:         ctr.containerID,
   101  		BundlePath: ctr.dir,
   102  		Stdin:      ctr.fifo(syscall.Stdin),
   103  		Stdout:     ctr.fifo(syscall.Stdout),
   104  		Stderr:     ctr.fifo(syscall.Stderr),
   105  		// check to see if we are running in ramdisk to disable pivot root
   106  		NoPivotRoot: os.Getenv("DOCKER_RAMDISK") != "",
   107  		Runtime:     ctr.runtime,
   108  		RuntimeArgs: ctr.runtimeArgs,
   109  	}
   110  	ctr.client.appendContainer(ctr)
   111  
   112  	resp, err := ctr.client.remote.apiClient.CreateContainer(context.Background(), r)
   113  	if err != nil {
   114  		ctr.closeFifos(iopipe)
   115  		return err
   116  	}
   117  	ctr.startedAt = time.Now()
   118  
   119  	if err := ctr.client.backend.AttachStreams(ctr.containerID, *iopipe); err != nil {
   120  		return err
   121  	}
   122  	ctr.systemPid = systemPid(resp.Container)
   123  
   124  	return ctr.client.backend.StateChanged(ctr.containerID, StateInfo{
   125  		CommonStateInfo: CommonStateInfo{
   126  			State: StateStart,
   127  			Pid:   ctr.systemPid,
   128  		}})
   129  }
   130  
   131  func (ctr *container) newProcess(friendlyName string) *process {
   132  	return &process{
   133  		dir: ctr.dir,
   134  		processCommon: processCommon{
   135  			containerID:  ctr.containerID,
   136  			friendlyName: friendlyName,
   137  			client:       ctr.client,
   138  		},
   139  	}
   140  }
   141  
   142  func (ctr *container) handleEvent(e *containerd.Event) error {
   143  	ctr.client.lock(ctr.containerID)
   144  	defer ctr.client.unlock(ctr.containerID)
   145  	switch e.Type {
   146  	case StateExit, StatePause, StateResume, StateOOM:
   147  		st := StateInfo{
   148  			CommonStateInfo: CommonStateInfo{
   149  				State:    e.Type,
   150  				ExitCode: e.Status,
   151  			},
   152  			OOMKilled: e.Type == StateExit && ctr.oom,
   153  		}
   154  		if e.Type == StateOOM {
   155  			ctr.oom = true
   156  		}
   157  		if e.Type == StateExit && e.Pid != InitFriendlyName {
   158  			st.ProcessID = e.Pid
   159  			st.State = StateExitProcess
   160  		}
   161  		if st.State == StateExit && ctr.restartManager != nil {
   162  			restart, wait, err := ctr.restartManager.ShouldRestart(e.Status, false, time.Since(ctr.startedAt))
   163  			if err != nil {
   164  				logrus.Warnf("container %s %v", ctr.containerID, err)
   165  			} else if restart {
   166  				st.State = StateRestart
   167  				ctr.restarting = true
   168  				ctr.client.deleteContainer(e.Id)
   169  				go func() {
   170  					err := <-wait
   171  					ctr.client.lock(ctr.containerID)
   172  					defer ctr.client.unlock(ctr.containerID)
   173  					ctr.restarting = false
   174  					if err != nil {
   175  						st.State = StateExit
   176  						ctr.clean()
   177  						ctr.client.q.append(e.Id, func() {
   178  							if err := ctr.client.backend.StateChanged(e.Id, st); err != nil {
   179  								logrus.Error(err)
   180  							}
   181  						})
   182  						if err != restartmanager.ErrRestartCanceled {
   183  							logrus.Error(err)
   184  						}
   185  					} else {
   186  						ctr.start()
   187  					}
   188  				}()
   189  			}
   190  		}
   191  
   192  		// Remove process from list if we have exited
   193  		// We need to do so here in case the Message Handler decides to restart it.
   194  		switch st.State {
   195  		case StateExit:
   196  			ctr.clean()
   197  			ctr.client.deleteContainer(e.Id)
   198  		case StateExitProcess:
   199  			ctr.cleanProcess(st.ProcessID)
   200  		}
   201  		ctr.client.q.append(e.Id, func() {
   202  			if err := ctr.client.backend.StateChanged(e.Id, st); err != nil {
   203  				logrus.Error(err)
   204  			}
   205  			if e.Type == StatePause || e.Type == StateResume {
   206  				ctr.pauseMonitor.handle(e.Type)
   207  			}
   208  			if e.Type == StateExit {
   209  				if en := ctr.client.getExitNotifier(e.Id); en != nil {
   210  					en.close()
   211  				}
   212  			}
   213  		})
   214  
   215  	default:
   216  		logrus.Debugf("event unhandled: %+v", e)
   217  	}
   218  	return nil
   219  }
   220  
   221  // discardFifos attempts to fully read the container fifos to unblock processes
   222  // that may be blocked on the writer side.
   223  func (ctr *container) discardFifos() {
   224  	for _, i := range []int{syscall.Stdout, syscall.Stderr} {
   225  		f := ctr.fifo(i)
   226  		c := make(chan struct{})
   227  		go func() {
   228  			close(c) // this channel is used to not close the writer too early, before readonly open has been called.
   229  			io.Copy(ioutil.Discard, openReaderFromFifo(f))
   230  		}()
   231  		<-c
   232  		closeReaderFifo(f) // avoid blocking permanently on open if there is no writer side
   233  	}
   234  }