gopkg.in/docker/docker.v20@v20.10.27/testutil/daemon/daemon.go (about)

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