github.com/slene/docker@v1.8.0-rc1/daemon/execdriver/native/driver.go (about)

     1  // +build linux,cgo
     2  
     3  package native
     4  
     5  import (
     6  	"fmt"
     7  	"io"
     8  	"os"
     9  	"os/exec"
    10  	"path/filepath"
    11  	"strings"
    12  	"sync"
    13  	"syscall"
    14  	"time"
    15  
    16  	"github.com/Sirupsen/logrus"
    17  	"github.com/docker/docker/daemon/execdriver"
    18  	"github.com/docker/docker/pkg/parsers"
    19  	"github.com/docker/docker/pkg/pools"
    20  	"github.com/docker/docker/pkg/reexec"
    21  	sysinfo "github.com/docker/docker/pkg/system"
    22  	"github.com/docker/docker/pkg/term"
    23  	"github.com/opencontainers/runc/libcontainer"
    24  	"github.com/opencontainers/runc/libcontainer/cgroups/systemd"
    25  	"github.com/opencontainers/runc/libcontainer/configs"
    26  	"github.com/opencontainers/runc/libcontainer/system"
    27  	"github.com/opencontainers/runc/libcontainer/utils"
    28  )
    29  
    30  const (
    31  	DriverName = "native"
    32  	Version    = "0.2"
    33  )
    34  
    35  type driver struct {
    36  	root             string
    37  	initPath         string
    38  	activeContainers map[string]libcontainer.Container
    39  	machineMemory    int64
    40  	factory          libcontainer.Factory
    41  	sync.Mutex
    42  }
    43  
    44  func NewDriver(root, initPath string, options []string) (*driver, error) {
    45  	meminfo, err := sysinfo.ReadMemInfo()
    46  	if err != nil {
    47  		return nil, err
    48  	}
    49  
    50  	if err := sysinfo.MkdirAll(root, 0700); err != nil {
    51  		return nil, err
    52  	}
    53  
    54  	// choose cgroup manager
    55  	// this makes sure there are no breaking changes to people
    56  	// who upgrade from versions without native.cgroupdriver opt
    57  	cgm := libcontainer.Cgroupfs
    58  	if systemd.UseSystemd() {
    59  		cgm = libcontainer.SystemdCgroups
    60  	}
    61  
    62  	// parse the options
    63  	for _, option := range options {
    64  		key, val, err := parsers.ParseKeyValueOpt(option)
    65  		if err != nil {
    66  			return nil, err
    67  		}
    68  		key = strings.ToLower(key)
    69  		switch key {
    70  		case "native.cgroupdriver":
    71  			// override the default if they set options
    72  			switch val {
    73  			case "systemd":
    74  				if systemd.UseSystemd() {
    75  					cgm = libcontainer.SystemdCgroups
    76  				} else {
    77  					// warn them that they chose the wrong driver
    78  					logrus.Warn("You cannot use systemd as native.cgroupdriver, using cgroupfs instead")
    79  				}
    80  			case "cgroupfs":
    81  				cgm = libcontainer.Cgroupfs
    82  			default:
    83  				return nil, fmt.Errorf("Unknown native.cgroupdriver given %q. try cgroupfs or systemd", val)
    84  			}
    85  		default:
    86  			return nil, fmt.Errorf("Unknown option %s\n", key)
    87  		}
    88  	}
    89  
    90  	f, err := libcontainer.New(
    91  		root,
    92  		cgm,
    93  		libcontainer.InitPath(reexec.Self(), DriverName),
    94  	)
    95  	if err != nil {
    96  		return nil, err
    97  	}
    98  
    99  	return &driver{
   100  		root:             root,
   101  		initPath:         initPath,
   102  		activeContainers: make(map[string]libcontainer.Container),
   103  		machineMemory:    meminfo.MemTotal,
   104  		factory:          f,
   105  	}, nil
   106  }
   107  
   108  type execOutput struct {
   109  	exitCode int
   110  	err      error
   111  }
   112  
   113  func (d *driver) Run(c *execdriver.Command, pipes *execdriver.Pipes, startCallback execdriver.StartCallback) (execdriver.ExitStatus, error) {
   114  	// take the Command and populate the libcontainer.Config from it
   115  	container, err := d.createContainer(c)
   116  	if err != nil {
   117  		return execdriver.ExitStatus{ExitCode: -1}, err
   118  	}
   119  
   120  	p := &libcontainer.Process{
   121  		Args: append([]string{c.ProcessConfig.Entrypoint}, c.ProcessConfig.Arguments...),
   122  		Env:  c.ProcessConfig.Env,
   123  		Cwd:  c.WorkingDir,
   124  		User: c.ProcessConfig.User,
   125  	}
   126  
   127  	if err := setupPipes(container, &c.ProcessConfig, p, pipes); err != nil {
   128  		return execdriver.ExitStatus{ExitCode: -1}, err
   129  	}
   130  
   131  	cont, err := d.factory.Create(c.ID, container)
   132  	if err != nil {
   133  		return execdriver.ExitStatus{ExitCode: -1}, err
   134  	}
   135  	d.Lock()
   136  	d.activeContainers[c.ID] = cont
   137  	d.Unlock()
   138  	defer func() {
   139  		cont.Destroy()
   140  		d.cleanContainer(c.ID)
   141  	}()
   142  
   143  	if err := cont.Start(p); err != nil {
   144  		return execdriver.ExitStatus{ExitCode: -1}, err
   145  	}
   146  
   147  	if startCallback != nil {
   148  		pid, err := p.Pid()
   149  		if err != nil {
   150  			p.Signal(os.Kill)
   151  			p.Wait()
   152  			return execdriver.ExitStatus{ExitCode: -1}, err
   153  		}
   154  		startCallback(&c.ProcessConfig, pid)
   155  	}
   156  
   157  	oom := notifyOnOOM(cont)
   158  	waitF := p.Wait
   159  	if nss := cont.Config().Namespaces; !nss.Contains(configs.NEWPID) {
   160  		// we need such hack for tracking processes with inherited fds,
   161  		// because cmd.Wait() waiting for all streams to be copied
   162  		waitF = waitInPIDHost(p, cont)
   163  	}
   164  	ps, err := waitF()
   165  	if err != nil {
   166  		execErr, ok := err.(*exec.ExitError)
   167  		if !ok {
   168  			return execdriver.ExitStatus{ExitCode: -1}, err
   169  		}
   170  		ps = execErr.ProcessState
   171  	}
   172  	cont.Destroy()
   173  	_, oomKill := <-oom
   174  	return execdriver.ExitStatus{ExitCode: utils.ExitStatus(ps.Sys().(syscall.WaitStatus)), OOMKilled: oomKill}, nil
   175  }
   176  
   177  // notifyOnOOM returns a channel that signals if the container received an OOM notification
   178  // for any process.  If it is unable to subscribe to OOM notifications then a closed
   179  // channel is returned as it will be non-blocking and return the correct result when read.
   180  func notifyOnOOM(container libcontainer.Container) <-chan struct{} {
   181  	oom, err := container.NotifyOOM()
   182  	if err != nil {
   183  		logrus.Warnf("Your kernel does not support OOM notifications: %s", err)
   184  		c := make(chan struct{})
   185  		close(c)
   186  		return c
   187  	}
   188  	return oom
   189  }
   190  
   191  func killCgroupProcs(c libcontainer.Container) {
   192  	var procs []*os.Process
   193  	if err := c.Pause(); err != nil {
   194  		logrus.Warn(err)
   195  	}
   196  	pids, err := c.Processes()
   197  	if err != nil {
   198  		// don't care about childs if we can't get them, this is mostly because cgroup already deleted
   199  		logrus.Warnf("Failed to get processes from container %s: %v", c.ID(), err)
   200  	}
   201  	for _, pid := range pids {
   202  		if p, err := os.FindProcess(pid); err == nil {
   203  			procs = append(procs, p)
   204  			if err := p.Kill(); err != nil {
   205  				logrus.Warn(err)
   206  			}
   207  		}
   208  	}
   209  	if err := c.Resume(); err != nil {
   210  		logrus.Warn(err)
   211  	}
   212  	for _, p := range procs {
   213  		if _, err := p.Wait(); err != nil {
   214  			logrus.Warn(err)
   215  		}
   216  	}
   217  }
   218  
   219  func waitInPIDHost(p *libcontainer.Process, c libcontainer.Container) func() (*os.ProcessState, error) {
   220  	return func() (*os.ProcessState, error) {
   221  		pid, err := p.Pid()
   222  		if err != nil {
   223  			return nil, err
   224  		}
   225  
   226  		process, err := os.FindProcess(pid)
   227  		s, err := process.Wait()
   228  		if err != nil {
   229  			execErr, ok := err.(*exec.ExitError)
   230  			if !ok {
   231  				return s, err
   232  			}
   233  			s = execErr.ProcessState
   234  		}
   235  		killCgroupProcs(c)
   236  		p.Wait()
   237  		return s, err
   238  	}
   239  }
   240  
   241  func (d *driver) Kill(c *execdriver.Command, sig int) error {
   242  	d.Lock()
   243  	active := d.activeContainers[c.ID]
   244  	d.Unlock()
   245  	if active == nil {
   246  		return fmt.Errorf("active container for %s does not exist", c.ID)
   247  	}
   248  	state, err := active.State()
   249  	if err != nil {
   250  		return err
   251  	}
   252  	return syscall.Kill(state.InitProcessPid, syscall.Signal(sig))
   253  }
   254  
   255  func (d *driver) Pause(c *execdriver.Command) error {
   256  	d.Lock()
   257  	active := d.activeContainers[c.ID]
   258  	d.Unlock()
   259  	if active == nil {
   260  		return fmt.Errorf("active container for %s does not exist", c.ID)
   261  	}
   262  	return active.Pause()
   263  }
   264  
   265  func (d *driver) Unpause(c *execdriver.Command) error {
   266  	d.Lock()
   267  	active := d.activeContainers[c.ID]
   268  	d.Unlock()
   269  	if active == nil {
   270  		return fmt.Errorf("active container for %s does not exist", c.ID)
   271  	}
   272  	return active.Resume()
   273  }
   274  
   275  func (d *driver) Terminate(c *execdriver.Command) error {
   276  	defer d.cleanContainer(c.ID)
   277  	container, err := d.factory.Load(c.ID)
   278  	if err != nil {
   279  		return err
   280  	}
   281  	defer container.Destroy()
   282  	state, err := container.State()
   283  	if err != nil {
   284  		return err
   285  	}
   286  	pid := state.InitProcessPid
   287  	currentStartTime, err := system.GetProcessStartTime(pid)
   288  	if err != nil {
   289  		return err
   290  	}
   291  	if state.InitProcessStartTime == currentStartTime {
   292  		err = syscall.Kill(pid, 9)
   293  		syscall.Wait4(pid, nil, 0, nil)
   294  	}
   295  	return err
   296  }
   297  
   298  func (d *driver) Info(id string) execdriver.Info {
   299  	return &info{
   300  		ID:     id,
   301  		driver: d,
   302  	}
   303  }
   304  
   305  func (d *driver) Name() string {
   306  	return fmt.Sprintf("%s-%s", DriverName, Version)
   307  }
   308  
   309  func (d *driver) GetPidsForContainer(id string) ([]int, error) {
   310  	d.Lock()
   311  	active := d.activeContainers[id]
   312  	d.Unlock()
   313  
   314  	if active == nil {
   315  		return nil, fmt.Errorf("active container for %s does not exist", id)
   316  	}
   317  	return active.Processes()
   318  }
   319  
   320  func (d *driver) cleanContainer(id string) error {
   321  	d.Lock()
   322  	delete(d.activeContainers, id)
   323  	d.Unlock()
   324  	return os.RemoveAll(filepath.Join(d.root, id))
   325  }
   326  
   327  func (d *driver) createContainerRoot(id string) error {
   328  	return os.MkdirAll(filepath.Join(d.root, id), 0655)
   329  }
   330  
   331  func (d *driver) Clean(id string) error {
   332  	return os.RemoveAll(filepath.Join(d.root, id))
   333  }
   334  
   335  func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
   336  	d.Lock()
   337  	c := d.activeContainers[id]
   338  	d.Unlock()
   339  	if c == nil {
   340  		return nil, execdriver.ErrNotRunning
   341  	}
   342  	now := time.Now()
   343  	stats, err := c.Stats()
   344  	if err != nil {
   345  		return nil, err
   346  	}
   347  	memoryLimit := c.Config().Cgroups.Memory
   348  	// if the container does not have any memory limit specified set the
   349  	// limit to the machines memory
   350  	if memoryLimit == 0 {
   351  		memoryLimit = d.machineMemory
   352  	}
   353  	return &execdriver.ResourceStats{
   354  		Stats:       stats,
   355  		Read:        now,
   356  		MemoryLimit: memoryLimit,
   357  	}, nil
   358  }
   359  
   360  type TtyConsole struct {
   361  	console libcontainer.Console
   362  }
   363  
   364  func NewTtyConsole(console libcontainer.Console, pipes *execdriver.Pipes) (*TtyConsole, error) {
   365  	tty := &TtyConsole{
   366  		console: console,
   367  	}
   368  
   369  	if err := tty.AttachPipes(pipes); err != nil {
   370  		tty.Close()
   371  		return nil, err
   372  	}
   373  
   374  	return tty, nil
   375  }
   376  
   377  func (t *TtyConsole) Resize(h, w int) error {
   378  	return term.SetWinsize(t.console.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
   379  }
   380  
   381  func (t *TtyConsole) AttachPipes(pipes *execdriver.Pipes) error {
   382  	go func() {
   383  		if wb, ok := pipes.Stdout.(interface {
   384  			CloseWriters() error
   385  		}); ok {
   386  			defer wb.CloseWriters()
   387  		}
   388  
   389  		pools.Copy(pipes.Stdout, t.console)
   390  	}()
   391  
   392  	if pipes.Stdin != nil {
   393  		go func() {
   394  			pools.Copy(t.console, pipes.Stdin)
   395  
   396  			pipes.Stdin.Close()
   397  		}()
   398  	}
   399  
   400  	return nil
   401  }
   402  
   403  func (t *TtyConsole) Close() error {
   404  	return t.console.Close()
   405  }
   406  
   407  func setupPipes(container *configs.Config, processConfig *execdriver.ProcessConfig, p *libcontainer.Process, pipes *execdriver.Pipes) error {
   408  	var term execdriver.Terminal
   409  	var err error
   410  
   411  	if processConfig.Tty {
   412  		rootuid, err := container.HostUID()
   413  		if err != nil {
   414  			return err
   415  		}
   416  		cons, err := p.NewConsole(rootuid)
   417  		if err != nil {
   418  			return err
   419  		}
   420  		term, err = NewTtyConsole(cons, pipes)
   421  	} else {
   422  		p.Stdout = pipes.Stdout
   423  		p.Stderr = pipes.Stderr
   424  		r, w, err := os.Pipe()
   425  		if err != nil {
   426  			return err
   427  		}
   428  		if pipes.Stdin != nil {
   429  			go func() {
   430  				io.Copy(w, pipes.Stdin)
   431  				w.Close()
   432  			}()
   433  			p.Stdin = r
   434  		}
   435  		term = &execdriver.StdConsole{}
   436  	}
   437  	if err != nil {
   438  		return err
   439  	}
   440  	processConfig.Terminal = term
   441  	return nil
   442  }