github.com/go/docker@v1.12.0-rc2/libcontainerd/client_linux.go (about) 1 package libcontainerd 2 3 import ( 4 "encoding/json" 5 "fmt" 6 "os" 7 "path/filepath" 8 "strings" 9 "sync" 10 "syscall" 11 "time" 12 13 "github.com/Sirupsen/logrus" 14 containerd "github.com/docker/containerd/api/grpc/types" 15 "github.com/docker/docker/pkg/idtools" 16 "github.com/docker/docker/pkg/mount" 17 specs "github.com/opencontainers/specs/specs-go" 18 "golang.org/x/net/context" 19 ) 20 21 type client struct { 22 clientCommon 23 24 // Platform specific properties below here. 25 remote *remote 26 q queue 27 exitNotifiers map[string]*exitNotifier 28 liveRestore bool 29 } 30 31 func (clnt *client) AddProcess(containerID, processFriendlyName string, specp Process) error { 32 clnt.lock(containerID) 33 defer clnt.unlock(containerID) 34 container, err := clnt.getContainer(containerID) 35 if err != nil { 36 return err 37 } 38 39 spec, err := container.spec() 40 if err != nil { 41 return err 42 } 43 sp := spec.Process 44 sp.Args = specp.Args 45 sp.Terminal = specp.Terminal 46 if specp.Env != nil { 47 sp.Env = specp.Env 48 } 49 if specp.Cwd != nil { 50 sp.Cwd = *specp.Cwd 51 } 52 if specp.User != nil { 53 sp.User = specs.User{ 54 UID: specp.User.UID, 55 GID: specp.User.GID, 56 AdditionalGids: specp.User.AdditionalGids, 57 } 58 } 59 if specp.Capabilities != nil { 60 sp.Capabilities = specp.Capabilities 61 } 62 63 p := container.newProcess(processFriendlyName) 64 65 r := &containerd.AddProcessRequest{ 66 Args: sp.Args, 67 Cwd: sp.Cwd, 68 Terminal: sp.Terminal, 69 Id: containerID, 70 Env: sp.Env, 71 User: &containerd.User{ 72 Uid: sp.User.UID, 73 Gid: sp.User.GID, 74 AdditionalGids: sp.User.AdditionalGids, 75 }, 76 Pid: processFriendlyName, 77 Stdin: p.fifo(syscall.Stdin), 78 Stdout: p.fifo(syscall.Stdout), 79 Stderr: p.fifo(syscall.Stderr), 80 Capabilities: sp.Capabilities, 81 ApparmorProfile: sp.ApparmorProfile, 82 SelinuxLabel: sp.SelinuxLabel, 83 NoNewPrivileges: sp.NoNewPrivileges, 84 Rlimits: convertRlimits(sp.Rlimits), 85 } 86 87 iopipe, err := p.openFifos(sp.Terminal) 88 if err != nil { 89 return err 90 } 91 92 if _, err := clnt.remote.apiClient.AddProcess(context.Background(), r); err != nil { 93 p.closeFifos(iopipe) 94 return err 95 } 96 97 container.processes[processFriendlyName] = p 98 99 clnt.unlock(containerID) 100 101 if err := clnt.backend.AttachStreams(processFriendlyName, *iopipe); err != nil { 102 return err 103 } 104 clnt.lock(containerID) 105 106 return nil 107 } 108 109 func (clnt *client) prepareBundleDir(uid, gid int) (string, error) { 110 root, err := filepath.Abs(clnt.remote.stateDir) 111 if err != nil { 112 return "", err 113 } 114 if uid == 0 && gid == 0 { 115 return root, nil 116 } 117 p := string(filepath.Separator) 118 for _, d := range strings.Split(root, string(filepath.Separator))[1:] { 119 p = filepath.Join(p, d) 120 fi, err := os.Stat(p) 121 if err != nil && !os.IsNotExist(err) { 122 return "", err 123 } 124 if os.IsNotExist(err) || fi.Mode()&1 == 0 { 125 p = fmt.Sprintf("%s.%d.%d", p, uid, gid) 126 if err := idtools.MkdirAs(p, 0700, uid, gid); err != nil && !os.IsExist(err) { 127 return "", err 128 } 129 } 130 } 131 return p, nil 132 } 133 134 func (clnt *client) Create(containerID string, spec Spec, options ...CreateOption) (err error) { 135 clnt.lock(containerID) 136 defer clnt.unlock(containerID) 137 138 if ctr, err := clnt.getContainer(containerID); err == nil { 139 if ctr.restarting { 140 ctr.restartManager.Cancel() 141 ctr.clean() 142 } else { 143 return fmt.Errorf("Container %s is already active", containerID) 144 } 145 } 146 147 uid, gid, err := getRootIDs(specs.Spec(spec)) 148 if err != nil { 149 return err 150 } 151 dir, err := clnt.prepareBundleDir(uid, gid) 152 if err != nil { 153 return err 154 } 155 156 container := clnt.newContainer(filepath.Join(dir, containerID), options...) 157 if err := container.clean(); err != nil { 158 return err 159 } 160 161 defer func() { 162 if err != nil { 163 container.clean() 164 clnt.deleteContainer(containerID) 165 } 166 }() 167 168 if err := idtools.MkdirAllAs(container.dir, 0700, uid, gid); err != nil && !os.IsExist(err) { 169 return err 170 } 171 172 f, err := os.Create(filepath.Join(container.dir, configFilename)) 173 if err != nil { 174 return err 175 } 176 defer f.Close() 177 if err := json.NewEncoder(f).Encode(spec); err != nil { 178 return err 179 } 180 181 return container.start() 182 } 183 184 func (clnt *client) Signal(containerID string, sig int) error { 185 clnt.lock(containerID) 186 defer clnt.unlock(containerID) 187 _, err := clnt.remote.apiClient.Signal(context.Background(), &containerd.SignalRequest{ 188 Id: containerID, 189 Pid: InitFriendlyName, 190 Signal: uint32(sig), 191 }) 192 return err 193 } 194 195 func (clnt *client) SignalProcess(containerID string, pid string, sig int) error { 196 clnt.lock(containerID) 197 defer clnt.unlock(containerID) 198 _, err := clnt.remote.apiClient.Signal(context.Background(), &containerd.SignalRequest{ 199 Id: containerID, 200 Pid: pid, 201 Signal: uint32(sig), 202 }) 203 return err 204 } 205 206 func (clnt *client) Resize(containerID, processFriendlyName string, width, height int) error { 207 clnt.lock(containerID) 208 defer clnt.unlock(containerID) 209 if _, err := clnt.getContainer(containerID); err != nil { 210 return err 211 } 212 _, err := clnt.remote.apiClient.UpdateProcess(context.Background(), &containerd.UpdateProcessRequest{ 213 Id: containerID, 214 Pid: processFriendlyName, 215 Width: uint32(width), 216 Height: uint32(height), 217 }) 218 return err 219 } 220 221 func (clnt *client) Pause(containerID string) error { 222 return clnt.setState(containerID, StatePause) 223 } 224 225 func (clnt *client) setState(containerID, state string) error { 226 clnt.lock(containerID) 227 container, err := clnt.getContainer(containerID) 228 if err != nil { 229 clnt.unlock(containerID) 230 return err 231 } 232 if container.systemPid == 0 { 233 clnt.unlock(containerID) 234 return fmt.Errorf("No active process for container %s", containerID) 235 } 236 st := "running" 237 if state == StatePause { 238 st = "paused" 239 } 240 chstate := make(chan struct{}) 241 _, err = clnt.remote.apiClient.UpdateContainer(context.Background(), &containerd.UpdateContainerRequest{ 242 Id: containerID, 243 Pid: InitFriendlyName, 244 Status: st, 245 }) 246 if err != nil { 247 clnt.unlock(containerID) 248 return err 249 } 250 container.pauseMonitor.append(state, chstate) 251 clnt.unlock(containerID) 252 <-chstate 253 return nil 254 } 255 256 func (clnt *client) Resume(containerID string) error { 257 return clnt.setState(containerID, StateResume) 258 } 259 260 func (clnt *client) Stats(containerID string) (*Stats, error) { 261 resp, err := clnt.remote.apiClient.Stats(context.Background(), &containerd.StatsRequest{containerID}) 262 if err != nil { 263 return nil, err 264 } 265 return (*Stats)(resp), nil 266 } 267 268 // Take care of the old 1.11.0 behavior in case the version upgrade 269 // happened without a clean daemon shutdown 270 func (clnt *client) cleanupOldRootfs(containerID string) { 271 // Unmount and delete the bundle folder 272 if mts, err := mount.GetMounts(); err == nil { 273 for _, mts := range mts { 274 if strings.HasSuffix(mts.Mountpoint, containerID+"/rootfs") { 275 if err := syscall.Unmount(mts.Mountpoint, syscall.MNT_DETACH); err == nil { 276 os.RemoveAll(strings.TrimSuffix(mts.Mountpoint, "/rootfs")) 277 } 278 break 279 } 280 } 281 } 282 } 283 284 func (clnt *client) setExited(containerID string) error { 285 clnt.lock(containerID) 286 defer clnt.unlock(containerID) 287 288 var exitCode uint32 289 if event, ok := clnt.remote.pastEvents[containerID]; ok { 290 exitCode = event.Status 291 delete(clnt.remote.pastEvents, containerID) 292 } 293 294 err := clnt.backend.StateChanged(containerID, StateInfo{ 295 CommonStateInfo: CommonStateInfo{ 296 State: StateExit, 297 ExitCode: exitCode, 298 }}) 299 300 clnt.cleanupOldRootfs(containerID) 301 302 return err 303 } 304 305 func (clnt *client) GetPidsForContainer(containerID string) ([]int, error) { 306 cont, err := clnt.getContainerdContainer(containerID) 307 if err != nil { 308 return nil, err 309 } 310 pids := make([]int, len(cont.Pids)) 311 for i, p := range cont.Pids { 312 pids[i] = int(p) 313 } 314 return pids, nil 315 } 316 317 // Summary returns a summary of the processes running in a container. 318 // This is a no-op on Linux. 319 func (clnt *client) Summary(containerID string) ([]Summary, error) { 320 return nil, nil 321 } 322 323 func (clnt *client) getContainerdContainer(containerID string) (*containerd.Container, error) { 324 resp, err := clnt.remote.apiClient.State(context.Background(), &containerd.StateRequest{Id: containerID}) 325 if err != nil { 326 return nil, err 327 } 328 for _, cont := range resp.Containers { 329 if cont.Id == containerID { 330 return cont, nil 331 } 332 } 333 return nil, fmt.Errorf("invalid state response") 334 } 335 336 func (clnt *client) newContainer(dir string, options ...CreateOption) *container { 337 container := &container{ 338 containerCommon: containerCommon{ 339 process: process{ 340 dir: dir, 341 processCommon: processCommon{ 342 containerID: filepath.Base(dir), 343 client: clnt, 344 friendlyName: InitFriendlyName, 345 }, 346 }, 347 processes: make(map[string]*process), 348 }, 349 } 350 for _, option := range options { 351 if err := option.Apply(container); err != nil { 352 logrus.Error(err) 353 } 354 } 355 return container 356 } 357 358 func (clnt *client) UpdateResources(containerID string, resources Resources) error { 359 clnt.lock(containerID) 360 defer clnt.unlock(containerID) 361 container, err := clnt.getContainer(containerID) 362 if err != nil { 363 return err 364 } 365 if container.systemPid == 0 { 366 return fmt.Errorf("No active process for container %s", containerID) 367 } 368 _, err = clnt.remote.apiClient.UpdateContainer(context.Background(), &containerd.UpdateContainerRequest{ 369 Id: containerID, 370 Pid: InitFriendlyName, 371 Resources: (*containerd.UpdateResource)(&resources), 372 }) 373 if err != nil { 374 return err 375 } 376 return nil 377 } 378 379 func (clnt *client) getExitNotifier(containerID string) *exitNotifier { 380 clnt.mapMutex.RLock() 381 defer clnt.mapMutex.RUnlock() 382 return clnt.exitNotifiers[containerID] 383 } 384 385 func (clnt *client) getOrCreateExitNotifier(containerID string) *exitNotifier { 386 clnt.mapMutex.Lock() 387 w, ok := clnt.exitNotifiers[containerID] 388 defer clnt.mapMutex.Unlock() 389 if !ok { 390 w = &exitNotifier{c: make(chan struct{}), client: clnt} 391 clnt.exitNotifiers[containerID] = w 392 } 393 return w 394 } 395 396 func (clnt *client) restore(cont *containerd.Container, options ...CreateOption) (err error) { 397 clnt.lock(cont.Id) 398 defer clnt.unlock(cont.Id) 399 400 logrus.Debugf("restore container %s state %s", cont.Id, cont.Status) 401 402 containerID := cont.Id 403 if _, err := clnt.getContainer(containerID); err == nil { 404 return fmt.Errorf("container %s is already active", containerID) 405 } 406 407 defer func() { 408 if err != nil { 409 clnt.deleteContainer(cont.Id) 410 } 411 }() 412 413 container := clnt.newContainer(cont.BundlePath, options...) 414 container.systemPid = systemPid(cont) 415 416 var terminal bool 417 for _, p := range cont.Processes { 418 if p.Pid == InitFriendlyName { 419 terminal = p.Terminal 420 } 421 } 422 423 iopipe, err := container.openFifos(terminal) 424 if err != nil { 425 return err 426 } 427 428 if err := clnt.backend.AttachStreams(containerID, *iopipe); err != nil { 429 return err 430 } 431 432 clnt.appendContainer(container) 433 434 err = clnt.backend.StateChanged(containerID, StateInfo{ 435 CommonStateInfo: CommonStateInfo{ 436 State: StateRestore, 437 Pid: container.systemPid, 438 }}) 439 440 if err != nil { 441 return err 442 } 443 444 if event, ok := clnt.remote.pastEvents[containerID]; ok { 445 // This should only be a pause or resume event 446 if event.Type == StatePause || event.Type == StateResume { 447 return clnt.backend.StateChanged(containerID, StateInfo{ 448 CommonStateInfo: CommonStateInfo{ 449 State: event.Type, 450 Pid: container.systemPid, 451 }}) 452 } 453 454 logrus.Warnf("unexpected backlog event: %#v", event) 455 } 456 457 return nil 458 } 459 460 func (clnt *client) Restore(containerID string, options ...CreateOption) error { 461 if clnt.liveRestore { 462 cont, err := clnt.getContainerdContainer(containerID) 463 if err == nil && cont.Status != "stopped" { 464 if err := clnt.restore(cont, options...); err != nil { 465 logrus.Errorf("error restoring %s: %v", containerID, err) 466 } 467 return nil 468 } 469 return clnt.setExited(containerID) 470 } 471 472 cont, err := clnt.getContainerdContainer(containerID) 473 if err == nil && cont.Status != "stopped" { 474 w := clnt.getOrCreateExitNotifier(containerID) 475 clnt.lock(cont.Id) 476 container := clnt.newContainer(cont.BundlePath) 477 container.systemPid = systemPid(cont) 478 clnt.appendContainer(container) 479 clnt.unlock(cont.Id) 480 481 container.discardFifos() 482 483 if err := clnt.Signal(containerID, int(syscall.SIGTERM)); err != nil { 484 logrus.Errorf("error sending sigterm to %v: %v", containerID, err) 485 } 486 select { 487 case <-time.After(10 * time.Second): 488 if err := clnt.Signal(containerID, int(syscall.SIGKILL)); err != nil { 489 logrus.Errorf("error sending sigkill to %v: %v", containerID, err) 490 } 491 select { 492 case <-time.After(2 * time.Second): 493 case <-w.wait(): 494 return nil 495 } 496 case <-w.wait(): 497 return nil 498 } 499 } 500 501 clnt.deleteContainer(containerID) 502 503 return clnt.setExited(containerID) 504 } 505 506 type exitNotifier struct { 507 id string 508 client *client 509 c chan struct{} 510 once sync.Once 511 } 512 513 func (en *exitNotifier) close() { 514 en.once.Do(func() { 515 close(en.c) 516 en.client.mapMutex.Lock() 517 if en == en.client.exitNotifiers[en.id] { 518 delete(en.client.exitNotifiers, en.id) 519 } 520 en.client.mapMutex.Unlock() 521 }) 522 } 523 func (en *exitNotifier) wait() <-chan struct{} { 524 return en.c 525 }