Browse code

Add Daemon test utils

Docker-DCO-1.1-Signed-off-by: Tibor Vass <teabee89@gmail.com> (github: tiborvass)

Tibor Vass authored on 2014/07/24 03:12:42
Showing 3 changed files
... ...
@@ -156,6 +156,7 @@ go_test_dir() {
156 156
 		testcover=( -cover -coverprofile "$coverprofile" $coverpkg )
157 157
 	fi
158 158
 	(
159
+		export DEST
159 160
 		echo '+ go test' $TESTFLAGS "${DOCKER_PKG}${dir#.}"
160 161
 		cd "$dir"
161 162
 		go test ${testcover[@]} -ldflags "$LDFLAGS" "${BUILDFLAGS[@]}" $TESTFLAGS
... ...
@@ -38,7 +38,9 @@ exec > >(tee -a $DEST/test.log) 2>&1
38 38
 
39 39
 	bundle_test_integration_cli
40 40
 
41
-	DOCKERD_PID=$(set -x; cat $DEST/docker.pid)
42
-	( set -x; kill $DOCKERD_PID )
43
-	wait $DOCKERD_PID || true
41
+	for pid in $(find "$DEST" -name docker.pid); do
42
+		DOCKER_PID=$(set -x; cat "$pid")
43
+		( set -x; kill $DOCKER_PID )
44
+		wait $DOCKERD_PID || true
45
+	done
44 46
 )
... ...
@@ -1,11 +1,14 @@
1 1
 package main
2 2
 
3 3
 import (
4
+	"errors"
4 5
 	"fmt"
5 6
 	"io"
6 7
 	"io/ioutil"
8
+	"net"
7 9
 	"net/http"
8 10
 	"net/http/httptest"
11
+	"net/http/httputil"
9 12
 	"os"
10 13
 	"os/exec"
11 14
 	"path"
... ...
@@ -13,8 +16,214 @@ import (
13 13
 	"strconv"
14 14
 	"strings"
15 15
 	"testing"
16
+	"time"
16 17
 )
17 18
 
19
+// Daemon represents a Docker daemon for the testing framework.
20
+type Daemon struct {
21
+	t              *testing.T
22
+	logFile        *os.File
23
+	folder         string
24
+	stdin          io.WriteCloser
25
+	stdout, stderr io.ReadCloser
26
+	cmd            *exec.Cmd
27
+	storageDriver  string
28
+	execDriver     string
29
+	wait           chan error
30
+}
31
+
32
+// NewDaemon returns a Daemon instance to be used for testing.
33
+// This will create a directory such as daemon123456789 in the folder specified by $DEST.
34
+// The daemon will not automatically start.
35
+func NewDaemon(t *testing.T) *Daemon {
36
+	dest := os.Getenv("DEST")
37
+	if dest == "" {
38
+		t.Fatal("Please set the DEST environment variable")
39
+	}
40
+
41
+	dir := filepath.Join(dest, fmt.Sprintf("daemon%d", time.Now().Unix()))
42
+	daemonFolder, err := filepath.Abs(dir)
43
+	if err != nil {
44
+		t.Fatal("Could not make '%s' an absolute path: %v", dir, err)
45
+	}
46
+
47
+	if err := os.MkdirAll(filepath.Join(daemonFolder, "graph"), 0600); err != nil {
48
+		t.Fatal("Could not create %s/graph directory", daemonFolder)
49
+	}
50
+
51
+	return &Daemon{
52
+		t:             t,
53
+		folder:        daemonFolder,
54
+		storageDriver: os.Getenv("DOCKER_GRAPHDRIVER"),
55
+		execDriver:    os.Getenv("DOCKER_EXECDRIVER"),
56
+	}
57
+}
58
+
59
+// Start will start the daemon and return once it is ready to receive requests.
60
+// You can specify additional daemon flags (e.g. "--restart=false").
61
+func (d *Daemon) Start(arg ...string) error {
62
+	dockerBinary, err := exec.LookPath(dockerBinary)
63
+	if err != nil {
64
+		d.t.Fatalf("could not find docker binary in $PATH: %v", err)
65
+	}
66
+
67
+	args := []string{
68
+		"--host", d.sock(),
69
+		"--daemon", "--debug",
70
+		"--graph", fmt.Sprintf("%s/graph", d.folder),
71
+		"--storage-driver", d.storageDriver,
72
+		"--exec-driver", d.execDriver,
73
+		"--pidfile", fmt.Sprintf("%s/docker.pid", d.folder),
74
+	}
75
+	args = append(args, arg...)
76
+	d.cmd = exec.Command(dockerBinary, args...)
77
+
78
+	d.logFile, err = os.OpenFile(filepath.Join(d.folder, "docker.log"), os.O_WRONLY|os.O_CREATE|os.O_APPEND, 0600)
79
+	if err != nil {
80
+		d.t.Fatalf("Could not create %s/docker.log: %v", d.folder, err)
81
+	}
82
+
83
+	d.cmd.Stdout = d.logFile
84
+	d.cmd.Stderr = d.logFile
85
+
86
+	if err := d.cmd.Start(); err != nil {
87
+		return fmt.Errorf("Could not start daemon container: %v", err)
88
+	}
89
+
90
+	d.wait = make(chan error)
91
+
92
+	go func() {
93
+		d.wait <- d.cmd.Wait()
94
+		d.t.Log("exiting daemon")
95
+		close(d.wait)
96
+	}()
97
+
98
+	tick := time.Tick(500 * time.Millisecond)
99
+	// make sure daemon is ready to receive requests
100
+	for {
101
+		d.t.Log("waiting for daemon to start")
102
+		select {
103
+		case <-time.After(2 * time.Second):
104
+			return errors.New("timeout: daemon does not respond")
105
+		case <-tick:
106
+			c, err := net.Dial("unix", filepath.Join(d.folder, "docker.sock"))
107
+			if err != nil {
108
+				continue
109
+			}
110
+
111
+			client := httputil.NewClientConn(c, nil)
112
+			defer client.Close()
113
+
114
+			req, err := http.NewRequest("GET", "/_ping", nil)
115
+			if err != nil {
116
+				d.t.Fatalf("could not create new request: %v", err)
117
+			}
118
+
119
+			resp, err := client.Do(req)
120
+			if err != nil {
121
+				continue
122
+			}
123
+			if resp.StatusCode != http.StatusOK {
124
+				d.t.Logf("received status != 200 OK: %s", resp.Status)
125
+			}
126
+
127
+			d.t.Log("daemon started")
128
+			return nil
129
+		}
130
+	}
131
+}
132
+
133
+// StartWithBusybox will first start the daemon with Daemon.Start()
134
+// then save the busybox image from the main daemon and load it into this Daemon instance.
135
+func (d *Daemon) StartWithBusybox(arg ...string) error {
136
+	if err := d.Start(arg...); err != nil {
137
+		return err
138
+	}
139
+	bb := filepath.Join(d.folder, "busybox.tar")
140
+	if _, err := os.Stat(bb); err != nil {
141
+		if !os.IsNotExist(err) {
142
+			return fmt.Errorf("unexpected error on busybox.tar stat: %v", err)
143
+		}
144
+		// saving busybox image from main daemon
145
+		if err := exec.Command(dockerBinary, "save", "--output", bb, "busybox:latest").Run(); err != nil {
146
+			return fmt.Errorf("could not save busybox image: %v", err)
147
+		}
148
+	}
149
+	// loading busybox image to this daemon
150
+	if _, err := d.Cmd("load", "--input", bb); err != nil {
151
+		return fmt.Errorf("could not load busybox image: %v", err)
152
+	}
153
+	if err := os.Remove(bb); err != nil {
154
+		d.t.Logf("Could not remove %s: %v", bb, err)
155
+	}
156
+	return nil
157
+}
158
+
159
+// Stop will send a SIGINT every second and wait for the daemon to stop.
160
+// If it timeouts, a SIGKILL is sent.
161
+// Stop will not delete the daemon directory. If a purged daemon is needed,
162
+// instantiate a new one with NewDaemon.
163
+func (d *Daemon) Stop() error {
164
+	if d.cmd == nil || d.wait == nil {
165
+		return errors.New("Daemon not started")
166
+	}
167
+
168
+	defer func() {
169
+		d.logFile.Close()
170
+		d.cmd = nil
171
+	}()
172
+
173
+	i := 1
174
+	tick := time.Tick(time.Second)
175
+
176
+	if err := d.cmd.Process.Signal(os.Interrupt); err != nil {
177
+		return fmt.Errorf("Could not send signal: %v", err)
178
+	}
179
+out:
180
+	for {
181
+		select {
182
+		case err := <-d.wait:
183
+			return err
184
+		case <-time.After(20 * time.Second):
185
+			d.t.Log("timeout")
186
+			break out
187
+		case <-tick:
188
+			d.t.Logf("Attempt #%d: daemon is still running with pid %d", i+1, d.cmd.Process.Pid)
189
+			if err := d.cmd.Process.Signal(os.Interrupt); err != nil {
190
+				return fmt.Errorf("Could not send signal: %v", err)
191
+			}
192
+			i++
193
+		}
194
+	}
195
+
196
+	if err := d.cmd.Process.Kill(); err != nil {
197
+		d.t.Logf("Could not kill daemon: %v", err)
198
+		return err
199
+	}
200
+
201
+	return nil
202
+}
203
+
204
+// Restart will restart the daemon by first stopping it and then starting it.
205
+func (d *Daemon) Restart(arg ...string) error {
206
+	d.Stop()
207
+	return d.Start(arg...)
208
+}
209
+
210
+func (d *Daemon) sock() string {
211
+	return fmt.Sprintf("unix://%s/docker.sock", d.folder)
212
+}
213
+
214
+// Cmd will execute a docker CLI command against this Daemon.
215
+// Example: d.Cmd("version") will run docker -H unix://path/to/unix.sock version
216
+func (d *Daemon) Cmd(name string, arg ...string) (string, error) {
217
+	args := []string{"--host", d.sock(), name}
218
+	args = append(args, arg...)
219
+	c := exec.Command(dockerBinary, args...)
220
+	b, err := c.CombinedOutput()
221
+	return string(b), err
222
+}
223
+
18 224
 func deleteContainer(container string) error {
19 225
 	container = strings.Replace(container, "\n", " ", -1)
20 226
 	container = strings.Trim(container, " ")