diff --git a/Makefile b/Makefile index 360ff91d4..a58e935bc 100644 --- a/Makefile +++ b/Makefile @@ -266,17 +266,17 @@ simple-tests: unit-tests # Compatibility target. INTEGRATION_TARGETS := //test/image:image_test //test/e2e:integration_test docker-tests: load-basic $(RUNTIME_BIN) - @$(call install_runtime,$(RUNTIME),) # Clear flags. + @$(call install_runtime,$(RUNTIME),--overlay2=none) @$(call install_runtime,$(RUNTIME)-fdlimit,--fdlimit=2000) # Used by TestRlimitNoFile. @$(call install_runtime,$(RUNTIME)-dcache,--fdlimit=2000 --dcache=100) # Used by TestDentryCacheLimit. @$(call install_runtime,$(RUNTIME)-host-uds,--host-uds=all) # Used by TestHostSocketConnect. @$(call install_runtime,$(RUNTIME)-overlay,--overlay2=all:self) # Used by TestOverlay*. + @$(call install_runtime,$(RUNTIME)-no-overlay,--overlay2=none) # Used by TestCheckpointRestore. @$(call test_runtime,$(RUNTIME),$(INTEGRATION_TARGETS) //test/e2e:integration_runtime_test) .PHONY: docker-tests -# TODO(b/241832602): Run overlay tests with host filestore option after S/R support is added. overlay-tests: load-basic $(RUNTIME_BIN) - @$(call install_runtime,$(RUNTIME),--overlay2=all:memory) + @$(call install_runtime,$(RUNTIME),--overlay2=all:dir=/tmp) @$(call test_runtime,$(RUNTIME),--test_env=TEST_OVERLAY=true $(INTEGRATION_TARGETS)) .PHONY: overlay-tests diff --git a/pkg/sentry/fsimpl/tmpfs/save_restore.go b/pkg/sentry/fsimpl/tmpfs/save_restore.go index a540e8915..d08975d82 100644 --- a/pkg/sentry/fsimpl/tmpfs/save_restore.go +++ b/pkg/sentry/fsimpl/tmpfs/save_restore.go @@ -17,7 +17,7 @@ package tmpfs // afterLoad is called by stateify. func (fs *filesystem) afterLoad() { if fs.privateMF { - // TODO(b/241832602): Add S/R support. + // TODO(b/271612187): Add S/R support. panic("S/R not supported for private memory files") } fs.mf = fs.mfp.MemoryFile() diff --git a/pkg/test/testutil/testutil.go b/pkg/test/testutil/testutil.go index bb8b9e823..db24a0482 100644 --- a/pkg/test/testutil/testutil.go +++ b/pkg/test/testutil/testutil.go @@ -467,6 +467,20 @@ func WaitForHTTP(ip string, port int, timeout time.Duration) error { return Poll(cb, timeout) } +// HTTPRequestSucceeds sends a request to a given url and checks that the status is OK. +func HTTPRequestSucceeds(client http.Client, server string, port int) error { + url := fmt.Sprintf("http://%s:%d", server, port) + // Ensure that content is being served. + resp, err := client.Get(url) + if err != nil { + return fmt.Errorf("error reaching http server: %v", err) + } + if want := http.StatusOK; resp.StatusCode != want { + return fmt.Errorf("wrong response code, got: %d, want: %d", resp.StatusCode, want) + } + return nil +} + // Reaper reaps child processes. type Reaper struct { // mu protects ch, which will be nil if the reaper is not running. diff --git a/runsc/cmd/do.go b/runsc/cmd/do.go index f6ef57456..8292f9040 100644 --- a/runsc/cmd/do.go +++ b/runsc/cmd/do.go @@ -146,9 +146,12 @@ func (c *Do) Execute(_ context.Context, f *flag.FlagSet, args ...any) subcommand return util.Errorf("Error to retrieve hostname: %v", err) } - // If c.overlay is set, then forcefully enable overlay. - if overlay2 := conf.GetOverlay2(); c.overlay && !overlay2.Enabled() { - conf.Overlay = true + // If c.overlay is set, then enable overlay. + conf.Overlay = false // conf.Overlay is deprecated. + if c.overlay { + conf.Overlay2 = config.Overlay2{RootMount: true, SubMounts: true, Medium: "memory"} + } else { + conf.Overlay2 = config.Overlay2{RootMount: false, SubMounts: false, Medium: ""} } absRoot, err := resolvePath(c.root) if err != nil { diff --git a/runsc/config/config.go b/runsc/config/config.go index 292e4d040..948b7675c 100644 --- a/runsc/config/config.go +++ b/runsc/config/config.go @@ -322,7 +322,7 @@ func (c *Config) validate() error { return fmt.Errorf("overlay flag has been replaced with overlay2 flag") } if overlay2 := c.GetOverlay2(); c.FileAccess == FileAccessShared && overlay2.Enabled() { - return fmt.Errorf("overlay flag is incompatible with shared file access") + return fmt.Errorf("overlay flag is incompatible with shared file access for rootfs") } if c.NumNetworkChannels <= 0 { return fmt.Errorf("num_network_channels must be > 0, got: %d", c.NumNetworkChannels) @@ -705,7 +705,9 @@ func defaultOverlay2() *Overlay2 { // Set implements flag.Value. func (o *Overlay2) Set(v string) error { if v == "none" { - // Defaults are correct. + o.RootMount = false + o.SubMounts = false + o.Medium = "" return nil } vs := strings.Split(v, ":") diff --git a/runsc/config/config_test.go b/runsc/config/config_test.go index 97bd431ab..679da8c62 100644 --- a/runsc/config/config_test.go +++ b/runsc/config/config_test.go @@ -252,7 +252,7 @@ func TestValidationFail(t *testing.T) { name: "shared+overlay", flags: map[string]string{ "file-access": "shared", - "overlay": "true", + "overlay2": "root:self", }, error: "overlay flag is incompatible", }, diff --git a/runsc/container/container_test.go b/runsc/container/container_test.go index 7591dfe89..f940ca272 100644 --- a/runsc/container/container_test.go +++ b/runsc/container/container_test.go @@ -408,6 +408,7 @@ func configs(t *testing.T, noOverlay bool) map[string]*config.Config { cs := make(map[string]*config.Config) for _, p := range ps { c := testutil.TestConfig(t) + c.Overlay2 = config.Overlay2{RootMount: false, SubMounts: false, Medium: ""} c.Platform = p cs[p] = c } @@ -417,7 +418,7 @@ func configs(t *testing.T, noOverlay bool) map[string]*config.Config { for _, p := range ps { c := testutil.TestConfig(t) c.Platform = p - c.Overlay = true + c.Overlay2 = config.Overlay2{RootMount: true, SubMounts: true, Medium: "memory"} cs[p+"-overlay"] = c } } @@ -616,7 +617,8 @@ func TestExePath(t *testing.T) { "default": testutil.TestConfig(t), "overlay": testutil.TestConfig(t), } - configs["overlay"].Overlay = true + configs["default"].Overlay2 = config.Overlay2{RootMount: false, SubMounts: false, Medium: ""} + configs["overlay"].Overlay2 = config.Overlay2{RootMount: true, SubMounts: true, Medium: "memory"} for name, conf := range configs { t.Run(name, func(t *testing.T) { diff --git a/runsc/container/multi_container_test.go b/runsc/container/multi_container_test.go index 4fa717d5f..2c9be0efa 100644 --- a/runsc/container/multi_container_test.go +++ b/runsc/container/multi_container_test.go @@ -1086,7 +1086,7 @@ func TestMultiContainerDifferentFilesystems(t *testing.T) { // Make sure overlay is enabled, and none of the root filesystems are // read-only, otherwise we won't be able to create the file. - conf.Overlay = true + conf.Overlay2 = config.Overlay2{RootMount: true, SubMounts: true, Medium: "memory"} specs, ids := createSpecs(cmdRoot, cmd, cmd) for _, s := range specs { s.Root.Readonly = false diff --git a/runsc/container/shared_volume_test.go b/runsc/container/shared_volume_test.go index 8d8402886..7d274de38 100644 --- a/runsc/container/shared_volume_test.go +++ b/runsc/container/shared_volume_test.go @@ -33,6 +33,7 @@ import ( // into and out of the sandbox. func TestSharedVolume(t *testing.T) { conf := testutil.TestConfig(t) + conf.Overlay2 = config.Overlay2{RootMount: false, SubMounts: false, Medium: ""} conf.FileAccess = config.FileAccessShared // Main process just sleeps. We will use "exec" to probe the state of @@ -145,11 +146,6 @@ func TestSharedVolume(t *testing.T) { t.Errorf("stat %q got error %v, wanted nil", filename, err) } - // File should exist outside the sandbox. - if _, err := os.Stat(filename); err != nil { - t.Errorf("stat %q got error %v, wanted nil", filename, err) - } - // Delete the file from within the sandbox. argsRemove := &control.ExecArgs{ Filename: "/bin/rm", @@ -186,6 +182,7 @@ func checkFile(conf *config.Config, c *Container, filename string, want []byte) // is reflected inside. func TestSharedVolumeFile(t *testing.T) { conf := testutil.TestConfig(t) + conf.Overlay2 = config.Overlay2{RootMount: false, SubMounts: false, Medium: ""} conf.FileAccess = config.FileAccessShared // Main process just sleeps. We will use "exec" to probe the state of diff --git a/test/e2e/integration_runtime_test.go b/test/e2e/integration_runtime_test.go index 0010197da..b101a4af7 100644 --- a/test/e2e/integration_runtime_test.go +++ b/test/e2e/integration_runtime_test.go @@ -27,6 +27,7 @@ import ( "fmt" "io/ioutil" "net" + "net/http" "os" "path/filepath" "strconv" @@ -213,3 +214,56 @@ func TestOverlayRootfsWhiteout(t *testing.T) { t.Errorf("root directory contains a file/directory whose name contains %q: output = %q", boot.SelfOverlayFilestorePrefix, got) } } + +// TODO(b/271612187): Once S/R support is added for file-backed overlays, move +// this test to integration_test.go so it can run with the default runsc +// configuration which uses file-backed overlay. +func TestCheckpointRestore(t *testing.T) { + if !testutil.IsCheckpointSupported() { + t.Skip("Pause/resume is not supported.") + } + dockerutil.EnsureDockerExperimentalEnabled() + + ctx := context.Background() + d := dockerutil.MakeContainerWithRuntime(ctx, t, "-no-overlay") + defer d.CleanUp(ctx) + + // Start the container. + port := 8080 + if err := d.Spawn(ctx, dockerutil.RunOpts{ + Image: "basic/python", + Ports: []int{port}, // See Dockerfile. + }); err != nil { + t.Fatalf("docker run failed: %v", err) + } + + // Create a snapshot. + if err := d.Checkpoint(ctx, "test"); err != nil { + t.Fatalf("docker checkpoint failed: %v", err) + } + if err := d.WaitTimeout(ctx, defaultWait); err != nil { + t.Fatalf("wait failed: %v", err) + } + + // TODO(b/143498576): Remove Poll after github.com/moby/moby/issues/38963 is fixed. + if err := testutil.Poll(func() error { return d.Restore(ctx, "test") }, defaultWait); err != nil { + t.Fatalf("docker restore failed: %v", err) + } + + // Find container IP address. + ip, err := d.FindIP(ctx, false) + if err != nil { + t.Fatalf("docker.FindIP failed: %v", err) + } + + // Wait until it's up and running. + if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil { + t.Fatalf("WaitForHTTP() timeout: %v", err) + } + + // Check if container is working again. + client := http.Client{Timeout: defaultWait} + if err := testutil.HTTPRequestSucceeds(client, ip.String(), port); err != nil { + t.Error("http request failed:", err) + } +} diff --git a/test/e2e/integration_test.go b/test/e2e/integration_test.go index d506f467b..f0bb18e86 100644 --- a/test/e2e/integration_test.go +++ b/test/e2e/integration_test.go @@ -56,20 +56,6 @@ func TestMain(m *testing.M) { os.Exit(m.Run()) } -// httpRequestSucceeds sends a request to a given url and checks that the status is OK. -func httpRequestSucceeds(client http.Client, server string, port int) error { - url := fmt.Sprintf("http://%s:%d", server, port) - // Ensure that content is being served. - resp, err := client.Get(url) - if err != nil { - return fmt.Errorf("error reaching http server: %v", err) - } - if want := http.StatusOK; resp.StatusCode != want { - return fmt.Errorf("wrong response code, got: %d, want: %d", resp.StatusCode, want) - } - return nil -} - // TestLifeCycle tests a basic Create/Start/Stop docker container life cycle. func TestLifeCycle(t *testing.T) { ctx := context.Background() @@ -96,7 +82,7 @@ func TestLifeCycle(t *testing.T) { t.Fatalf("WaitForHTTP() timeout: %v", err) } client := http.Client{Timeout: defaultWait} - if err := httpRequestSucceeds(client, ip.String(), port); err != nil { + if err := testutil.HTTPRequestSucceeds(client, ip.String(), port); err != nil { t.Errorf("http request failed: %v", err) } @@ -139,7 +125,7 @@ func TestPauseResume(t *testing.T) { // Check that container is working. client := http.Client{Timeout: defaultWait} - if err := httpRequestSucceeds(client, ip.String(), port); err != nil { + if err := testutil.HTTPRequestSucceeds(client, ip.String(), port); err != nil { t.Error("http request failed:", err) } @@ -171,57 +157,7 @@ func TestPauseResume(t *testing.T) { // Check if container is working again. client = http.Client{Timeout: defaultWait} - if err := httpRequestSucceeds(client, ip.String(), port); err != nil { - t.Error("http request failed:", err) - } -} - -func TestCheckpointRestore(t *testing.T) { - if !testutil.IsCheckpointSupported() { - t.Skip("Pause/resume is not supported.") - } - dockerutil.EnsureDockerExperimentalEnabled() - - ctx := context.Background() - d := dockerutil.MakeContainer(ctx, t) - defer d.CleanUp(ctx) - - // Start the container. - port := 8080 - if err := d.Spawn(ctx, dockerutil.RunOpts{ - Image: "basic/python", - Ports: []int{port}, // See Dockerfile. - }); err != nil { - t.Fatalf("docker run failed: %v", err) - } - - // Create a snapshot. - if err := d.Checkpoint(ctx, "test"); err != nil { - t.Fatalf("docker checkpoint failed: %v", err) - } - if err := d.WaitTimeout(ctx, defaultWait); err != nil { - t.Fatalf("wait failed: %v", err) - } - - // TODO(b/143498576): Remove Poll after github.com/moby/moby/issues/38963 is fixed. - if err := testutil.Poll(func() error { return d.Restore(ctx, "test") }, defaultWait); err != nil { - t.Fatalf("docker restore failed: %v", err) - } - - // Find container IP address. - ip, err := d.FindIP(ctx, false) - if err != nil { - t.Fatalf("docker.FindIP failed: %v", err) - } - - // Wait until it's up and running. - if err := testutil.WaitForHTTP(ip.String(), port, defaultWait); err != nil { - t.Fatalf("WaitForHTTP() timeout: %v", err) - } - - // Check if container is working again. - client := http.Client{Timeout: defaultWait} - if err := httpRequestSucceeds(client, ip.String(), port); err != nil { + if err := testutil.HTTPRequestSucceeds(client, ip.String(), port); err != nil { t.Error("http request failed:", err) } } diff --git a/test/runner/main.go b/test/runner/main.go index 63592841f..c017499dd 100644 --- a/test/runner/main.go +++ b/test/runner/main.go @@ -238,6 +238,8 @@ func runRunsc(tc *gtest.TestCase, spec *specs.Spec) error { } if *overlay { args = append(args, "-overlay2=all:dir=/tmp") + } else { + args = append(args, "-overlay2=none") } if *debug { args = append(args, "-debug", "-log-packets=true") diff --git a/test/runtimes/exclude/python3.10.2.csv b/test/runtimes/exclude/python3.10.2.csv index f665b22c7..6ca462a12 100644 --- a/test/runtimes/exclude/python3.10.2.csv +++ b/test/runtimes/exclude/python3.10.2.csv @@ -8,3 +8,4 @@ test_readline,b/162980389,TestReadline hangs forever test_resource,b/76174079, test_signal,,Flaky - signal: alarm clock test_socket,,Broken test +test_os,b/271473320,TestScandir.test_attributes fails with overlay