… 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>
| ... | ... |
@@ -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) |
| ... | ... |
@@ -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 {
|
| ... | ... |
@@ -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 |
|
| ... | ... |
@@ -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 |
|
| ... | ... |
@@ -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 |
+} |