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 }