mirror of https://github.com/docker/docs.git
Merge pull request #7191 from tiborvass/test-daemon
Add Daemon restart test along with Daemon utils
This commit is contained in:
commit
6db078dea7
|
@ -156,6 +156,7 @@ go_test_dir() {
|
||||||
testcover=( -cover -coverprofile "$coverprofile" $coverpkg )
|
testcover=( -cover -coverprofile "$coverprofile" $coverpkg )
|
||||||
fi
|
fi
|
||||||
(
|
(
|
||||||
|
export DEST
|
||||||
echo '+ go test' $TESTFLAGS "${DOCKER_PKG}${dir#.}"
|
echo '+ go test' $TESTFLAGS "${DOCKER_PKG}${dir#.}"
|
||||||
cd "$dir"
|
cd "$dir"
|
||||||
go test ${testcover[@]} -ldflags "$LDFLAGS" "${BUILDFLAGS[@]}" $TESTFLAGS
|
go test ${testcover[@]} -ldflags "$LDFLAGS" "${BUILDFLAGS[@]}" $TESTFLAGS
|
||||||
|
|
|
@ -38,7 +38,9 @@ exec > >(tee -a $DEST/test.log) 2>&1
|
||||||
|
|
||||||
bundle_test_integration_cli
|
bundle_test_integration_cli
|
||||||
|
|
||||||
DOCKERD_PID=$(set -x; cat $DEST/docker.pid)
|
for pid in $(find "$DEST" -name docker.pid); do
|
||||||
( set -x; kill $DOCKERD_PID )
|
DOCKER_PID=$(set -x; cat "$pid")
|
||||||
wait $DOCKERD_PID || true
|
( set -x; kill $DOCKER_PID )
|
||||||
|
wait $DOCKERD_PID || true
|
||||||
|
done
|
||||||
)
|
)
|
||||||
|
|
|
@ -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")
|
||||||
|
}
|
|
@ -1,11 +1,14 @@
|
||||||
package main
|
package main
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io"
|
"io"
|
||||||
"io/ioutil"
|
"io/ioutil"
|
||||||
|
"net"
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"net/http/httputil"
|
||||||
"os"
|
"os"
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"path"
|
"path"
|
||||||
|
@ -13,8 +16,214 @@ import (
|
||||||
"strconv"
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"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 {
|
func deleteContainer(container string) error {
|
||||||
container = strings.Replace(container, "\n", " ", -1)
|
container = strings.Replace(container, "\n", " ", -1)
|
||||||
container = strings.Trim(container, " ")
|
container = strings.Trim(container, " ")
|
||||||
|
|
Loading…
Reference in New Issue