github.com/akerouanton/docker@v1.11.0-rc3/integration-cli/daemon.go (about)

     1  package main
     2  
     3  import (
     4  	"encoding/json"
     5  	"errors"
     6  	"fmt"
     7  	"io"
     8  	"net/http"
     9  	"os"
    10  	"os/exec"
    11  	"path/filepath"
    12  	"strconv"
    13  	"strings"
    14  	"time"
    15  
    16  	"github.com/docker/docker/opts"
    17  	"github.com/docker/docker/pkg/integration/checker"
    18  	"github.com/docker/docker/pkg/ioutils"
    19  	"github.com/docker/docker/pkg/tlsconfig"
    20  	"github.com/docker/go-connections/sockets"
    21  	"github.com/go-check/check"
    22  )
    23  
    24  // Daemon represents a Docker daemon for the testing framework.
    25  type Daemon struct {
    26  	// Defaults to "daemon"
    27  	// Useful to set to --daemon or -d for checking backwards compatibility
    28  	Command     string
    29  	GlobalFlags []string
    30  
    31  	id                string
    32  	c                 *check.C
    33  	logFile           *os.File
    34  	folder            string
    35  	root              string
    36  	stdin             io.WriteCloser
    37  	stdout, stderr    io.ReadCloser
    38  	cmd               *exec.Cmd
    39  	storageDriver     string
    40  	wait              chan error
    41  	userlandProxy     bool
    42  	useDefaultHost    bool
    43  	useDefaultTLSHost bool
    44  }
    45  
    46  type clientConfig struct {
    47  	transport *http.Transport
    48  	scheme    string
    49  	addr      string
    50  }
    51  
    52  // NewDaemon returns a Daemon instance to be used for testing.
    53  // This will create a directory such as d123456789 in the folder specified by $DEST.
    54  // The daemon will not automatically start.
    55  func NewDaemon(c *check.C) *Daemon {
    56  	dest := os.Getenv("DEST")
    57  	c.Assert(dest, check.Not(check.Equals), "", check.Commentf("Please set the DEST environment variable"))
    58  
    59  	id := fmt.Sprintf("d%d", time.Now().UnixNano()%100000000)
    60  	dir := filepath.Join(dest, id)
    61  	daemonFolder, err := filepath.Abs(dir)
    62  	c.Assert(err, check.IsNil, check.Commentf("Could not make %q an absolute path", dir))
    63  	daemonRoot := filepath.Join(daemonFolder, "root")
    64  
    65  	c.Assert(os.MkdirAll(daemonRoot, 0755), check.IsNil, check.Commentf("Could not create daemon root %q", dir))
    66  
    67  	userlandProxy := true
    68  	if env := os.Getenv("DOCKER_USERLANDPROXY"); env != "" {
    69  		if val, err := strconv.ParseBool(env); err != nil {
    70  			userlandProxy = val
    71  		}
    72  	}
    73  
    74  	return &Daemon{
    75  		Command:       "daemon",
    76  		id:            id,
    77  		c:             c,
    78  		folder:        daemonFolder,
    79  		root:          daemonRoot,
    80  		storageDriver: os.Getenv("DOCKER_GRAPHDRIVER"),
    81  		userlandProxy: userlandProxy,
    82  	}
    83  }
    84  
    85  func (d *Daemon) getClientConfig() (*clientConfig, error) {
    86  	var (
    87  		transport *http.Transport
    88  		scheme    string
    89  		addr      string
    90  		proto     string
    91  	)
    92  	if d.useDefaultTLSHost {
    93  		option := &tlsconfig.Options{
    94  			CAFile:   "fixtures/https/ca.pem",
    95  			CertFile: "fixtures/https/client-cert.pem",
    96  			KeyFile:  "fixtures/https/client-key.pem",
    97  		}
    98  		tlsConfig, err := tlsconfig.Client(*option)
    99  		if err != nil {
   100  			return nil, err
   101  		}
   102  		transport = &http.Transport{
   103  			TLSClientConfig: tlsConfig,
   104  		}
   105  		addr = fmt.Sprintf("%s:%d", opts.DefaultHTTPHost, opts.DefaultTLSHTTPPort)
   106  		scheme = "https"
   107  		proto = "tcp"
   108  	} else if d.useDefaultHost {
   109  		addr = opts.DefaultUnixSocket
   110  		proto = "unix"
   111  		scheme = "http"
   112  		transport = &http.Transport{}
   113  	} else {
   114  		addr = filepath.Join(d.folder, "docker.sock")
   115  		proto = "unix"
   116  		scheme = "http"
   117  		transport = &http.Transport{}
   118  	}
   119  
   120  	d.c.Assert(sockets.ConfigureTransport(transport, proto, addr), check.IsNil)
   121  
   122  	return &clientConfig{
   123  		transport: transport,
   124  		scheme:    scheme,
   125  		addr:      addr,
   126  	}, nil
   127  }
   128  
   129  // Start will start the daemon and return once it is ready to receive requests.
   130  // You can specify additional daemon flags.
   131  func (d *Daemon) Start(args ...string) error {
   132  	logFile, err := os.OpenFile(filepath.Join(d.folder, "docker.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
   133  	d.c.Assert(err, check.IsNil, check.Commentf("[%s] Could not create %s/docker.log", d.id, d.folder))
   134  
   135  	return d.StartWithLogFile(logFile, args...)
   136  }
   137  
   138  // StartWithLogFile will start the daemon and attach its streams to a given file.
   139  func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error {
   140  	dockerBinary, err := exec.LookPath(dockerBinary)
   141  	d.c.Assert(err, check.IsNil, check.Commentf("[%s] could not find docker binary in $PATH", d.id))
   142  
   143  	args := append(d.GlobalFlags,
   144  		d.Command,
   145  		"--containerd", "/var/run/docker/libcontainerd/docker-containerd.sock",
   146  		"--graph", d.root,
   147  		"--exec-root", filepath.Join(d.folder, "exec-root"),
   148  		"--pidfile", fmt.Sprintf("%s/docker.pid", d.folder),
   149  		fmt.Sprintf("--userland-proxy=%t", d.userlandProxy),
   150  	)
   151  	if !(d.useDefaultHost || d.useDefaultTLSHost) {
   152  		args = append(args, []string{"--host", d.sock()}...)
   153  	}
   154  	if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" {
   155  		args = append(args, []string{"--userns-remap", root}...)
   156  	}
   157  
   158  	// If we don't explicitly set the log-level or debug flag(-D) then
   159  	// turn on debug mode
   160  	foundLog := false
   161  	foundSd := false
   162  	for _, a := range providedArgs {
   163  		if strings.Contains(a, "--log-level") || strings.Contains(a, "-D") || strings.Contains(a, "--debug") {
   164  			foundLog = true
   165  		}
   166  		if strings.Contains(a, "--storage-driver") {
   167  			foundSd = true
   168  		}
   169  	}
   170  	if !foundLog {
   171  		args = append(args, "--debug")
   172  	}
   173  	if d.storageDriver != "" && !foundSd {
   174  		args = append(args, "--storage-driver", d.storageDriver)
   175  	}
   176  
   177  	args = append(args, providedArgs...)
   178  	d.cmd = exec.Command(dockerBinary, args...)
   179  
   180  	d.cmd.Stdout = out
   181  	d.cmd.Stderr = out
   182  	d.logFile = out
   183  
   184  	if err := d.cmd.Start(); err != nil {
   185  		return fmt.Errorf("[%s] could not start daemon container: %v", d.id, err)
   186  	}
   187  
   188  	wait := make(chan error)
   189  
   190  	go func() {
   191  		wait <- d.cmd.Wait()
   192  		d.c.Logf("[%s] exiting daemon", d.id)
   193  		close(wait)
   194  	}()
   195  
   196  	d.wait = wait
   197  
   198  	tick := time.Tick(500 * time.Millisecond)
   199  	// make sure daemon is ready to receive requests
   200  	startTime := time.Now().Unix()
   201  	for {
   202  		d.c.Logf("[%s] waiting for daemon to start", d.id)
   203  		if time.Now().Unix()-startTime > 5 {
   204  			// After 5 seconds, give up
   205  			return fmt.Errorf("[%s] Daemon exited and never started", d.id)
   206  		}
   207  		select {
   208  		case <-time.After(2 * time.Second):
   209  			return fmt.Errorf("[%s] timeout: daemon does not respond", d.id)
   210  		case <-tick:
   211  			clientConfig, err := d.getClientConfig()
   212  			if err != nil {
   213  				return err
   214  			}
   215  
   216  			client := &http.Client{
   217  				Transport: clientConfig.transport,
   218  			}
   219  
   220  			req, err := http.NewRequest("GET", "/_ping", nil)
   221  			d.c.Assert(err, check.IsNil, check.Commentf("[%s] could not create new request", d.id))
   222  			req.URL.Host = clientConfig.addr
   223  			req.URL.Scheme = clientConfig.scheme
   224  			resp, err := client.Do(req)
   225  			if err != nil {
   226  				continue
   227  			}
   228  			if resp.StatusCode != http.StatusOK {
   229  				d.c.Logf("[%s] received status != 200 OK: %s", d.id, resp.Status)
   230  			}
   231  			d.c.Logf("[%s] daemon started", d.id)
   232  			d.root, err = d.queryRootDir()
   233  			if err != nil {
   234  				return fmt.Errorf("[%s] error querying daemon for root directory: %v", d.id, err)
   235  			}
   236  			return nil
   237  		}
   238  	}
   239  }
   240  
   241  // StartWithBusybox will first start the daemon with Daemon.Start()
   242  // then save the busybox image from the main daemon and load it into this Daemon instance.
   243  func (d *Daemon) StartWithBusybox(arg ...string) error {
   244  	if err := d.Start(arg...); err != nil {
   245  		return err
   246  	}
   247  	return d.LoadBusybox()
   248  }
   249  
   250  // Kill will send a SIGKILL to the daemon
   251  func (d *Daemon) Kill() error {
   252  	if d.cmd == nil || d.wait == nil {
   253  		return errors.New("daemon not started")
   254  	}
   255  
   256  	defer func() {
   257  		d.logFile.Close()
   258  		d.cmd = nil
   259  	}()
   260  
   261  	if err := d.cmd.Process.Kill(); err != nil {
   262  		d.c.Logf("Could not kill daemon: %v", err)
   263  		return err
   264  	}
   265  
   266  	if err := os.Remove(fmt.Sprintf("%s/docker.pid", d.folder)); err != nil {
   267  		return err
   268  	}
   269  
   270  	return nil
   271  }
   272  
   273  // Stop will send a SIGINT every second and wait for the daemon to stop.
   274  // If it timeouts, a SIGKILL is sent.
   275  // Stop will not delete the daemon directory. If a purged daemon is needed,
   276  // instantiate a new one with NewDaemon.
   277  func (d *Daemon) Stop() error {
   278  	if d.cmd == nil || d.wait == nil {
   279  		return errors.New("daemon not started")
   280  	}
   281  
   282  	defer func() {
   283  		d.logFile.Close()
   284  		d.cmd = nil
   285  	}()
   286  
   287  	i := 1
   288  	tick := time.Tick(time.Second)
   289  
   290  	if err := d.cmd.Process.Signal(os.Interrupt); err != nil {
   291  		return fmt.Errorf("could not send signal: %v", err)
   292  	}
   293  out1:
   294  	for {
   295  		select {
   296  		case err := <-d.wait:
   297  			return err
   298  		case <-time.After(15 * time.Second):
   299  			// time for stopping jobs and run onShutdown hooks
   300  			d.c.Log("timeout")
   301  			break out1
   302  		}
   303  	}
   304  
   305  out2:
   306  	for {
   307  		select {
   308  		case err := <-d.wait:
   309  			return err
   310  		case <-tick:
   311  			i++
   312  			if i > 4 {
   313  				d.c.Logf("tried to interrupt daemon for %d times, now try to kill it", i)
   314  				break out2
   315  			}
   316  			d.c.Logf("Attempt #%d: daemon is still running with pid %d", i, d.cmd.Process.Pid)
   317  			if err := d.cmd.Process.Signal(os.Interrupt); err != nil {
   318  				return fmt.Errorf("could not send signal: %v", err)
   319  			}
   320  		}
   321  	}
   322  
   323  	if err := d.cmd.Process.Kill(); err != nil {
   324  		d.c.Logf("Could not kill daemon: %v", err)
   325  		return err
   326  	}
   327  
   328  	if err := os.Remove(fmt.Sprintf("%s/docker.pid", d.folder)); err != nil {
   329  		return err
   330  	}
   331  
   332  	return nil
   333  }
   334  
   335  // Restart will restart the daemon by first stopping it and then starting it.
   336  func (d *Daemon) Restart(arg ...string) error {
   337  	d.Stop()
   338  	// in the case of tests running a user namespace-enabled daemon, we have resolved
   339  	// d.root to be the actual final path of the graph dir after the "uid.gid" of
   340  	// remapped root is added--we need to subtract it from the path before calling
   341  	// start or else we will continue making subdirectories rather than truly restarting
   342  	// with the same location/root:
   343  	if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" {
   344  		d.root = filepath.Dir(d.root)
   345  	}
   346  	return d.Start(arg...)
   347  }
   348  
   349  // LoadBusybox will load the stored busybox into a newly started daemon
   350  func (d *Daemon) LoadBusybox() error {
   351  	bb := filepath.Join(d.folder, "busybox.tar")
   352  	if _, err := os.Stat(bb); err != nil {
   353  		if !os.IsNotExist(err) {
   354  			return fmt.Errorf("unexpected error on busybox.tar stat: %v", err)
   355  		}
   356  		// saving busybox image from main daemon
   357  		if err := exec.Command(dockerBinary, "save", "--output", bb, "busybox:latest").Run(); err != nil {
   358  			return fmt.Errorf("could not save busybox image: %v", err)
   359  		}
   360  	}
   361  	// loading busybox image to this daemon
   362  	if out, err := d.Cmd("load", "--input", bb); err != nil {
   363  		return fmt.Errorf("could not load busybox image: %s", out)
   364  	}
   365  	if err := os.Remove(bb); err != nil {
   366  		d.c.Logf("could not remove %s: %v", bb, err)
   367  	}
   368  	return nil
   369  }
   370  
   371  func (d *Daemon) queryRootDir() (string, error) {
   372  	// update daemon root by asking /info endpoint (to support user
   373  	// namespaced daemon with root remapped uid.gid directory)
   374  	clientConfig, err := d.getClientConfig()
   375  	if err != nil {
   376  		return "", err
   377  	}
   378  
   379  	client := &http.Client{
   380  		Transport: clientConfig.transport,
   381  	}
   382  
   383  	req, err := http.NewRequest("GET", "/info", nil)
   384  	if err != nil {
   385  		return "", err
   386  	}
   387  	req.Header.Set("Content-Type", "application/json")
   388  	req.URL.Host = clientConfig.addr
   389  	req.URL.Scheme = clientConfig.scheme
   390  
   391  	resp, err := client.Do(req)
   392  	if err != nil {
   393  		return "", err
   394  	}
   395  	body := ioutils.NewReadCloserWrapper(resp.Body, func() error {
   396  		return resp.Body.Close()
   397  	})
   398  
   399  	type Info struct {
   400  		DockerRootDir string
   401  	}
   402  	var b []byte
   403  	var i Info
   404  	b, err = readBody(body)
   405  	if err == nil && resp.StatusCode == 200 {
   406  		// read the docker root dir
   407  		if err = json.Unmarshal(b, &i); err == nil {
   408  			return i.DockerRootDir, nil
   409  		}
   410  	}
   411  	return "", err
   412  }
   413  
   414  func (d *Daemon) sock() string {
   415  	return fmt.Sprintf("unix://%s/docker.sock", d.folder)
   416  }
   417  
   418  func (d *Daemon) waitRun(contID string) error {
   419  	args := []string{"--host", d.sock()}
   420  	return waitInspectWithArgs(contID, "{{.State.Running}}", "true", 10*time.Second, args...)
   421  }
   422  
   423  func (d *Daemon) getBaseDeviceSize(c *check.C) int64 {
   424  	infoCmdOutput, _, err := runCommandPipelineWithOutput(
   425  		exec.Command(dockerBinary, "-H", d.sock(), "info"),
   426  		exec.Command("grep", "Base Device Size"),
   427  	)
   428  	c.Assert(err, checker.IsNil)
   429  	basesizeSlice := strings.Split(infoCmdOutput, ":")
   430  	basesize := strings.Trim(basesizeSlice[1], " ")
   431  	basesize = strings.Trim(basesize, "\n")[:len(basesize)-3]
   432  	basesizeFloat, err := strconv.ParseFloat(strings.Trim(basesize, " "), 64)
   433  	c.Assert(err, checker.IsNil)
   434  	basesizeBytes := int64(basesizeFloat) * (1024 * 1024 * 1024)
   435  	return basesizeBytes
   436  }
   437  
   438  // Cmd will execute a docker CLI command against this Daemon.
   439  // Example: d.Cmd("version") will run docker -H unix://path/to/unix.sock version
   440  func (d *Daemon) Cmd(name string, arg ...string) (string, error) {
   441  	args := []string{"--host", d.sock(), name}
   442  	args = append(args, arg...)
   443  	c := exec.Command(dockerBinary, args...)
   444  	b, err := c.CombinedOutput()
   445  	return string(b), err
   446  }
   447  
   448  // CmdWithArgs will execute a docker CLI command against a daemon with the
   449  // given additional arguments
   450  func (d *Daemon) CmdWithArgs(daemonArgs []string, name string, arg ...string) (string, error) {
   451  	args := append(daemonArgs, name)
   452  	args = append(args, arg...)
   453  	c := exec.Command(dockerBinary, args...)
   454  	b, err := c.CombinedOutput()
   455  	return string(b), err
   456  }
   457  
   458  // LogFileName returns the path the the daemon's log file
   459  func (d *Daemon) LogFileName() string {
   460  	return d.logFile.Name()
   461  }
   462  
   463  func (d *Daemon) getIDByName(name string) (string, error) {
   464  	return d.inspectFieldWithError(name, "Id")
   465  }
   466  
   467  func (d *Daemon) inspectFilter(name, filter string) (string, error) {
   468  	format := fmt.Sprintf("{{%s}}", filter)
   469  	out, err := d.Cmd("inspect", "-f", format, name)
   470  	if err != nil {
   471  		return "", fmt.Errorf("failed to inspect %s: %s", name, out)
   472  	}
   473  	return strings.TrimSpace(out), nil
   474  }
   475  
   476  func (d *Daemon) inspectFieldWithError(name, field string) (string, error) {
   477  	return d.inspectFilter(name, fmt.Sprintf(".%s", field))
   478  }
   479  
   480  func (d *Daemon) findContainerIP(id string) string {
   481  	out, err := d.Cmd("inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.bridge.IPAddress }}'"), id)
   482  	if err != nil {
   483  		d.c.Log(err)
   484  	}
   485  	return strings.Trim(out, " \r\n'")
   486  }