integration-cli: move daemon stuff to its own file
Signed-off-by: Antonio Murdaca <runcom@redhat.com>
This commit is contained in:
parent
ea5acb08fa
commit
9a9e2bb61d
2 changed files with 457 additions and 437 deletions
457
integration-cli/daemon.go
Normal file
457
integration-cli/daemon.go
Normal file
|
@ -0,0 +1,457 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"os"
|
||||
"os/exec"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/docker/pkg/integration/checker"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/tlsconfig"
|
||||
"github.com/docker/go-connections/sockets"
|
||||
"github.com/go-check/check"
|
||||
)
|
||||
|
||||
// Daemon represents a Docker daemon for the testing framework.
|
||||
type Daemon struct {
|
||||
// Defaults to "daemon"
|
||||
// Useful to set to --daemon or -d for checking backwards compatibility
|
||||
Command string
|
||||
GlobalFlags []string
|
||||
|
||||
id string
|
||||
c *check.C
|
||||
logFile *os.File
|
||||
folder string
|
||||
root string
|
||||
stdin io.WriteCloser
|
||||
stdout, stderr io.ReadCloser
|
||||
cmd *exec.Cmd
|
||||
storageDriver string
|
||||
wait chan error
|
||||
userlandProxy bool
|
||||
useDefaultHost bool
|
||||
useDefaultTLSHost bool
|
||||
}
|
||||
|
||||
type clientConfig struct {
|
||||
transport *http.Transport
|
||||
scheme string
|
||||
addr string
|
||||
}
|
||||
|
||||
// NewDaemon returns a Daemon instance to be used for testing.
|
||||
// This will create a directory such as d123456789 in the folder specified by $DEST.
|
||||
// The daemon will not automatically start.
|
||||
func NewDaemon(c *check.C) *Daemon {
|
||||
dest := os.Getenv("DEST")
|
||||
c.Assert(dest, check.Not(check.Equals), "", check.Commentf("Please set the DEST environment variable"))
|
||||
|
||||
id := fmt.Sprintf("d%d", time.Now().UnixNano()%100000000)
|
||||
dir := filepath.Join(dest, id)
|
||||
daemonFolder, err := filepath.Abs(dir)
|
||||
c.Assert(err, check.IsNil, check.Commentf("Could not make %q an absolute path", dir))
|
||||
daemonRoot := filepath.Join(daemonFolder, "root")
|
||||
|
||||
c.Assert(os.MkdirAll(daemonRoot, 0755), check.IsNil, check.Commentf("Could not create daemon root %q", dir))
|
||||
|
||||
userlandProxy := true
|
||||
if env := os.Getenv("DOCKER_USERLANDPROXY"); env != "" {
|
||||
if val, err := strconv.ParseBool(env); err != nil {
|
||||
userlandProxy = val
|
||||
}
|
||||
}
|
||||
|
||||
return &Daemon{
|
||||
Command: "daemon",
|
||||
id: id,
|
||||
c: c,
|
||||
folder: daemonFolder,
|
||||
root: daemonRoot,
|
||||
storageDriver: os.Getenv("DOCKER_GRAPHDRIVER"),
|
||||
userlandProxy: userlandProxy,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Daemon) getClientConfig() (*clientConfig, error) {
|
||||
var (
|
||||
transport *http.Transport
|
||||
scheme string
|
||||
addr string
|
||||
proto string
|
||||
)
|
||||
if d.useDefaultTLSHost {
|
||||
option := &tlsconfig.Options{
|
||||
CAFile: "fixtures/https/ca.pem",
|
||||
CertFile: "fixtures/https/client-cert.pem",
|
||||
KeyFile: "fixtures/https/client-key.pem",
|
||||
}
|
||||
tlsConfig, err := tlsconfig.Client(*option)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transport = &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
addr = fmt.Sprintf("%s:%d", opts.DefaultHTTPHost, opts.DefaultTLSHTTPPort)
|
||||
scheme = "https"
|
||||
proto = "tcp"
|
||||
} else if d.useDefaultHost {
|
||||
addr = opts.DefaultUnixSocket
|
||||
proto = "unix"
|
||||
scheme = "http"
|
||||
transport = &http.Transport{}
|
||||
} else {
|
||||
addr = filepath.Join(d.folder, "docker.sock")
|
||||
proto = "unix"
|
||||
scheme = "http"
|
||||
transport = &http.Transport{}
|
||||
}
|
||||
|
||||
d.c.Assert(sockets.ConfigureTransport(transport, proto, addr), check.IsNil)
|
||||
|
||||
return &clientConfig{
|
||||
transport: transport,
|
||||
scheme: scheme,
|
||||
addr: addr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start will start the daemon and return once it is ready to receive requests.
|
||||
// You can specify additional daemon flags.
|
||||
func (d *Daemon) Start(args ...string) error {
|
||||
logFile, err := os.OpenFile(filepath.Join(d.folder, "docker.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
|
||||
d.c.Assert(err, check.IsNil, check.Commentf("[%s] Could not create %s/docker.log", d.id, d.folder))
|
||||
|
||||
return d.StartWithLogFile(logFile, args...)
|
||||
}
|
||||
|
||||
// StartWithLogFile will start the daemon and attach its streams to a given file.
|
||||
func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error {
|
||||
dockerBinary, err := exec.LookPath(dockerBinary)
|
||||
d.c.Assert(err, check.IsNil, check.Commentf("[%s] could not find docker binary in $PATH", d.id))
|
||||
|
||||
args := append(d.GlobalFlags,
|
||||
d.Command,
|
||||
"--graph", d.root,
|
||||
"--pidfile", fmt.Sprintf("%s/docker.pid", d.folder),
|
||||
fmt.Sprintf("--userland-proxy=%t", d.userlandProxy),
|
||||
)
|
||||
if !(d.useDefaultHost || d.useDefaultTLSHost) {
|
||||
args = append(args, []string{"--host", d.sock()}...)
|
||||
}
|
||||
if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" {
|
||||
args = append(args, []string{"--userns-remap", root}...)
|
||||
}
|
||||
|
||||
// If we don't explicitly set the log-level or debug flag(-D) then
|
||||
// turn on debug mode
|
||||
foundLog := false
|
||||
foundSd := false
|
||||
for _, a := range providedArgs {
|
||||
if strings.Contains(a, "--log-level") || strings.Contains(a, "-D") || strings.Contains(a, "--debug") {
|
||||
foundLog = true
|
||||
}
|
||||
if strings.Contains(a, "--storage-driver") {
|
||||
foundSd = true
|
||||
}
|
||||
}
|
||||
if !foundLog {
|
||||
args = append(args, "--debug")
|
||||
}
|
||||
if d.storageDriver != "" && !foundSd {
|
||||
args = append(args, "--storage-driver", d.storageDriver)
|
||||
}
|
||||
|
||||
args = append(args, providedArgs...)
|
||||
d.cmd = exec.Command(dockerBinary, args...)
|
||||
|
||||
d.cmd.Stdout = out
|
||||
d.cmd.Stderr = out
|
||||
d.logFile = out
|
||||
|
||||
if err := d.cmd.Start(); err != nil {
|
||||
return fmt.Errorf("[%s] could not start daemon container: %v", d.id, err)
|
||||
}
|
||||
|
||||
wait := make(chan error)
|
||||
|
||||
go func() {
|
||||
wait <- d.cmd.Wait()
|
||||
d.c.Logf("[%s] exiting daemon", d.id)
|
||||
close(wait)
|
||||
}()
|
||||
|
||||
d.wait = wait
|
||||
|
||||
tick := time.Tick(500 * time.Millisecond)
|
||||
// make sure daemon is ready to receive requests
|
||||
startTime := time.Now().Unix()
|
||||
for {
|
||||
d.c.Logf("[%s] waiting for daemon to start", d.id)
|
||||
if time.Now().Unix()-startTime > 5 {
|
||||
// After 5 seconds, give up
|
||||
return fmt.Errorf("[%s] Daemon exited and never started", d.id)
|
||||
}
|
||||
select {
|
||||
case <-time.After(2 * time.Second):
|
||||
return fmt.Errorf("[%s] timeout: daemon does not respond", d.id)
|
||||
case <-tick:
|
||||
clientConfig, err := d.getClientConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: clientConfig.transport,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", "/_ping", nil)
|
||||
d.c.Assert(err, check.IsNil, check.Commentf("[%s] could not create new request", d.id))
|
||||
req.URL.Host = clientConfig.addr
|
||||
req.URL.Scheme = clientConfig.scheme
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
d.c.Logf("[%s] received status != 200 OK: %s", d.id, resp.Status)
|
||||
}
|
||||
d.c.Logf("[%s] daemon started", d.id)
|
||||
d.root, err = d.queryRootDir()
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] error querying daemon for root directory: %v", d.id, err)
|
||||
}
|
||||
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
|
||||
}
|
||||
return d.LoadBusybox()
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
out1:
|
||||
for {
|
||||
select {
|
||||
case err := <-d.wait:
|
||||
return err
|
||||
case <-time.After(15 * time.Second):
|
||||
// time for stopping jobs and run onShutdown hooks
|
||||
d.c.Log("timeout")
|
||||
break out1
|
||||
}
|
||||
}
|
||||
|
||||
out2:
|
||||
for {
|
||||
select {
|
||||
case err := <-d.wait:
|
||||
return err
|
||||
case <-tick:
|
||||
i++
|
||||
if i > 4 {
|
||||
d.c.Logf("tried to interrupt daemon for %d times, now try to kill it", i)
|
||||
break out2
|
||||
}
|
||||
d.c.Logf("Attempt #%d: daemon is still running with pid %d", i, d.cmd.Process.Pid)
|
||||
if err := d.cmd.Process.Signal(os.Interrupt); err != nil {
|
||||
return fmt.Errorf("could not send signal: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := d.cmd.Process.Kill(); err != nil {
|
||||
d.c.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()
|
||||
// in the case of tests running a user namespace-enabled daemon, we have resolved
|
||||
// d.root to be the actual final path of the graph dir after the "uid.gid" of
|
||||
// remapped root is added--we need to subtract it from the path before calling
|
||||
// start or else we will continue making subdirectories rather than truly restarting
|
||||
// with the same location/root:
|
||||
if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" {
|
||||
d.root = filepath.Dir(d.root)
|
||||
}
|
||||
return d.Start(arg...)
|
||||
}
|
||||
|
||||
// LoadBusybox will load the stored busybox into a newly started daemon
|
||||
func (d *Daemon) LoadBusybox() error {
|
||||
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 out, err := d.Cmd("load", "--input", bb); err != nil {
|
||||
return fmt.Errorf("could not load busybox image: %s", out)
|
||||
}
|
||||
if err := os.Remove(bb); err != nil {
|
||||
d.c.Logf("could not remove %s: %v", bb, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Daemon) queryRootDir() (string, error) {
|
||||
// update daemon root by asking /info endpoint (to support user
|
||||
// namespaced daemon with root remapped uid.gid directory)
|
||||
clientConfig, err := d.getClientConfig()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: clientConfig.transport,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", "/info", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.URL.Host = clientConfig.addr
|
||||
req.URL.Scheme = clientConfig.scheme
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
body := ioutils.NewReadCloserWrapper(resp.Body, func() error {
|
||||
return resp.Body.Close()
|
||||
})
|
||||
|
||||
type Info struct {
|
||||
DockerRootDir string
|
||||
}
|
||||
var b []byte
|
||||
var i Info
|
||||
b, err = readBody(body)
|
||||
if err == nil && resp.StatusCode == 200 {
|
||||
// read the docker root dir
|
||||
if err = json.Unmarshal(b, &i); err == nil {
|
||||
return i.DockerRootDir, nil
|
||||
}
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
func (d *Daemon) sock() string {
|
||||
return fmt.Sprintf("unix://%s/docker.sock", d.folder)
|
||||
}
|
||||
|
||||
func (d *Daemon) waitRun(contID string) error {
|
||||
args := []string{"--host", d.sock()}
|
||||
return waitInspectWithArgs(contID, "{{.State.Running}}", "true", 10*time.Second, args...)
|
||||
}
|
||||
|
||||
func (d *Daemon) getBaseDeviceSize(c *check.C) int64 {
|
||||
infoCmdOutput, _, err := runCommandPipelineWithOutput(
|
||||
exec.Command(dockerBinary, "-H", d.sock(), "info"),
|
||||
exec.Command("grep", "Base Device Size"),
|
||||
)
|
||||
c.Assert(err, checker.IsNil)
|
||||
basesizeSlice := strings.Split(infoCmdOutput, ":")
|
||||
basesize := strings.Trim(basesizeSlice[1], " ")
|
||||
basesize = strings.Trim(basesize, "\n")[:len(basesize)-3]
|
||||
basesizeFloat, err := strconv.ParseFloat(strings.Trim(basesize, " "), 64)
|
||||
c.Assert(err, checker.IsNil)
|
||||
basesizeBytes := int64(basesizeFloat) * (1024 * 1024 * 1024)
|
||||
return basesizeBytes
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// CmdWithArgs will execute a docker CLI command against a daemon with the
|
||||
// given additional arguments
|
||||
func (d *Daemon) CmdWithArgs(daemonArgs []string, name string, arg ...string) (string, error) {
|
||||
args := append(daemonArgs, name)
|
||||
args = append(args, arg...)
|
||||
c := exec.Command(dockerBinary, args...)
|
||||
b, err := c.CombinedOutput()
|
||||
return string(b), err
|
||||
}
|
||||
|
||||
// LogFileName returns the path the the daemon's log file
|
||||
func (d *Daemon) LogFileName() string {
|
||||
return d.logFile.Name()
|
||||
}
|
||||
|
||||
func (d *Daemon) getIDByName(name string) (string, error) {
|
||||
return d.inspectFieldWithError(name, "Id")
|
||||
}
|
||||
|
||||
func (d *Daemon) inspectFilter(name, filter string) (string, error) {
|
||||
format := fmt.Sprintf("{{%s}}", filter)
|
||||
out, err := d.Cmd("inspect", "-f", format, name)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to inspect %s: %s", name, out)
|
||||
}
|
||||
return strings.TrimSpace(out), nil
|
||||
}
|
||||
|
||||
func (d *Daemon) inspectFieldWithError(name, field string) (string, error) {
|
||||
return d.inspectFilter(name, fmt.Sprintf(".%s", field))
|
||||
}
|
||||
|
||||
func (d *Daemon) findContainerIP(id string) string {
|
||||
out, err := d.Cmd("inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.bridge.IPAddress }}'"), id)
|
||||
if err != nil {
|
||||
d.c.Log(err)
|
||||
}
|
||||
return strings.Trim(out, " \r\n'")
|
||||
}
|
|
@ -25,11 +25,9 @@ import (
|
|||
"github.com/docker/docker/opts"
|
||||
"github.com/docker/docker/pkg/httputils"
|
||||
"github.com/docker/docker/pkg/integration"
|
||||
"github.com/docker/docker/pkg/integration/checker"
|
||||
"github.com/docker/docker/pkg/ioutils"
|
||||
"github.com/docker/docker/pkg/stringutils"
|
||||
"github.com/docker/engine-api/types"
|
||||
"github.com/docker/go-connections/sockets"
|
||||
"github.com/docker/go-connections/tlsconfig"
|
||||
"github.com/docker/go-units"
|
||||
"github.com/go-check/check"
|
||||
|
@ -102,391 +100,6 @@ func init() {
|
|||
}
|
||||
}
|
||||
|
||||
// Daemon represents a Docker daemon for the testing framework.
|
||||
type Daemon struct {
|
||||
// Defaults to "daemon"
|
||||
// Useful to set to --daemon or -d for checking backwards compatibility
|
||||
Command string
|
||||
GlobalFlags []string
|
||||
|
||||
id string
|
||||
c *check.C
|
||||
logFile *os.File
|
||||
folder string
|
||||
root string
|
||||
stdin io.WriteCloser
|
||||
stdout, stderr io.ReadCloser
|
||||
cmd *exec.Cmd
|
||||
storageDriver string
|
||||
wait chan error
|
||||
userlandProxy bool
|
||||
useDefaultHost bool
|
||||
useDefaultTLSHost bool
|
||||
}
|
||||
|
||||
type clientConfig struct {
|
||||
transport *http.Transport
|
||||
scheme string
|
||||
addr string
|
||||
}
|
||||
|
||||
// NewDaemon returns a Daemon instance to be used for testing.
|
||||
// This will create a directory such as d123456789 in the folder specified by $DEST.
|
||||
// The daemon will not automatically start.
|
||||
func NewDaemon(c *check.C) *Daemon {
|
||||
dest := os.Getenv("DEST")
|
||||
c.Assert(dest, check.Not(check.Equals), "", check.Commentf("Please set the DEST environment variable"))
|
||||
|
||||
id := fmt.Sprintf("d%d", time.Now().UnixNano()%100000000)
|
||||
dir := filepath.Join(dest, id)
|
||||
daemonFolder, err := filepath.Abs(dir)
|
||||
c.Assert(err, check.IsNil, check.Commentf("Could not make %q an absolute path", dir))
|
||||
daemonRoot := filepath.Join(daemonFolder, "root")
|
||||
|
||||
c.Assert(os.MkdirAll(daemonRoot, 0755), check.IsNil, check.Commentf("Could not create daemon root %q", dir))
|
||||
|
||||
userlandProxy := true
|
||||
if env := os.Getenv("DOCKER_USERLANDPROXY"); env != "" {
|
||||
if val, err := strconv.ParseBool(env); err != nil {
|
||||
userlandProxy = val
|
||||
}
|
||||
}
|
||||
|
||||
return &Daemon{
|
||||
Command: "daemon",
|
||||
id: id,
|
||||
c: c,
|
||||
folder: daemonFolder,
|
||||
root: daemonRoot,
|
||||
storageDriver: os.Getenv("DOCKER_GRAPHDRIVER"),
|
||||
userlandProxy: userlandProxy,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *Daemon) getClientConfig() (*clientConfig, error) {
|
||||
var (
|
||||
transport *http.Transport
|
||||
scheme string
|
||||
addr string
|
||||
proto string
|
||||
)
|
||||
if d.useDefaultTLSHost {
|
||||
option := &tlsconfig.Options{
|
||||
CAFile: "fixtures/https/ca.pem",
|
||||
CertFile: "fixtures/https/client-cert.pem",
|
||||
KeyFile: "fixtures/https/client-key.pem",
|
||||
}
|
||||
tlsConfig, err := tlsconfig.Client(*option)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
transport = &http.Transport{
|
||||
TLSClientConfig: tlsConfig,
|
||||
}
|
||||
addr = fmt.Sprintf("%s:%d", opts.DefaultHTTPHost, opts.DefaultTLSHTTPPort)
|
||||
scheme = "https"
|
||||
proto = "tcp"
|
||||
} else if d.useDefaultHost {
|
||||
addr = opts.DefaultUnixSocket
|
||||
proto = "unix"
|
||||
scheme = "http"
|
||||
transport = &http.Transport{}
|
||||
} else {
|
||||
addr = filepath.Join(d.folder, "docker.sock")
|
||||
proto = "unix"
|
||||
scheme = "http"
|
||||
transport = &http.Transport{}
|
||||
}
|
||||
|
||||
d.c.Assert(sockets.ConfigureTransport(transport, proto, addr), check.IsNil)
|
||||
|
||||
return &clientConfig{
|
||||
transport: transport,
|
||||
scheme: scheme,
|
||||
addr: addr,
|
||||
}, nil
|
||||
}
|
||||
|
||||
// Start will start the daemon and return once it is ready to receive requests.
|
||||
// You can specify additional daemon flags.
|
||||
func (d *Daemon) Start(args ...string) error {
|
||||
logFile, err := os.OpenFile(filepath.Join(d.folder, "docker.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600)
|
||||
d.c.Assert(err, check.IsNil, check.Commentf("[%s] Could not create %s/docker.log", d.id, d.folder))
|
||||
|
||||
return d.StartWithLogFile(logFile, args...)
|
||||
}
|
||||
|
||||
// StartWithLogFile will start the daemon and attach its streams to a given file.
|
||||
func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error {
|
||||
dockerBinary, err := exec.LookPath(dockerBinary)
|
||||
d.c.Assert(err, check.IsNil, check.Commentf("[%s] could not find docker binary in $PATH", d.id))
|
||||
|
||||
args := append(d.GlobalFlags,
|
||||
d.Command,
|
||||
"--graph", d.root,
|
||||
"--pidfile", fmt.Sprintf("%s/docker.pid", d.folder),
|
||||
fmt.Sprintf("--userland-proxy=%t", d.userlandProxy),
|
||||
)
|
||||
if !(d.useDefaultHost || d.useDefaultTLSHost) {
|
||||
args = append(args, []string{"--host", d.sock()}...)
|
||||
}
|
||||
if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" {
|
||||
args = append(args, []string{"--userns-remap", root}...)
|
||||
}
|
||||
|
||||
// If we don't explicitly set the log-level or debug flag(-D) then
|
||||
// turn on debug mode
|
||||
foundLog := false
|
||||
foundSd := false
|
||||
for _, a := range providedArgs {
|
||||
if strings.Contains(a, "--log-level") || strings.Contains(a, "-D") || strings.Contains(a, "--debug") {
|
||||
foundLog = true
|
||||
}
|
||||
if strings.Contains(a, "--storage-driver") {
|
||||
foundSd = true
|
||||
}
|
||||
}
|
||||
if !foundLog {
|
||||
args = append(args, "--debug")
|
||||
}
|
||||
if d.storageDriver != "" && !foundSd {
|
||||
args = append(args, "--storage-driver", d.storageDriver)
|
||||
}
|
||||
|
||||
args = append(args, providedArgs...)
|
||||
d.cmd = exec.Command(dockerBinary, args...)
|
||||
|
||||
d.cmd.Stdout = out
|
||||
d.cmd.Stderr = out
|
||||
d.logFile = out
|
||||
|
||||
if err := d.cmd.Start(); err != nil {
|
||||
return fmt.Errorf("[%s] could not start daemon container: %v", d.id, err)
|
||||
}
|
||||
|
||||
wait := make(chan error)
|
||||
|
||||
go func() {
|
||||
wait <- d.cmd.Wait()
|
||||
d.c.Logf("[%s] exiting daemon", d.id)
|
||||
close(wait)
|
||||
}()
|
||||
|
||||
d.wait = wait
|
||||
|
||||
tick := time.Tick(500 * time.Millisecond)
|
||||
// make sure daemon is ready to receive requests
|
||||
startTime := time.Now().Unix()
|
||||
for {
|
||||
d.c.Logf("[%s] waiting for daemon to start", d.id)
|
||||
if time.Now().Unix()-startTime > 5 {
|
||||
// After 5 seconds, give up
|
||||
return fmt.Errorf("[%s] Daemon exited and never started", d.id)
|
||||
}
|
||||
select {
|
||||
case <-time.After(2 * time.Second):
|
||||
return fmt.Errorf("[%s] timeout: daemon does not respond", d.id)
|
||||
case <-tick:
|
||||
clientConfig, err := d.getClientConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: clientConfig.transport,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", "/_ping", nil)
|
||||
d.c.Assert(err, check.IsNil, check.Commentf("[%s] could not create new request", d.id))
|
||||
req.URL.Host = clientConfig.addr
|
||||
req.URL.Scheme = clientConfig.scheme
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if resp.StatusCode != http.StatusOK {
|
||||
d.c.Logf("[%s] received status != 200 OK: %s", d.id, resp.Status)
|
||||
}
|
||||
d.c.Logf("[%s] daemon started", d.id)
|
||||
d.root, err = d.queryRootDir()
|
||||
if err != nil {
|
||||
return fmt.Errorf("[%s] error querying daemon for root directory: %v", d.id, err)
|
||||
}
|
||||
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
|
||||
}
|
||||
return d.LoadBusybox()
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
out1:
|
||||
for {
|
||||
select {
|
||||
case err := <-d.wait:
|
||||
return err
|
||||
case <-time.After(15 * time.Second):
|
||||
// time for stopping jobs and run onShutdown hooks
|
||||
d.c.Log("timeout")
|
||||
break out1
|
||||
}
|
||||
}
|
||||
|
||||
out2:
|
||||
for {
|
||||
select {
|
||||
case err := <-d.wait:
|
||||
return err
|
||||
case <-tick:
|
||||
i++
|
||||
if i > 4 {
|
||||
d.c.Logf("tried to interrupt daemon for %d times, now try to kill it", i)
|
||||
break out2
|
||||
}
|
||||
d.c.Logf("Attempt #%d: daemon is still running with pid %d", i, d.cmd.Process.Pid)
|
||||
if err := d.cmd.Process.Signal(os.Interrupt); err != nil {
|
||||
return fmt.Errorf("could not send signal: %v", err)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := d.cmd.Process.Kill(); err != nil {
|
||||
d.c.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()
|
||||
// in the case of tests running a user namespace-enabled daemon, we have resolved
|
||||
// d.root to be the actual final path of the graph dir after the "uid.gid" of
|
||||
// remapped root is added--we need to subtract it from the path before calling
|
||||
// start or else we will continue making subdirectories rather than truly restarting
|
||||
// with the same location/root:
|
||||
if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" {
|
||||
d.root = filepath.Dir(d.root)
|
||||
}
|
||||
return d.Start(arg...)
|
||||
}
|
||||
|
||||
// LoadBusybox will load the stored busybox into a newly started daemon
|
||||
func (d *Daemon) LoadBusybox() error {
|
||||
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 out, err := d.Cmd("load", "--input", bb); err != nil {
|
||||
return fmt.Errorf("could not load busybox image: %s", out)
|
||||
}
|
||||
if err := os.Remove(bb); err != nil {
|
||||
d.c.Logf("could not remove %s: %v", bb, err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (d *Daemon) queryRootDir() (string, error) {
|
||||
// update daemon root by asking /info endpoint (to support user
|
||||
// namespaced daemon with root remapped uid.gid directory)
|
||||
clientConfig, err := d.getClientConfig()
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
client := &http.Client{
|
||||
Transport: clientConfig.transport,
|
||||
}
|
||||
|
||||
req, err := http.NewRequest("GET", "/info", nil)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
req.Header.Set("Content-Type", "application/json")
|
||||
req.URL.Host = clientConfig.addr
|
||||
req.URL.Scheme = clientConfig.scheme
|
||||
|
||||
resp, err := client.Do(req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
body := ioutils.NewReadCloserWrapper(resp.Body, func() error {
|
||||
return resp.Body.Close()
|
||||
})
|
||||
|
||||
type Info struct {
|
||||
DockerRootDir string
|
||||
}
|
||||
var b []byte
|
||||
var i Info
|
||||
b, err = readBody(body)
|
||||
if err == nil && resp.StatusCode == 200 {
|
||||
// read the docker root dir
|
||||
if err = json.Unmarshal(b, &i); err == nil {
|
||||
return i.DockerRootDir, nil
|
||||
}
|
||||
}
|
||||
return "", err
|
||||
}
|
||||
|
||||
func (d *Daemon) sock() string {
|
||||
return fmt.Sprintf("unix://%s/docker.sock", d.folder)
|
||||
}
|
||||
|
||||
func (d *Daemon) waitRun(contID string) error {
|
||||
args := []string{"--host", d.sock()}
|
||||
return waitInspectWithArgs(contID, "{{.State.Running}}", "true", 10*time.Second, args...)
|
||||
}
|
||||
|
||||
func (d *Daemon) getBaseDeviceSize(c *check.C) int64 {
|
||||
infoCmdOutput, _, err := runCommandPipelineWithOutput(
|
||||
exec.Command(dockerBinary, "-H", d.sock(), "info"),
|
||||
exec.Command("grep", "Base Device Size"),
|
||||
)
|
||||
c.Assert(err, checker.IsNil)
|
||||
basesizeSlice := strings.Split(infoCmdOutput, ":")
|
||||
basesize := strings.Trim(basesizeSlice[1], " ")
|
||||
basesize = strings.Trim(basesize, "\n")[:len(basesize)-3]
|
||||
basesizeFloat, err := strconv.ParseFloat(strings.Trim(basesize, " "), 64)
|
||||
c.Assert(err, checker.IsNil)
|
||||
basesizeBytes := int64(basesizeFloat) * (1024 * 1024 * 1024)
|
||||
return basesizeBytes
|
||||
}
|
||||
|
||||
func convertBasesize(basesizeBytes int64) (int64, error) {
|
||||
basesize := units.HumanSize(float64(basesizeBytes))
|
||||
basesize = strings.Trim(basesize, " ")[:len(basesize)-3]
|
||||
|
@ -497,48 +110,6 @@ func convertBasesize(basesizeBytes int64) (int64, error) {
|
|||
return int64(basesizeFloat) * 1024 * 1024 * 1024, nil
|
||||
}
|
||||
|
||||
// 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
|
||||
}
|
||||
|
||||
// CmdWithArgs will execute a docker CLI command against a daemon with the
|
||||
// given additional arguments
|
||||
func (d *Daemon) CmdWithArgs(daemonArgs []string, name string, arg ...string) (string, error) {
|
||||
args := append(daemonArgs, name)
|
||||
args = append(args, arg...)
|
||||
c := exec.Command(dockerBinary, args...)
|
||||
b, err := c.CombinedOutput()
|
||||
return string(b), err
|
||||
}
|
||||
|
||||
// LogFileName returns the path the the daemon's log file
|
||||
func (d *Daemon) LogFileName() string {
|
||||
return d.logFile.Name()
|
||||
}
|
||||
|
||||
func (d *Daemon) getIDByName(name string) (string, error) {
|
||||
return d.inspectFieldWithError(name, "Id")
|
||||
}
|
||||
|
||||
func (d *Daemon) inspectFilter(name, filter string) (string, error) {
|
||||
format := fmt.Sprintf("{{%s}}", filter)
|
||||
out, err := d.Cmd("inspect", "-f", format, name)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("failed to inspect %s: %s", name, out)
|
||||
}
|
||||
return strings.TrimSpace(out), nil
|
||||
}
|
||||
|
||||
func (d *Daemon) inspectFieldWithError(name, field string) (string, error) {
|
||||
return d.inspectFilter(name, fmt.Sprintf(".%s", field))
|
||||
}
|
||||
|
||||
func daemonHost() string {
|
||||
daemonURLStr := "unix://" + opts.DefaultUnixSocket
|
||||
if daemonHostVar := os.Getenv("DOCKER_HOST"); daemonHostVar != "" {
|
||||
|
@ -972,14 +543,6 @@ func findContainerIP(c *check.C, id string, network string) string {
|
|||
return strings.Trim(out, " \r\n'")
|
||||
}
|
||||
|
||||
func (d *Daemon) findContainerIP(id string) string {
|
||||
out, err := d.Cmd("inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.bridge.IPAddress }}'"), id)
|
||||
if err != nil {
|
||||
d.c.Log(err)
|
||||
}
|
||||
return strings.Trim(out, " \r\n'")
|
||||
}
|
||||
|
||||
func getContainerCount() (int, error) {
|
||||
const containers = "Containers:"
|
||||
|
||||
|
|
Loading…
Reference in a new issue