github.com/rita33cool1/iot-system-gateway@v0.0.0-20200911033302-e65bde238cc5/docker-engine/internal/test/daemon/daemon.go (about) 1 package daemon // import "github.com/docker/docker/internal/test/daemon" 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "io/ioutil" 8 "net/http" 9 "os" 10 "os/exec" 11 "path/filepath" 12 "strconv" 13 "strings" 14 "time" 15 16 "github.com/docker/docker/api/types" 17 "github.com/docker/docker/api/types/events" 18 "github.com/docker/docker/client" 19 "github.com/docker/docker/integration-cli/request" 20 "github.com/docker/docker/opts" 21 "github.com/docker/docker/pkg/ioutils" 22 "github.com/docker/docker/pkg/stringid" 23 "github.com/docker/go-connections/sockets" 24 "github.com/docker/go-connections/tlsconfig" 25 "github.com/gotestyourself/gotestyourself/assert" 26 "github.com/pkg/errors" 27 ) 28 29 type testingT interface { 30 assert.TestingT 31 logT 32 Fatalf(string, ...interface{}) 33 } 34 35 type logT interface { 36 Logf(string, ...interface{}) 37 } 38 39 const defaultDockerdBinary = "dockerd" 40 41 var errDaemonNotStarted = errors.New("daemon not started") 42 43 // SockRoot holds the path of the default docker integration daemon socket 44 var SockRoot = filepath.Join(os.TempDir(), "docker-integration") 45 46 type clientConfig struct { 47 transport *http.Transport 48 scheme string 49 addr string 50 } 51 52 // Daemon represents a Docker daemon for the testing framework 53 type Daemon struct { 54 GlobalFlags []string 55 Root string 56 Folder string 57 Wait chan error 58 UseDefaultHost bool 59 UseDefaultTLSHost bool 60 61 id string 62 logFile *os.File 63 cmd *exec.Cmd 64 storageDriver string 65 userlandProxy bool 66 execRoot string 67 experimental bool 68 dockerdBinary string 69 log logT 70 71 // swarm related field 72 swarmListenAddr string 73 SwarmPort int // FIXME(vdemeester) should probably not be exported 74 75 // cached information 76 CachedInfo types.Info 77 } 78 79 // New returns a Daemon instance to be used for testing. 80 // This will create a directory such as d123456789 in the folder specified by $DOCKER_INTEGRATION_DAEMON_DEST or $DEST. 81 // The daemon will not automatically start. 82 func New(t testingT, ops ...func(*Daemon)) *Daemon { 83 dest := os.Getenv("DOCKER_INTEGRATION_DAEMON_DEST") 84 if dest == "" { 85 dest = os.Getenv("DEST") 86 } 87 assert.Check(t, dest != "", "Please set the DOCKER_INTEGRATION_DAEMON_DEST or the DEST environment variable") 88 89 storageDriver := os.Getenv("DOCKER_GRAPHDRIVER") 90 91 assert.NilError(t, os.MkdirAll(SockRoot, 0700), "could not create daemon socket root") 92 93 id := fmt.Sprintf("d%s", stringid.TruncateID(stringid.GenerateRandomID())) 94 dir := filepath.Join(dest, id) 95 daemonFolder, err := filepath.Abs(dir) 96 assert.NilError(t, err, "Could not make %q an absolute path", dir) 97 daemonRoot := filepath.Join(daemonFolder, "root") 98 99 assert.NilError(t, os.MkdirAll(daemonRoot, 0755), "Could not create daemon root %q", dir) 100 101 userlandProxy := true 102 if env := os.Getenv("DOCKER_USERLANDPROXY"); env != "" { 103 if val, err := strconv.ParseBool(env); err != nil { 104 userlandProxy = val 105 } 106 } 107 d := &Daemon{ 108 id: id, 109 Folder: daemonFolder, 110 Root: daemonRoot, 111 storageDriver: storageDriver, 112 userlandProxy: userlandProxy, 113 execRoot: filepath.Join(os.TempDir(), "docker-execroot", id), 114 dockerdBinary: defaultDockerdBinary, 115 swarmListenAddr: defaultSwarmListenAddr, 116 SwarmPort: defaultSwarmPort, 117 log: t, 118 } 119 120 for _, op := range ops { 121 op(d) 122 } 123 124 return d 125 } 126 127 // RootDir returns the root directory of the daemon. 128 func (d *Daemon) RootDir() string { 129 return d.Root 130 } 131 132 // ID returns the generated id of the daemon 133 func (d *Daemon) ID() string { 134 return d.id 135 } 136 137 // StorageDriver returns the configured storage driver of the daemon 138 func (d *Daemon) StorageDriver() string { 139 return d.storageDriver 140 } 141 142 // Sock returns the socket path of the daemon 143 func (d *Daemon) Sock() string { 144 return fmt.Sprintf("unix://" + d.sockPath()) 145 } 146 147 func (d *Daemon) sockPath() string { 148 return filepath.Join(SockRoot, d.id+".sock") 149 } 150 151 // LogFileName returns the path the daemon's log file 152 func (d *Daemon) LogFileName() string { 153 return d.logFile.Name() 154 } 155 156 // ReadLogFile returns the content of the daemon log file 157 func (d *Daemon) ReadLogFile() ([]byte, error) { 158 return ioutil.ReadFile(d.logFile.Name()) 159 } 160 161 // NewClient creates new client based on daemon's socket path 162 // FIXME(vdemeester): replace NewClient with NewClientT 163 func (d *Daemon) NewClient() (*client.Client, error) { 164 return client.NewClientWithOpts( 165 client.FromEnv, 166 client.WithHost(d.Sock())) 167 } 168 169 // NewClientT creates new client based on daemon's socket path 170 // FIXME(vdemeester): replace NewClient with NewClientT 171 func (d *Daemon) NewClientT(t assert.TestingT) *client.Client { 172 c, err := client.NewClientWithOpts( 173 client.FromEnv, 174 client.WithHost(d.Sock())) 175 assert.NilError(t, err, "cannot create daemon client") 176 return c 177 } 178 179 // CleanupExecRoot cleans the daemon exec root (network namespaces, ...) 180 func (d *Daemon) CleanupExecRoot(t testingT) { 181 cleanupExecRoot(t, d.execRoot) 182 } 183 184 // Start starts the daemon and return once it is ready to receive requests. 185 func (d *Daemon) Start(t testingT, args ...string) { 186 if err := d.StartWithError(args...); err != nil { 187 t.Fatalf("Error starting daemon with arguments: %v", args) 188 } 189 } 190 191 // StartWithError starts the daemon and return once it is ready to receive requests. 192 // It returns an error in case it couldn't start. 193 func (d *Daemon) StartWithError(args ...string) error { 194 logFile, err := os.OpenFile(filepath.Join(d.Folder, "docker.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0600) 195 if err != nil { 196 return errors.Wrapf(err, "[%s] Could not create %s/docker.log", d.id, d.Folder) 197 } 198 199 return d.StartWithLogFile(logFile, args...) 200 } 201 202 // StartWithLogFile will start the daemon and attach its streams to a given file. 203 func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error { 204 dockerdBinary, err := exec.LookPath(d.dockerdBinary) 205 if err != nil { 206 return errors.Wrapf(err, "[%s] could not find docker binary in $PATH", d.id) 207 } 208 args := append(d.GlobalFlags, 209 "--containerd", "/var/run/docker/containerd/docker-containerd.sock", 210 "--data-root", d.Root, 211 "--exec-root", d.execRoot, 212 "--pidfile", fmt.Sprintf("%s/docker.pid", d.Folder), 213 fmt.Sprintf("--userland-proxy=%t", d.userlandProxy), 214 ) 215 if d.experimental { 216 args = append(args, "--experimental", "--init") 217 } 218 if !(d.UseDefaultHost || d.UseDefaultTLSHost) { 219 args = append(args, []string{"--host", d.Sock()}...) 220 } 221 if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" { 222 args = append(args, []string{"--userns-remap", root}...) 223 } 224 225 // If we don't explicitly set the log-level or debug flag(-D) then 226 // turn on debug mode 227 foundLog := false 228 foundSd := false 229 for _, a := range providedArgs { 230 if strings.Contains(a, "--log-level") || strings.Contains(a, "-D") || strings.Contains(a, "--debug") { 231 foundLog = true 232 } 233 if strings.Contains(a, "--storage-driver") { 234 foundSd = true 235 } 236 } 237 if !foundLog { 238 args = append(args, "--debug") 239 } 240 if d.storageDriver != "" && !foundSd { 241 args = append(args, "--storage-driver", d.storageDriver) 242 } 243 244 args = append(args, providedArgs...) 245 d.cmd = exec.Command(dockerdBinary, args...) 246 d.cmd.Env = append(os.Environ(), "DOCKER_SERVICE_PREFER_OFFLINE_IMAGE=1") 247 d.cmd.Stdout = out 248 d.cmd.Stderr = out 249 d.logFile = out 250 251 if err := d.cmd.Start(); err != nil { 252 return errors.Errorf("[%s] could not start daemon container: %v", d.id, err) 253 } 254 255 wait := make(chan error) 256 257 go func() { 258 wait <- d.cmd.Wait() 259 d.log.Logf("[%s] exiting daemon", d.id) 260 close(wait) 261 }() 262 263 d.Wait = wait 264 265 tick := time.Tick(500 * time.Millisecond) 266 // make sure daemon is ready to receive requests 267 startTime := time.Now().Unix() 268 for { 269 d.log.Logf("[%s] waiting for daemon to start", d.id) 270 if time.Now().Unix()-startTime > 5 { 271 // After 5 seconds, give up 272 return errors.Errorf("[%s] Daemon exited and never started", d.id) 273 } 274 select { 275 case <-time.After(2 * time.Second): 276 return errors.Errorf("[%s] timeout: daemon does not respond", d.id) 277 case <-tick: 278 clientConfig, err := d.getClientConfig() 279 if err != nil { 280 return err 281 } 282 283 client := &http.Client{ 284 Transport: clientConfig.transport, 285 } 286 287 req, err := http.NewRequest("GET", "/_ping", nil) 288 if err != nil { 289 return errors.Wrapf(err, "[%s] could not create new request", d.id) 290 } 291 req.URL.Host = clientConfig.addr 292 req.URL.Scheme = clientConfig.scheme 293 resp, err := client.Do(req) 294 if err != nil { 295 continue 296 } 297 resp.Body.Close() 298 if resp.StatusCode != http.StatusOK { 299 d.log.Logf("[%s] received status != 200 OK: %s\n", d.id, resp.Status) 300 } 301 d.log.Logf("[%s] daemon started\n", d.id) 302 d.Root, err = d.queryRootDir() 303 if err != nil { 304 return errors.Errorf("[%s] error querying daemon for root directory: %v", d.id, err) 305 } 306 return nil 307 case <-d.Wait: 308 return errors.Errorf("[%s] Daemon exited during startup", d.id) 309 } 310 } 311 } 312 313 // StartWithBusybox will first start the daemon with Daemon.Start() 314 // then save the busybox image from the main daemon and load it into this Daemon instance. 315 func (d *Daemon) StartWithBusybox(t testingT, arg ...string) { 316 d.Start(t, arg...) 317 d.LoadBusybox(t) 318 } 319 320 // Kill will send a SIGKILL to the daemon 321 func (d *Daemon) Kill() error { 322 if d.cmd == nil || d.Wait == nil { 323 return errDaemonNotStarted 324 } 325 326 defer func() { 327 d.logFile.Close() 328 d.cmd = nil 329 }() 330 331 if err := d.cmd.Process.Kill(); err != nil { 332 return err 333 } 334 335 return os.Remove(fmt.Sprintf("%s/docker.pid", d.Folder)) 336 } 337 338 // Pid returns the pid of the daemon 339 func (d *Daemon) Pid() int { 340 return d.cmd.Process.Pid 341 } 342 343 // Interrupt stops the daemon by sending it an Interrupt signal 344 func (d *Daemon) Interrupt() error { 345 return d.Signal(os.Interrupt) 346 } 347 348 // Signal sends the specified signal to the daemon if running 349 func (d *Daemon) Signal(signal os.Signal) error { 350 if d.cmd == nil || d.Wait == nil { 351 return errDaemonNotStarted 352 } 353 return d.cmd.Process.Signal(signal) 354 } 355 356 // DumpStackAndQuit sends SIGQUIT to the daemon, which triggers it to dump its 357 // stack to its log file and exit 358 // This is used primarily for gathering debug information on test timeout 359 func (d *Daemon) DumpStackAndQuit() { 360 if d.cmd == nil || d.cmd.Process == nil { 361 return 362 } 363 SignalDaemonDump(d.cmd.Process.Pid) 364 } 365 366 // Stop will send a SIGINT every second and wait for the daemon to stop. 367 // If it times out, a SIGKILL is sent. 368 // Stop will not delete the daemon directory. If a purged daemon is needed, 369 // instantiate a new one with NewDaemon. 370 // If an error occurs while starting the daemon, the test will fail. 371 func (d *Daemon) Stop(t testingT) { 372 err := d.StopWithError() 373 if err != nil { 374 if err != errDaemonNotStarted { 375 t.Fatalf("Error while stopping the daemon %s : %v", d.id, err) 376 } else { 377 t.Logf("Daemon %s is not started", d.id) 378 } 379 } 380 } 381 382 // StopWithError will send a SIGINT every second and wait for the daemon to stop. 383 // If it timeouts, a SIGKILL is sent. 384 // Stop will not delete the daemon directory. If a purged daemon is needed, 385 // instantiate a new one with NewDaemon. 386 func (d *Daemon) StopWithError() error { 387 if d.cmd == nil || d.Wait == nil { 388 return errDaemonNotStarted 389 } 390 391 defer func() { 392 d.logFile.Close() 393 d.cmd = nil 394 }() 395 396 i := 1 397 tick := time.Tick(time.Second) 398 399 if err := d.cmd.Process.Signal(os.Interrupt); err != nil { 400 if strings.Contains(err.Error(), "os: process already finished") { 401 return errDaemonNotStarted 402 } 403 return errors.Errorf("could not send signal: %v", err) 404 } 405 out1: 406 for { 407 select { 408 case err := <-d.Wait: 409 return err 410 case <-time.After(20 * time.Second): 411 // time for stopping jobs and run onShutdown hooks 412 d.log.Logf("[%s] daemon started", d.id) 413 break out1 414 } 415 } 416 417 out2: 418 for { 419 select { 420 case err := <-d.Wait: 421 return err 422 case <-tick: 423 i++ 424 if i > 5 { 425 d.log.Logf("tried to interrupt daemon for %d times, now try to kill it", i) 426 break out2 427 } 428 d.log.Logf("Attempt #%d: daemon is still running with pid %d", i, d.cmd.Process.Pid) 429 if err := d.cmd.Process.Signal(os.Interrupt); err != nil { 430 return errors.Errorf("could not send signal: %v", err) 431 } 432 } 433 } 434 435 if err := d.cmd.Process.Kill(); err != nil { 436 d.log.Logf("Could not kill daemon: %v", err) 437 return err 438 } 439 440 d.cmd.Wait() 441 442 return os.Remove(fmt.Sprintf("%s/docker.pid", d.Folder)) 443 } 444 445 // Restart will restart the daemon by first stopping it and the starting it. 446 // If an error occurs while starting the daemon, the test will fail. 447 func (d *Daemon) Restart(t testingT, args ...string) { 448 d.Stop(t) 449 d.handleUserns() 450 d.Start(t, args...) 451 } 452 453 // RestartWithError will restart the daemon by first stopping it and then starting it. 454 func (d *Daemon) RestartWithError(arg ...string) error { 455 if err := d.StopWithError(); err != nil { 456 return err 457 } 458 d.handleUserns() 459 return d.StartWithError(arg...) 460 } 461 462 func (d *Daemon) handleUserns() { 463 // in the case of tests running a user namespace-enabled daemon, we have resolved 464 // d.Root to be the actual final path of the graph dir after the "uid.gid" of 465 // remapped root is added--we need to subtract it from the path before calling 466 // start or else we will continue making subdirectories rather than truly restarting 467 // with the same location/root: 468 if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" { 469 d.Root = filepath.Dir(d.Root) 470 } 471 } 472 473 // ReloadConfig asks the daemon to reload its configuration 474 func (d *Daemon) ReloadConfig() error { 475 if d.cmd == nil || d.cmd.Process == nil { 476 return errors.New("daemon is not running") 477 } 478 479 errCh := make(chan error) 480 started := make(chan struct{}) 481 go func() { 482 _, body, err := request.DoOnHost(d.Sock(), "/events", request.Method(http.MethodGet)) 483 close(started) 484 if err != nil { 485 errCh <- err 486 } 487 defer body.Close() 488 dec := json.NewDecoder(body) 489 for { 490 var e events.Message 491 if err := dec.Decode(&e); err != nil { 492 errCh <- err 493 return 494 } 495 if e.Type != events.DaemonEventType { 496 continue 497 } 498 if e.Action != "reload" { 499 continue 500 } 501 close(errCh) // notify that we are done 502 return 503 } 504 }() 505 506 <-started 507 if err := signalDaemonReload(d.cmd.Process.Pid); err != nil { 508 return errors.Errorf("error signaling daemon reload: %v", err) 509 } 510 select { 511 case err := <-errCh: 512 if err != nil { 513 return errors.Errorf("error waiting for daemon reload event: %v", err) 514 } 515 case <-time.After(30 * time.Second): 516 return errors.New("timeout waiting for daemon reload event") 517 } 518 return nil 519 } 520 521 // LoadBusybox image into the daemon 522 func (d *Daemon) LoadBusybox(t assert.TestingT) { 523 clientHost, err := client.NewEnvClient() 524 assert.NilError(t, err, "failed to create client") 525 defer clientHost.Close() 526 527 ctx := context.Background() 528 reader, err := clientHost.ImageSave(ctx, []string{"busybox:latest"}) 529 assert.NilError(t, err, "failed to download busybox") 530 defer reader.Close() 531 532 client, err := d.NewClient() 533 assert.NilError(t, err, "failed to create client") 534 defer client.Close() 535 536 resp, err := client.ImageLoad(ctx, reader, true) 537 assert.NilError(t, err, "failed to load busybox") 538 defer resp.Body.Close() 539 } 540 541 func (d *Daemon) getClientConfig() (*clientConfig, error) { 542 var ( 543 transport *http.Transport 544 scheme string 545 addr string 546 proto string 547 ) 548 if d.UseDefaultTLSHost { 549 option := &tlsconfig.Options{ 550 CAFile: "fixtures/https/ca.pem", 551 CertFile: "fixtures/https/client-cert.pem", 552 KeyFile: "fixtures/https/client-key.pem", 553 } 554 tlsConfig, err := tlsconfig.Client(*option) 555 if err != nil { 556 return nil, err 557 } 558 transport = &http.Transport{ 559 TLSClientConfig: tlsConfig, 560 } 561 addr = fmt.Sprintf("%s:%d", opts.DefaultHTTPHost, opts.DefaultTLSHTTPPort) 562 scheme = "https" 563 proto = "tcp" 564 } else if d.UseDefaultHost { 565 addr = opts.DefaultUnixSocket 566 proto = "unix" 567 scheme = "http" 568 transport = &http.Transport{} 569 } else { 570 addr = d.sockPath() 571 proto = "unix" 572 scheme = "http" 573 transport = &http.Transport{} 574 } 575 576 if err := sockets.ConfigureTransport(transport, proto, addr); err != nil { 577 return nil, err 578 } 579 transport.DisableKeepAlives = true 580 581 return &clientConfig{ 582 transport: transport, 583 scheme: scheme, 584 addr: addr, 585 }, nil 586 } 587 588 func (d *Daemon) queryRootDir() (string, error) { 589 // update daemon root by asking /info endpoint (to support user 590 // namespaced daemon with root remapped uid.gid directory) 591 clientConfig, err := d.getClientConfig() 592 if err != nil { 593 return "", err 594 } 595 596 client := &http.Client{ 597 Transport: clientConfig.transport, 598 } 599 600 req, err := http.NewRequest("GET", "/info", nil) 601 if err != nil { 602 return "", err 603 } 604 req.Header.Set("Content-Type", "application/json") 605 req.URL.Host = clientConfig.addr 606 req.URL.Scheme = clientConfig.scheme 607 608 resp, err := client.Do(req) 609 if err != nil { 610 return "", err 611 } 612 body := ioutils.NewReadCloserWrapper(resp.Body, func() error { 613 return resp.Body.Close() 614 }) 615 616 type Info struct { 617 DockerRootDir string 618 } 619 var b []byte 620 var i Info 621 b, err = request.ReadBody(body) 622 if err == nil && resp.StatusCode == http.StatusOK { 623 // read the docker root dir 624 if err = json.Unmarshal(b, &i); err == nil { 625 return i.DockerRootDir, nil 626 } 627 } 628 return "", err 629 } 630 631 // Info returns the info struct for this daemon 632 func (d *Daemon) Info(t assert.TestingT) types.Info { 633 apiclient, err := d.NewClient() 634 assert.NilError(t, err) 635 info, err := apiclient.Info(context.Background()) 636 assert.NilError(t, err) 637 return info 638 }