Browse code

Move integration-cli daemon package to internal/test…

… and do not use the `docker` cli in it. One of the reason of this
move is to not make `integration` package using legacy
`integration-cli` package.

Next move will be to support swarm within this package *and* provide
some helper function using the api (compared to the one using cli in
`integration-cli/daemon` package).

Signed-off-by: Vincent Demeester <vincent@sbr.pm>

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