daemon.go 27 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993
  1. package daemon // import "github.com/docker/docker/testutil/daemon"
  2. import (
  3. "bufio"
  4. "context"
  5. "encoding/json"
  6. "io"
  7. "net/http"
  8. "os"
  9. "os/exec"
  10. "os/user"
  11. "path/filepath"
  12. "strconv"
  13. "strings"
  14. "testing"
  15. "time"
  16. "github.com/docker/docker/api/types/events"
  17. "github.com/docker/docker/api/types/system"
  18. "github.com/docker/docker/client"
  19. "github.com/docker/docker/container"
  20. "github.com/docker/docker/pkg/ioutils"
  21. "github.com/docker/docker/pkg/stringid"
  22. "github.com/docker/docker/pkg/tailfile"
  23. "github.com/docker/docker/testutil/request"
  24. "github.com/docker/go-connections/sockets"
  25. "github.com/docker/go-connections/tlsconfig"
  26. "github.com/pkg/errors"
  27. "gotest.tools/v3/assert"
  28. "gotest.tools/v3/poll"
  29. )
  30. // LogT is the subset of the testing.TB interface used by the daemon.
  31. type LogT interface {
  32. Logf(string, ...interface{})
  33. }
  34. // nopLog is a no-op implementation of LogT that is used in daemons created by
  35. // NewDaemon (where no testing.TB is available).
  36. type nopLog struct{}
  37. func (nopLog) Logf(string, ...interface{}) {}
  38. const (
  39. defaultDockerdBinary = "dockerd"
  40. defaultContainerdSocket = "/var/run/docker/containerd/containerd.sock"
  41. defaultDockerdRootlessBinary = "dockerd-rootless.sh"
  42. defaultUnixSocket = "/var/run/docker.sock"
  43. defaultTLSHost = "localhost:2376"
  44. )
  45. var errDaemonNotStarted = errors.New("daemon not started")
  46. // SockRoot holds the path of the default docker integration daemon socket
  47. var SockRoot = filepath.Join(os.TempDir(), "docker-integration")
  48. type clientConfig struct {
  49. transport *http.Transport
  50. scheme string
  51. addr string
  52. }
  53. // Daemon represents a Docker daemon for the testing framework
  54. type Daemon struct {
  55. Root string
  56. Folder string
  57. Wait chan error
  58. UseDefaultHost bool
  59. UseDefaultTLSHost bool
  60. id string
  61. logFile *os.File
  62. cmd *exec.Cmd
  63. storageDriver string
  64. userlandProxy bool
  65. defaultCgroupNamespaceMode string
  66. execRoot string
  67. experimental bool
  68. init bool
  69. dockerdBinary string
  70. log LogT
  71. pidFile string
  72. args []string
  73. extraEnv []string
  74. containerdSocket string
  75. usernsRemap string
  76. rootlessUser *user.User
  77. rootlessXDGRuntimeDir string
  78. // swarm related field
  79. swarmListenAddr string
  80. SwarmPort int // FIXME(vdemeester) should probably not be exported
  81. DefaultAddrPool []string
  82. SubnetSize uint32
  83. DataPathPort uint32
  84. OOMScoreAdjust int
  85. // cached information
  86. CachedInfo system.Info
  87. }
  88. // NewDaemon returns a Daemon instance to be used for testing.
  89. // The daemon will not automatically start.
  90. // The daemon will modify and create files under workingDir.
  91. func NewDaemon(workingDir string, ops ...Option) (*Daemon, error) {
  92. storageDriver := os.Getenv("DOCKER_GRAPHDRIVER")
  93. if err := os.MkdirAll(SockRoot, 0o700); err != nil {
  94. return nil, errors.Wrapf(err, "failed to create daemon socket root %q", SockRoot)
  95. }
  96. id := "d" + stringid.TruncateID(stringid.GenerateRandomID())
  97. dir := filepath.Join(workingDir, id)
  98. daemonFolder, err := filepath.Abs(dir)
  99. if err != nil {
  100. return nil, err
  101. }
  102. daemonRoot := filepath.Join(daemonFolder, "root")
  103. if err := os.MkdirAll(daemonRoot, 0o755); err != nil {
  104. return nil, errors.Wrapf(err, "failed to create daemon root %q", daemonRoot)
  105. }
  106. userlandProxy := true
  107. if env := os.Getenv("DOCKER_USERLANDPROXY"); env != "" {
  108. if val, err := strconv.ParseBool(env); err != nil {
  109. userlandProxy = val
  110. }
  111. }
  112. d := &Daemon{
  113. id: id,
  114. Folder: daemonFolder,
  115. Root: daemonRoot,
  116. storageDriver: storageDriver,
  117. userlandProxy: userlandProxy,
  118. // dxr stands for docker-execroot (shortened for avoiding unix(7) path length limitation)
  119. execRoot: filepath.Join(os.TempDir(), "dxr", id),
  120. dockerdBinary: defaultDockerdBinary,
  121. swarmListenAddr: defaultSwarmListenAddr,
  122. SwarmPort: DefaultSwarmPort,
  123. log: nopLog{},
  124. containerdSocket: defaultContainerdSocket,
  125. }
  126. for _, op := range ops {
  127. op(d)
  128. }
  129. if d.rootlessUser != nil {
  130. if err := os.Chmod(SockRoot, 0o777); err != nil {
  131. return nil, err
  132. }
  133. uid, err := strconv.Atoi(d.rootlessUser.Uid)
  134. if err != nil {
  135. return nil, err
  136. }
  137. gid, err := strconv.Atoi(d.rootlessUser.Gid)
  138. if err != nil {
  139. return nil, err
  140. }
  141. if err := os.Chown(d.Folder, uid, gid); err != nil {
  142. return nil, err
  143. }
  144. if err := os.Chown(d.Root, uid, gid); err != nil {
  145. return nil, err
  146. }
  147. if err := os.MkdirAll(filepath.Dir(d.execRoot), 0o700); err != nil {
  148. return nil, err
  149. }
  150. if err := os.Chown(filepath.Dir(d.execRoot), uid, gid); err != nil {
  151. return nil, err
  152. }
  153. if err := os.MkdirAll(d.execRoot, 0o700); err != nil {
  154. return nil, err
  155. }
  156. if err := os.Chown(d.execRoot, uid, gid); err != nil {
  157. return nil, err
  158. }
  159. d.rootlessXDGRuntimeDir = filepath.Join(d.Folder, "xdgrun")
  160. if err := os.MkdirAll(d.rootlessXDGRuntimeDir, 0o700); err != nil {
  161. return nil, err
  162. }
  163. if err := os.Chown(d.rootlessXDGRuntimeDir, uid, gid); err != nil {
  164. return nil, err
  165. }
  166. d.containerdSocket = ""
  167. }
  168. return d, nil
  169. }
  170. // New returns a Daemon instance to be used for testing.
  171. // This will create a directory such as d123456789 in the folder specified by
  172. // $DOCKER_INTEGRATION_DAEMON_DEST or $DEST.
  173. // The daemon will not automatically start.
  174. func New(t testing.TB, ops ...Option) *Daemon {
  175. t.Helper()
  176. dest := os.Getenv("DOCKER_INTEGRATION_DAEMON_DEST")
  177. if dest == "" {
  178. dest = os.Getenv("DEST")
  179. }
  180. dest = filepath.Join(dest, t.Name())
  181. assert.Check(t, dest != "", "Please set the DOCKER_INTEGRATION_DAEMON_DEST or the DEST environment variable")
  182. if os.Getenv("DOCKER_ROOTLESS") != "" {
  183. if os.Getenv("DOCKER_REMAP_ROOT") != "" {
  184. t.Skip("DOCKER_ROOTLESS doesn't support DOCKER_REMAP_ROOT currently")
  185. }
  186. if env := os.Getenv("DOCKER_USERLANDPROXY"); env != "" {
  187. if val, err := strconv.ParseBool(env); err == nil && !val {
  188. t.Skip("DOCKER_ROOTLESS doesn't support DOCKER_USERLANDPROXY=false")
  189. }
  190. }
  191. ops = append(ops, WithRootlessUser("unprivilegeduser"))
  192. }
  193. ops = append(ops, WithOOMScoreAdjust(-500))
  194. d, err := NewDaemon(dest, ops...)
  195. assert.NilError(t, err, "could not create daemon at %q", dest)
  196. if d.rootlessUser != nil && d.dockerdBinary != defaultDockerdBinary {
  197. t.Skipf("DOCKER_ROOTLESS doesn't support specifying non-default dockerd binary path %q", d.dockerdBinary)
  198. }
  199. return d
  200. }
  201. // BinaryPath returns the binary and its arguments.
  202. func (d *Daemon) BinaryPath() (string, error) {
  203. dockerdBinary, err := exec.LookPath(d.dockerdBinary)
  204. if err != nil {
  205. return "", errors.Wrapf(err, "[%s] could not find docker binary in $PATH", d.id)
  206. }
  207. return dockerdBinary, nil
  208. }
  209. // ContainersNamespace returns the containerd namespace used for containers.
  210. func (d *Daemon) ContainersNamespace() string {
  211. return d.id
  212. }
  213. // RootDir returns the root directory of the daemon.
  214. func (d *Daemon) RootDir() string {
  215. return d.Root
  216. }
  217. // ID returns the generated id of the daemon
  218. func (d *Daemon) ID() string {
  219. return d.id
  220. }
  221. // StorageDriver returns the configured storage driver of the daemon
  222. func (d *Daemon) StorageDriver() string {
  223. return d.storageDriver
  224. }
  225. // Sock returns the socket path of the daemon
  226. func (d *Daemon) Sock() string {
  227. return "unix://" + d.sockPath()
  228. }
  229. func (d *Daemon) sockPath() string {
  230. return filepath.Join(SockRoot, d.id+".sock")
  231. }
  232. // LogFileName returns the path the daemon's log file
  233. func (d *Daemon) LogFileName() string {
  234. return d.logFile.Name()
  235. }
  236. // ReadLogFile returns the content of the daemon log file
  237. func (d *Daemon) ReadLogFile() ([]byte, error) {
  238. _ = d.logFile.Sync()
  239. return os.ReadFile(d.logFile.Name())
  240. }
  241. // NewClientT creates new client based on daemon's socket path
  242. func (d *Daemon) NewClientT(t testing.TB, extraOpts ...client.Opt) *client.Client {
  243. t.Helper()
  244. c, err := d.NewClient(extraOpts...)
  245. assert.NilError(t, err, "[%s] could not create daemon client", d.id)
  246. t.Cleanup(func() { c.Close() })
  247. return c
  248. }
  249. // NewClient creates new client based on daemon's socket path
  250. func (d *Daemon) NewClient(extraOpts ...client.Opt) (*client.Client, error) {
  251. clientOpts := []client.Opt{
  252. client.FromEnv,
  253. client.WithHost(d.Sock()),
  254. }
  255. clientOpts = append(clientOpts, extraOpts...)
  256. return client.NewClientWithOpts(clientOpts...)
  257. }
  258. // Cleanup cleans the daemon files : exec root (network namespaces, ...), swarmkit files
  259. func (d *Daemon) Cleanup(t testing.TB) {
  260. t.Helper()
  261. cleanupMount(t, d)
  262. cleanupRaftDir(t, d)
  263. cleanupDaemonStorage(t, d)
  264. cleanupNetworkNamespace(t, d)
  265. }
  266. // TailLogsT attempts to tail N lines from the daemon logs.
  267. // If there is an error the error is only logged, it does not cause an error with the test.
  268. func (d *Daemon) TailLogsT(t LogT, n int) {
  269. lines, err := d.TailLogs(n)
  270. if err != nil {
  271. t.Logf("[%s] %v", d.id, err)
  272. return
  273. }
  274. for _, l := range lines {
  275. t.Logf("[%s] %s", d.id, string(l))
  276. }
  277. }
  278. // PollCheckLogs is a poll.Check that checks the daemon logs using the passed in match function.
  279. func (d *Daemon) PollCheckLogs(ctx context.Context, match func(s string) bool) poll.Check {
  280. return func(t poll.LogT) poll.Result {
  281. ok, _, err := d.ScanLogs(ctx, match)
  282. if err != nil {
  283. return poll.Error(err)
  284. }
  285. if !ok {
  286. return poll.Continue("waiting for daemon logs match")
  287. }
  288. return poll.Success()
  289. }
  290. }
  291. // ScanLogsMatchString returns a function that can be used to scan the daemon logs for the passed in string (`contains`).
  292. func ScanLogsMatchString(contains string) func(string) bool {
  293. return func(line string) bool {
  294. return strings.Contains(line, contains)
  295. }
  296. }
  297. // ScanLogsMatchAll returns a function that can be used to scan the daemon logs until *all* the passed in strings are matched
  298. func ScanLogsMatchAll(contains ...string) func(string) bool {
  299. matched := make(map[string]bool)
  300. return func(line string) bool {
  301. for _, c := range contains {
  302. if strings.Contains(line, c) {
  303. matched[c] = true
  304. }
  305. }
  306. return len(matched) == len(contains)
  307. }
  308. }
  309. // ScanLogsT uses `ScanLogs` to match the daemon logs using the passed in match function.
  310. // If there is an error or the match fails, the test will fail.
  311. func (d *Daemon) ScanLogsT(ctx context.Context, t testing.TB, match func(s string) bool) (bool, string) {
  312. t.Helper()
  313. ok, line, err := d.ScanLogs(ctx, match)
  314. assert.NilError(t, err)
  315. return ok, line
  316. }
  317. // ScanLogs scans the daemon logs and passes each line to the match function.
  318. func (d *Daemon) ScanLogs(ctx context.Context, match func(s string) bool) (bool, string, error) {
  319. stat, err := d.logFile.Stat()
  320. if err != nil {
  321. return false, "", err
  322. }
  323. rdr := io.NewSectionReader(d.logFile, 0, stat.Size())
  324. scanner := bufio.NewScanner(rdr)
  325. for scanner.Scan() {
  326. if match(scanner.Text()) {
  327. return true, scanner.Text(), nil
  328. }
  329. select {
  330. case <-ctx.Done():
  331. return false, "", ctx.Err()
  332. default:
  333. }
  334. }
  335. return false, "", scanner.Err()
  336. }
  337. // TailLogs tails N lines from the daemon logs
  338. func (d *Daemon) TailLogs(n int) ([][]byte, error) {
  339. logF, err := os.Open(d.logFile.Name())
  340. if err != nil {
  341. return nil, errors.Wrap(err, "error opening daemon log file after failed start")
  342. }
  343. defer logF.Close()
  344. lines, err := tailfile.TailFile(logF, n)
  345. if err != nil {
  346. return nil, errors.Wrap(err, "error tailing log daemon logs")
  347. }
  348. return lines, nil
  349. }
  350. // Start starts the daemon and return once it is ready to receive requests.
  351. func (d *Daemon) Start(t testing.TB, args ...string) {
  352. t.Helper()
  353. if err := d.StartWithError(args...); err != nil {
  354. d.TailLogsT(t, 20)
  355. d.DumpStackAndQuit() // in case the daemon is stuck
  356. t.Fatalf("[%s] failed to start daemon with arguments %v : %v", d.id, d.args, err)
  357. }
  358. }
  359. // StartWithError starts the daemon and return once it is ready to receive requests.
  360. // It returns an error in case it couldn't start.
  361. func (d *Daemon) StartWithError(args ...string) error {
  362. logFile, err := os.OpenFile(filepath.Join(d.Folder, "docker.log"), os.O_RDWR|os.O_CREATE|os.O_APPEND, 0o600)
  363. if err != nil {
  364. return errors.Wrapf(err, "[%s] failed to create logfile", d.id)
  365. }
  366. return d.StartWithLogFile(logFile, args...)
  367. }
  368. // StartWithLogFile will start the daemon and attach its streams to a given file.
  369. func (d *Daemon) StartWithLogFile(out *os.File, providedArgs ...string) error {
  370. d.handleUserns()
  371. dockerdBinary, err := d.BinaryPath()
  372. if err != nil {
  373. return err
  374. }
  375. if d.pidFile == "" {
  376. d.pidFile = filepath.Join(d.Folder, "docker.pid")
  377. }
  378. d.args = []string{}
  379. if d.rootlessUser != nil {
  380. if d.dockerdBinary != defaultDockerdBinary {
  381. return errors.Errorf("[%s] DOCKER_ROOTLESS doesn't support non-default dockerd binary path %q", d.id, d.dockerdBinary)
  382. }
  383. dockerdBinary = "sudo"
  384. d.args = append(d.args,
  385. "-u", d.rootlessUser.Username,
  386. "--preserve-env",
  387. "--preserve-env=PATH", // Pass through PATH, overriding secure_path.
  388. "XDG_RUNTIME_DIR="+d.rootlessXDGRuntimeDir,
  389. "HOME="+d.rootlessUser.HomeDir,
  390. "--",
  391. defaultDockerdRootlessBinary,
  392. )
  393. }
  394. d.args = append(d.args,
  395. "--data-root", d.Root,
  396. "--exec-root", d.execRoot,
  397. "--pidfile", d.pidFile,
  398. "--userland-proxy="+strconv.FormatBool(d.userlandProxy),
  399. "--containerd-namespace", d.id,
  400. "--containerd-plugins-namespace", d.id+"p",
  401. )
  402. if d.containerdSocket != "" {
  403. d.args = append(d.args, "--containerd", d.containerdSocket)
  404. }
  405. if d.usernsRemap != "" {
  406. d.args = append(d.args, "--userns-remap", d.usernsRemap)
  407. }
  408. if d.defaultCgroupNamespaceMode != "" {
  409. d.args = append(d.args, "--default-cgroupns-mode", d.defaultCgroupNamespaceMode)
  410. }
  411. if d.experimental {
  412. d.args = append(d.args, "--experimental")
  413. }
  414. if d.init {
  415. d.args = append(d.args, "--init")
  416. }
  417. if !(d.UseDefaultHost || d.UseDefaultTLSHost) {
  418. d.args = append(d.args, "--host", d.Sock())
  419. }
  420. if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" {
  421. d.args = append(d.args, "--userns-remap", root)
  422. }
  423. // If we don't explicitly set the log-level or debug flag(-D) then
  424. // turn on debug mode
  425. foundLog := false
  426. foundSd := false
  427. for _, a := range providedArgs {
  428. if strings.Contains(a, "--log-level") || strings.Contains(a, "-D") || strings.Contains(a, "--debug") {
  429. foundLog = true
  430. }
  431. if strings.Contains(a, "--storage-driver") {
  432. foundSd = true
  433. }
  434. }
  435. if !foundLog {
  436. d.args = append(d.args, "--debug")
  437. }
  438. if d.storageDriver != "" && !foundSd {
  439. d.args = append(d.args, "--storage-driver", d.storageDriver)
  440. }
  441. d.args = append(d.args, providedArgs...)
  442. cmd := exec.Command(dockerdBinary, d.args...)
  443. cmd.Env = append(os.Environ(), "DOCKER_SERVICE_PREFER_OFFLINE_IMAGE=1")
  444. cmd.Env = append(cmd.Env, d.extraEnv...)
  445. cmd.Env = append(cmd.Env, "OTEL_SERVICE_NAME=dockerd-"+d.id)
  446. cmd.Stdout = out
  447. cmd.Stderr = out
  448. d.logFile = out
  449. if d.rootlessUser != nil {
  450. // sudo requires this for propagating signals
  451. setsid(cmd)
  452. }
  453. if err := cmd.Start(); err != nil {
  454. return errors.Wrapf(err, "[%s] could not start daemon container", d.id)
  455. }
  456. wait := make(chan error, 1)
  457. d.cmd = cmd
  458. d.Wait = wait
  459. go func() {
  460. ret := cmd.Wait()
  461. d.log.Logf("[%s] exiting daemon", d.id)
  462. // If we send before logging, we might accidentally log _after_ the test is done.
  463. // As of Go 1.12, this incurs a panic instead of silently being dropped.
  464. wait <- ret
  465. close(wait)
  466. }()
  467. clientConfig, err := d.getClientConfig()
  468. if err != nil {
  469. return err
  470. }
  471. client := &http.Client{
  472. Transport: clientConfig.transport,
  473. }
  474. req, err := http.NewRequest(http.MethodGet, "/_ping", nil)
  475. if err != nil {
  476. return errors.Wrapf(err, "[%s] could not create new request", d.id)
  477. }
  478. req.URL.Host = clientConfig.addr
  479. req.URL.Scheme = clientConfig.scheme
  480. ctx, cancel := context.WithTimeout(context.Background(), 60*time.Second)
  481. defer cancel()
  482. // make sure daemon is ready to receive requests
  483. for i := 0; ; i++ {
  484. d.log.Logf("[%s] waiting for daemon to start", d.id)
  485. select {
  486. case <-ctx.Done():
  487. return errors.Wrapf(ctx.Err(), "[%s] daemon exited and never started", d.id)
  488. case err := <-d.Wait:
  489. return errors.Wrapf(err, "[%s] daemon exited during startup", d.id)
  490. default:
  491. rctx, rcancel := context.WithTimeout(context.TODO(), 2*time.Second)
  492. defer rcancel()
  493. resp, err := client.Do(req.WithContext(rctx))
  494. if err != nil {
  495. if i > 2 { // don't log the first couple, this ends up just being noise
  496. d.log.Logf("[%s] error pinging daemon on start: %v", d.id, err)
  497. }
  498. select {
  499. case <-ctx.Done():
  500. case <-time.After(500 * time.Millisecond):
  501. }
  502. continue
  503. }
  504. resp.Body.Close()
  505. if resp.StatusCode != http.StatusOK {
  506. d.log.Logf("[%s] received status != 200 OK: %s\n", d.id, resp.Status)
  507. }
  508. d.log.Logf("[%s] daemon started\n", d.id)
  509. d.Root, err = d.queryRootDir()
  510. if err != nil {
  511. return errors.Wrapf(err, "[%s] error querying daemon for root directory", d.id)
  512. }
  513. return nil
  514. }
  515. }
  516. }
  517. // StartWithBusybox will first start the daemon with Daemon.Start()
  518. // then save the busybox image from the main daemon and load it into this Daemon instance.
  519. func (d *Daemon) StartWithBusybox(ctx context.Context, t testing.TB, arg ...string) {
  520. t.Helper()
  521. d.Start(t, arg...)
  522. d.LoadBusybox(ctx, t)
  523. }
  524. // Kill will send a SIGKILL to the daemon
  525. func (d *Daemon) Kill() error {
  526. if d.cmd == nil || d.Wait == nil {
  527. return errDaemonNotStarted
  528. }
  529. defer func() {
  530. d.logFile.Close()
  531. d.cmd = nil
  532. }()
  533. if err := d.cmd.Process.Kill(); err != nil {
  534. return err
  535. }
  536. if d.pidFile != "" {
  537. _ = os.Remove(d.pidFile)
  538. }
  539. return nil
  540. }
  541. // Pid returns the pid of the daemon
  542. func (d *Daemon) Pid() int {
  543. return d.cmd.Process.Pid
  544. }
  545. // Interrupt stops the daemon by sending it an Interrupt signal
  546. func (d *Daemon) Interrupt() error {
  547. return d.Signal(os.Interrupt)
  548. }
  549. // Signal sends the specified signal to the daemon if running
  550. func (d *Daemon) Signal(signal os.Signal) error {
  551. if d.cmd == nil || d.Wait == nil {
  552. return errDaemonNotStarted
  553. }
  554. return d.cmd.Process.Signal(signal)
  555. }
  556. // DumpStackAndQuit sends SIGQUIT to the daemon, which triggers it to dump its
  557. // stack to its log file and exit
  558. // This is used primarily for gathering debug information on test timeout
  559. func (d *Daemon) DumpStackAndQuit() {
  560. if d.cmd == nil || d.cmd.Process == nil {
  561. return
  562. }
  563. SignalDaemonDump(d.cmd.Process.Pid)
  564. }
  565. // Stop will send a SIGINT every second and wait for the daemon to stop.
  566. // If it times out, a SIGKILL is sent.
  567. // Stop will not delete the daemon directory. If a purged daemon is needed,
  568. // instantiate a new one with NewDaemon.
  569. // If an error occurs while starting the daemon, the test will fail.
  570. func (d *Daemon) Stop(t testing.TB) {
  571. t.Helper()
  572. err := d.StopWithError()
  573. if err != nil {
  574. if err != errDaemonNotStarted {
  575. t.Fatalf("[%s] error while stopping the daemon: %v", d.id, err)
  576. } else {
  577. t.Logf("[%s] daemon is not started", d.id)
  578. }
  579. }
  580. }
  581. // StopWithError will send a SIGINT every second and wait for the daemon to stop.
  582. // If it timeouts, a SIGKILL is sent.
  583. // Stop will not delete the daemon directory. If a purged daemon is needed,
  584. // instantiate a new one with NewDaemon.
  585. func (d *Daemon) StopWithError() (err error) {
  586. if d.cmd == nil || d.Wait == nil {
  587. return errDaemonNotStarted
  588. }
  589. defer func() {
  590. if err != nil {
  591. d.log.Logf("[%s] error while stopping daemon: %v", d.id, err)
  592. } else {
  593. d.log.Logf("[%s] daemon stopped", d.id)
  594. if d.pidFile != "" {
  595. _ = os.Remove(d.pidFile)
  596. }
  597. }
  598. if err := d.logFile.Close(); err != nil {
  599. d.log.Logf("[%s] failed to close daemon logfile: %v", d.id, err)
  600. }
  601. d.cmd = nil
  602. }()
  603. i := 1
  604. ticker := time.NewTicker(time.Second)
  605. defer ticker.Stop()
  606. tick := ticker.C
  607. d.log.Logf("[%s] stopping daemon", d.id)
  608. if err := d.cmd.Process.Signal(os.Interrupt); err != nil {
  609. if strings.Contains(err.Error(), "os: process already finished") {
  610. return errDaemonNotStarted
  611. }
  612. return errors.Wrapf(err, "[%s] could not send signal", d.id)
  613. }
  614. out1:
  615. for {
  616. select {
  617. case err := <-d.Wait:
  618. return err
  619. case <-time.After(20 * time.Second):
  620. // time for stopping jobs and run onShutdown hooks
  621. d.log.Logf("[%s] daemon stop timed out after 20 seconds", d.id)
  622. break out1
  623. }
  624. }
  625. out2:
  626. for {
  627. select {
  628. case err := <-d.Wait:
  629. return err
  630. case <-tick:
  631. i++
  632. if i > 5 {
  633. d.log.Logf("[%s] tried to interrupt daemon for %d times, now try to kill it", d.id, i)
  634. break out2
  635. }
  636. d.log.Logf("[%d] attempt #%d/5: daemon is still running with pid %d", i, d.cmd.Process.Pid)
  637. if err := d.cmd.Process.Signal(os.Interrupt); err != nil {
  638. return errors.Wrapf(err, "[%s] attempt #%d/5 could not send signal", d.id, i)
  639. }
  640. }
  641. }
  642. if err := d.cmd.Process.Kill(); err != nil {
  643. d.log.Logf("[%s] failed to kill daemon: %v", d.id, err)
  644. return err
  645. }
  646. return nil
  647. }
  648. // Restart will restart the daemon by first stopping it and the starting it.
  649. // If an error occurs while starting the daemon, the test will fail.
  650. func (d *Daemon) Restart(t testing.TB, args ...string) {
  651. t.Helper()
  652. d.Stop(t)
  653. d.Start(t, args...)
  654. }
  655. // RestartWithError will restart the daemon by first stopping it and then starting it.
  656. func (d *Daemon) RestartWithError(arg ...string) error {
  657. if err := d.StopWithError(); err != nil {
  658. return err
  659. }
  660. return d.StartWithError(arg...)
  661. }
  662. func (d *Daemon) handleUserns() {
  663. // in the case of tests running a user namespace-enabled daemon, we have resolved
  664. // d.Root to be the actual final path of the graph dir after the "uid.gid" of
  665. // remapped root is added--we need to subtract it from the path before calling
  666. // start or else we will continue making subdirectories rather than truly restarting
  667. // with the same location/root:
  668. if root := os.Getenv("DOCKER_REMAP_ROOT"); root != "" {
  669. d.Root = filepath.Dir(d.Root)
  670. }
  671. }
  672. // ReloadConfig asks the daemon to reload its configuration
  673. func (d *Daemon) ReloadConfig() error {
  674. if d.cmd == nil || d.cmd.Process == nil {
  675. return errors.New("daemon is not running")
  676. }
  677. errCh := make(chan error, 1)
  678. started := make(chan struct{})
  679. go func() {
  680. _, body, err := request.Get(context.TODO(), "/events", request.Host(d.Sock()))
  681. close(started)
  682. if err != nil {
  683. errCh <- err
  684. return
  685. }
  686. defer body.Close()
  687. dec := json.NewDecoder(body)
  688. for {
  689. var e events.Message
  690. if err := dec.Decode(&e); err != nil {
  691. errCh <- err
  692. return
  693. }
  694. if e.Type != events.DaemonEventType {
  695. continue
  696. }
  697. if e.Action != events.ActionReload {
  698. continue
  699. }
  700. close(errCh) // notify that we are done
  701. return
  702. }
  703. }()
  704. <-started
  705. if err := signalDaemonReload(d.cmd.Process.Pid); err != nil {
  706. return errors.Wrapf(err, "[%s] error signaling daemon reload", d.id)
  707. }
  708. select {
  709. case err := <-errCh:
  710. if err != nil {
  711. return errors.Wrapf(err, "[%s] error waiting for daemon reload event", d.id)
  712. }
  713. case <-time.After(30 * time.Second):
  714. return errors.Errorf("[%s] daemon reload event timed out after 30 seconds", d.id)
  715. }
  716. return nil
  717. }
  718. // LoadBusybox image into the daemon
  719. func (d *Daemon) LoadBusybox(ctx context.Context, t testing.TB) {
  720. t.Helper()
  721. clientHost, err := client.NewClientWithOpts(client.FromEnv)
  722. assert.NilError(t, err, "[%s] failed to create client", d.id)
  723. defer clientHost.Close()
  724. reader, err := clientHost.ImageSave(ctx, []string{"busybox:latest"})
  725. assert.NilError(t, err, "[%s] failed to download busybox", d.id)
  726. defer reader.Close()
  727. c := d.NewClientT(t)
  728. defer c.Close()
  729. resp, err := c.ImageLoad(ctx, reader, true)
  730. assert.NilError(t, err, "[%s] failed to load busybox", d.id)
  731. defer resp.Body.Close()
  732. }
  733. func (d *Daemon) getClientConfig() (*clientConfig, error) {
  734. var (
  735. transport *http.Transport
  736. scheme string
  737. addr string
  738. proto string
  739. )
  740. if d.UseDefaultTLSHost {
  741. option := &tlsconfig.Options{
  742. CAFile: "fixtures/https/ca.pem",
  743. CertFile: "fixtures/https/client-cert.pem",
  744. KeyFile: "fixtures/https/client-key.pem",
  745. }
  746. tlsConfig, err := tlsconfig.Client(*option)
  747. if err != nil {
  748. return nil, err
  749. }
  750. transport = &http.Transport{
  751. TLSClientConfig: tlsConfig,
  752. }
  753. addr = defaultTLSHost
  754. scheme = "https"
  755. proto = "tcp"
  756. } else if d.UseDefaultHost {
  757. addr = defaultUnixSocket
  758. proto = "unix"
  759. scheme = "http"
  760. transport = &http.Transport{}
  761. } else {
  762. addr = d.sockPath()
  763. proto = "unix"
  764. scheme = "http"
  765. transport = &http.Transport{}
  766. }
  767. if err := sockets.ConfigureTransport(transport, proto, addr); err != nil {
  768. return nil, err
  769. }
  770. transport.DisableKeepAlives = true
  771. if proto == "unix" {
  772. addr = filepath.Base(addr)
  773. }
  774. return &clientConfig{
  775. transport: transport,
  776. scheme: scheme,
  777. addr: addr,
  778. }, nil
  779. }
  780. func (d *Daemon) queryRootDir() (string, error) {
  781. // update daemon root by asking /info endpoint (to support user
  782. // namespaced daemon with root remapped uid.gid directory)
  783. clientConfig, err := d.getClientConfig()
  784. if err != nil {
  785. return "", err
  786. }
  787. c := &http.Client{
  788. Transport: clientConfig.transport,
  789. }
  790. req, err := http.NewRequest(http.MethodGet, "/info", nil)
  791. if err != nil {
  792. return "", err
  793. }
  794. req.Header.Set("Content-Type", "application/json")
  795. req.URL.Host = clientConfig.addr
  796. req.URL.Scheme = clientConfig.scheme
  797. resp, err := c.Do(req)
  798. if err != nil {
  799. return "", err
  800. }
  801. body := ioutils.NewReadCloserWrapper(resp.Body, func() error {
  802. return resp.Body.Close()
  803. })
  804. type Info struct {
  805. DockerRootDir string
  806. }
  807. var b []byte
  808. var i Info
  809. b, err = request.ReadBody(body)
  810. if err == nil && resp.StatusCode == http.StatusOK {
  811. // read the docker root dir
  812. if err = json.Unmarshal(b, &i); err == nil {
  813. return i.DockerRootDir, nil
  814. }
  815. }
  816. return "", err
  817. }
  818. // Info returns the info struct for this daemon
  819. func (d *Daemon) Info(t testing.TB) system.Info {
  820. t.Helper()
  821. c := d.NewClientT(t)
  822. info, err := c.Info(context.Background())
  823. assert.NilError(t, err)
  824. assert.NilError(t, c.Close())
  825. return info
  826. }
  827. // TamperWithContainerConfig modifies the on-disk config of a container.
  828. func (d *Daemon) TamperWithContainerConfig(t testing.TB, containerID string, tamper func(*container.Container)) {
  829. t.Helper()
  830. configPath := filepath.Join(d.Root, "containers", containerID, "config.v2.json")
  831. configBytes, err := os.ReadFile(configPath)
  832. assert.NilError(t, err)
  833. var c container.Container
  834. assert.NilError(t, json.Unmarshal(configBytes, &c))
  835. c.State = container.NewState()
  836. tamper(&c)
  837. configBytes, err = json.Marshal(&c)
  838. assert.NilError(t, err)
  839. assert.NilError(t, os.WriteFile(configPath, configBytes, 0o600))
  840. }
  841. // cleanupRaftDir removes swarmkit wal files if present
  842. func cleanupRaftDir(t testing.TB, d *Daemon) {
  843. t.Helper()
  844. for _, p := range []string{"wal", "wal-v3-encrypted", "snap-v3-encrypted"} {
  845. dir := filepath.Join(d.Root, "swarm/raft", p)
  846. if err := os.RemoveAll(dir); err != nil {
  847. t.Logf("[%s] error removing %v: %v", d.id, dir, err)
  848. }
  849. }
  850. }
  851. // cleanupDaemonStorage removes the daemon's storage directory.
  852. //
  853. // Note that we don't delete the whole directory, as some files (e.g. daemon
  854. // logs) are collected for inclusion in the "bundles" that are stored as Jenkins
  855. // artifacts.
  856. //
  857. // We currently do not include container logs in the bundles, so this also
  858. // removes the "containers" sub-directory.
  859. func cleanupDaemonStorage(t testing.TB, d *Daemon) {
  860. t.Helper()
  861. dirs := []string{
  862. "builder",
  863. "buildkit",
  864. "containers",
  865. "image",
  866. "network",
  867. "plugins",
  868. "tmp",
  869. "trust",
  870. "volumes",
  871. // note: this assumes storage-driver name matches the subdirectory,
  872. // which is currently true, but not guaranteed.
  873. d.storageDriver,
  874. }
  875. for _, p := range dirs {
  876. dir := filepath.Join(d.Root, p)
  877. if err := os.RemoveAll(dir); err != nil {
  878. t.Logf("[%s] error removing %v: %v", d.id, dir, err)
  879. }
  880. }
  881. }