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