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  }