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