github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/integration/daemon/daemon_test.go (about)

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