daemon_test.go 20 KB

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