github.com/rish1988/moby@v25.0.2+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 )) 212 c := d.NewClientT(t) 213 214 d.Start(t, "--iptables=false") 215 defer d.Stop(t) 216 217 info := d.Info(t) 218 assert.Check(t, is.Equal(info.HTTPProxy, proxyServer.URL)) 219 assert.Check(t, is.Equal(info.HTTPSProxy, proxyServer.URL)) 220 assert.Check(t, is.Equal(info.NoProxy, "example.com")) 221 222 _, err := c.ImagePull(ctx, "example.org:5000/some/image:latest", image.PullOptions{}) 223 assert.ErrorContains(t, err, "", "pulling should have failed") 224 assert.Equal(t, received, "example.org:5000") 225 226 // Test NoProxy: example.com should not hit the proxy, and "received" variable should not be changed. 227 _, err = c.ImagePull(ctx, "example.com/some/image:latest", image.PullOptions{}) 228 assert.ErrorContains(t, err, "", "pulling should have failed") 229 assert.Equal(t, received, "example.org:5000", "should not have used proxy") 230 }) 231 232 // Configure proxy through command-line flags 233 t.Run("command-line options", func(t *testing.T) { 234 t.Parallel() 235 236 ctx := testutil.StartSpan(ctx, t) 237 238 var received string 239 proxyServer := newProxy(&received, t) 240 241 d := daemon.New(t, daemon.WithEnvVars( 242 "HTTP_PROXY="+"http://"+userPass+"from-env-http.invalid", 243 "http_proxy="+"http://"+userPass+"from-env-http.invalid", 244 "HTTPS_PROXY="+"https://"+userPass+"myuser:mypassword@from-env-https-invalid", 245 "https_proxy="+"https://"+userPass+"myuser:mypassword@from-env-https-invalid", 246 "NO_PROXY=ignore.invalid", 247 "no_proxy=ignore.invalid", 248 )) 249 d.Start(t, "--iptables=false", "--http-proxy", proxyServer.URL, "--https-proxy", proxyServer.URL, "--no-proxy", "example.com") 250 defer d.Stop(t) 251 252 c := d.NewClientT(t) 253 254 info := d.Info(t) 255 assert.Check(t, is.Equal(info.HTTPProxy, proxyServer.URL)) 256 assert.Check(t, is.Equal(info.HTTPSProxy, proxyServer.URL)) 257 assert.Check(t, is.Equal(info.NoProxy, "example.com")) 258 259 ok, _ := d.ScanLogsT(ctx, t, daemon.ScanLogsMatchAll( 260 "overriding existing proxy variable with value from configuration", 261 "http_proxy", 262 "HTTP_PROXY", 263 "https_proxy", 264 "HTTPS_PROXY", 265 "no_proxy", 266 "NO_PROXY", 267 )) 268 assert.Assert(t, ok) 269 270 ok, logs := d.ScanLogsT(ctx, t, daemon.ScanLogsMatchString(userPass)) 271 assert.Assert(t, !ok, "logs should not contain the non-sanitized proxy URL: %s", logs) 272 273 _, err := c.ImagePull(ctx, "example.org:5001/some/image:latest", image.PullOptions{}) 274 assert.ErrorContains(t, err, "", "pulling should have failed") 275 assert.Equal(t, received, "example.org:5001") 276 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:5001", "should not have used proxy") 281 }) 282 283 // Configure proxy through configuration file 284 t.Run("configuration file", func(t *testing.T) { 285 t.Parallel() 286 ctx := testutil.StartSpan(ctx, t) 287 288 var received string 289 proxyServer := newProxy(&received, t) 290 291 d := daemon.New(t, daemon.WithEnvVars( 292 "HTTP_PROXY="+"http://"+userPass+"from-env-http.invalid", 293 "http_proxy="+"http://"+userPass+"from-env-http.invalid", 294 "HTTPS_PROXY="+"https://"+userPass+"myuser:mypassword@from-env-https-invalid", 295 "https_proxy="+"https://"+userPass+"myuser:mypassword@from-env-https-invalid", 296 "NO_PROXY=ignore.invalid", 297 "no_proxy=ignore.invalid", 298 )) 299 c := d.NewClientT(t) 300 301 configFile := filepath.Join(d.RootDir(), "daemon.json") 302 configJSON := fmt.Sprintf(`{"proxies":{"http-proxy":%[1]q, "https-proxy": %[1]q, "no-proxy": "example.com"}}`, proxyServer.URL) 303 assert.NilError(t, os.WriteFile(configFile, []byte(configJSON), 0o644)) 304 305 d.Start(t, "--iptables=false", "--config-file", configFile) 306 defer d.Stop(t) 307 308 info := d.Info(t) 309 assert.Check(t, is.Equal(info.HTTPProxy, proxyServer.URL)) 310 assert.Check(t, is.Equal(info.HTTPSProxy, proxyServer.URL)) 311 assert.Check(t, is.Equal(info.NoProxy, "example.com")) 312 313 d.ScanLogsT(ctx, t, daemon.ScanLogsMatchAll( 314 "overriding existing proxy variable with value from configuration", 315 "http_proxy", 316 "HTTP_PROXY", 317 "https_proxy", 318 "HTTPS_PROXY", 319 "no_proxy", 320 "NO_PROXY", 321 )) 322 323 _, err := c.ImagePull(ctx, "example.org:5002/some/image:latest", image.PullOptions{}) 324 assert.ErrorContains(t, err, "", "pulling should have failed") 325 assert.Equal(t, received, "example.org:5002") 326 327 // Test NoProxy: example.com should not hit the proxy, and "received" variable should not be changed. 328 _, err = c.ImagePull(ctx, "example.com/some/image:latest", image.PullOptions{}) 329 assert.ErrorContains(t, err, "", "pulling should have failed") 330 assert.Equal(t, received, "example.org:5002", "should not have used proxy") 331 }) 332 333 // Conflicting options (passed both through command-line options and config file) 334 t.Run("conflicting options", func(t *testing.T) { 335 ctx := testutil.StartSpan(ctx, t) 336 const ( 337 proxyRawURL = "https://" + userPass + "example.org" 338 proxyURL = "https://xxxxx:xxxxx@example.org" 339 ) 340 341 d := daemon.New(t) 342 343 configFile := filepath.Join(d.RootDir(), "daemon.json") 344 configJSON := fmt.Sprintf(`{"proxies":{"http-proxy":%[1]q, "https-proxy": %[1]q, "no-proxy": "example.com"}}`, proxyRawURL) 345 assert.NilError(t, os.WriteFile(configFile, []byte(configJSON), 0o644)) 346 347 err := d.StartWithError("--http-proxy", proxyRawURL, "--https-proxy", proxyRawURL, "--no-proxy", "example.com", "--config-file", configFile, "--validate") 348 assert.ErrorContains(t, err, "daemon exited during startup") 349 350 expected := fmt.Sprintf( 351 `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)`, 352 proxyURL, 353 ) 354 poll.WaitOn(t, d.PollCheckLogs(ctx, daemon.ScanLogsMatchString(expected))) 355 }) 356 357 // Make sure values are sanitized when reloading the daemon-config 358 t.Run("reload sanitized", func(t *testing.T) { 359 t.Parallel() 360 ctx := testutil.StartSpan(ctx, t) 361 362 const ( 363 proxyRawURL = "https://" + userPass + "example.org" 364 proxyURL = "https://xxxxx:xxxxx@example.org" 365 ) 366 367 d := daemon.New(t) 368 d.Start(t, "--iptables=false", "--http-proxy", proxyRawURL, "--https-proxy", proxyRawURL, "--no-proxy", "example.com") 369 defer d.Stop(t) 370 err := d.Signal(syscall.SIGHUP) 371 assert.NilError(t, err) 372 373 poll.WaitOn(t, d.PollCheckLogs(ctx, daemon.ScanLogsMatchAll("Reloaded configuration:", proxyURL))) 374 375 ok, logs := d.ScanLogsT(ctx, t, daemon.ScanLogsMatchString(userPass)) 376 assert.Assert(t, !ok, "logs should not contain the non-sanitized proxy URL: %s", logs) 377 }) 378 } 379 380 func TestLiveRestore(t *testing.T) { 381 skip.If(t, runtime.GOOS == "windows", "cannot start multiple daemons on windows") 382 _ = testutil.StartSpan(baseContext, t) 383 384 t.Run("volume references", testLiveRestoreVolumeReferences) 385 t.Run("autoremove", testLiveRestoreAutoRemove) 386 } 387 388 func testLiveRestoreAutoRemove(t *testing.T) { 389 skip.If(t, testEnv.IsRootless(), "restarted rootless daemon will have a new process namespace") 390 391 t.Parallel() 392 ctx := testutil.StartSpan(baseContext, t) 393 394 run := func(t *testing.T) (*daemon.Daemon, func(), string) { 395 d := daemon.New(t) 396 d.StartWithBusybox(ctx, t, "--live-restore", "--iptables=false") 397 t.Cleanup(func() { 398 d.Stop(t) 399 d.Cleanup(t) 400 }) 401 402 tmpDir := t.TempDir() 403 404 apiClient := d.NewClientT(t) 405 406 cID := container.Run(ctx, t, apiClient, 407 container.WithBind(tmpDir, "/v"), 408 // Run until a 'stop' file is created. 409 container.WithCmd("sh", "-c", "while [ ! -f /v/stop ]; do sleep 0.1; done"), 410 container.WithAutoRemove) 411 t.Cleanup(func() { apiClient.ContainerRemove(ctx, cID, containertypes.RemoveOptions{Force: true}) }) 412 finishContainer := func() { 413 file, err := os.Create(filepath.Join(tmpDir, "stop")) 414 assert.NilError(t, err, "Failed to create 'stop' file") 415 file.Close() 416 } 417 return d, finishContainer, cID 418 } 419 420 t.Run("engine restart shouldnt kill alive containers", func(t *testing.T) { 421 d, finishContainer, cID := run(t) 422 423 d.Restart(t, "--live-restore", "--iptables=false") 424 425 apiClient := d.NewClientT(t) 426 _, err := apiClient.ContainerInspect(ctx, cID) 427 assert.NilError(t, err, "Container shouldn't be removed after engine restart") 428 429 finishContainer() 430 431 poll.WaitOn(t, container.IsRemoved(ctx, apiClient, cID)) 432 }) 433 t.Run("engine restart should remove containers that exited", func(t *testing.T) { 434 d, finishContainer, cID := run(t) 435 436 apiClient := d.NewClientT(t) 437 438 // Get PID of the container process. 439 inspect, err := apiClient.ContainerInspect(ctx, cID) 440 assert.NilError(t, err) 441 pid := inspect.State.Pid 442 443 d.Stop(t) 444 445 finishContainer() 446 poll.WaitOn(t, process.NotAlive(pid)) 447 448 d.Start(t, "--live-restore", "--iptables=false") 449 450 poll.WaitOn(t, container.IsRemoved(ctx, apiClient, cID)) 451 }) 452 } 453 454 func testLiveRestoreVolumeReferences(t *testing.T) { 455 t.Parallel() 456 ctx := testutil.StartSpan(baseContext, t) 457 458 d := daemon.New(t) 459 d.StartWithBusybox(ctx, t, "--live-restore", "--iptables=false") 460 defer func() { 461 d.Stop(t) 462 d.Cleanup(t) 463 }() 464 465 c := d.NewClientT(t) 466 467 runTest := func(t *testing.T, policy containertypes.RestartPolicyMode) { 468 t.Run(string(policy), func(t *testing.T) { 469 ctx := testutil.StartSpan(ctx, t) 470 volName := "test-live-restore-volume-references-" + string(policy) 471 _, err := c.VolumeCreate(ctx, volume.CreateOptions{Name: volName}) 472 assert.NilError(t, err) 473 474 // Create a container that uses the volume 475 m := mount.Mount{ 476 Type: mount.TypeVolume, 477 Source: volName, 478 Target: "/foo", 479 } 480 cID := container.Run(ctx, t, c, container.WithMount(m), container.WithCmd("top"), container.WithRestartPolicy(policy)) 481 defer c.ContainerRemove(ctx, cID, containertypes.RemoveOptions{Force: true}) 482 483 // Stop the daemon 484 d.Restart(t, "--live-restore", "--iptables=false") 485 486 // Try to remove the volume 487 err = c.VolumeRemove(ctx, volName, false) 488 assert.ErrorContains(t, err, "volume is in use") 489 490 _, err = c.VolumeInspect(ctx, volName) 491 assert.NilError(t, err) 492 }) 493 } 494 495 t.Run("restartPolicy", func(t *testing.T) { 496 runTest(t, containertypes.RestartPolicyAlways) 497 runTest(t, containertypes.RestartPolicyUnlessStopped) 498 runTest(t, containertypes.RestartPolicyOnFailure) 499 runTest(t, containertypes.RestartPolicyDisabled) 500 }) 501 502 // Make sure that the local volume driver's mount ref count is restored 503 // Addresses https://github.com/moby/moby/issues/44422 504 t.Run("local volume with mount options", func(t *testing.T) { 505 ctx := testutil.StartSpan(ctx, t) 506 v, err := c.VolumeCreate(ctx, volume.CreateOptions{ 507 Driver: "local", 508 Name: "test-live-restore-volume-references-local", 509 DriverOpts: map[string]string{ 510 "type": "tmpfs", 511 "device": "tmpfs", 512 }, 513 }) 514 assert.NilError(t, err) 515 m := mount.Mount{ 516 Type: mount.TypeVolume, 517 Source: v.Name, 518 Target: "/foo", 519 } 520 521 const testContent = "hello" 522 cID := container.Run(ctx, t, c, container.WithMount(m), container.WithCmd("sh", "-c", "echo "+testContent+">>/foo/test.txt; sleep infinity")) 523 defer c.ContainerRemove(ctx, cID, containertypes.RemoveOptions{Force: true}) 524 525 // Wait until container creates a file in the volume. 526 poll.WaitOn(t, func(t poll.LogT) poll.Result { 527 stat, err := c.ContainerStatPath(ctx, cID, "/foo/test.txt") 528 if err != nil { 529 if errdefs.IsNotFound(err) { 530 return poll.Continue("file doesn't yet exist") 531 } 532 return poll.Error(err) 533 } 534 535 if int(stat.Size) != len(testContent)+1 { 536 return poll.Error(fmt.Errorf("unexpected test file size: %d", stat.Size)) 537 } 538 539 return poll.Success() 540 }) 541 542 d.Restart(t, "--live-restore", "--iptables=false") 543 544 // Try to remove the volume 545 // This should fail since its used by a container 546 err = c.VolumeRemove(ctx, v.Name, false) 547 assert.ErrorContains(t, err, "volume is in use") 548 549 t.Run("volume still mounted", func(t *testing.T) { 550 skip.If(t, testEnv.IsRootless(), "restarted rootless daemon has a new mount namespace and it won't have the previous mounts") 551 552 // Check if a new container with the same volume has access to the previous content. 553 // This fails if the volume gets unmounted at startup. 554 cID2 := container.Run(ctx, t, c, container.WithMount(m), container.WithCmd("cat", "/foo/test.txt")) 555 defer c.ContainerRemove(ctx, cID2, containertypes.RemoveOptions{Force: true}) 556 557 poll.WaitOn(t, container.IsStopped(ctx, c, cID2)) 558 559 inspect, err := c.ContainerInspect(ctx, cID2) 560 if assert.Check(t, err) { 561 assert.Check(t, is.Equal(inspect.State.ExitCode, 0), "volume doesn't have the same file") 562 } 563 564 logs, err := c.ContainerLogs(ctx, cID2, containertypes.LogsOptions{ShowStdout: true}) 565 assert.NilError(t, err) 566 defer logs.Close() 567 568 var stdoutBuf bytes.Buffer 569 _, err = stdcopy.StdCopy(&stdoutBuf, io.Discard, logs) 570 assert.NilError(t, err) 571 572 assert.Check(t, is.Equal(strings.TrimSpace(stdoutBuf.String()), testContent)) 573 }) 574 575 // Remove that container which should free the references in the volume 576 err = c.ContainerRemove(ctx, cID, containertypes.RemoveOptions{Force: true}) 577 assert.NilError(t, err) 578 579 // Now we should be able to remove the volume 580 err = c.VolumeRemove(ctx, v.Name, false) 581 assert.NilError(t, err) 582 }) 583 584 // Make sure that we don't panic if the container has bind-mounts 585 // (which should not be "restored") 586 // Regression test for https://github.com/moby/moby/issues/45898 587 t.Run("container with bind-mounts", func(t *testing.T) { 588 ctx := testutil.StartSpan(ctx, t) 589 m := mount.Mount{ 590 Type: mount.TypeBind, 591 Source: os.TempDir(), 592 Target: "/foo", 593 } 594 cID := container.Run(ctx, t, c, container.WithMount(m), container.WithCmd("top")) 595 defer c.ContainerRemove(ctx, cID, containertypes.RemoveOptions{Force: true}) 596 597 d.Restart(t, "--live-restore", "--iptables=false") 598 599 err := c.ContainerRemove(ctx, cID, containertypes.RemoveOptions{Force: true}) 600 assert.NilError(t, err) 601 }) 602 } 603 604 func TestDaemonDefaultBridgeWithFixedCidrButNoBip(t *testing.T) { 605 skip.If(t, runtime.GOOS == "windows") 606 607 ctx := testutil.StartSpan(baseContext, t) 608 609 bridgeName := "ext-bridge1" 610 d := daemon.New(t, daemon.WithEnvVars("DOCKER_TEST_CREATE_DEFAULT_BRIDGE="+bridgeName)) 611 defer func() { 612 d.Stop(t) 613 d.Cleanup(t) 614 }() 615 616 defer func() { 617 // No need to clean up when running this test in rootless mode, as the 618 // interface is deleted when the daemon is stopped and the netns 619 // reclaimed by the kernel. 620 if !testEnv.IsRootless() { 621 deleteInterface(t, bridgeName) 622 } 623 }() 624 d.StartWithBusybox(ctx, t, "--bridge", bridgeName, "--fixed-cidr", "192.168.130.0/24") 625 } 626 627 func deleteInterface(t *testing.T, ifName string) { 628 icmd.RunCommand("ip", "link", "delete", ifName).Assert(t, icmd.Success) 629 icmd.RunCommand("iptables", "-t", "nat", "--flush").Assert(t, icmd.Success) 630 icmd.RunCommand("iptables", "--flush").Assert(t, icmd.Success) 631 }