github.com/hms58/moby@v1.13.1/integration-cli/daemon.go (about) 1 package main 2 3 import ( 4 "bytes" 5 "encoding/json" 6 "errors" 7 "fmt" 8 "io" 9 "net/http" 10 "os" 11 "os/exec" 12 "path/filepath" 13 "strconv" 14 "strings" 15 "time" 16 17 "github.com/docker/docker/api/types/events" 18 "github.com/docker/docker/opts" 19 "github.com/docker/docker/pkg/integration/checker" 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/go-check/check" 25 ) 26 27 var daemonSockRoot = filepath.Join(os.TempDir(), "docker-integration") 28 29 // Daemon represents a Docker daemon for the testing framework. 30 type Daemon struct { 31 GlobalFlags []string 32 33 id string 34 c *check.C 35 logFile *os.File 36 folder string 37 root string 38 stdin io.WriteCloser 39 stdout, stderr io.ReadCloser 40 cmd *exec.Cmd 41 storageDriver string 42 wait chan error 43 userlandProxy bool 44 useDefaultHost bool 45 useDefaultTLSHost bool 46 execRoot string 47 } 48 49 type clientConfig struct { 50 transport *http.Transport 51 scheme string 52 addr string 53 } 54 55 // NewDaemon returns a Daemon instance to be used for testing. 56 // This will create a directory such as d123456789 in the folder specified by $DEST. 57 // The daemon will not automatically start. 58 func NewDaemon(c *check.C) *Daemon { 59 dest := os.Getenv("DEST") 60 c.Assert(dest, check.Not(check.Equals), "", check.Commentf("Please set the DEST environment variable")) 61 62 err := os.MkdirAll(daemonSockRoot, 0700) 63 c.Assert(err, checker.IsNil, check.Commentf("could not create daemon socket root")) 64 65 id := fmt.Sprintf("d%s", stringid.TruncateID(stringid.GenerateRandomID())) 66 dir := filepath.Join(dest, id) 67 daemonFolder, err := filepath.Abs(dir) 68 c.Assert(err, check.IsNil, check.Commentf("Could not make %q an absolute path", dir)) 69 daemonRoot := filepath.Join(daemonFolder, "root") 70 71 c.Assert(os.MkdirAll(daemonRoot, 0755), check.IsNil, check.Commentf("Could not create daemon root %q", dir)) 72 73 userlandProxy := true 74 if env := os.Getenv("DOCKER_USERLANDPROXY"); env != "" { 75 if val, err := strconv.ParseBool(env); err != nil { 76 userlandProxy = val 77 } 78 } 79 80 return &Daemon{ 81 id: id, 82 c: c, 83 folder: daemonFolder, 84 root: daemonRoot, 85 storageDriver: os.Getenv("DOCKER_GRAPHDRIVER"), 86 userlandProxy: userlandProxy, 87 execRoot: filepath.Join(os.TempDir(), "docker-execroot", id), 88 } 89 } 90 91 // RootDir returns the root directory of the daemon. 92 func (d *Daemon) RootDir() string { 93 return d.root 94 } 95 96 func (d *Daemon) getClientConfig() (*clientConfig, error) { 97 var ( 98 transport *http.Transport 99 scheme string 100 addr string 101 proto string 102 ) 103 if d.useDefaultTLSHost { 104 option := &tlsconfig.Options{ 105 CAFile: "fixtures/https/ca.pem", 106 CertFile: "fixtures/https/client-cert.pem", 107 KeyFile: "fixtures/https/client-key.pem", 108 } 109 tlsConfig, err := tlsconfig.Client(*option) 110 if err != nil { 111 return nil, err 112 } 113 transport = &http.Transport{ 114 TLSClientConfig: tlsConfig, 115 } 116 addr = fmt.Sprintf("%s:%d", opts.DefaultHTTPHost, opts.DefaultTLSHTTPPort) 117 scheme = "https" 118 proto = "tcp" 119 } else if d.useDefaultHost { 120 addr = opts.DefaultUnixSocket 121 proto = "unix" 122 scheme = "http" 123 transport = &http.Transport{} 124 } else { 125 addr = d.sockPath() 126 proto = "unix" 127 scheme = "http" 128 transport = &http.Transport{} 129 } 130 131 d.c.Assert(sockets.ConfigureTransport(transport, proto, addr), check.IsNil) 132 133 return &clientConfig{ 134 transport: transport, 135 scheme: scheme, 136 addr: addr, 137 }, nil 138 } 139 140 // Start will start the daemon and return once it is ready to receive requests. 141 // You can specify additional daemon flags. 142 func (d *Daemon) Start(args ...string) error { 143 logFile, err := os.OpenFile(filepath.Join(d.folder, "docker.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) 144 d.c.Assert(err, check.IsNil, check.Commentf("[%s] Could not create %s/docker.log", d.id, d.folder)) 145 146 return d.StartWithLogFile(logFile, args...) 147 } 148 149 // StartWithLogFile will start the daemon and attach its streams to a given file. 150 func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error { 151 dockerdBinary, err := exec.LookPath(dockerdBinary) 152 d.c.Assert(err, check.IsNil, check.Commentf("[%s] could not find docker binary in $PATH", d.id)) 153 154 args := append(d.GlobalFlags, 155 "--containerd", "/var/run/docker/libcontainerd/docker-containerd.sock", 156 "--graph", d.root, 157 "--exec-root", d.execRoot, 158 "--pidfile", fmt.Sprintf("%s/docker.pid", d.folder), 159 fmt.Sprintf("--userland-proxy=%t", d.userlandProxy), 160 ) 161 if experimentalDaemon { 162 args = append(args, "--experimental", "--init") 163 } 164 if !(d.useDefaultHost || d.useDefaultTLSHost) { 165 args = append(args, []string{"--host", d.sock()}...) 166 } 167 if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" { 168 args = append(args, []string{"--userns-remap", root}...) 169 } 170 171 // If we don't explicitly set the log-level or debug flag(-D) then 172 // turn on debug mode 173 foundLog := false 174 foundSd := false 175 for _, a := range providedArgs { 176 if strings.Contains(a, "--log-level") || strings.Contains(a, "-D") || strings.Contains(a, "--debug") { 177 foundLog = true 178 } 179 if strings.Contains(a, "--storage-driver") { 180 foundSd = true 181 } 182 } 183 if !foundLog { 184 args = append(args, "--debug") 185 } 186 if d.storageDriver != "" && !foundSd { 187 args = append(args, "--storage-driver", d.storageDriver) 188 } 189 190 args = append(args, providedArgs...) 191 d.cmd = exec.Command(dockerdBinary, args...) 192 d.cmd.Env = append(os.Environ(), "DOCKER_SERVICE_PREFER_OFFLINE_IMAGE=1") 193 d.cmd.Stdout = out 194 d.cmd.Stderr = out 195 d.logFile = out 196 197 if err := d.cmd.Start(); err != nil { 198 return fmt.Errorf("[%s] could not start daemon container: %v", d.id, err) 199 } 200 201 wait := make(chan error) 202 203 go func() { 204 wait <- d.cmd.Wait() 205 d.c.Logf("[%s] exiting daemon", d.id) 206 close(wait) 207 }() 208 209 d.wait = wait 210 211 tick := time.Tick(500 * time.Millisecond) 212 // make sure daemon is ready to receive requests 213 startTime := time.Now().Unix() 214 for { 215 d.c.Logf("[%s] waiting for daemon to start", d.id) 216 if time.Now().Unix()-startTime > 5 { 217 // After 5 seconds, give up 218 return fmt.Errorf("[%s] Daemon exited and never started", d.id) 219 } 220 select { 221 case <-time.After(2 * time.Second): 222 return fmt.Errorf("[%s] timeout: daemon does not respond", d.id) 223 case <-tick: 224 clientConfig, err := d.getClientConfig() 225 if err != nil { 226 return err 227 } 228 229 client := &http.Client{ 230 Transport: clientConfig.transport, 231 } 232 233 req, err := http.NewRequest("GET", "/_ping", nil) 234 d.c.Assert(err, check.IsNil, check.Commentf("[%s] could not create new request", d.id)) 235 req.URL.Host = clientConfig.addr 236 req.URL.Scheme = clientConfig.scheme 237 resp, err := client.Do(req) 238 if err != nil { 239 continue 240 } 241 if resp.StatusCode != http.StatusOK { 242 d.c.Logf("[%s] received status != 200 OK: %s", d.id, resp.Status) 243 } 244 d.c.Logf("[%s] daemon started", d.id) 245 d.root, err = d.queryRootDir() 246 if err != nil { 247 return fmt.Errorf("[%s] error querying daemon for root directory: %v", d.id, err) 248 } 249 return nil 250 case <-d.wait: 251 return fmt.Errorf("[%s] Daemon exited during startup", d.id) 252 } 253 } 254 } 255 256 // StartWithBusybox will first start the daemon with Daemon.Start() 257 // then save the busybox image from the main daemon and load it into this Daemon instance. 258 func (d *Daemon) StartWithBusybox(arg ...string) error { 259 if err := d.Start(arg...); err != nil { 260 return err 261 } 262 return d.LoadBusybox() 263 } 264 265 // Kill will send a SIGKILL to the daemon 266 func (d *Daemon) Kill() error { 267 if d.cmd == nil || d.wait == nil { 268 return errors.New("daemon not started") 269 } 270 271 defer func() { 272 d.logFile.Close() 273 d.cmd = nil 274 }() 275 276 if err := d.cmd.Process.Kill(); err != nil { 277 d.c.Logf("Could not kill daemon: %v", err) 278 return err 279 } 280 281 if err := os.Remove(fmt.Sprintf("%s/docker.pid", d.folder)); err != nil { 282 return err 283 } 284 285 return nil 286 } 287 288 // DumpStackAndQuit sends SIGQUIT to the daemon, which triggers it to dump its 289 // stack to its log file and exit 290 // This is used primarily for gathering debug information on test timeout 291 func (d *Daemon) DumpStackAndQuit() { 292 if d.cmd == nil || d.cmd.Process == nil { 293 return 294 } 295 signalDaemonDump(d.cmd.Process.Pid) 296 } 297 298 // Stop will send a SIGINT every second and wait for the daemon to stop. 299 // If it timeouts, a SIGKILL is sent. 300 // Stop will not delete the daemon directory. If a purged daemon is needed, 301 // instantiate a new one with NewDaemon. 302 func (d *Daemon) Stop() error { 303 if d.cmd == nil || d.wait == nil { 304 return errors.New("daemon not started") 305 } 306 307 defer func() { 308 d.logFile.Close() 309 d.cmd = nil 310 }() 311 312 i := 1 313 tick := time.Tick(time.Second) 314 315 if err := d.cmd.Process.Signal(os.Interrupt); err != nil { 316 return fmt.Errorf("could not send signal: %v", err) 317 } 318 out1: 319 for { 320 select { 321 case err := <-d.wait: 322 return err 323 case <-time.After(20 * time.Second): 324 // time for stopping jobs and run onShutdown hooks 325 d.c.Logf("timeout: %v", d.id) 326 break out1 327 } 328 } 329 330 out2: 331 for { 332 select { 333 case err := <-d.wait: 334 return err 335 case <-tick: 336 i++ 337 if i > 5 { 338 d.c.Logf("tried to interrupt daemon for %d times, now try to kill it", i) 339 break out2 340 } 341 d.c.Logf("Attempt #%d: daemon is still running with pid %d", i, d.cmd.Process.Pid) 342 if err := d.cmd.Process.Signal(os.Interrupt); err != nil { 343 return fmt.Errorf("could not send signal: %v", err) 344 } 345 } 346 } 347 348 if err := d.cmd.Process.Kill(); err != nil { 349 d.c.Logf("Could not kill daemon: %v", err) 350 return err 351 } 352 353 if err := os.Remove(fmt.Sprintf("%s/docker.pid", d.folder)); err != nil { 354 return err 355 } 356 357 return nil 358 } 359 360 // Restart will restart the daemon by first stopping it and then starting it. 361 func (d *Daemon) Restart(arg ...string) error { 362 d.Stop() 363 // in the case of tests running a user namespace-enabled daemon, we have resolved 364 // d.root to be the actual final path of the graph dir after the "uid.gid" of 365 // remapped root is added--we need to subtract it from the path before calling 366 // start or else we will continue making subdirectories rather than truly restarting 367 // with the same location/root: 368 if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" { 369 d.root = filepath.Dir(d.root) 370 } 371 return d.Start(arg...) 372 } 373 374 // LoadBusybox will load the stored busybox into a newly started daemon 375 func (d *Daemon) LoadBusybox() error { 376 bb := filepath.Join(d.folder, "busybox.tar") 377 if _, err := os.Stat(bb); err != nil { 378 if !os.IsNotExist(err) { 379 return fmt.Errorf("unexpected error on busybox.tar stat: %v", err) 380 } 381 // saving busybox image from main daemon 382 if out, err := exec.Command(dockerBinary, "save", "--output", bb, "busybox:latest").CombinedOutput(); err != nil { 383 imagesOut, _ := exec.Command(dockerBinary, "images", "--format", "{{ .Repository }}:{{ .Tag }}").CombinedOutput() 384 return fmt.Errorf("could not save busybox image: %s\n%s", string(out), strings.TrimSpace(string(imagesOut))) 385 } 386 } 387 // loading busybox image to this daemon 388 if out, err := d.Cmd("load", "--input", bb); err != nil { 389 return fmt.Errorf("could not load busybox image: %s", out) 390 } 391 if err := os.Remove(bb); err != nil { 392 d.c.Logf("could not remove %s: %v", bb, err) 393 } 394 return nil 395 } 396 397 func (d *Daemon) queryRootDir() (string, error) { 398 // update daemon root by asking /info endpoint (to support user 399 // namespaced daemon with root remapped uid.gid directory) 400 clientConfig, err := d.getClientConfig() 401 if err != nil { 402 return "", err 403 } 404 405 client := &http.Client{ 406 Transport: clientConfig.transport, 407 } 408 409 req, err := http.NewRequest("GET", "/info", nil) 410 if err != nil { 411 return "", err 412 } 413 req.Header.Set("Content-Type", "application/json") 414 req.URL.Host = clientConfig.addr 415 req.URL.Scheme = clientConfig.scheme 416 417 resp, err := client.Do(req) 418 if err != nil { 419 return "", err 420 } 421 body := ioutils.NewReadCloserWrapper(resp.Body, func() error { 422 return resp.Body.Close() 423 }) 424 425 type Info struct { 426 DockerRootDir string 427 } 428 var b []byte 429 var i Info 430 b, err = readBody(body) 431 if err == nil && resp.StatusCode == http.StatusOK { 432 // read the docker root dir 433 if err = json.Unmarshal(b, &i); err == nil { 434 return i.DockerRootDir, nil 435 } 436 } 437 return "", err 438 } 439 440 func (d *Daemon) sock() string { 441 return fmt.Sprintf("unix://" + d.sockPath()) 442 } 443 444 func (d *Daemon) sockPath() string { 445 return filepath.Join(daemonSockRoot, d.id+".sock") 446 } 447 448 func (d *Daemon) waitRun(contID string) error { 449 args := []string{"--host", d.sock()} 450 return waitInspectWithArgs(contID, "{{.State.Running}}", "true", 10*time.Second, args...) 451 } 452 453 func (d *Daemon) getBaseDeviceSize(c *check.C) int64 { 454 infoCmdOutput, _, err := runCommandPipelineWithOutput( 455 exec.Command(dockerBinary, "-H", d.sock(), "info"), 456 exec.Command("grep", "Base Device Size"), 457 ) 458 c.Assert(err, checker.IsNil) 459 basesizeSlice := strings.Split(infoCmdOutput, ":") 460 basesize := strings.Trim(basesizeSlice[1], " ") 461 basesize = strings.Trim(basesize, "\n")[:len(basesize)-3] 462 basesizeFloat, err := strconv.ParseFloat(strings.Trim(basesize, " "), 64) 463 c.Assert(err, checker.IsNil) 464 basesizeBytes := int64(basesizeFloat) * (1024 * 1024 * 1024) 465 return basesizeBytes 466 } 467 468 // Cmd will execute a docker CLI command against this Daemon. 469 // Example: d.Cmd("version") will run docker -H unix://path/to/unix.sock version 470 func (d *Daemon) Cmd(args ...string) (string, error) { 471 b, err := d.command(args...).CombinedOutput() 472 return string(b), err 473 } 474 475 func (d *Daemon) command(args ...string) *exec.Cmd { 476 return exec.Command(dockerBinary, d.prependHostArg(args)...) 477 } 478 479 func (d *Daemon) prependHostArg(args []string) []string { 480 for _, arg := range args { 481 if arg == "--host" || arg == "-H" { 482 return args 483 } 484 } 485 return append([]string{"--host", d.sock()}, args...) 486 } 487 488 // SockRequest executes a socket request on a daemon and returns statuscode and output. 489 func (d *Daemon) SockRequest(method, endpoint string, data interface{}) (int, []byte, error) { 490 jsonData := bytes.NewBuffer(nil) 491 if err := json.NewEncoder(jsonData).Encode(data); err != nil { 492 return -1, nil, err 493 } 494 495 res, body, err := d.SockRequestRaw(method, endpoint, jsonData, "application/json") 496 if err != nil { 497 return -1, nil, err 498 } 499 b, err := readBody(body) 500 return res.StatusCode, b, err 501 } 502 503 // SockRequestRaw executes a socket request on a daemon and returns an http 504 // response and a reader for the output data. 505 func (d *Daemon) SockRequestRaw(method, endpoint string, data io.Reader, ct string) (*http.Response, io.ReadCloser, error) { 506 return sockRequestRawToDaemon(method, endpoint, data, ct, d.sock()) 507 } 508 509 // LogFileName returns the path the the daemon's log file 510 func (d *Daemon) LogFileName() string { 511 return d.logFile.Name() 512 } 513 514 func (d *Daemon) getIDByName(name string) (string, error) { 515 return d.inspectFieldWithError(name, "Id") 516 } 517 518 func (d *Daemon) activeContainers() (ids []string) { 519 out, _ := d.Cmd("ps", "-q") 520 for _, id := range strings.Split(out, "\n") { 521 if id = strings.TrimSpace(id); id != "" { 522 ids = append(ids, id) 523 } 524 } 525 return 526 } 527 528 func (d *Daemon) inspectFilter(name, filter string) (string, error) { 529 format := fmt.Sprintf("{{%s}}", filter) 530 out, err := d.Cmd("inspect", "-f", format, name) 531 if err != nil { 532 return "", fmt.Errorf("failed to inspect %s: %s", name, out) 533 } 534 return strings.TrimSpace(out), nil 535 } 536 537 func (d *Daemon) inspectFieldWithError(name, field string) (string, error) { 538 return d.inspectFilter(name, fmt.Sprintf(".%s", field)) 539 } 540 541 func (d *Daemon) findContainerIP(id string) string { 542 out, err := d.Cmd("inspect", fmt.Sprintf("--format='{{ .NetworkSettings.Networks.bridge.IPAddress }}'"), id) 543 if err != nil { 544 d.c.Log(err) 545 } 546 return strings.Trim(out, " \r\n'") 547 } 548 549 func (d *Daemon) buildImageWithOut(name, dockerfile string, useCache bool, buildFlags ...string) (string, int, error) { 550 buildCmd := buildImageCmdWithHost(name, dockerfile, d.sock(), useCache, buildFlags...) 551 return runCommandWithOutput(buildCmd) 552 } 553 554 func (d *Daemon) checkActiveContainerCount(c *check.C) (interface{}, check.CommentInterface) { 555 out, err := d.Cmd("ps", "-q") 556 c.Assert(err, checker.IsNil) 557 if len(strings.TrimSpace(out)) == 0 { 558 return 0, nil 559 } 560 return len(strings.Split(strings.TrimSpace(out), "\n")), check.Commentf("output: %q", string(out)) 561 } 562 563 func (d *Daemon) reloadConfig() error { 564 if d.cmd == nil || d.cmd.Process == nil { 565 return fmt.Errorf("daemon is not running") 566 } 567 568 errCh := make(chan error) 569 started := make(chan struct{}) 570 go func() { 571 _, body, err := sockRequestRawToDaemon("GET", "/events", nil, "", d.sock()) 572 close(started) 573 if err != nil { 574 errCh <- err 575 } 576 defer body.Close() 577 dec := json.NewDecoder(body) 578 for { 579 var e events.Message 580 if err := dec.Decode(&e); err != nil { 581 errCh <- err 582 return 583 } 584 if e.Type != events.DaemonEventType { 585 continue 586 } 587 if e.Action != "reload" { 588 continue 589 } 590 close(errCh) // notify that we are done 591 return 592 } 593 }() 594 595 <-started 596 if err := signalDaemonReload(d.cmd.Process.Pid); err != nil { 597 return fmt.Errorf("error signaling daemon reload: %v", err) 598 } 599 select { 600 case err := <-errCh: 601 if err != nil { 602 return fmt.Errorf("error waiting for daemon reload event: %v", err) 603 } 604 case <-time.After(30 * time.Second): 605 return fmt.Errorf("timeout waiting for daemon reload event") 606 } 607 return nil 608 }