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