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