daemon.go 18 KB

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