浏览代码

Merge pull request #7191 from tiborvass/test-daemon

Add Daemon restart test along with Daemon utils
unclejack 11 年之前
父节点
当前提交
6db078dea7
共有 4 个文件被更改,包括 265 次插入3 次删除
  1. 1 0
      hack/make.sh
  2. 5 3
      hack/make/test-integration-cli
  3. 50 0
      integration-cli/docker_cli_daemon_test.go
  4. 209 0
      integration-cli/docker_utils.go

+ 1 - 0
hack/make.sh

@@ -156,6 +156,7 @@ go_test_dir() {
 		testcover=( -cover -coverprofile "$coverprofile" $coverpkg )
 	fi
 	(
+		export DEST
 		echo '+ go test' $TESTFLAGS "${DOCKER_PKG}${dir#.}"
 		cd "$dir"
 		go test ${testcover[@]} -ldflags "$LDFLAGS" "${BUILDFLAGS[@]}" $TESTFLAGS

+ 5 - 3
hack/make/test-integration-cli

@@ -38,7 +38,9 @@ exec > >(tee -a $DEST/test.log) 2>&1
 
 	bundle_test_integration_cli
 
-	DOCKERD_PID=$(set -x; cat $DEST/docker.pid)
-	( set -x; kill $DOCKERD_PID )
-	wait $DOCKERD_PID || true
+	for pid in $(find "$DEST" -name docker.pid); do
+		DOCKER_PID=$(set -x; cat "$pid")
+		( set -x; kill $DOCKER_PID )
+		wait $DOCKERD_PID || true
+	done
 )

+ 50 - 0
integration-cli/docker_cli_daemon_test.go

@@ -0,0 +1,50 @@
+package main
+
+import (
+	"strings"
+	"testing"
+)
+
+func TestDaemonRestartWithRunningContainersPorts(t *testing.T) {
+	d := NewDaemon(t)
+	if err := d.StartWithBusybox(); err != nil {
+		t.Fatalf("Could not start daemon with busybox: %v", err)
+	}
+	defer d.Stop()
+
+	if out, err := d.Cmd("run", "-d", "--name", "top1", "-p", "1234:80", "--restart", "always", "busybox:latest", "top"); err != nil {
+		t.Fatalf("Could not run top1: err=%v\n%s", err, out)
+	}
+	// --restart=no by default
+	if out, err := d.Cmd("run", "-d", "--name", "top2", "-p", "80", "busybox:latest", "top"); err != nil {
+		t.Fatalf("Could not run top2: err=%v\n%s", err, out)
+	}
+
+	testRun := func(m map[string]bool, prefix string) {
+		var format string
+		for c, shouldRun := range m {
+			out, err := d.Cmd("ps")
+			if err != nil {
+				t.Fatalf("Could not run ps: err=%v\n%q", err, out)
+			}
+			if shouldRun {
+				format = "%scontainer %q is not running"
+			} else {
+				format = "%scontainer %q is running"
+			}
+			if shouldRun != strings.Contains(out, c) {
+				t.Fatalf(format, prefix, c)
+			}
+		}
+	}
+
+	testRun(map[string]bool{"top1": true, "top2": true}, "")
+
+	if err := d.Restart(); err != nil {
+		t.Fatalf("Could not restart daemon: %v", err)
+	}
+
+	testRun(map[string]bool{"top1": true, "top2": false}, "After daemon restart: ")
+
+	logDone("daemon - running containers on daemon restart")
+}

+ 209 - 0
integration-cli/docker_utils.go

@@ -1,11 +1,14 @@
 package main
 
 import (
+	"errors"
 	"fmt"
 	"io"
 	"io/ioutil"
+	"net"
 	"net/http"
 	"net/http/httptest"
+	"net/http/httputil"
 	"os"
 	"os/exec"
 	"path"
@@ -13,8 +16,214 @@ import (
 	"strconv"
 	"strings"
 	"testing"
+	"time"
 )
 
+// Daemon represents a Docker daemon for the testing framework.
+type Daemon struct {
+	t              *testing.T
+	logFile        *os.File
+	folder         string
+	stdin          io.WriteCloser
+	stdout, stderr io.ReadCloser
+	cmd            *exec.Cmd
+	storageDriver  string
+	execDriver     string
+	wait           chan error
+}
+
+// NewDaemon returns a Daemon instance to be used for testing.
+// This will create a directory such as daemon123456789 in the folder specified by $DEST.
+// The daemon will not automatically start.
+func NewDaemon(t *testing.T) *Daemon {
+	dest := os.Getenv("DEST")
+	if dest == "" {
+		t.Fatal("Please set the DEST environment variable")
+	}
+
+	dir := filepath.Join(dest, fmt.Sprintf("daemon%d", time.Now().Unix()))
+	daemonFolder, err := filepath.Abs(dir)
+	if err != nil {
+		t.Fatal("Could not make '%s' an absolute path: %v", dir, err)
+	}
+
+	if err := os.MkdirAll(filepath.Join(daemonFolder, "graph"), 0600); err != nil {
+		t.Fatal("Could not create %s/graph directory", daemonFolder)
+	}
+
+	return &Daemon{
+		t:             t,
+		folder:        daemonFolder,
+		storageDriver: os.Getenv("DOCKER_GRAPHDRIVER"),
+		execDriver:    os.Getenv("DOCKER_EXECDRIVER"),
+	}
+}
+
+// Start will start the daemon and return once it is ready to receive requests.
+// You can specify additional daemon flags (e.g. "--restart=false").
+func (d *Daemon) Start(arg ...string) error {
+	dockerBinary, err := exec.LookPath(dockerBinary)
+	if err != nil {
+		d.t.Fatalf("could not find docker binary in $PATH: %v", err)
+	}
+
+	args := []string{
+		"--host", d.sock(),
+		"--daemon", "--debug",
+		"--graph", fmt.Sprintf("%s/graph", d.folder),
+		"--storage-driver", d.storageDriver,
+		"--exec-driver", d.execDriver,
+		"--pidfile", fmt.Sprintf("%s/docker.pid", d.folder),
+	}
+	args = append(args, arg...)
+	d.cmd = exec.Command(dockerBinary, args...)
+
+	d.logFile, err = os.OpenFile(filepath.Join(d.folder, "docker.log"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
+	if err != nil {
+		d.t.Fatalf("Could not create %s/docker.log: %v", d.folder, err)
+	}
+
+	d.cmd.Stdout = d.logFile
+	d.cmd.Stderr = d.logFile
+
+	if err := d.cmd.Start(); err != nil {
+		return fmt.Errorf("Could not start daemon container: %v", err)
+	}
+
+	d.wait = make(chan error)
+
+	go func() {
+		d.wait <- d.cmd.Wait()
+		d.t.Log("exiting daemon")
+		close(d.wait)
+	}()
+
+	tick := time.Tick(500 * time.Millisecond)
+	// make sure daemon is ready to receive requests
+	for {
+		d.t.Log("waiting for daemon to start")
+		select {
+		case <-time.After(2 * time.Second):
+			return errors.New("timeout: daemon does not respond")
+		case <-tick:
+			c, err := net.Dial("unix", filepath.Join(d.folder, "docker.sock"))
+			if err != nil {
+				continue
+			}
+
+			client := httputil.NewClientConn(c, nil)
+			defer client.Close()
+
+			req, err := http.NewRequest("GET", "/_ping", nil)
+			if err != nil {
+				d.t.Fatalf("could not create new request: %v", err)
+			}
+
+			resp, err := client.Do(req)
+			if err != nil {
+				continue
+			}
+			if resp.StatusCode != http.StatusOK {
+				d.t.Logf("received status != 200 OK: %s", resp.Status)
+			}
+
+			d.t.Log("daemon started")
+			return nil
+		}
+	}
+}
+
+// StartWithBusybox will first start the daemon with Daemon.Start()
+// then save the busybox image from the main daemon and load it into this Daemon instance.
+func (d *Daemon) StartWithBusybox(arg ...string) error {
+	if err := d.Start(arg...); err != nil {
+		return err
+	}
+	bb := filepath.Join(d.folder, "busybox.tar")
+	if _, err := os.Stat(bb); err != nil {
+		if !os.IsNotExist(err) {
+			return fmt.Errorf("unexpected error on busybox.tar stat: %v", err)
+		}
+		// saving busybox image from main daemon
+		if err := exec.Command(dockerBinary, "save", "--output", bb, "busybox:latest").Run(); err != nil {
+			return fmt.Errorf("could not save busybox image: %v", err)
+		}
+	}
+	// loading busybox image to this daemon
+	if _, err := d.Cmd("load", "--input", bb); err != nil {
+		return fmt.Errorf("could not load busybox image: %v", err)
+	}
+	if err := os.Remove(bb); err != nil {
+		d.t.Logf("Could not remove %s: %v", bb, err)
+	}
+	return nil
+}
+
+// Stop will send a SIGINT every second and wait for the daemon to stop.
+// If it timeouts, a SIGKILL is sent.
+// Stop will not delete the daemon directory. If a purged daemon is needed,
+// instantiate a new one with NewDaemon.
+func (d *Daemon) Stop() error {
+	if d.cmd == nil || d.wait == nil {
+		return errors.New("Daemon not started")
+	}
+
+	defer func() {
+		d.logFile.Close()
+		d.cmd = nil
+	}()
+
+	i := 1
+	tick := time.Tick(time.Second)
+
+	if err := d.cmd.Process.Signal(os.Interrupt); err != nil {
+		return fmt.Errorf("Could not send signal: %v", err)
+	}
+out:
+	for {
+		select {
+		case err := <-d.wait:
+			return err
+		case <-time.After(20 * time.Second):
+			d.t.Log("timeout")
+			break out
+		case <-tick:
+			d.t.Logf("Attempt #%d: daemon is still running with pid %d", i+1, d.cmd.Process.Pid)
+			if err := d.cmd.Process.Signal(os.Interrupt); err != nil {
+				return fmt.Errorf("Could not send signal: %v", err)
+			}
+			i++
+		}
+	}
+
+	if err := d.cmd.Process.Kill(); err != nil {
+		d.t.Logf("Could not kill daemon: %v", err)
+		return err
+	}
+
+	return nil
+}
+
+// Restart will restart the daemon by first stopping it and then starting it.
+func (d *Daemon) Restart(arg ...string) error {
+	d.Stop()
+	return d.Start(arg...)
+}
+
+func (d *Daemon) sock() string {
+	return fmt.Sprintf("unix://%s/docker.sock", d.folder)
+}
+
+// Cmd will execute a docker CLI command against this Daemon.
+// Example: d.Cmd("version") will run docker -H unix://path/to/unix.sock version
+func (d *Daemon) Cmd(name string, arg ...string) (string, error) {
+	args := []string{"--host", d.sock(), name}
+	args = append(args, arg...)
+	c := exec.Command(dockerBinary, args...)
+	b, err := c.CombinedOutput()
+	return string(b), err
+}
+
 func deleteContainer(container string) error {
 	container = strings.Replace(container, "\n", " ", -1)
 	container = strings.Trim(container, " ")