github.com/nullne/docker@v1.13.0-rc1/libcontainerd/client_linux.go (about) 1 package libcontainerd 2 3 import ( 4 "fmt" 5 "os" 6 "strings" 7 "sync" 8 "syscall" 9 "time" 10 11 "github.com/Sirupsen/logrus" 12 containerd "github.com/docker/containerd/api/grpc/types" 13 "github.com/docker/docker/pkg/ioutils" 14 "github.com/docker/docker/pkg/mount" 15 "github.com/golang/protobuf/ptypes" 16 "github.com/golang/protobuf/ptypes/timestamp" 17 specs "github.com/opencontainers/runtime-spec/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 // GetServerVersion returns the connected server version information 32 func (clnt *client) GetServerVersion(ctx context.Context) (*ServerVersion, error) { 33 resp, err := clnt.remote.apiClient.GetServerVersion(ctx, &containerd.GetServerVersionRequest{}) 34 if err != nil { 35 return nil, err 36 } 37 38 sv := &ServerVersion{ 39 GetServerVersionResponse: *resp, 40 } 41 42 return sv, nil 43 } 44 45 // AddProcess is the handler for adding a process to an already running 46 // container. It's called through docker exec. It returns the system pid of the 47 // exec'd process. 48 func (clnt *client) AddProcess(ctx context.Context, containerID, processFriendlyName string, specp Process, attachStdio StdioCallback) (int, error) { 49 clnt.lock(containerID) 50 defer clnt.unlock(containerID) 51 container, err := clnt.getContainer(containerID) 52 if err != nil { 53 return -1, err 54 } 55 56 spec, err := container.spec() 57 if err != nil { 58 return -1, err 59 } 60 sp := spec.Process 61 sp.Args = specp.Args 62 sp.Terminal = specp.Terminal 63 if len(specp.Env) > 0 { 64 sp.Env = specp.Env 65 } 66 if specp.Cwd != nil { 67 sp.Cwd = *specp.Cwd 68 } 69 if specp.User != nil { 70 sp.User = specs.User{ 71 UID: specp.User.UID, 72 GID: specp.User.GID, 73 AdditionalGids: specp.User.AdditionalGids, 74 } 75 } 76 if specp.Capabilities != nil { 77 sp.Capabilities = specp.Capabilities 78 } 79 80 p := container.newProcess(processFriendlyName) 81 82 r := &containerd.AddProcessRequest{ 83 Args: sp.Args, 84 Cwd: sp.Cwd, 85 Terminal: sp.Terminal, 86 Id: containerID, 87 Env: sp.Env, 88 User: &containerd.User{ 89 Uid: sp.User.UID, 90 Gid: sp.User.GID, 91 AdditionalGids: sp.User.AdditionalGids, 92 }, 93 Pid: processFriendlyName, 94 Stdin: p.fifo(syscall.Stdin), 95 Stdout: p.fifo(syscall.Stdout), 96 Stderr: p.fifo(syscall.Stderr), 97 Capabilities: sp.Capabilities, 98 ApparmorProfile: sp.ApparmorProfile, 99 SelinuxLabel: sp.SelinuxLabel, 100 NoNewPrivileges: sp.NoNewPrivileges, 101 Rlimits: convertRlimits(sp.Rlimits), 102 } 103 104 iopipe, err := p.openFifos(sp.Terminal) 105 if err != nil { 106 return -1, err 107 } 108 109 resp, err := clnt.remote.apiClient.AddProcess(ctx, r) 110 if err != nil { 111 p.closeFifos(iopipe) 112 return -1, err 113 } 114 115 var stdinOnce sync.Once 116 stdin := iopipe.Stdin 117 iopipe.Stdin = ioutils.NewWriteCloserWrapper(stdin, func() error { 118 var err error 119 stdinOnce.Do(func() { // on error from attach we don't know if stdin was already closed 120 err = stdin.Close() 121 if err2 := p.sendCloseStdin(); err == nil { 122 err = err2 123 } 124 }) 125 return err 126 }) 127 128 container.processes[processFriendlyName] = p 129 130 if err := attachStdio(*iopipe); err != nil { 131 p.closeFifos(iopipe) 132 return -1, err 133 } 134 135 return int(resp.SystemPid), nil 136 } 137 138 func (clnt *client) SignalProcess(containerID string, pid string, sig int) error { 139 clnt.lock(containerID) 140 defer clnt.unlock(containerID) 141 _, err := clnt.remote.apiClient.Signal(context.Background(), &containerd.SignalRequest{ 142 Id: containerID, 143 Pid: pid, 144 Signal: uint32(sig), 145 }) 146 return err 147 } 148 149 func (clnt *client) Resize(containerID, processFriendlyName string, width, height int) error { 150 clnt.lock(containerID) 151 defer clnt.unlock(containerID) 152 if _, err := clnt.getContainer(containerID); err != nil { 153 return err 154 } 155 _, err := clnt.remote.apiClient.UpdateProcess(context.Background(), &containerd.UpdateProcessRequest{ 156 Id: containerID, 157 Pid: processFriendlyName, 158 Width: uint32(width), 159 Height: uint32(height), 160 }) 161 return err 162 } 163 164 func (clnt *client) Pause(containerID string) error { 165 return clnt.setState(containerID, StatePause) 166 } 167 168 func (clnt *client) setState(containerID, state string) error { 169 clnt.lock(containerID) 170 container, err := clnt.getContainer(containerID) 171 if err != nil { 172 clnt.unlock(containerID) 173 return err 174 } 175 if container.systemPid == 0 { 176 clnt.unlock(containerID) 177 return fmt.Errorf("No active process for container %s", containerID) 178 } 179 st := "running" 180 if state == StatePause { 181 st = "paused" 182 } 183 chstate := make(chan struct{}) 184 _, err = clnt.remote.apiClient.UpdateContainer(context.Background(), &containerd.UpdateContainerRequest{ 185 Id: containerID, 186 Pid: InitFriendlyName, 187 Status: st, 188 }) 189 if err != nil { 190 clnt.unlock(containerID) 191 return err 192 } 193 container.pauseMonitor.append(state, chstate) 194 clnt.unlock(containerID) 195 <-chstate 196 return nil 197 } 198 199 func (clnt *client) Resume(containerID string) error { 200 return clnt.setState(containerID, StateResume) 201 } 202 203 func (clnt *client) Stats(containerID string) (*Stats, error) { 204 resp, err := clnt.remote.apiClient.Stats(context.Background(), &containerd.StatsRequest{containerID}) 205 if err != nil { 206 return nil, err 207 } 208 return (*Stats)(resp), nil 209 } 210 211 // Take care of the old 1.11.0 behavior in case the version upgrade 212 // happened without a clean daemon shutdown 213 func (clnt *client) cleanupOldRootfs(containerID string) { 214 // Unmount and delete the bundle folder 215 if mts, err := mount.GetMounts(); err == nil { 216 for _, mts := range mts { 217 if strings.HasSuffix(mts.Mountpoint, containerID+"/rootfs") { 218 if err := syscall.Unmount(mts.Mountpoint, syscall.MNT_DETACH); err == nil { 219 os.RemoveAll(strings.TrimSuffix(mts.Mountpoint, "/rootfs")) 220 } 221 break 222 } 223 } 224 } 225 } 226 227 func (clnt *client) setExited(containerID string, exitCode uint32) error { 228 clnt.lock(containerID) 229 defer clnt.unlock(containerID) 230 231 err := clnt.backend.StateChanged(containerID, StateInfo{ 232 CommonStateInfo: CommonStateInfo{ 233 State: StateExit, 234 ExitCode: exitCode, 235 }}) 236 237 clnt.cleanupOldRootfs(containerID) 238 239 return err 240 } 241 242 func (clnt *client) GetPidsForContainer(containerID string) ([]int, error) { 243 cont, err := clnt.getContainerdContainer(containerID) 244 if err != nil { 245 return nil, err 246 } 247 pids := make([]int, len(cont.Pids)) 248 for i, p := range cont.Pids { 249 pids[i] = int(p) 250 } 251 return pids, nil 252 } 253 254 // Summary returns a summary of the processes running in a container. 255 // This is a no-op on Linux. 256 func (clnt *client) Summary(containerID string) ([]Summary, error) { 257 return nil, nil 258 } 259 260 func (clnt *client) getContainerdContainer(containerID string) (*containerd.Container, error) { 261 resp, err := clnt.remote.apiClient.State(context.Background(), &containerd.StateRequest{Id: containerID}) 262 if err != nil { 263 return nil, err 264 } 265 for _, cont := range resp.Containers { 266 if cont.Id == containerID { 267 return cont, nil 268 } 269 } 270 return nil, fmt.Errorf("invalid state response") 271 } 272 273 func (clnt *client) UpdateResources(containerID string, resources Resources) error { 274 clnt.lock(containerID) 275 defer clnt.unlock(containerID) 276 container, err := clnt.getContainer(containerID) 277 if err != nil { 278 return err 279 } 280 if container.systemPid == 0 { 281 return fmt.Errorf("No active process for container %s", containerID) 282 } 283 _, err = clnt.remote.apiClient.UpdateContainer(context.Background(), &containerd.UpdateContainerRequest{ 284 Id: containerID, 285 Pid: InitFriendlyName, 286 Resources: (*containerd.UpdateResource)(&resources), 287 }) 288 if err != nil { 289 return err 290 } 291 return nil 292 } 293 294 func (clnt *client) getExitNotifier(containerID string) *exitNotifier { 295 clnt.mapMutex.RLock() 296 defer clnt.mapMutex.RUnlock() 297 return clnt.exitNotifiers[containerID] 298 } 299 300 func (clnt *client) getOrCreateExitNotifier(containerID string) *exitNotifier { 301 clnt.mapMutex.Lock() 302 w, ok := clnt.exitNotifiers[containerID] 303 defer clnt.mapMutex.Unlock() 304 if !ok { 305 w = &exitNotifier{c: make(chan struct{}), client: clnt} 306 clnt.exitNotifiers[containerID] = w 307 } 308 return w 309 } 310 311 func (clnt *client) restore(cont *containerd.Container, lastEvent *containerd.Event, attachStdio StdioCallback, options ...CreateOption) (err error) { 312 clnt.lock(cont.Id) 313 defer clnt.unlock(cont.Id) 314 315 logrus.Debugf("libcontainerd: restore container %s state %s", cont.Id, cont.Status) 316 317 containerID := cont.Id 318 if _, err := clnt.getContainer(containerID); err == nil { 319 return fmt.Errorf("container %s is already active", containerID) 320 } 321 322 defer func() { 323 if err != nil { 324 clnt.deleteContainer(cont.Id) 325 } 326 }() 327 328 container := clnt.newContainer(cont.BundlePath, options...) 329 container.systemPid = systemPid(cont) 330 331 var terminal bool 332 for _, p := range cont.Processes { 333 if p.Pid == InitFriendlyName { 334 terminal = p.Terminal 335 } 336 } 337 338 iopipe, err := container.openFifos(terminal) 339 if err != nil { 340 return err 341 } 342 var stdinOnce sync.Once 343 stdin := iopipe.Stdin 344 iopipe.Stdin = ioutils.NewWriteCloserWrapper(stdin, func() error { 345 var err error 346 stdinOnce.Do(func() { // on error from attach we don't know if stdin was already closed 347 err = stdin.Close() 348 }) 349 return err 350 }) 351 352 if err := attachStdio(*iopipe); err != nil { 353 container.closeFifos(iopipe) 354 return err 355 } 356 357 clnt.appendContainer(container) 358 359 err = clnt.backend.StateChanged(containerID, StateInfo{ 360 CommonStateInfo: CommonStateInfo{ 361 State: StateRestore, 362 Pid: container.systemPid, 363 }}) 364 365 if err != nil { 366 container.closeFifos(iopipe) 367 return err 368 } 369 370 if lastEvent != nil { 371 // This should only be a pause or resume event 372 if lastEvent.Type == StatePause || lastEvent.Type == StateResume { 373 return clnt.backend.StateChanged(containerID, StateInfo{ 374 CommonStateInfo: CommonStateInfo{ 375 State: lastEvent.Type, 376 Pid: container.systemPid, 377 }}) 378 } 379 380 logrus.Warnf("libcontainerd: unexpected backlog event: %#v", lastEvent) 381 } 382 383 return nil 384 } 385 386 func (clnt *client) getContainerLastEventSinceTime(id string, tsp *timestamp.Timestamp) (*containerd.Event, error) { 387 er := &containerd.EventsRequest{ 388 Timestamp: tsp, 389 StoredOnly: true, 390 Id: id, 391 } 392 events, err := clnt.remote.apiClient.Events(context.Background(), er) 393 if err != nil { 394 logrus.Errorf("libcontainerd: failed to get container events stream for %s: %q", er.Id, err) 395 return nil, err 396 } 397 398 var ev *containerd.Event 399 for { 400 e, err := events.Recv() 401 if err != nil { 402 if err.Error() == "EOF" { 403 break 404 } 405 logrus.Errorf("libcontainerd: failed to get container event for %s: %q", id, err) 406 return nil, err 407 } 408 409 logrus.Debugf("libcontainerd: received past event %#v", e) 410 411 switch e.Type { 412 case StateExit, StatePause, StateResume: 413 ev = e 414 } 415 } 416 417 return ev, nil 418 } 419 420 func (clnt *client) getContainerLastEvent(id string) (*containerd.Event, error) { 421 ev, err := clnt.getContainerLastEventSinceTime(id, clnt.remote.restoreFromTimestamp) 422 if err == nil && ev == nil { 423 // If ev is nil and the container is running in containerd, 424 // we already consumed all the event of the 425 // container, included the "exit" one. 426 // Thus, we request all events containerd has in memory for 427 // this container in order to get the last one (which should 428 // be an exit event) 429 logrus.Warnf("libcontainerd: client is out of sync, restore was called on a fully synced container (%s).", id) 430 // Request all events since beginning of time 431 t := time.Unix(0, 0) 432 tsp, err := ptypes.TimestampProto(t) 433 if err != nil { 434 logrus.Errorf("libcontainerd: getLastEventSinceTime() failed to convert timestamp: %q", err) 435 return nil, err 436 } 437 438 return clnt.getContainerLastEventSinceTime(id, tsp) 439 } 440 441 return ev, err 442 } 443 444 func (clnt *client) Restore(containerID string, attachStdio StdioCallback, options ...CreateOption) error { 445 // Synchronize with live events 446 clnt.remote.Lock() 447 defer clnt.remote.Unlock() 448 // Check that containerd still knows this container. 449 // 450 // In the unlikely event that Restore for this container process 451 // the its past event before the main loop, the event will be 452 // processed twice. However, this is not an issue as all those 453 // events will do is change the state of the container to be 454 // exactly the same. 455 cont, err := clnt.getContainerdContainer(containerID) 456 // Get its last event 457 ev, eerr := clnt.getContainerLastEvent(containerID) 458 if err != nil || cont.Status == "Stopped" { 459 if err != nil && !strings.Contains(err.Error(), "container not found") { 460 // Legitimate error 461 return err 462 } 463 464 if ev == nil { 465 if _, err := clnt.getContainer(containerID); err == nil { 466 // If ev is nil and the container is running in containerd, 467 // we already consumed all the event of the 468 // container, included the "exit" one. 469 // Thus we return to avoid overriding the Exit Code. 470 logrus.Warnf("libcontainerd: restore was called on a fully synced container (%s)", containerID) 471 return nil 472 } 473 // the container is not running so we need to fix the state within docker 474 ev = &containerd.Event{ 475 Type: StateExit, 476 Status: 1, 477 } 478 } 479 480 // get the exit status for this container 481 ec := uint32(0) 482 if eerr == nil && ev.Type == StateExit { 483 ec = ev.Status 484 } 485 clnt.setExited(containerID, ec) 486 487 return nil 488 } 489 490 // container is still alive 491 if clnt.liveRestore { 492 if err := clnt.restore(cont, ev, attachStdio, options...); err != nil { 493 logrus.Errorf("libcontainerd: error restoring %s: %v", containerID, err) 494 } 495 return nil 496 } 497 498 // Kill the container if liveRestore == false 499 w := clnt.getOrCreateExitNotifier(containerID) 500 clnt.lock(cont.Id) 501 container := clnt.newContainer(cont.BundlePath) 502 container.systemPid = systemPid(cont) 503 clnt.appendContainer(container) 504 clnt.unlock(cont.Id) 505 506 container.discardFifos() 507 508 if err := clnt.Signal(containerID, int(syscall.SIGTERM)); err != nil { 509 logrus.Errorf("libcontainerd: error sending sigterm to %v: %v", containerID, err) 510 } 511 // Let the main loop handle the exit event 512 clnt.remote.Unlock() 513 select { 514 case <-time.After(10 * time.Second): 515 if err := clnt.Signal(containerID, int(syscall.SIGKILL)); err != nil { 516 logrus.Errorf("libcontainerd: error sending sigkill to %v: %v", containerID, err) 517 } 518 select { 519 case <-time.After(2 * time.Second): 520 case <-w.wait(): 521 // relock because of the defer 522 clnt.remote.Lock() 523 return nil 524 } 525 case <-w.wait(): 526 // relock because of the defer 527 clnt.remote.Lock() 528 return nil 529 } 530 // relock because of the defer 531 clnt.remote.Lock() 532 533 clnt.deleteContainer(containerID) 534 535 return clnt.setExited(containerID, uint32(255)) 536 } 537 538 func (clnt *client) CreateCheckpoint(containerID string, checkpointID string, checkpointDir string, exit bool) error { 539 clnt.lock(containerID) 540 defer clnt.unlock(containerID) 541 if _, err := clnt.getContainer(containerID); err != nil { 542 return err 543 } 544 545 _, err := clnt.remote.apiClient.CreateCheckpoint(context.Background(), &containerd.CreateCheckpointRequest{ 546 Id: containerID, 547 Checkpoint: &containerd.Checkpoint{ 548 Name: checkpointID, 549 Exit: exit, 550 Tcp: true, 551 UnixSockets: true, 552 Shell: false, 553 EmptyNS: []string{"network"}, 554 }, 555 CheckpointDir: checkpointDir, 556 }) 557 return err 558 } 559 560 func (clnt *client) DeleteCheckpoint(containerID string, checkpointID string, checkpointDir string) error { 561 clnt.lock(containerID) 562 defer clnt.unlock(containerID) 563 if _, err := clnt.getContainer(containerID); err != nil { 564 return err 565 } 566 567 _, err := clnt.remote.apiClient.DeleteCheckpoint(context.Background(), &containerd.DeleteCheckpointRequest{ 568 Id: containerID, 569 Name: checkpointID, 570 CheckpointDir: checkpointDir, 571 }) 572 return err 573 } 574 575 func (clnt *client) ListCheckpoints(containerID string, checkpointDir string) (*Checkpoints, error) { 576 clnt.lock(containerID) 577 defer clnt.unlock(containerID) 578 if _, err := clnt.getContainer(containerID); err != nil { 579 return nil, err 580 } 581 582 resp, err := clnt.remote.apiClient.ListCheckpoint(context.Background(), &containerd.ListCheckpointRequest{ 583 Id: containerID, 584 CheckpointDir: checkpointDir, 585 }) 586 if err != nil { 587 return nil, err 588 } 589 return (*Checkpoints)(resp), nil 590 }