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 }