github.com/moby/docker@v26.1.3+incompatible/testutil/daemon/daemon.go (about)

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