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