github.com/hustcat/docker@v1.3.3-0.20160314103604-901c67a8eeab/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  		"--graph", d.root,
   146  		"--pidfile", fmt.Sprintf("%s/docker.pid", d.folder),
   147  		fmt.Sprintf("--userland-proxy=%t", d.userlandProxy),
   148  	)
   149  	if !(d.useDefaultHost || d.useDefaultTLSHost) {
   150  		args = append(args, []string{"--host", d.sock()}...)
   151  	}
   152  	if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" {
   153  		args = append(args, []string{"--userns-remap", root}...)
   154  	}
   155  
   156  	// If we don't explicitly set the log-level or debug flag(-D) then
   157  	// turn on debug mode
   158  	foundLog := false
   159  	foundSd := false
   160  	for _, a := range providedArgs {
   161  		if strings.Contains(a, "--log-level") || strings.Contains(a, "-D") || strings.Contains(a, "--debug") {
   162  			foundLog = true
   163  		}
   164  		if strings.Contains(a, "--storage-driver") {
   165  			foundSd = true
   166  		}
   167  	}
   168  	if !foundLog {
   169  		args = append(args, "--debug")
   170  	}
   171  	if d.storageDriver != "" && !foundSd {
   172  		args = append(args, "--storage-driver", d.storageDriver)
   173  	}
   174  
   175  	args = append(args, providedArgs...)
   176  	d.cmd = exec.Command(dockerBinary, args...)
   177  
   178  	d.cmd.Stdout = out
   179  	d.cmd.Stderr = out
   180  	d.logFile = out
   181  
   182  	if err := d.cmd.Start(); err != nil {
   183  		return fmt.Errorf("[%s] could not start daemon container: %v", d.id, err)
   184  	}
   185  
   186  	wait := make(chan error)
   187  
   188  	go func() {
   189  		wait <- d.cmd.Wait()
   190  		d.c.Logf("[%s] exiting daemon", d.id)
   191  		close(wait)
   192  	}()
   193  
   194  	d.wait = wait
   195  
   196  	tick := time.Tick(500 * time.Millisecond)
   197  	// make sure daemon is ready to receive requests
   198  	startTime := time.Now().Unix()
   199  	for {
   200  		d.c.Logf("[%s] waiting for daemon to start", d.id)
   201  		if time.Now().Unix()-startTime > 5 {
   202  			// After 5 seconds, give up
   203  			return fmt.Errorf("[%s] Daemon exited and never started", d.id)
   204  		}
   205  		select {
   206  		case <-time.After(2 * time.Second):
   207  			return fmt.Errorf("[%s] timeout: daemon does not respond", d.id)
   208  		case <-tick:
   209  			clientConfig, err := d.getClientConfig()
   210  			if err != nil {
   211  				return err
   212  			}
   213  
   214  			client := &http.Client{
   215  				Transport: clientConfig.transport,
   216  			}
   217  
   218  			req, err := http.NewRequest("GET", "/_ping", nil)
   219  			d.c.Assert(err, check.IsNil, check.Commentf("[%s] could not create new request", d.id))
   220  			req.URL.Host = clientConfig.addr
   221  			req.URL.Scheme = clientConfig.scheme
   222  			resp, err := client.Do(req)
   223  			if err != nil {
   224  				continue
   225  			}
   226  			if resp.StatusCode != http.StatusOK {
   227  				d.c.Logf("[%s] received status != 200 OK: %s", d.id, resp.Status)
   228  			}
   229  			d.c.Logf("[%s] daemon started", d.id)
   230  			d.root, err = d.queryRootDir()
   231  			if err != nil {
   232  				return fmt.Errorf("[%s] error querying daemon for root directory: %v", d.id, err)
   233  			}
   234  			return nil
   235  		}
   236  	}
   237  }
   238  
   239  // StartWithBusybox will first start the daemon with Daemon.Start()
   240  // then save the busybox image from the main daemon and load it into this Daemon instance.
   241  func (d *Daemon) StartWithBusybox(arg ...string) error {
   242  	if err := d.Start(arg...); err != nil {
   243  		return err
   244  	}
   245  	return d.LoadBusybox()
   246  }
   247  
   248  // Stop will send a SIGINT every second and wait for the daemon to stop.
   249  // If it timeouts, a SIGKILL is sent.
   250  // Stop will not delete the daemon directory. If a purged daemon is needed,
   251  // instantiate a new one with NewDaemon.
   252  func (d *Daemon) Stop() error {
   253  	if d.cmd == nil || d.wait == nil {
   254  		return errors.New("daemon not started")
   255  	}
   256  
   257  	defer func() {
   258  		d.logFile.Close()
   259  		d.cmd = nil
   260  	}()
   261  
   262  	i := 1
   263  	tick := time.Tick(time.Second)
   264  
   265  	if err := d.cmd.Process.Signal(os.Interrupt); err != nil {
   266  		return fmt.Errorf("could not send signal: %v", err)
   267  	}
   268  out1:
   269  	for {
   270  		select {
   271  		case err := <-d.wait:
   272  			return err
   273  		case <-time.After(15 * time.Second):
   274  			// time for stopping jobs and run onShutdown hooks
   275  			d.c.Log("timeout")
   276  			break out1
   277  		}
   278  	}
   279  
   280  out2:
   281  	for {
   282  		select {
   283  		case err := <-d.wait:
   284  			return err
   285  		case <-tick:
   286  			i++
   287  			if i > 4 {
   288  				d.c.Logf("tried to interrupt daemon for %d times, now try to kill it", i)
   289  				break out2
   290  			}
   291  			d.c.Logf("Attempt #%d: daemon is still running with pid %d", i, d.cmd.Process.Pid)
   292  			if err := d.cmd.Process.Signal(os.Interrupt); err != nil {
   293  				return fmt.Errorf("could not send signal: %v", err)
   294  			}
   295  		}
   296  	}
   297  
   298  	if err := d.cmd.Process.Kill(); err != nil {
   299  		d.c.Logf("Could not kill daemon: %v", err)
   300  		return err
   301  	}
   302  
   303  	return nil
   304  }
   305  
   306  // Restart will restart the daemon by first stopping it and then starting it.
   307  func (d *Daemon) Restart(arg ...string) error {
   308  	d.Stop()
   309  	// in the case of tests running a user namespace-enabled daemon, we have resolved
   310  	// d.root to be the actual final path of the graph dir after the "uid.gid" of
   311  	// remapped root is added--we need to subtract it from the path before calling
   312  	// start or else we will continue making subdirectories rather than truly restarting
   313  	// with the same location/root:
   314  	if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" {
   315  		d.root = filepath.Dir(d.root)
   316  	}
   317  	return d.Start(arg...)
   318  }
   319  
   320  // LoadBusybox will load the stored busybox into a newly started daemon
   321  func (d *Daemon) LoadBusybox() error {
   322  	bb := filepath.Join(d.folder, "busybox.tar")
   323  	if _, err := os.Stat(bb); err != nil {
   324  		if !os.IsNotExist(err) {
   325  			return fmt.Errorf("unexpected error on busybox.tar stat: %v", err)
   326  		}
   327  		// saving busybox image from main daemon
   328  		if err := exec.Command(dockerBinary, "save", "--output", bb, "busybox:latest").Run(); err != nil {
   329  			return fmt.Errorf("could not save busybox image: %v", err)
   330  		}
   331  	}
   332  	// loading busybox image to this daemon
   333  	if out, err := d.Cmd("load", "--input", bb); err != nil {
   334  		return fmt.Errorf("could not load busybox image: %s", out)
   335  	}
   336  	if err := os.Remove(bb); err != nil {
   337  		d.c.Logf("could not remove %s: %v", bb, err)
   338  	}
   339  	return nil
   340  }
   341  
   342  func (d *Daemon) queryRootDir() (string, error) {
   343  	// update daemon root by asking /info endpoint (to support user
   344  	// namespaced daemon with root remapped uid.gid directory)
   345  	clientConfig, err := d.getClientConfig()
   346  	if err != nil {
   347  		return "", err
   348  	}
   349  
   350  	client := &http.Client{
   351  		Transport: clientConfig.transport,
   352  	}
   353  
   354  	req, err := http.NewRequest("GET", "/info", nil)
   355  	if err != nil {
   356  		return "", err
   357  	}
   358  	req.Header.Set("Content-Type", "application/json")
   359  	req.URL.Host = clientConfig.addr
   360  	req.URL.Scheme = clientConfig.scheme
   361  
   362  	resp, err := client.Do(req)
   363  	if err != nil {
   364  		return "", err
   365  	}
   366  	body := ioutils.NewReadCloserWrapper(resp.Body, func() error {
   367  		return resp.Body.Close()
   368  	})
   369  
   370  	type Info struct {
   371  		DockerRootDir string
   372  	}
   373  	var b []byte
   374  	var i Info
   375  	b, err = readBody(body)
   376  	if err == nil && resp.StatusCode == 200 {
   377  		// read the docker root dir
   378  		if err = json.Unmarshal(b, &i); err == nil {
   379  			return i.DockerRootDir, nil
   380  		}
   381  	}
   382  	return "", err
   383  }
   384  
   385  func (d *Daemon) sock() string {
   386  	return fmt.Sprintf("unix://%s/docker.sock", d.folder)
   387  }
   388  
   389  func (d *Daemon) waitRun(contID string) error {
   390  	args := []string{"--host", d.sock()}
   391  	return waitInspectWithArgs(contID, "{{.State.Running}}", "true", 10*time.Second, args...)
   392  }
   393  
   394  func (d *Daemon) getBaseDeviceSize(c *check.C) int64 {
   395  	infoCmdOutput, _, err := runCommandPipelineWithOutput(
   396  		exec.Command(dockerBinary, "-H", d.sock(), "info"),
   397  		exec.Command("grep", "Base Device Size"),
   398  	)
   399  	c.Assert(err, checker.IsNil)
   400  	basesizeSlice := strings.Split(infoCmdOutput, ":")
   401  	basesize := strings.Trim(basesizeSlice[1], " ")
   402  	basesize = strings.Trim(basesize, "\n")[:len(basesize)-3]
   403  	basesizeFloat, err := strconv.ParseFloat(strings.Trim(basesize, " "), 64)
   404  	c.Assert(err, checker.IsNil)
   405  	basesizeBytes := int64(basesizeFloat) * (1024 * 1024 * 1024)
   406  	return basesizeBytes
   407  }
   408  
   409  // Cmd will execute a docker CLI command against this Daemon.
   410  // Example: d.Cmd("version") will run docker -H unix://path/to/unix.sock version
   411  func (d *Daemon) Cmd(name string, arg ...string) (string, error) {
   412  	args := []string{"--host", d.sock(), name}
   413  	args = append(args, arg...)
   414  	c := exec.Command(dockerBinary, args...)
   415  	b, err := c.CombinedOutput()
   416  	return string(b), err
   417  }
   418  
   419  // CmdWithArgs will execute a docker CLI command against a daemon with the
   420  // given additional arguments
   421  func (d *Daemon) CmdWithArgs(daemonArgs []string, name string, arg ...string) (string, error) {
   422  	args := append(daemonArgs, name)
   423  	args = append(args, arg...)
   424  	c := exec.Command(dockerBinary, args...)
   425  	b, err := c.CombinedOutput()
   426  	return string(b), err
   427  }
   428  
   429  // LogFileName returns the path the the daemon's log file
   430  func (d *Daemon) LogFileName() string {
   431  	return d.logFile.Name()
   432  }
   433  
   434  func (d *Daemon) getIDByName(name string) (string, error) {
   435  	return d.inspectFieldWithError(name, "Id")
   436  }
   437  
   438  func (d *Daemon) inspectFilter(name, filter string) (string, error) {
   439  	format := fmt.Sprintf("{{%s}}", filter)
   440  	out, err := d.Cmd("inspect", "-f", format, name)
   441  	if err != nil {
   442  		return "", fmt.Errorf("failed to inspect %s: %s", name, out)
   443  	}
   444  	return strings.TrimSpace(out), nil
   445  }
   446  
   447  func (d *Daemon) inspectFieldWithError(name, field string) (string, error) {
   448  	return d.inspectFilter(name, fmt.Sprintf(".%s", field))
   449  }
   450  
   451  func (d *Daemon) findContainerIP(id string) string {
   452  	out, err := d.Cmd("inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.bridge.IPAddress }}'"), id)
   453  	if err != nil {
   454  		d.c.Log(err)
   455  	}
   456  	return strings.Trim(out, " \r\n'")
   457  }