daemon_test.go 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631
  1. package daemon // import "github.com/docker/docker/integration/daemon"
  2. import (
  3. "bytes"
  4. "fmt"
  5. "io"
  6. "net/http"
  7. "net/http/httptest"
  8. "os"
  9. "os/exec"
  10. "path/filepath"
  11. "runtime"
  12. "strings"
  13. "syscall"
  14. "testing"
  15. "github.com/docker/docker/api/types"
  16. containertypes "github.com/docker/docker/api/types/container"
  17. "github.com/docker/docker/api/types/mount"
  18. "github.com/docker/docker/api/types/volume"
  19. "github.com/docker/docker/daemon/config"
  20. "github.com/docker/docker/errdefs"
  21. "github.com/docker/docker/integration/internal/container"
  22. "github.com/docker/docker/integration/internal/process"
  23. "github.com/docker/docker/pkg/stdcopy"
  24. "github.com/docker/docker/testutil"
  25. "github.com/docker/docker/testutil/daemon"
  26. "gotest.tools/v3/assert"
  27. is "gotest.tools/v3/assert/cmp"
  28. "gotest.tools/v3/icmd"
  29. "gotest.tools/v3/poll"
  30. "gotest.tools/v3/skip"
  31. )
  32. func TestConfigDaemonID(t *testing.T) {
  33. skip.If(t, runtime.GOOS == "windows")
  34. _ = testutil.StartSpan(baseContext, t)
  35. d := daemon.New(t)
  36. defer d.Stop(t)
  37. d.Start(t, "--iptables=false")
  38. info := d.Info(t)
  39. assert.Check(t, info.ID != "")
  40. d.Stop(t)
  41. // Verify that (if present) the engine-id file takes precedence
  42. const engineID = "this-is-the-engine-id"
  43. idFile := filepath.Join(d.RootDir(), "engine-id")
  44. assert.Check(t, os.Remove(idFile))
  45. // Using 0644 to allow rootless daemons to read the file (ideally
  46. // we'd chown the file to have the remapped user as owner).
  47. err := os.WriteFile(idFile, []byte(engineID), 0o644)
  48. assert.NilError(t, err)
  49. d.Start(t, "--iptables=false")
  50. info = d.Info(t)
  51. assert.Equal(t, info.ID, engineID)
  52. d.Stop(t)
  53. }
  54. func TestDaemonConfigValidation(t *testing.T) {
  55. skip.If(t, runtime.GOOS == "windows")
  56. ctx := testutil.StartSpan(baseContext, t)
  57. d := daemon.New(t)
  58. dockerBinary, err := d.BinaryPath()
  59. assert.NilError(t, err)
  60. params := []string{"--validate", "--config-file"}
  61. dest := os.Getenv("DOCKER_INTEGRATION_DAEMON_DEST")
  62. if dest == "" {
  63. dest = os.Getenv("DEST")
  64. }
  65. testdata := filepath.Join(dest, "..", "..", "integration", "daemon", "testdata")
  66. const (
  67. validOut = "configuration OK"
  68. failedOut = "unable to configure the Docker daemon with file"
  69. )
  70. tests := []struct {
  71. name string
  72. args []string
  73. expectedOut string
  74. }{
  75. {
  76. name: "config with no content",
  77. args: append(params, filepath.Join(testdata, "empty-config-1.json")),
  78. expectedOut: validOut,
  79. },
  80. {
  81. name: "config with {}",
  82. args: append(params, filepath.Join(testdata, "empty-config-2.json")),
  83. expectedOut: validOut,
  84. },
  85. {
  86. name: "invalid config",
  87. args: append(params, filepath.Join(testdata, "invalid-config-1.json")),
  88. expectedOut: failedOut,
  89. },
  90. {
  91. name: "malformed config",
  92. args: append(params, filepath.Join(testdata, "malformed-config.json")),
  93. expectedOut: failedOut,
  94. },
  95. {
  96. name: "valid config",
  97. args: append(params, filepath.Join(testdata, "valid-config-1.json")),
  98. expectedOut: validOut,
  99. },
  100. }
  101. for _, tc := range tests {
  102. tc := tc
  103. t.Run(tc.name, func(t *testing.T) {
  104. t.Parallel()
  105. _ = testutil.StartSpan(ctx, t)
  106. cmd := exec.Command(dockerBinary, tc.args...)
  107. out, err := cmd.CombinedOutput()
  108. assert.Check(t, is.Contains(string(out), tc.expectedOut))
  109. if tc.expectedOut == failedOut {
  110. assert.ErrorContains(t, err, "", "expected an error, but got none")
  111. } else {
  112. assert.NilError(t, err)
  113. }
  114. })
  115. }
  116. }
  117. func TestConfigDaemonSeccompProfiles(t *testing.T) {
  118. skip.If(t, runtime.GOOS == "windows")
  119. ctx := testutil.StartSpan(baseContext, t)
  120. d := daemon.New(t)
  121. defer d.Stop(t)
  122. tests := []struct {
  123. doc string
  124. profile string
  125. expectedProfile string
  126. }{
  127. {
  128. doc: "empty profile set",
  129. profile: "",
  130. expectedProfile: config.SeccompProfileDefault,
  131. },
  132. {
  133. doc: "default profile",
  134. profile: config.SeccompProfileDefault,
  135. expectedProfile: config.SeccompProfileDefault,
  136. },
  137. {
  138. doc: "unconfined profile",
  139. profile: config.SeccompProfileUnconfined,
  140. expectedProfile: config.SeccompProfileUnconfined,
  141. },
  142. }
  143. for _, tc := range tests {
  144. tc := tc
  145. t.Run(tc.doc, func(t *testing.T) {
  146. _ = testutil.StartSpan(ctx, t)
  147. d.Start(t, "--seccomp-profile="+tc.profile)
  148. info := d.Info(t)
  149. assert.Assert(t, is.Contains(info.SecurityOptions, "name=seccomp,profile="+tc.expectedProfile))
  150. d.Stop(t)
  151. cfg := filepath.Join(d.RootDir(), "daemon.json")
  152. err := os.WriteFile(cfg, []byte(`{"seccomp-profile": "`+tc.profile+`"}`), 0o644)
  153. assert.NilError(t, err)
  154. d.Start(t, "--config-file", cfg)
  155. info = d.Info(t)
  156. assert.Assert(t, is.Contains(info.SecurityOptions, "name=seccomp,profile="+tc.expectedProfile))
  157. d.Stop(t)
  158. })
  159. }
  160. }
  161. func TestDaemonProxy(t *testing.T) {
  162. skip.If(t, runtime.GOOS == "windows", "cannot start multiple daemons on windows")
  163. skip.If(t, os.Getenv("DOCKER_ROOTLESS") != "", "cannot connect to localhost proxy in rootless environment")
  164. ctx := testutil.StartSpan(baseContext, t)
  165. newProxy := func(rcvd *string, t *testing.T) *httptest.Server {
  166. s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
  167. *rcvd = r.Host
  168. w.Header().Set("Content-Type", "application/json")
  169. _, _ = w.Write([]byte("OK"))
  170. }))
  171. t.Cleanup(s.Close)
  172. return s
  173. }
  174. const userPass = "myuser:mypassword@"
  175. // Configure proxy through env-vars
  176. t.Run("environment variables", func(t *testing.T) {
  177. t.Parallel()
  178. ctx := testutil.StartSpan(ctx, t)
  179. var received string
  180. proxyServer := newProxy(&received, t)
  181. d := daemon.New(t, daemon.WithEnvVars(
  182. "HTTP_PROXY="+proxyServer.URL,
  183. "HTTPS_PROXY="+proxyServer.URL,
  184. "NO_PROXY=example.com",
  185. ))
  186. c := d.NewClientT(t)
  187. d.Start(t, "--iptables=false")
  188. defer d.Stop(t)
  189. info := d.Info(t)
  190. assert.Check(t, is.Equal(info.HTTPProxy, proxyServer.URL))
  191. assert.Check(t, is.Equal(info.HTTPSProxy, proxyServer.URL))
  192. assert.Check(t, is.Equal(info.NoProxy, "example.com"))
  193. _, err := c.ImagePull(ctx, "example.org:5000/some/image:latest", types.ImagePullOptions{})
  194. assert.ErrorContains(t, err, "", "pulling should have failed")
  195. assert.Equal(t, received, "example.org:5000")
  196. // Test NoProxy: example.com should not hit the proxy, and "received" variable should not be changed.
  197. _, err = c.ImagePull(ctx, "example.com/some/image:latest", types.ImagePullOptions{})
  198. assert.ErrorContains(t, err, "", "pulling should have failed")
  199. assert.Equal(t, received, "example.org:5000", "should not have used proxy")
  200. })
  201. // Configure proxy through command-line flags
  202. t.Run("command-line options", func(t *testing.T) {
  203. t.Parallel()
  204. ctx := testutil.StartSpan(ctx, t)
  205. var received string
  206. proxyServer := newProxy(&received, t)
  207. d := daemon.New(t, daemon.WithEnvVars(
  208. "HTTP_PROXY="+"http://"+userPass+"from-env-http.invalid",
  209. "http_proxy="+"http://"+userPass+"from-env-http.invalid",
  210. "HTTPS_PROXY="+"https://"+userPass+"myuser:mypassword@from-env-https-invalid",
  211. "https_proxy="+"https://"+userPass+"myuser:mypassword@from-env-https-invalid",
  212. "NO_PROXY=ignore.invalid",
  213. "no_proxy=ignore.invalid",
  214. ))
  215. d.Start(t, "--iptables=false", "--http-proxy", proxyServer.URL, "--https-proxy", proxyServer.URL, "--no-proxy", "example.com")
  216. defer d.Stop(t)
  217. c := d.NewClientT(t)
  218. info := d.Info(t)
  219. assert.Check(t, is.Equal(info.HTTPProxy, proxyServer.URL))
  220. assert.Check(t, is.Equal(info.HTTPSProxy, proxyServer.URL))
  221. assert.Check(t, is.Equal(info.NoProxy, "example.com"))
  222. ok, _ := d.ScanLogsT(ctx, t, daemon.ScanLogsMatchAll(
  223. "overriding existing proxy variable with value from configuration",
  224. "http_proxy",
  225. "HTTP_PROXY",
  226. "https_proxy",
  227. "HTTPS_PROXY",
  228. "no_proxy",
  229. "NO_PROXY",
  230. ))
  231. assert.Assert(t, ok)
  232. ok, logs := d.ScanLogsT(ctx, t, daemon.ScanLogsMatchString(userPass))
  233. assert.Assert(t, !ok, "logs should not contain the non-sanitized proxy URL: %s", logs)
  234. _, err := c.ImagePull(ctx, "example.org:5001/some/image:latest", types.ImagePullOptions{})
  235. assert.ErrorContains(t, err, "", "pulling should have failed")
  236. assert.Equal(t, received, "example.org:5001")
  237. // Test NoProxy: example.com should not hit the proxy, and "received" variable should not be changed.
  238. _, err = c.ImagePull(ctx, "example.com/some/image:latest", types.ImagePullOptions{})
  239. assert.ErrorContains(t, err, "", "pulling should have failed")
  240. assert.Equal(t, received, "example.org:5001", "should not have used proxy")
  241. })
  242. // Configure proxy through configuration file
  243. t.Run("configuration file", func(t *testing.T) {
  244. t.Parallel()
  245. ctx := testutil.StartSpan(ctx, t)
  246. var received string
  247. proxyServer := newProxy(&received, t)
  248. d := daemon.New(t, daemon.WithEnvVars(
  249. "HTTP_PROXY="+"http://"+userPass+"from-env-http.invalid",
  250. "http_proxy="+"http://"+userPass+"from-env-http.invalid",
  251. "HTTPS_PROXY="+"https://"+userPass+"myuser:mypassword@from-env-https-invalid",
  252. "https_proxy="+"https://"+userPass+"myuser:mypassword@from-env-https-invalid",
  253. "NO_PROXY=ignore.invalid",
  254. "no_proxy=ignore.invalid",
  255. ))
  256. c := d.NewClientT(t)
  257. configFile := filepath.Join(d.RootDir(), "daemon.json")
  258. configJSON := fmt.Sprintf(`{"proxies":{"http-proxy":%[1]q, "https-proxy": %[1]q, "no-proxy": "example.com"}}`, proxyServer.URL)
  259. assert.NilError(t, os.WriteFile(configFile, []byte(configJSON), 0o644))
  260. d.Start(t, "--iptables=false", "--config-file", configFile)
  261. defer d.Stop(t)
  262. info := d.Info(t)
  263. assert.Check(t, is.Equal(info.HTTPProxy, proxyServer.URL))
  264. assert.Check(t, is.Equal(info.HTTPSProxy, proxyServer.URL))
  265. assert.Check(t, is.Equal(info.NoProxy, "example.com"))
  266. d.ScanLogsT(ctx, t, daemon.ScanLogsMatchAll(
  267. "overriding existing proxy variable with value from configuration",
  268. "http_proxy",
  269. "HTTP_PROXY",
  270. "https_proxy",
  271. "HTTPS_PROXY",
  272. "no_proxy",
  273. "NO_PROXY",
  274. ))
  275. _, err := c.ImagePull(ctx, "example.org:5002/some/image:latest", types.ImagePullOptions{})
  276. assert.ErrorContains(t, err, "", "pulling should have failed")
  277. assert.Equal(t, received, "example.org:5002")
  278. // Test NoProxy: example.com should not hit the proxy, and "received" variable should not be changed.
  279. _, err = c.ImagePull(ctx, "example.com/some/image:latest", types.ImagePullOptions{})
  280. assert.ErrorContains(t, err, "", "pulling should have failed")
  281. assert.Equal(t, received, "example.org:5002", "should not have used proxy")
  282. })
  283. // Conflicting options (passed both through command-line options and config file)
  284. t.Run("conflicting options", func(t *testing.T) {
  285. ctx := testutil.StartSpan(ctx, t)
  286. const (
  287. proxyRawURL = "https://" + userPass + "example.org"
  288. proxyURL = "https://xxxxx:xxxxx@example.org"
  289. )
  290. d := daemon.New(t)
  291. configFile := filepath.Join(d.RootDir(), "daemon.json")
  292. configJSON := fmt.Sprintf(`{"proxies":{"http-proxy":%[1]q, "https-proxy": %[1]q, "no-proxy": "example.com"}}`, proxyRawURL)
  293. assert.NilError(t, os.WriteFile(configFile, []byte(configJSON), 0o644))
  294. err := d.StartWithError("--http-proxy", proxyRawURL, "--https-proxy", proxyRawURL, "--no-proxy", "example.com", "--config-file", configFile, "--validate")
  295. assert.ErrorContains(t, err, "daemon exited during startup")
  296. expected := fmt.Sprintf(
  297. `the following directives are specified both as a flag and in the configuration file: http-proxy: (from flag: %[1]s, from file: %[1]s), https-proxy: (from flag: %[1]s, from file: %[1]s), no-proxy: (from flag: example.com, from file: example.com)`,
  298. proxyURL,
  299. )
  300. poll.WaitOn(t, d.PollCheckLogs(ctx, daemon.ScanLogsMatchString(expected)))
  301. })
  302. // Make sure values are sanitized when reloading the daemon-config
  303. t.Run("reload sanitized", func(t *testing.T) {
  304. t.Parallel()
  305. ctx := testutil.StartSpan(ctx, t)
  306. const (
  307. proxyRawURL = "https://" + userPass + "example.org"
  308. proxyURL = "https://xxxxx:xxxxx@example.org"
  309. )
  310. d := daemon.New(t)
  311. d.Start(t, "--iptables=false", "--http-proxy", proxyRawURL, "--https-proxy", proxyRawURL, "--no-proxy", "example.com")
  312. defer d.Stop(t)
  313. err := d.Signal(syscall.SIGHUP)
  314. assert.NilError(t, err)
  315. poll.WaitOn(t, d.PollCheckLogs(ctx, daemon.ScanLogsMatchAll("Reloaded configuration:", proxyURL)))
  316. ok, logs := d.ScanLogsT(ctx, t, daemon.ScanLogsMatchString(userPass))
  317. assert.Assert(t, !ok, "logs should not contain the non-sanitized proxy URL: %s", logs)
  318. })
  319. }
  320. func TestLiveRestore(t *testing.T) {
  321. skip.If(t, runtime.GOOS == "windows", "cannot start multiple daemons on windows")
  322. _ = testutil.StartSpan(baseContext, t)
  323. t.Run("volume references", testLiveRestoreVolumeReferences)
  324. t.Run("autoremove", testLiveRestoreAutoRemove)
  325. }
  326. func testLiveRestoreAutoRemove(t *testing.T) {
  327. skip.If(t, testEnv.IsRootless(), "restarted rootless daemon will have a new process namespace")
  328. t.Parallel()
  329. ctx := testutil.StartSpan(baseContext, t)
  330. run := func(t *testing.T) (*daemon.Daemon, func(), string) {
  331. d := daemon.New(t)
  332. d.StartWithBusybox(ctx, t, "--live-restore", "--iptables=false")
  333. t.Cleanup(func() {
  334. d.Stop(t)
  335. d.Cleanup(t)
  336. })
  337. tmpDir := t.TempDir()
  338. apiClient := d.NewClientT(t)
  339. cID := container.Run(ctx, t, apiClient,
  340. container.WithBind(tmpDir, "/v"),
  341. // Run until a 'stop' file is created.
  342. container.WithCmd("sh", "-c", "while [ ! -f /v/stop ]; do sleep 0.1; done"),
  343. container.WithAutoRemove)
  344. t.Cleanup(func() { apiClient.ContainerRemove(ctx, cID, containertypes.RemoveOptions{Force: true}) })
  345. finishContainer := func() {
  346. file, err := os.Create(filepath.Join(tmpDir, "stop"))
  347. assert.NilError(t, err, "Failed to create 'stop' file")
  348. file.Close()
  349. }
  350. return d, finishContainer, cID
  351. }
  352. t.Run("engine restart shouldnt kill alive containers", func(t *testing.T) {
  353. d, finishContainer, cID := run(t)
  354. d.Restart(t, "--live-restore", "--iptables=false")
  355. apiClient := d.NewClientT(t)
  356. _, err := apiClient.ContainerInspect(ctx, cID)
  357. assert.NilError(t, err, "Container shouldn't be removed after engine restart")
  358. finishContainer()
  359. poll.WaitOn(t, container.IsRemoved(ctx, apiClient, cID))
  360. })
  361. t.Run("engine restart should remove containers that exited", func(t *testing.T) {
  362. d, finishContainer, cID := run(t)
  363. apiClient := d.NewClientT(t)
  364. // Get PID of the container process.
  365. inspect, err := apiClient.ContainerInspect(ctx, cID)
  366. assert.NilError(t, err)
  367. pid := inspect.State.Pid
  368. d.Stop(t)
  369. finishContainer()
  370. poll.WaitOn(t, process.NotAlive(pid))
  371. d.Start(t, "--live-restore", "--iptables=false")
  372. poll.WaitOn(t, container.IsRemoved(ctx, apiClient, cID))
  373. })
  374. }
  375. func testLiveRestoreVolumeReferences(t *testing.T) {
  376. t.Parallel()
  377. ctx := testutil.StartSpan(baseContext, t)
  378. d := daemon.New(t)
  379. d.StartWithBusybox(ctx, t, "--live-restore", "--iptables=false")
  380. defer func() {
  381. d.Stop(t)
  382. d.Cleanup(t)
  383. }()
  384. c := d.NewClientT(t)
  385. runTest := func(t *testing.T, policy containertypes.RestartPolicyMode) {
  386. t.Run(string(policy), func(t *testing.T) {
  387. ctx := testutil.StartSpan(ctx, t)
  388. volName := "test-live-restore-volume-references-" + string(policy)
  389. _, err := c.VolumeCreate(ctx, volume.CreateOptions{Name: volName})
  390. assert.NilError(t, err)
  391. // Create a container that uses the volume
  392. m := mount.Mount{
  393. Type: mount.TypeVolume,
  394. Source: volName,
  395. Target: "/foo",
  396. }
  397. cID := container.Run(ctx, t, c, container.WithMount(m), container.WithCmd("top"), container.WithRestartPolicy(policy))
  398. defer c.ContainerRemove(ctx, cID, containertypes.RemoveOptions{Force: true})
  399. // Stop the daemon
  400. d.Restart(t, "--live-restore", "--iptables=false")
  401. // Try to remove the volume
  402. err = c.VolumeRemove(ctx, volName, false)
  403. assert.ErrorContains(t, err, "volume is in use")
  404. _, err = c.VolumeInspect(ctx, volName)
  405. assert.NilError(t, err)
  406. })
  407. }
  408. t.Run("restartPolicy", func(t *testing.T) {
  409. runTest(t, containertypes.RestartPolicyAlways)
  410. runTest(t, containertypes.RestartPolicyUnlessStopped)
  411. runTest(t, containertypes.RestartPolicyOnFailure)
  412. runTest(t, containertypes.RestartPolicyDisabled)
  413. })
  414. // Make sure that the local volume driver's mount ref count is restored
  415. // Addresses https://github.com/moby/moby/issues/44422
  416. t.Run("local volume with mount options", func(t *testing.T) {
  417. ctx := testutil.StartSpan(ctx, t)
  418. v, err := c.VolumeCreate(ctx, volume.CreateOptions{
  419. Driver: "local",
  420. Name: "test-live-restore-volume-references-local",
  421. DriverOpts: map[string]string{
  422. "type": "tmpfs",
  423. "device": "tmpfs",
  424. },
  425. })
  426. assert.NilError(t, err)
  427. m := mount.Mount{
  428. Type: mount.TypeVolume,
  429. Source: v.Name,
  430. Target: "/foo",
  431. }
  432. const testContent = "hello"
  433. cID := container.Run(ctx, t, c, container.WithMount(m), container.WithCmd("sh", "-c", "echo "+testContent+">>/foo/test.txt; sleep infinity"))
  434. defer c.ContainerRemove(ctx, cID, containertypes.RemoveOptions{Force: true})
  435. // Wait until container creates a file in the volume.
  436. poll.WaitOn(t, func(t poll.LogT) poll.Result {
  437. stat, err := c.ContainerStatPath(ctx, cID, "/foo/test.txt")
  438. if err != nil {
  439. if errdefs.IsNotFound(err) {
  440. return poll.Continue("file doesn't yet exist")
  441. }
  442. return poll.Error(err)
  443. }
  444. if int(stat.Size) != len(testContent)+1 {
  445. return poll.Error(fmt.Errorf("unexpected test file size: %d", stat.Size))
  446. }
  447. return poll.Success()
  448. })
  449. d.Restart(t, "--live-restore", "--iptables=false")
  450. // Try to remove the volume
  451. // This should fail since its used by a container
  452. err = c.VolumeRemove(ctx, v.Name, false)
  453. assert.ErrorContains(t, err, "volume is in use")
  454. t.Run("volume still mounted", func(t *testing.T) {
  455. skip.If(t, testEnv.IsRootless(), "restarted rootless daemon has a new mount namespace and it won't have the previous mounts")
  456. // Check if a new container with the same volume has access to the previous content.
  457. // This fails if the volume gets unmounted at startup.
  458. cID2 := container.Run(ctx, t, c, container.WithMount(m), container.WithCmd("cat", "/foo/test.txt"))
  459. defer c.ContainerRemove(ctx, cID2, containertypes.RemoveOptions{Force: true})
  460. poll.WaitOn(t, container.IsStopped(ctx, c, cID2))
  461. inspect, err := c.ContainerInspect(ctx, cID2)
  462. if assert.Check(t, err) {
  463. assert.Check(t, is.Equal(inspect.State.ExitCode, 0), "volume doesn't have the same file")
  464. }
  465. logs, err := c.ContainerLogs(ctx, cID2, containertypes.LogsOptions{ShowStdout: true})
  466. assert.NilError(t, err)
  467. defer logs.Close()
  468. var stdoutBuf bytes.Buffer
  469. _, err = stdcopy.StdCopy(&stdoutBuf, io.Discard, logs)
  470. assert.NilError(t, err)
  471. assert.Check(t, is.Equal(strings.TrimSpace(stdoutBuf.String()), testContent))
  472. })
  473. // Remove that container which should free the references in the volume
  474. err = c.ContainerRemove(ctx, cID, containertypes.RemoveOptions{Force: true})
  475. assert.NilError(t, err)
  476. // Now we should be able to remove the volume
  477. err = c.VolumeRemove(ctx, v.Name, false)
  478. assert.NilError(t, err)
  479. })
  480. // Make sure that we don't panic if the container has bind-mounts
  481. // (which should not be "restored")
  482. // Regression test for https://github.com/moby/moby/issues/45898
  483. t.Run("container with bind-mounts", func(t *testing.T) {
  484. ctx := testutil.StartSpan(ctx, t)
  485. m := mount.Mount{
  486. Type: mount.TypeBind,
  487. Source: os.TempDir(),
  488. Target: "/foo",
  489. }
  490. cID := container.Run(ctx, t, c, container.WithMount(m), container.WithCmd("top"))
  491. defer c.ContainerRemove(ctx, cID, containertypes.RemoveOptions{Force: true})
  492. d.Restart(t, "--live-restore", "--iptables=false")
  493. err := c.ContainerRemove(ctx, cID, containertypes.RemoveOptions{Force: true})
  494. assert.NilError(t, err)
  495. })
  496. }
  497. func TestDaemonDefaultBridgeWithFixedCidrButNoBip(t *testing.T) {
  498. skip.If(t, runtime.GOOS == "windows")
  499. ctx := testutil.StartSpan(baseContext, t)
  500. bridgeName := "ext-bridge1"
  501. d := daemon.New(t, daemon.WithEnvVars("DOCKER_TEST_CREATE_DEFAULT_BRIDGE="+bridgeName))
  502. defer func() {
  503. d.Stop(t)
  504. d.Cleanup(t)
  505. }()
  506. defer func() {
  507. // No need to clean up when running this test in rootless mode, as the
  508. // interface is deleted when the daemon is stopped and the netns
  509. // reclaimed by the kernel.
  510. if !testEnv.IsRootless() {
  511. deleteInterface(t, bridgeName)
  512. }
  513. }()
  514. d.StartWithBusybox(ctx, t, "--bridge", bridgeName, "--fixed-cidr", "192.168.130.0/24")
  515. }
  516. func deleteInterface(t *testing.T, ifName string) {
  517. icmd.RunCommand("ip", "link", "delete", ifName).Assert(t, icmd.Success)
  518. icmd.RunCommand("iptables", "-t", "nat", "--flush").Assert(t, icmd.Success)
  519. icmd.RunCommand("iptables", "--flush").Assert(t, icmd.Success)
  520. }