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