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  }