github.com/rita33cool1/iot-system-gateway@v0.0.0-20200911033302-e65bde238cc5/docker-engine/internal/test/daemon/daemon.go (about)

     1  package daemon // import "github.com/docker/docker/internal/test/daemon"
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"io/ioutil"
     8  	"net/http"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"strconv"
    13  	"strings"
    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/integration-cli/request"
    20  	"github.com/docker/docker/opts"
    21  	"github.com/docker/docker/pkg/ioutils"
    22  	"github.com/docker/docker/pkg/stringid"
    23  	"github.com/docker/go-connections/sockets"
    24  	"github.com/docker/go-connections/tlsconfig"
    25  	"github.com/gotestyourself/gotestyourself/assert"
    26  	"github.com/pkg/errors"
    27  )
    28  
    29  type testingT interface {
    30  	assert.TestingT
    31  	logT
    32  	Fatalf(string, ...interface{})
    33  }
    34  
    35  type logT interface {
    36  	Logf(string, ...interface{})
    37  }
    38  
    39  const defaultDockerdBinary = "dockerd"
    40  
    41  var errDaemonNotStarted = errors.New("daemon not started")
    42  
    43  // SockRoot holds the path of the default docker integration daemon socket
    44  var SockRoot = filepath.Join(os.TempDir(), "docker-integration")
    45  
    46  type clientConfig struct {
    47  	transport *http.Transport
    48  	scheme    string
    49  	addr      string
    50  }
    51  
    52  // Daemon represents a Docker daemon for the testing framework
    53  type Daemon struct {
    54  	GlobalFlags       []string
    55  	Root              string
    56  	Folder            string
    57  	Wait              chan error
    58  	UseDefaultHost    bool
    59  	UseDefaultTLSHost bool
    60  
    61  	id            string
    62  	logFile       *os.File
    63  	cmd           *exec.Cmd
    64  	storageDriver string
    65  	userlandProxy bool
    66  	execRoot      string
    67  	experimental  bool
    68  	dockerdBinary string
    69  	log           logT
    70  
    71  	// swarm related field
    72  	swarmListenAddr string
    73  	SwarmPort       int // FIXME(vdemeester) should probably not be exported
    74  
    75  	// cached information
    76  	CachedInfo types.Info
    77  }
    78  
    79  // New returns a Daemon instance to be used for testing.
    80  // This will create a directory such as d123456789 in the folder specified by $DOCKER_INTEGRATION_DAEMON_DEST or $DEST.
    81  // The daemon will not automatically start.
    82  func New(t testingT, ops ...func(*Daemon)) *Daemon {
    83  	dest := os.Getenv("DOCKER_INTEGRATION_DAEMON_DEST")
    84  	if dest == "" {
    85  		dest = os.Getenv("DEST")
    86  	}
    87  	assert.Check(t, dest != "", "Please set the DOCKER_INTEGRATION_DAEMON_DEST or the DEST environment variable")
    88  
    89  	storageDriver := os.Getenv("DOCKER_GRAPHDRIVER")
    90  
    91  	assert.NilError(t, os.MkdirAll(SockRoot, 0700), "could not create daemon socket root")
    92  
    93  	id := fmt.Sprintf("d%s", stringid.TruncateID(stringid.GenerateRandomID()))
    94  	dir := filepath.Join(dest, id)
    95  	daemonFolder, err := filepath.Abs(dir)
    96  	assert.NilError(t, err, "Could not make %q an absolute path", dir)
    97  	daemonRoot := filepath.Join(daemonFolder, "root")
    98  
    99  	assert.NilError(t, os.MkdirAll(daemonRoot, 0755), "Could not create daemon root %q", dir)
   100  
   101  	userlandProxy := true
   102  	if env := os.Getenv("DOCKER_USERLANDPROXY"); env != "" {
   103  		if val, err := strconv.ParseBool(env); err != nil {
   104  			userlandProxy = val
   105  		}
   106  	}
   107  	d := &Daemon{
   108  		id:              id,
   109  		Folder:          daemonFolder,
   110  		Root:            daemonRoot,
   111  		storageDriver:   storageDriver,
   112  		userlandProxy:   userlandProxy,
   113  		execRoot:        filepath.Join(os.TempDir(), "docker-execroot", id),
   114  		dockerdBinary:   defaultDockerdBinary,
   115  		swarmListenAddr: defaultSwarmListenAddr,
   116  		SwarmPort:       defaultSwarmPort,
   117  		log:             t,
   118  	}
   119  
   120  	for _, op := range ops {
   121  		op(d)
   122  	}
   123  
   124  	return d
   125  }
   126  
   127  // RootDir returns the root directory of the daemon.
   128  func (d *Daemon) RootDir() string {
   129  	return d.Root
   130  }
   131  
   132  // ID returns the generated id of the daemon
   133  func (d *Daemon) ID() string {
   134  	return d.id
   135  }
   136  
   137  // StorageDriver returns the configured storage driver of the daemon
   138  func (d *Daemon) StorageDriver() string {
   139  	return d.storageDriver
   140  }
   141  
   142  // Sock returns the socket path of the daemon
   143  func (d *Daemon) Sock() string {
   144  	return fmt.Sprintf("unix://" + d.sockPath())
   145  }
   146  
   147  func (d *Daemon) sockPath() string {
   148  	return filepath.Join(SockRoot, d.id+".sock")
   149  }
   150  
   151  // LogFileName returns the path the daemon's log file
   152  func (d *Daemon) LogFileName() string {
   153  	return d.logFile.Name()
   154  }
   155  
   156  // ReadLogFile returns the content of the daemon log file
   157  func (d *Daemon) ReadLogFile() ([]byte, error) {
   158  	return ioutil.ReadFile(d.logFile.Name())
   159  }
   160  
   161  // NewClient creates new client based on daemon's socket path
   162  // FIXME(vdemeester): replace NewClient with NewClientT
   163  func (d *Daemon) NewClient() (*client.Client, error) {
   164  	return client.NewClientWithOpts(
   165  		client.FromEnv,
   166  		client.WithHost(d.Sock()))
   167  }
   168  
   169  // NewClientT creates new client based on daemon's socket path
   170  // FIXME(vdemeester): replace NewClient with NewClientT
   171  func (d *Daemon) NewClientT(t assert.TestingT) *client.Client {
   172  	c, err := client.NewClientWithOpts(
   173  		client.FromEnv,
   174  		client.WithHost(d.Sock()))
   175  	assert.NilError(t, err, "cannot create daemon client")
   176  	return c
   177  }
   178  
   179  // CleanupExecRoot cleans the daemon exec root (network namespaces, ...)
   180  func (d *Daemon) CleanupExecRoot(t testingT) {
   181  	cleanupExecRoot(t, d.execRoot)
   182  }
   183  
   184  // Start starts the daemon and return once it is ready to receive requests.
   185  func (d *Daemon) Start(t testingT, args ...string) {
   186  	if err := d.StartWithError(args...); err != nil {
   187  		t.Fatalf("Error starting daemon with arguments: %v", args)
   188  	}
   189  }
   190  
   191  // StartWithError starts the daemon and return once it is ready to receive requests.
   192  // It returns an error in case it couldn't start.
   193  func (d *Daemon) StartWithError(args ...string) error {
   194  	logFile, err := os.OpenFile(filepath.Join(d.Folder, "docker.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
   195  	if err != nil {
   196  		return errors.Wrapf(err, "[%s] Could not create %s/docker.log", d.id, d.Folder)
   197  	}
   198  
   199  	return d.StartWithLogFile(logFile, args...)
   200  }
   201  
   202  // StartWithLogFile will start the daemon and attach its streams to a given file.
   203  func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error {
   204  	dockerdBinary, err := exec.LookPath(d.dockerdBinary)
   205  	if err != nil {
   206  		return errors.Wrapf(err, "[%s] could not find docker binary in $PATH", d.id)
   207  	}
   208  	args := append(d.GlobalFlags,
   209  		"--containerd", "/var/run/docker/containerd/docker-containerd.sock",
   210  		"--data-root", d.Root,
   211  		"--exec-root", d.execRoot,
   212  		"--pidfile", fmt.Sprintf("%s/docker.pid", d.Folder),
   213  		fmt.Sprintf("--userland-proxy=%t", d.userlandProxy),
   214  	)
   215  	if d.experimental {
   216  		args = append(args, "--experimental", "--init")
   217  	}
   218  	if !(d.UseDefaultHost || d.UseDefaultTLSHost) {
   219  		args = append(args, []string{"--host", d.Sock()}...)
   220  	}
   221  	if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" {
   222  		args = append(args, []string{"--userns-remap", root}...)
   223  	}
   224  
   225  	// If we don't explicitly set the log-level or debug flag(-D) then
   226  	// turn on debug mode
   227  	foundLog := false
   228  	foundSd := false
   229  	for _, a := range providedArgs {
   230  		if strings.Contains(a, "--log-level") || strings.Contains(a, "-D") || strings.Contains(a, "--debug") {
   231  			foundLog = true
   232  		}
   233  		if strings.Contains(a, "--storage-driver") {
   234  			foundSd = true
   235  		}
   236  	}
   237  	if !foundLog {
   238  		args = append(args, "--debug")
   239  	}
   240  	if d.storageDriver != "" && !foundSd {
   241  		args = append(args, "--storage-driver", d.storageDriver)
   242  	}
   243  
   244  	args = append(args, providedArgs...)
   245  	d.cmd = exec.Command(dockerdBinary, args...)
   246  	d.cmd.Env = append(os.Environ(), "DOCKER_SERVICE_PREFER_OFFLINE_IMAGE=1")
   247  	d.cmd.Stdout = out
   248  	d.cmd.Stderr = out
   249  	d.logFile = out
   250  
   251  	if err := d.cmd.Start(); err != nil {
   252  		return errors.Errorf("[%s] could not start daemon container: %v", d.id, err)
   253  	}
   254  
   255  	wait := make(chan error)
   256  
   257  	go func() {
   258  		wait <- d.cmd.Wait()
   259  		d.log.Logf("[%s] exiting daemon", d.id)
   260  		close(wait)
   261  	}()
   262  
   263  	d.Wait = wait
   264  
   265  	tick := time.Tick(500 * time.Millisecond)
   266  	// make sure daemon is ready to receive requests
   267  	startTime := time.Now().Unix()
   268  	for {
   269  		d.log.Logf("[%s] waiting for daemon to start", d.id)
   270  		if time.Now().Unix()-startTime > 5 {
   271  			// After 5 seconds, give up
   272  			return errors.Errorf("[%s] Daemon exited and never started", d.id)
   273  		}
   274  		select {
   275  		case <-time.After(2 * time.Second):
   276  			return errors.Errorf("[%s] timeout: daemon does not respond", d.id)
   277  		case <-tick:
   278  			clientConfig, err := d.getClientConfig()
   279  			if err != nil {
   280  				return err
   281  			}
   282  
   283  			client := &http.Client{
   284  				Transport: clientConfig.transport,
   285  			}
   286  
   287  			req, err := http.NewRequest("GET", "/_ping", nil)
   288  			if err != nil {
   289  				return errors.Wrapf(err, "[%s] could not create new request", d.id)
   290  			}
   291  			req.URL.Host = clientConfig.addr
   292  			req.URL.Scheme = clientConfig.scheme
   293  			resp, err := client.Do(req)
   294  			if err != nil {
   295  				continue
   296  			}
   297  			resp.Body.Close()
   298  			if resp.StatusCode != http.StatusOK {
   299  				d.log.Logf("[%s] received status != 200 OK: %s\n", d.id, resp.Status)
   300  			}
   301  			d.log.Logf("[%s] daemon started\n", d.id)
   302  			d.Root, err = d.queryRootDir()
   303  			if err != nil {
   304  				return errors.Errorf("[%s] error querying daemon for root directory: %v", d.id, err)
   305  			}
   306  			return nil
   307  		case <-d.Wait:
   308  			return errors.Errorf("[%s] Daemon exited during startup", d.id)
   309  		}
   310  	}
   311  }
   312  
   313  // StartWithBusybox will first start the daemon with Daemon.Start()
   314  // then save the busybox image from the main daemon and load it into this Daemon instance.
   315  func (d *Daemon) StartWithBusybox(t testingT, arg ...string) {
   316  	d.Start(t, arg...)
   317  	d.LoadBusybox(t)
   318  }
   319  
   320  // Kill will send a SIGKILL to the daemon
   321  func (d *Daemon) Kill() error {
   322  	if d.cmd == nil || d.Wait == nil {
   323  		return errDaemonNotStarted
   324  	}
   325  
   326  	defer func() {
   327  		d.logFile.Close()
   328  		d.cmd = nil
   329  	}()
   330  
   331  	if err := d.cmd.Process.Kill(); err != nil {
   332  		return err
   333  	}
   334  
   335  	return os.Remove(fmt.Sprintf("%s/docker.pid", d.Folder))
   336  }
   337  
   338  // Pid returns the pid of the daemon
   339  func (d *Daemon) Pid() int {
   340  	return d.cmd.Process.Pid
   341  }
   342  
   343  // Interrupt stops the daemon by sending it an Interrupt signal
   344  func (d *Daemon) Interrupt() error {
   345  	return d.Signal(os.Interrupt)
   346  }
   347  
   348  // Signal sends the specified signal to the daemon if running
   349  func (d *Daemon) Signal(signal os.Signal) error {
   350  	if d.cmd == nil || d.Wait == nil {
   351  		return errDaemonNotStarted
   352  	}
   353  	return d.cmd.Process.Signal(signal)
   354  }
   355  
   356  // DumpStackAndQuit sends SIGQUIT to the daemon, which triggers it to dump its
   357  // stack to its log file and exit
   358  // This is used primarily for gathering debug information on test timeout
   359  func (d *Daemon) DumpStackAndQuit() {
   360  	if d.cmd == nil || d.cmd.Process == nil {
   361  		return
   362  	}
   363  	SignalDaemonDump(d.cmd.Process.Pid)
   364  }
   365  
   366  // Stop will send a SIGINT every second and wait for the daemon to stop.
   367  // If it times out, a SIGKILL is sent.
   368  // Stop will not delete the daemon directory. If a purged daemon is needed,
   369  // instantiate a new one with NewDaemon.
   370  // If an error occurs while starting the daemon, the test will fail.
   371  func (d *Daemon) Stop(t testingT) {
   372  	err := d.StopWithError()
   373  	if err != nil {
   374  		if err != errDaemonNotStarted {
   375  			t.Fatalf("Error while stopping the daemon %s : %v", d.id, err)
   376  		} else {
   377  			t.Logf("Daemon %s is not started", d.id)
   378  		}
   379  	}
   380  }
   381  
   382  // StopWithError will send a SIGINT every second and wait for the daemon to stop.
   383  // If it timeouts, a SIGKILL is sent.
   384  // Stop will not delete the daemon directory. If a purged daemon is needed,
   385  // instantiate a new one with NewDaemon.
   386  func (d *Daemon) StopWithError() error {
   387  	if d.cmd == nil || d.Wait == nil {
   388  		return errDaemonNotStarted
   389  	}
   390  
   391  	defer func() {
   392  		d.logFile.Close()
   393  		d.cmd = nil
   394  	}()
   395  
   396  	i := 1
   397  	tick := time.Tick(time.Second)
   398  
   399  	if err := d.cmd.Process.Signal(os.Interrupt); err != nil {
   400  		if strings.Contains(err.Error(), "os: process already finished") {
   401  			return errDaemonNotStarted
   402  		}
   403  		return errors.Errorf("could not send signal: %v", err)
   404  	}
   405  out1:
   406  	for {
   407  		select {
   408  		case err := <-d.Wait:
   409  			return err
   410  		case <-time.After(20 * time.Second):
   411  			// time for stopping jobs and run onShutdown hooks
   412  			d.log.Logf("[%s] daemon started", d.id)
   413  			break out1
   414  		}
   415  	}
   416  
   417  out2:
   418  	for {
   419  		select {
   420  		case err := <-d.Wait:
   421  			return err
   422  		case <-tick:
   423  			i++
   424  			if i > 5 {
   425  				d.log.Logf("tried to interrupt daemon for %d times, now try to kill it", i)
   426  				break out2
   427  			}
   428  			d.log.Logf("Attempt #%d: daemon is still running with pid %d", i, d.cmd.Process.Pid)
   429  			if err := d.cmd.Process.Signal(os.Interrupt); err != nil {
   430  				return errors.Errorf("could not send signal: %v", err)
   431  			}
   432  		}
   433  	}
   434  
   435  	if err := d.cmd.Process.Kill(); err != nil {
   436  		d.log.Logf("Could not kill daemon: %v", err)
   437  		return err
   438  	}
   439  
   440  	d.cmd.Wait()
   441  
   442  	return os.Remove(fmt.Sprintf("%s/docker.pid", d.Folder))
   443  }
   444  
   445  // Restart will restart the daemon by first stopping it and the starting it.
   446  // If an error occurs while starting the daemon, the test will fail.
   447  func (d *Daemon) Restart(t testingT, args ...string) {
   448  	d.Stop(t)
   449  	d.handleUserns()
   450  	d.Start(t, args...)
   451  }
   452  
   453  // RestartWithError will restart the daemon by first stopping it and then starting it.
   454  func (d *Daemon) RestartWithError(arg ...string) error {
   455  	if err := d.StopWithError(); err != nil {
   456  		return err
   457  	}
   458  	d.handleUserns()
   459  	return d.StartWithError(arg...)
   460  }
   461  
   462  func (d *Daemon) handleUserns() {
   463  	// in the case of tests running a user namespace-enabled daemon, we have resolved
   464  	// d.Root to be the actual final path of the graph dir after the "uid.gid" of
   465  	// remapped root is added--we need to subtract it from the path before calling
   466  	// start or else we will continue making subdirectories rather than truly restarting
   467  	// with the same location/root:
   468  	if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" {
   469  		d.Root = filepath.Dir(d.Root)
   470  	}
   471  }
   472  
   473  // ReloadConfig asks the daemon to reload its configuration
   474  func (d *Daemon) ReloadConfig() error {
   475  	if d.cmd == nil || d.cmd.Process == nil {
   476  		return errors.New("daemon is not running")
   477  	}
   478  
   479  	errCh := make(chan error)
   480  	started := make(chan struct{})
   481  	go func() {
   482  		_, body, err := request.DoOnHost(d.Sock(), "/events", request.Method(http.MethodGet))
   483  		close(started)
   484  		if err != nil {
   485  			errCh <- err
   486  		}
   487  		defer body.Close()
   488  		dec := json.NewDecoder(body)
   489  		for {
   490  			var e events.Message
   491  			if err := dec.Decode(&e); err != nil {
   492  				errCh <- err
   493  				return
   494  			}
   495  			if e.Type != events.DaemonEventType {
   496  				continue
   497  			}
   498  			if e.Action != "reload" {
   499  				continue
   500  			}
   501  			close(errCh) // notify that we are done
   502  			return
   503  		}
   504  	}()
   505  
   506  	<-started
   507  	if err := signalDaemonReload(d.cmd.Process.Pid); err != nil {
   508  		return errors.Errorf("error signaling daemon reload: %v", err)
   509  	}
   510  	select {
   511  	case err := <-errCh:
   512  		if err != nil {
   513  			return errors.Errorf("error waiting for daemon reload event: %v", err)
   514  		}
   515  	case <-time.After(30 * time.Second):
   516  		return errors.New("timeout waiting for daemon reload event")
   517  	}
   518  	return nil
   519  }
   520  
   521  // LoadBusybox image into the daemon
   522  func (d *Daemon) LoadBusybox(t assert.TestingT) {
   523  	clientHost, err := client.NewEnvClient()
   524  	assert.NilError(t, err, "failed to create client")
   525  	defer clientHost.Close()
   526  
   527  	ctx := context.Background()
   528  	reader, err := clientHost.ImageSave(ctx, []string{"busybox:latest"})
   529  	assert.NilError(t, err, "failed to download busybox")
   530  	defer reader.Close()
   531  
   532  	client, err := d.NewClient()
   533  	assert.NilError(t, err, "failed to create client")
   534  	defer client.Close()
   535  
   536  	resp, err := client.ImageLoad(ctx, reader, true)
   537  	assert.NilError(t, err, "failed to load busybox")
   538  	defer resp.Body.Close()
   539  }
   540  
   541  func (d *Daemon) getClientConfig() (*clientConfig, error) {
   542  	var (
   543  		transport *http.Transport
   544  		scheme    string
   545  		addr      string
   546  		proto     string
   547  	)
   548  	if d.UseDefaultTLSHost {
   549  		option := &tlsconfig.Options{
   550  			CAFile:   "fixtures/https/ca.pem",
   551  			CertFile: "fixtures/https/client-cert.pem",
   552  			KeyFile:  "fixtures/https/client-key.pem",
   553  		}
   554  		tlsConfig, err := tlsconfig.Client(*option)
   555  		if err != nil {
   556  			return nil, err
   557  		}
   558  		transport = &http.Transport{
   559  			TLSClientConfig: tlsConfig,
   560  		}
   561  		addr = fmt.Sprintf("%s:%d", opts.DefaultHTTPHost, opts.DefaultTLSHTTPPort)
   562  		scheme = "https"
   563  		proto = "tcp"
   564  	} else if d.UseDefaultHost {
   565  		addr = opts.DefaultUnixSocket
   566  		proto = "unix"
   567  		scheme = "http"
   568  		transport = &http.Transport{}
   569  	} else {
   570  		addr = d.sockPath()
   571  		proto = "unix"
   572  		scheme = "http"
   573  		transport = &http.Transport{}
   574  	}
   575  
   576  	if err := sockets.ConfigureTransport(transport, proto, addr); err != nil {
   577  		return nil, err
   578  	}
   579  	transport.DisableKeepAlives = true
   580  
   581  	return &clientConfig{
   582  		transport: transport,
   583  		scheme:    scheme,
   584  		addr:      addr,
   585  	}, nil
   586  }
   587  
   588  func (d *Daemon) queryRootDir() (string, error) {
   589  	// update daemon root by asking /info endpoint (to support user
   590  	// namespaced daemon with root remapped uid.gid directory)
   591  	clientConfig, err := d.getClientConfig()
   592  	if err != nil {
   593  		return "", err
   594  	}
   595  
   596  	client := &http.Client{
   597  		Transport: clientConfig.transport,
   598  	}
   599  
   600  	req, err := http.NewRequest("GET", "/info", nil)
   601  	if err != nil {
   602  		return "", err
   603  	}
   604  	req.Header.Set("Content-Type", "application/json")
   605  	req.URL.Host = clientConfig.addr
   606  	req.URL.Scheme = clientConfig.scheme
   607  
   608  	resp, err := client.Do(req)
   609  	if err != nil {
   610  		return "", err
   611  	}
   612  	body := ioutils.NewReadCloserWrapper(resp.Body, func() error {
   613  		return resp.Body.Close()
   614  	})
   615  
   616  	type Info struct {
   617  		DockerRootDir string
   618  	}
   619  	var b []byte
   620  	var i Info
   621  	b, err = request.ReadBody(body)
   622  	if err == nil && resp.StatusCode == http.StatusOK {
   623  		// read the docker root dir
   624  		if err = json.Unmarshal(b, &i); err == nil {
   625  			return i.DockerRootDir, nil
   626  		}
   627  	}
   628  	return "", err
   629  }
   630  
   631  // Info returns the info struct for this daemon
   632  func (d *Daemon) Info(t assert.TestingT) types.Info {
   633  	apiclient, err := d.NewClient()
   634  	assert.NilError(t, err)
   635  	info, err := apiclient.Info(context.Background())
   636  	assert.NilError(t, err)
   637  	return info
   638  }