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