github.com/codemac/docker@v1.2.1-0.20150518222241-6a18412d5b9c/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  	active := d.activeContainers[c.ID]
   263  	if active == nil {
   264  		return fmt.Errorf("active container for %s does not exist", c.ID)
   265  	}
   266  	return active.Pause()
   267  }
   268  
   269  func (d *driver) Unpause(c *execdriver.Command) error {
   270  	active := d.activeContainers[c.ID]
   271  	if active == nil {
   272  		return fmt.Errorf("active container for %s does not exist", c.ID)
   273  	}
   274  	return active.Resume()
   275  }
   276  
   277  func (d *driver) Terminate(c *execdriver.Command) error {
   278  	defer d.cleanContainer(c.ID)
   279  	container, err := d.factory.Load(c.ID)
   280  	if err != nil {
   281  		return err
   282  	}
   283  	defer container.Destroy()
   284  	state, err := container.State()
   285  	if err != nil {
   286  		return err
   287  	}
   288  	pid := state.InitProcessPid
   289  	currentStartTime, err := system.GetProcessStartTime(pid)
   290  	if err != nil {
   291  		return err
   292  	}
   293  	if state.InitProcessStartTime == currentStartTime {
   294  		err = syscall.Kill(pid, 9)
   295  		syscall.Wait4(pid, nil, 0, nil)
   296  	}
   297  	return err
   298  }
   299  
   300  func (d *driver) Info(id string) execdriver.Info {
   301  	return &info{
   302  		ID:     id,
   303  		driver: d,
   304  	}
   305  }
   306  
   307  func (d *driver) Name() string {
   308  	return fmt.Sprintf("%s-%s", DriverName, Version)
   309  }
   310  
   311  func (d *driver) GetPidsForContainer(id string) ([]int, error) {
   312  	d.Lock()
   313  	active := d.activeContainers[id]
   314  	d.Unlock()
   315  
   316  	if active == nil {
   317  		return nil, fmt.Errorf("active container for %s does not exist", id)
   318  	}
   319  	return active.Processes()
   320  }
   321  
   322  func (d *driver) cleanContainer(id string) error {
   323  	d.Lock()
   324  	delete(d.activeContainers, id)
   325  	d.Unlock()
   326  	return os.RemoveAll(filepath.Join(d.root, id))
   327  }
   328  
   329  func (d *driver) createContainerRoot(id string) error {
   330  	return os.MkdirAll(filepath.Join(d.root, id), 0655)
   331  }
   332  
   333  func (d *driver) Clean(id string) error {
   334  	return os.RemoveAll(filepath.Join(d.root, id))
   335  }
   336  
   337  func (d *driver) Stats(id string) (*execdriver.ResourceStats, error) {
   338  	c := d.activeContainers[id]
   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, rootuid int) (*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) Master() libcontainer.Console {
   378  	return t.console
   379  }
   380  
   381  func (t *TtyConsole) Resize(h, w int) error {
   382  	return term.SetWinsize(t.console.Fd(), &term.Winsize{Height: uint16(h), Width: uint16(w)})
   383  }
   384  
   385  func (t *TtyConsole) AttachPipes(pipes *execdriver.Pipes) error {
   386  	go func() {
   387  		if wb, ok := pipes.Stdout.(interface {
   388  			CloseWriters() error
   389  		}); ok {
   390  			defer wb.CloseWriters()
   391  		}
   392  
   393  		io.Copy(pipes.Stdout, t.console)
   394  	}()
   395  
   396  	if pipes.Stdin != nil {
   397  		go func() {
   398  			io.Copy(t.console, pipes.Stdin)
   399  
   400  			pipes.Stdin.Close()
   401  		}()
   402  	}
   403  
   404  	return nil
   405  }
   406  
   407  func (t *TtyConsole) Close() error {
   408  	return t.console.Close()
   409  }
   410  
   411  func setupPipes(container *configs.Config, processConfig *execdriver.ProcessConfig, p *libcontainer.Process, pipes *execdriver.Pipes) error {
   412  	var term execdriver.Terminal
   413  	var err error
   414  
   415  	if processConfig.Tty {
   416  		rootuid, err := container.HostUID()
   417  		if err != nil {
   418  			return err
   419  		}
   420  		cons, err := p.NewConsole(rootuid)
   421  		if err != nil {
   422  			return err
   423  		}
   424  		term, err = NewTtyConsole(cons, pipes, rootuid)
   425  	} else {
   426  		p.Stdout = pipes.Stdout
   427  		p.Stderr = pipes.Stderr
   428  		r, w, err := os.Pipe()
   429  		if err != nil {
   430  			return err
   431  		}
   432  		if pipes.Stdin != nil {
   433  			go func() {
   434  				io.Copy(w, pipes.Stdin)
   435  				w.Close()
   436  			}()
   437  			p.Stdin = r
   438  		}
   439  		term = &execdriver.StdConsole{}
   440  	}
   441  	if err != nil {
   442  		return err
   443  	}
   444  	processConfig.Terminal = term
   445  	return nil
   446  }