github.com/devdivbcp/moby@v17.12.0-ce-rc1.0.20200726071732-2d4bfdc789ad+incompatible/internal/test/daemon/daemon.go (about) 1 package daemon // import "github.com/docker/docker/internal/test/daemon" 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "strconv" 13 "strings" 14 "time" 15 16 "github.com/docker/docker/api/types" 17 "github.com/docker/docker/api/types/events" 18 "github.com/docker/docker/client" 19 "github.com/docker/docker/internal/test" 20 "github.com/docker/docker/internal/test/request" 21 "github.com/docker/docker/opts" 22 "github.com/docker/docker/pkg/ioutils" 23 "github.com/docker/docker/pkg/stringid" 24 "github.com/docker/go-connections/sockets" 25 "github.com/docker/go-connections/tlsconfig" 26 "github.com/pkg/errors" 27 "gotest.tools/assert" 28 ) 29 30 type testingT interface { 31 assert.TestingT 32 logT 33 Fatalf(string, ...interface{}) 34 } 35 36 type namer interface { 37 Name() string 38 } 39 type testNamer interface { 40 TestName() string 41 } 42 43 type logT interface { 44 Logf(string, ...interface{}) 45 } 46 47 const defaultDockerdBinary = "dockerd" 48 const containerdSocket = "/var/run/docker/containerd/containerd.sock" 49 50 var errDaemonNotStarted = errors.New("daemon not started") 51 52 // SockRoot holds the path of the default docker integration daemon socket 53 var SockRoot = filepath.Join(os.TempDir(), "docker-integration") 54 55 type clientConfig struct { 56 transport *http.Transport 57 scheme string 58 addr string 59 } 60 61 // Daemon represents a Docker daemon for the testing framework 62 type Daemon struct { 63 GlobalFlags []string 64 Root string 65 Folder string 66 Wait chan error 67 UseDefaultHost bool 68 UseDefaultTLSHost bool 69 70 id string 71 logFile *os.File 72 cmd *exec.Cmd 73 storageDriver string 74 userlandProxy bool 75 execRoot string 76 experimental bool 77 init bool 78 dockerdBinary string 79 log logT 80 81 // swarm related field 82 swarmListenAddr string 83 SwarmPort int // FIXME(vdemeester) should probably not be exported 84 DefaultAddrPool []string 85 SubnetSize uint32 86 DataPathPort uint32 87 // cached information 88 CachedInfo types.Info 89 } 90 91 // New returns a Daemon instance to be used for testing. 92 // This will create a directory such as d123456789 in the folder specified by $DOCKER_INTEGRATION_DAEMON_DEST or $DEST. 93 // The daemon will not automatically start. 94 func New(t testingT, ops ...func(*Daemon)) *Daemon { 95 if ht, ok := t.(test.HelperT); ok { 96 ht.Helper() 97 } 98 dest := os.Getenv("DOCKER_INTEGRATION_DAEMON_DEST") 99 if dest == "" { 100 dest = os.Getenv("DEST") 101 } 102 switch v := t.(type) { 103 case namer: 104 dest = filepath.Join(dest, v.Name()) 105 case testNamer: 106 dest = filepath.Join(dest, v.TestName()) 107 } 108 t.Logf("Creating a new daemon at: %s", dest) 109 assert.Check(t, dest != "", "Please set the DOCKER_INTEGRATION_DAEMON_DEST or the DEST environment variable") 110 111 storageDriver := os.Getenv("DOCKER_GRAPHDRIVER") 112 113 assert.NilError(t, os.MkdirAll(SockRoot, 0700), "could not create daemon socket root") 114 115 id := fmt.Sprintf("d%s", stringid.TruncateID(stringid.GenerateRandomID())) 116 dir := filepath.Join(dest, id) 117 daemonFolder, err := filepath.Abs(dir) 118 assert.NilError(t, err, "Could not make %q an absolute path", dir) 119 daemonRoot := filepath.Join(daemonFolder, "root") 120 121 assert.NilError(t, os.MkdirAll(daemonRoot, 0755), "Could not create daemon root %q", dir) 122 123 userlandProxy := true 124 if env := os.Getenv("DOCKER_USERLANDPROXY"); env != "" { 125 if val, err := strconv.ParseBool(env); err != nil { 126 userlandProxy = val 127 } 128 } 129 d := &Daemon{ 130 id: id, 131 Folder: daemonFolder, 132 Root: daemonRoot, 133 storageDriver: storageDriver, 134 userlandProxy: userlandProxy, 135 // dxr stands for docker-execroot (shortened for avoiding unix(7) path length limitation) 136 execRoot: filepath.Join(os.TempDir(), "dxr", id), 137 dockerdBinary: defaultDockerdBinary, 138 swarmListenAddr: defaultSwarmListenAddr, 139 SwarmPort: DefaultSwarmPort, 140 log: t, 141 } 142 143 for _, op := range ops { 144 op(d) 145 } 146 147 return d 148 } 149 150 // ContainersNamespace returns the containerd namespace used for containers. 151 func (d *Daemon) ContainersNamespace() string { 152 return d.id 153 } 154 155 // RootDir returns the root directory of the daemon. 156 func (d *Daemon) RootDir() string { 157 return d.Root 158 } 159 160 // ID returns the generated id of the daemon 161 func (d *Daemon) ID() string { 162 return d.id 163 } 164 165 // StorageDriver returns the configured storage driver of the daemon 166 func (d *Daemon) StorageDriver() string { 167 return d.storageDriver 168 } 169 170 // Sock returns the socket path of the daemon 171 func (d *Daemon) Sock() string { 172 return fmt.Sprintf("unix://" + d.sockPath()) 173 } 174 175 func (d *Daemon) sockPath() string { 176 return filepath.Join(SockRoot, d.id+".sock") 177 } 178 179 // LogFileName returns the path the daemon's log file 180 func (d *Daemon) LogFileName() string { 181 return d.logFile.Name() 182 } 183 184 // ReadLogFile returns the content of the daemon log file 185 func (d *Daemon) ReadLogFile() ([]byte, error) { 186 return ioutil.ReadFile(d.logFile.Name()) 187 } 188 189 // NewClientT creates new client based on daemon's socket path 190 func (d *Daemon) NewClientT(t assert.TestingT) *client.Client { 191 if ht, ok := t.(test.HelperT); ok { 192 ht.Helper() 193 } 194 c, err := client.NewClientWithOpts( 195 client.FromEnv, 196 client.WithHost(d.Sock())) 197 assert.NilError(t, err, "cannot create daemon client") 198 return c 199 } 200 201 // Cleanup cleans the daemon files : exec root (network namespaces, ...), swarmkit files 202 func (d *Daemon) Cleanup(t testingT) { 203 if ht, ok := t.(test.HelperT); ok { 204 ht.Helper() 205 } 206 // Cleanup swarmkit wal files if present 207 cleanupRaftDir(t, d.Root) 208 cleanupNetworkNamespace(t, d.execRoot) 209 } 210 211 // Start starts the daemon and return once it is ready to receive requests. 212 func (d *Daemon) Start(t testingT, args ...string) { 213 if ht, ok := t.(test.HelperT); ok { 214 ht.Helper() 215 } 216 if err := d.StartWithError(args...); err != nil { 217 t.Fatalf("failed to start daemon with arguments %v : %v", args, err) 218 } 219 } 220 221 // StartWithError starts the daemon and return once it is ready to receive requests. 222 // It returns an error in case it couldn't start. 223 func (d *Daemon) StartWithError(args ...string) error { 224 logFile, err := os.OpenFile(filepath.Join(d.Folder, "docker.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) 225 if err != nil { 226 return errors.Wrapf(err, "[%s] Could not create %s/docker.log", d.id, d.Folder) 227 } 228 229 return d.StartWithLogFile(logFile, args...) 230 } 231 232 // StartWithLogFile will start the daemon and attach its streams to a given file. 233 func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error { 234 d.handleUserns() 235 dockerdBinary, err := exec.LookPath(d.dockerdBinary) 236 if err != nil { 237 return errors.Wrapf(err, "[%s] could not find docker binary in $PATH", d.id) 238 } 239 240 args := append(d.GlobalFlags, 241 "--containerd", containerdSocket, 242 "--data-root", d.Root, 243 "--exec-root", d.execRoot, 244 "--pidfile", fmt.Sprintf("%s/docker.pid", d.Folder), 245 fmt.Sprintf("--userland-proxy=%t", d.userlandProxy), 246 "--containerd-namespace", d.id, 247 "--containerd-plugins-namespace", d.id+"p", 248 ) 249 if d.experimental { 250 args = append(args, "--experimental") 251 } 252 if d.init { 253 args = append(args, "--init") 254 } 255 if !(d.UseDefaultHost || d.UseDefaultTLSHost) { 256 args = append(args, []string{"--host", d.Sock()}...) 257 } 258 if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" { 259 args = append(args, []string{"--userns-remap", root}...) 260 } 261 262 // If we don't explicitly set the log-level or debug flag(-D) then 263 // turn on debug mode 264 foundLog := false 265 foundSd := false 266 for _, a := range providedArgs { 267 if strings.Contains(a, "--log-level") || strings.Contains(a, "-D") || strings.Contains(a, "--debug") { 268 foundLog = true 269 } 270 if strings.Contains(a, "--storage-driver") { 271 foundSd = true 272 } 273 } 274 if !foundLog { 275 args = append(args, "--debug") 276 } 277 if d.storageDriver != "" && !foundSd { 278 args = append(args, "--storage-driver", d.storageDriver) 279 } 280 281 args = append(args, providedArgs...) 282 d.cmd = exec.Command(dockerdBinary, args...) 283 d.cmd.Env = append(os.Environ(), "DOCKER_SERVICE_PREFER_OFFLINE_IMAGE=1") 284 d.cmd.Stdout = out 285 d.cmd.Stderr = out 286 d.logFile = out 287 288 if err := d.cmd.Start(); err != nil { 289 return errors.Errorf("[%s] could not start daemon container: %v", d.id, err) 290 } 291 292 wait := make(chan error, 1) 293 294 go func() { 295 ret := d.cmd.Wait() 296 d.log.Logf("[%s] exiting daemon", d.id) 297 // If we send before logging, we might accidentally log _after_ the test is done. 298 // As of Go 1.12, this incurs a panic instead of silently being dropped. 299 wait <- ret 300 close(wait) 301 }() 302 303 d.Wait = wait 304 305 clientConfig, err := d.getClientConfig() 306 if err != nil { 307 return err 308 } 309 client := &http.Client{ 310 Transport: clientConfig.transport, 311 } 312 313 req, err := http.NewRequest("GET", "/_ping", nil) 314 if err != nil { 315 return errors.Wrapf(err, "[%s] could not create new request", d.id) 316 } 317 req.URL.Host = clientConfig.addr 318 req.URL.Scheme = clientConfig.scheme 319 320 ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second) 321 defer cancel() 322 323 // make sure daemon is ready to receive requests 324 for i := 0; ; i++ { 325 d.log.Logf("[%s] waiting for daemon to start", d.id) 326 327 select { 328 case <-ctx.Done(): 329 return errors.Errorf("[%s] Daemon exited and never started: %s", d.id, ctx.Err()) 330 case err := <-d.Wait: 331 return errors.Errorf("[%s] Daemon exited during startup: %v", d.id, err) 332 default: 333 rctx, rcancel := context.WithTimeout(context.TODO(), 2*time.Second) 334 defer rcancel() 335 336 resp, err := client.Do(req.WithContext(rctx)) 337 if err != nil { 338 if i > 2 { // don't log the first couple, this ends up just being noise 339 d.log.Logf("[%s] error pinging daemon on start: %v", d.id, err) 340 } 341 342 select { 343 case <-ctx.Done(): 344 case <-time.After(500 * time.Millisecond): 345 } 346 continue 347 } 348 349 resp.Body.Close() 350 if resp.StatusCode != http.StatusOK { 351 d.log.Logf("[%s] received status != 200 OK: %s\n", d.id, resp.Status) 352 } 353 d.log.Logf("[%s] daemon started\n", d.id) 354 d.Root, err = d.queryRootDir() 355 if err != nil { 356 return errors.Errorf("[%s] error querying daemon for root directory: %v", d.id, err) 357 } 358 return nil 359 } 360 } 361 } 362 363 // StartWithBusybox will first start the daemon with Daemon.Start() 364 // then save the busybox image from the main daemon and load it into this Daemon instance. 365 func (d *Daemon) StartWithBusybox(t testingT, arg ...string) { 366 if ht, ok := t.(test.HelperT); ok { 367 ht.Helper() 368 } 369 d.Start(t, arg...) 370 d.LoadBusybox(t) 371 } 372 373 // Kill will send a SIGKILL to the daemon 374 func (d *Daemon) Kill() error { 375 if d.cmd == nil || d.Wait == nil { 376 return errDaemonNotStarted 377 } 378 379 defer func() { 380 d.logFile.Close() 381 d.cmd = nil 382 }() 383 384 if err := d.cmd.Process.Kill(); err != nil { 385 return err 386 } 387 388 return os.Remove(fmt.Sprintf("%s/docker.pid", d.Folder)) 389 } 390 391 // Pid returns the pid of the daemon 392 func (d *Daemon) Pid() int { 393 return d.cmd.Process.Pid 394 } 395 396 // Interrupt stops the daemon by sending it an Interrupt signal 397 func (d *Daemon) Interrupt() error { 398 return d.Signal(os.Interrupt) 399 } 400 401 // Signal sends the specified signal to the daemon if running 402 func (d *Daemon) Signal(signal os.Signal) error { 403 if d.cmd == nil || d.Wait == nil { 404 return errDaemonNotStarted 405 } 406 return d.cmd.Process.Signal(signal) 407 } 408 409 // DumpStackAndQuit sends SIGQUIT to the daemon, which triggers it to dump its 410 // stack to its log file and exit 411 // This is used primarily for gathering debug information on test timeout 412 func (d *Daemon) DumpStackAndQuit() { 413 if d.cmd == nil || d.cmd.Process == nil { 414 return 415 } 416 SignalDaemonDump(d.cmd.Process.Pid) 417 } 418 419 // Stop will send a SIGINT every second and wait for the daemon to stop. 420 // If it times out, a SIGKILL is sent. 421 // Stop will not delete the daemon directory. If a purged daemon is needed, 422 // instantiate a new one with NewDaemon. 423 // If an error occurs while starting the daemon, the test will fail. 424 func (d *Daemon) Stop(t testingT) { 425 if ht, ok := t.(test.HelperT); ok { 426 ht.Helper() 427 } 428 err := d.StopWithError() 429 if err != nil { 430 if err != errDaemonNotStarted { 431 t.Fatalf("Error while stopping the daemon %s : %v", d.id, err) 432 } else { 433 t.Logf("Daemon %s is not started", d.id) 434 } 435 } 436 } 437 438 // StopWithError will send a SIGINT every second and wait for the daemon to stop. 439 // If it timeouts, a SIGKILL is sent. 440 // Stop will not delete the daemon directory. If a purged daemon is needed, 441 // instantiate a new one with NewDaemon. 442 func (d *Daemon) StopWithError() (err error) { 443 if d.cmd == nil || d.Wait == nil { 444 return errDaemonNotStarted 445 } 446 defer func() { 447 if err == nil { 448 d.log.Logf("[%s] Daemon stopped", d.id) 449 } else { 450 d.log.Logf("[%s] Error when stopping daemon: %v", d.id, err) 451 } 452 d.logFile.Close() 453 d.cmd = nil 454 }() 455 456 i := 1 457 ticker := time.NewTicker(time.Second) 458 defer ticker.Stop() 459 tick := ticker.C 460 461 d.log.Logf("[%s] Stopping daemon", d.id) 462 463 if err := d.cmd.Process.Signal(os.Interrupt); err != nil { 464 if strings.Contains(err.Error(), "os: process already finished") { 465 return errDaemonNotStarted 466 } 467 return errors.Errorf("could not send signal: %v", err) 468 } 469 470 out1: 471 for { 472 select { 473 case err := <-d.Wait: 474 return err 475 case <-time.After(20 * time.Second): 476 // time for stopping jobs and run onShutdown hooks 477 d.log.Logf("[%s] daemon stop timeout", d.id) 478 break out1 479 } 480 } 481 482 out2: 483 for { 484 select { 485 case err := <-d.Wait: 486 return err 487 case <-tick: 488 i++ 489 if i > 5 { 490 d.log.Logf("tried to interrupt daemon for %d times, now try to kill it", i) 491 break out2 492 } 493 d.log.Logf("Attempt #%d: daemon is still running with pid %d", i, d.cmd.Process.Pid) 494 if err := d.cmd.Process.Signal(os.Interrupt); err != nil { 495 return errors.Errorf("could not send signal: %v", err) 496 } 497 } 498 } 499 500 if err := d.cmd.Process.Kill(); err != nil { 501 d.log.Logf("Could not kill daemon: %v", err) 502 return err 503 } 504 505 d.cmd.Wait() 506 507 return os.Remove(fmt.Sprintf("%s/docker.pid", d.Folder)) 508 } 509 510 // Restart will restart the daemon by first stopping it and the starting it. 511 // If an error occurs while starting the daemon, the test will fail. 512 func (d *Daemon) Restart(t testingT, args ...string) { 513 if ht, ok := t.(test.HelperT); ok { 514 ht.Helper() 515 } 516 d.Stop(t) 517 d.Start(t, args...) 518 } 519 520 // RestartWithError will restart the daemon by first stopping it and then starting it. 521 func (d *Daemon) RestartWithError(arg ...string) error { 522 if err := d.StopWithError(); err != nil { 523 return err 524 } 525 return d.StartWithError(arg...) 526 } 527 528 func (d *Daemon) handleUserns() { 529 // in the case of tests running a user namespace-enabled daemon, we have resolved 530 // d.Root to be the actual final path of the graph dir after the "uid.gid" of 531 // remapped root is added--we need to subtract it from the path before calling 532 // start or else we will continue making subdirectories rather than truly restarting 533 // with the same location/root: 534 if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" { 535 d.Root = filepath.Dir(d.Root) 536 } 537 } 538 539 // ReloadConfig asks the daemon to reload its configuration 540 func (d *Daemon) ReloadConfig() error { 541 if d.cmd == nil || d.cmd.Process == nil { 542 return errors.New("daemon is not running") 543 } 544 545 errCh := make(chan error) 546 started := make(chan struct{}) 547 go func() { 548 _, body, err := request.Get("/events", request.Host(d.Sock())) 549 close(started) 550 if err != nil { 551 errCh <- err 552 } 553 defer body.Close() 554 dec := json.NewDecoder(body) 555 for { 556 var e events.Message 557 if err := dec.Decode(&e); err != nil { 558 errCh <- err 559 return 560 } 561 if e.Type != events.DaemonEventType { 562 continue 563 } 564 if e.Action != "reload" { 565 continue 566 } 567 close(errCh) // notify that we are done 568 return 569 } 570 }() 571 572 <-started 573 if err := signalDaemonReload(d.cmd.Process.Pid); err != nil { 574 return errors.Errorf("error signaling daemon reload: %v", err) 575 } 576 select { 577 case err := <-errCh: 578 if err != nil { 579 return errors.Errorf("error waiting for daemon reload event: %v", err) 580 } 581 case <-time.After(30 * time.Second): 582 return errors.New("timeout waiting for daemon reload event") 583 } 584 return nil 585 } 586 587 // LoadBusybox image into the daemon 588 func (d *Daemon) LoadBusybox(t assert.TestingT) { 589 if ht, ok := t.(test.HelperT); ok { 590 ht.Helper() 591 } 592 clientHost, err := client.NewClientWithOpts(client.FromEnv) 593 assert.NilError(t, err, "failed to create client") 594 defer clientHost.Close() 595 596 ctx := context.Background() 597 reader, err := clientHost.ImageSave(ctx, []string{"busybox:latest"}) 598 assert.NilError(t, err, "failed to download busybox") 599 defer reader.Close() 600 601 c := d.NewClientT(t) 602 defer c.Close() 603 604 resp, err := c.ImageLoad(ctx, reader, true) 605 assert.NilError(t, err, "failed to load busybox") 606 defer resp.Body.Close() 607 } 608 609 func (d *Daemon) getClientConfig() (*clientConfig, error) { 610 var ( 611 transport *http.Transport 612 scheme string 613 addr string 614 proto string 615 ) 616 if d.UseDefaultTLSHost { 617 option := &tlsconfig.Options{ 618 CAFile: "fixtures/https/ca.pem", 619 CertFile: "fixtures/https/client-cert.pem", 620 KeyFile: "fixtures/https/client-key.pem", 621 } 622 tlsConfig, err := tlsconfig.Client(*option) 623 if err != nil { 624 return nil, err 625 } 626 transport = &http.Transport{ 627 TLSClientConfig: tlsConfig, 628 } 629 addr = fmt.Sprintf("%s:%d", opts.DefaultHTTPHost, opts.DefaultTLSHTTPPort) 630 scheme = "https" 631 proto = "tcp" 632 } else if d.UseDefaultHost { 633 addr = opts.DefaultUnixSocket 634 proto = "unix" 635 scheme = "http" 636 transport = &http.Transport{} 637 } else { 638 addr = d.sockPath() 639 proto = "unix" 640 scheme = "http" 641 transport = &http.Transport{} 642 } 643 644 if err := sockets.ConfigureTransport(transport, proto, addr); err != nil { 645 return nil, err 646 } 647 transport.DisableKeepAlives = true 648 if proto == "unix" { 649 addr = filepath.Base(addr) 650 } 651 return &clientConfig{ 652 transport: transport, 653 scheme: scheme, 654 addr: addr, 655 }, nil 656 } 657 658 func (d *Daemon) queryRootDir() (string, error) { 659 // update daemon root by asking /info endpoint (to support user 660 // namespaced daemon with root remapped uid.gid directory) 661 clientConfig, err := d.getClientConfig() 662 if err != nil { 663 return "", err 664 } 665 666 c := &http.Client{ 667 Transport: clientConfig.transport, 668 } 669 670 req, err := http.NewRequest("GET", "/info", nil) 671 if err != nil { 672 return "", err 673 } 674 req.Header.Set("Content-Type", "application/json") 675 req.URL.Host = clientConfig.addr 676 req.URL.Scheme = clientConfig.scheme 677 678 resp, err := c.Do(req) 679 if err != nil { 680 return "", err 681 } 682 body := ioutils.NewReadCloserWrapper(resp.Body, func() error { 683 return resp.Body.Close() 684 }) 685 686 type Info struct { 687 DockerRootDir string 688 } 689 var b []byte 690 var i Info 691 b, err = request.ReadBody(body) 692 if err == nil && resp.StatusCode == http.StatusOK { 693 // read the docker root dir 694 if err = json.Unmarshal(b, &i); err == nil { 695 return i.DockerRootDir, nil 696 } 697 } 698 return "", err 699 } 700 701 // Info returns the info struct for this daemon 702 func (d *Daemon) Info(t assert.TestingT) types.Info { 703 if ht, ok := t.(test.HelperT); ok { 704 ht.Helper() 705 } 706 c := d.NewClientT(t) 707 info, err := c.Info(context.Background()) 708 assert.NilError(t, err) 709 return info 710 } 711 712 func cleanupRaftDir(t testingT, rootPath string) { 713 if ht, ok := t.(test.HelperT); ok { 714 ht.Helper() 715 } 716 for _, p := range []string{"wal", "wal-v3-encrypted", "snap-v3-encrypted"} { 717 dir := filepath.Join(rootPath, "swarm/raft", p) 718 if err := os.RemoveAll(dir); err != nil { 719 t.Logf("error removing %v: %v", dir, err) 720 } 721 } 722 }