From 9a60e1cc874402d10eb7e45f62cce25bf5c41d30 Mon Sep 17 00:00:00 2001 From: Kir Kolyshkin Date: Wed, 19 Jul 2017 11:24:54 -0700 Subject: [PATCH] Test cases for new ipc modes These test cases cover various arguments for docker create/run --ipc option, as well as daemon's --default-ipc-mode cli option and configuration file parameter. For the description of container IPC modes, see previous commit. To run these: TESTFLAGS='-check.f IpcMode' make test-integration-cli [v2: simplify TestDaemonEvents(), add default-ipc-mode presense check] [v3: add TestDaemonIpcModeVSRestart] [v4: ipcmode test now uses client lib instead of CLI (except for exec)] [v5: nitpicks in comments] [v6: add test case for "none"; fix a typo; simplify TestDaemonEvents() more] Signed-off-by: Kir Kolyshkin --- integration-cli/docker_api_ipcmode_test.go | 222 ++++++++++++++++++ integration-cli/docker_cli_daemon_test.go | 159 +++++++++++++ .../docker_cli_events_unix_test.go | 27 ++- 3 files changed, 407 insertions(+), 1 deletion(-) create mode 100644 integration-cli/docker_api_ipcmode_test.go diff --git a/integration-cli/docker_api_ipcmode_test.go b/integration-cli/docker_api_ipcmode_test.go new file mode 100644 index 0000000000..52dfee85a7 --- /dev/null +++ b/integration-cli/docker_api_ipcmode_test.go @@ -0,0 +1,222 @@ +// build +linux +package main + +import ( + "bufio" + "fmt" + "io/ioutil" + "os" + "strings" + + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" + "github.com/docker/docker/integration-cli/checker" + "github.com/docker/docker/integration-cli/cli" + "github.com/docker/docker/integration-cli/request" + "github.com/go-check/check" + "golang.org/x/net/context" +) + +/* testIpcCheckDevExists checks whether a given mount (identified by its + * major:minor pair from /proc/self/mountinfo) exists on the host system. + * + * The format of /proc/self/mountinfo is like: + * + * 29 23 0:24 / /dev/shm rw,nosuid,nodev shared:4 - tmpfs tmpfs rw + * ^^^^\ + * - this is the minor:major we look for + */ +func testIpcCheckDevExists(mm string) (bool, error) { + f, err := os.Open("/proc/self/mountinfo") + if err != nil { + return false, err + } + defer f.Close() + + s := bufio.NewScanner(f) + for s.Scan() { + fields := strings.Fields(s.Text()) + if len(fields) < 7 { + continue + } + if fields[2] == mm { + return true, nil + } + } + + if err := s.Err(); err != nil { + return false, err + } + + return false, nil +} + +// testIpcNonePrivateShareable is a helper function to test "none", +// "private" and "shareable" modes. +func testIpcNonePrivateShareable(c *check.C, mode string, mustBeMounted bool, mustBeShared bool) { + cfg := container.Config{ + Image: "busybox", + Cmd: []string{"top"}, + } + hostCfg := container.HostConfig{ + IpcMode: container.IpcMode(mode), + } + ctx := context.Background() + + client, err := request.NewClient() + c.Assert(err, checker.IsNil) + + resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "") + c.Assert(err, checker.IsNil) + c.Assert(len(resp.Warnings), checker.Equals, 0) + + err = client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{}) + c.Assert(err, checker.IsNil) + + // get major:minor pair for /dev/shm from container's /proc/self/mountinfo + cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo" + mm := cli.DockerCmd(c, "exec", "-i", resp.ID, "sh", "-c", cmd).Combined() + if !mustBeMounted { + c.Assert(mm, checker.Equals, "") + // no more checks to perform + return + } + c.Assert(mm, checker.Matches, "^[0-9]+:[0-9]+$") + + shared, err := testIpcCheckDevExists(mm) + c.Assert(err, checker.IsNil) + c.Logf("[testIpcPrivateShareable] ipcmode: %v, ipcdev: %v, shared: %v, mustBeShared: %v\n", mode, mm, shared, mustBeShared) + c.Assert(shared, checker.Equals, mustBeShared) +} + +/* TestAPIIpcModeNone checks the container "none" IPC mode + * (--ipc none) works as expected. It makes sure there is no + * /dev/shm mount inside the container. + */ +func (s *DockerSuite) TestAPIIpcModeNone(c *check.C) { + testRequires(c, DaemonIsLinux) + testIpcNonePrivateShareable(c, "none", false, false) +} + +/* TestAPIIpcModePrivate checks the container private IPC mode + * (--ipc private) works as expected. It gets the minor:major pair + * of /dev/shm mount from the container, and makes sure there is no + * such pair on the host. + */ +func (s *DockerSuite) TestAPIIpcModePrivate(c *check.C) { + testRequires(c, DaemonIsLinux) + testIpcNonePrivateShareable(c, "private", true, false) +} + +/* TestAPIIpcModeShareable checks the container shareable IPC mode + * (--ipc shareable) works as expected. It gets the minor:major pair + * of /dev/shm mount from the container, and makes sure such pair + * also exists on the host. + */ +func (s *DockerSuite) TestAPIIpcModeShareable(c *check.C) { + testRequires(c, DaemonIsLinux) + testIpcNonePrivateShareable(c, "shareable", true, true) +} + +// testIpcContainer is a helper function to test --ipc container:NNN mode in various scenarios +func testIpcContainer(s *DockerSuite, c *check.C, donorMode string, mustWork bool) { + cfg := container.Config{ + Image: "busybox", + Cmd: []string{"top"}, + } + hostCfg := container.HostConfig{ + IpcMode: container.IpcMode(donorMode), + } + ctx := context.Background() + + client, err := request.NewClient() + c.Assert(err, checker.IsNil) + + // create and start the "donor" container + resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "") + c.Assert(err, checker.IsNil) + c.Assert(len(resp.Warnings), checker.Equals, 0) + name1 := resp.ID + + err = client.ContainerStart(ctx, name1, types.ContainerStartOptions{}) + c.Assert(err, checker.IsNil) + + // create and start the second container + hostCfg.IpcMode = container.IpcMode("container:" + name1) + resp, err = client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "") + c.Assert(err, checker.IsNil) + c.Assert(len(resp.Warnings), checker.Equals, 0) + name2 := resp.ID + + err = client.ContainerStart(ctx, name2, types.ContainerStartOptions{}) + if !mustWork { + // start should fail with a specific error + c.Assert(err, checker.NotNil) + c.Assert(fmt.Sprintf("%v", err), checker.Contains, "non-shareable IPC") + // no more checks to perform here + return + } + + // start should succeed + c.Assert(err, checker.IsNil) + + // check that IPC is shared + // 1. create a file in the first container + cli.DockerCmd(c, "exec", name1, "sh", "-c", "printf covfefe > /dev/shm/bar") + // 2. check it's the same file in the second one + out := cli.DockerCmd(c, "exec", "-i", name2, "cat", "/dev/shm/bar").Combined() + c.Assert(out, checker.Matches, "^covfefe$") +} + +/* TestAPIIpcModeShareableAndContainer checks that a container created with + * --ipc container:ID can use IPC of another shareable container. + */ +func (s *DockerSuite) TestAPIIpcModeShareableAndContainer(c *check.C) { + testRequires(c, DaemonIsLinux) + testIpcContainer(s, c, "shareable", true) +} + +/* TestAPIIpcModePrivateAndContainer checks that a container created with + * --ipc container:ID can NOT use IPC of another private container. + */ +func (s *DockerSuite) TestAPIIpcModePrivateAndContainer(c *check.C) { + testRequires(c, DaemonIsLinux) + testIpcContainer(s, c, "private", false) +} + +/* TestAPIIpcModeHost checks that a container created with --ipc host + * can use IPC of the host system. + */ +func (s *DockerSuite) TestAPIIpcModeHost(c *check.C) { + testRequires(c, DaemonIsLinux) + + cfg := container.Config{ + Image: "busybox", + Cmd: []string{"top"}, + } + hostCfg := container.HostConfig{ + IpcMode: container.IpcMode("host"), + } + ctx := context.Background() + + client, err := request.NewClient() + c.Assert(err, checker.IsNil) + + resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "") + c.Assert(err, checker.IsNil) + c.Assert(len(resp.Warnings), checker.Equals, 0) + name := resp.ID + + err = client.ContainerStart(ctx, name, types.ContainerStartOptions{}) + c.Assert(err, checker.IsNil) + + // check that IPC is shared + // 1. create a file inside container + cli.DockerCmd(c, "exec", name, "sh", "-c", "printf covfefe > /dev/shm/."+name) + // 2. check it's the same on the host + bytes, err := ioutil.ReadFile("/dev/shm/." + name) + c.Assert(err, checker.IsNil) + c.Assert(string(bytes), checker.Matches, "^covfefe$") + // 3. clean up + cli.DockerCmd(c, "exec", name, "rm", "-f", "/dev/shm/."+name) +} diff --git a/integration-cli/docker_cli_daemon_test.go b/integration-cli/docker_cli_daemon_test.go index 6a98cabdfa..55b45a9724 100644 --- a/integration-cli/docker_cli_daemon_test.go +++ b/integration-cli/docker_cli_daemon_test.go @@ -2985,6 +2985,165 @@ func (s *DockerDaemonSuite) TestShmSizeReload(c *check.C) { c.Assert(strings.TrimSpace(out), check.Equals, fmt.Sprintf("%v", size)) } +// this is used to test both "private" and "shareable" daemon default ipc modes +func testDaemonIpcPrivateShareable(d *daemon.Daemon, c *check.C, mustExist bool) { + name := "test-ipcmode" + _, err := d.Cmd("run", "-d", "--name", name, "busybox", "top") + c.Assert(err, checker.IsNil) + + // get major:minor pair for /dev/shm from container's /proc/self/mountinfo + cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo" + mm, err := d.Cmd("exec", "-i", name, "sh", "-c", cmd) + c.Assert(err, checker.IsNil) + c.Assert(mm, checker.Matches, "^[0-9]+:[0-9]+$") + + exists, err := testIpcCheckDevExists(mm) + c.Assert(err, checker.IsNil) + c.Logf("[testDaemonIpcPrivateShareable] ipcdev: %v, exists: %v, mustExist: %v\n", mm, exists, mustExist) + c.Assert(exists, checker.Equals, mustExist) +} + +// TestDaemonIpcModeShareable checks that --default-ipc-mode shareable works as intended. +func (s *DockerDaemonSuite) TestDaemonIpcModeShareable(c *check.C) { + testRequires(c, DaemonIsLinux) + + s.d.StartWithBusybox(c, "--default-ipc-mode", "shareable") + testDaemonIpcPrivateShareable(s.d, c, true) +} + +// TestDaemonIpcModePrivate checks that --default-ipc-mode private works as intended. +func (s *DockerDaemonSuite) TestDaemonIpcModePrivate(c *check.C) { + testRequires(c, DaemonIsLinux) + + s.d.StartWithBusybox(c, "--default-ipc-mode", "private") + testDaemonIpcPrivateShareable(s.d, c, false) +} + +// used to check if an IpcMode given in config works as intended +func testDaemonIpcFromConfig(s *DockerDaemonSuite, c *check.C, mode string, mustExist bool) { + f, err := ioutil.TempFile("", "test-daemon-ipc-config") + c.Assert(err, checker.IsNil) + defer os.Remove(f.Name()) + + config := `{"default-ipc-mode": "` + mode + `"}` + _, err = f.WriteString(config) + c.Assert(f.Close(), checker.IsNil) + c.Assert(err, checker.IsNil) + + s.d.StartWithBusybox(c, "--config-file", f.Name()) + testDaemonIpcPrivateShareable(s.d, c, mustExist) +} + +// TestDaemonIpcModePrivateFromConfig checks that "default-ipc-mode: private" config works as intended. +func (s *DockerDaemonSuite) TestDaemonIpcModePrivateFromConfig(c *check.C) { + testDaemonIpcFromConfig(s, c, "private", false) +} + +// TestDaemonIpcModeShareableFromConfig checks that "default-ipc-mode: shareable" config works as intended. +func (s *DockerDaemonSuite) TestDaemonIpcModeShareableFromConfig(c *check.C) { + testDaemonIpcFromConfig(s, c, "shareable", true) +} + +func testDaemonStartIpcMode(c *check.C, from, mode string, valid bool) { + testRequires(c, DaemonIsLinux) + + d := daemon.New(c, dockerBinary, dockerdBinary, daemon.Config{ + Experimental: testEnv.ExperimentalDaemon(), + }) + c.Logf("Checking IpcMode %s set from %s\n", mode, from) + var serr error + switch from { + case "config": + f, err := ioutil.TempFile("", "test-daemon-ipc-config") + c.Assert(err, checker.IsNil) + defer os.Remove(f.Name()) + config := `{"default-ipc-mode": "` + mode + `"}` + _, err = f.WriteString(config) + c.Assert(f.Close(), checker.IsNil) + c.Assert(err, checker.IsNil) + + serr = d.StartWithError("--config-file", f.Name()) + case "cli": + serr = d.StartWithError("--default-ipc-mode", mode) + default: + c.Fatalf("testDaemonStartIpcMode: invalid 'from' argument") + } + if serr == nil { + d.Stop(c) + } + + if valid { + c.Assert(serr, check.IsNil) + } else { + c.Assert(serr, check.NotNil) + icmd.RunCommand("grep", "-E", "IPC .* is (invalid|not supported)", d.LogFileName()).Assert(c, icmd.Success) + } +} + +// TestDaemonStartWithIpcModes checks that daemon starts fine given correct +// arguments for default IPC mode, and bails out with incorrect ones. +// Both CLI option (--default-ipc-mode) and config parameter are tested. +func (s *DockerDaemonSuite) TestDaemonStartWithIpcModes(c *check.C) { + ipcModes := []struct { + mode string + valid bool + }{ + {"private", true}, + {"shareable", true}, + + {"host", false}, + {"container:123", false}, + {"nosuchmode", false}, + } + + for _, from := range []string{"config", "cli"} { + for _, m := range ipcModes { + testDaemonStartIpcMode(c, from, m.mode, m.valid) + } + } +} + +// TestDaemonRestartIpcMode makes sure a container keeps its ipc mode +// (derived from daemon default) even after the daemon is restarted +// with a different default ipc mode. +func (s *DockerDaemonSuite) TestDaemonRestartIpcMode(c *check.C) { + f, err := ioutil.TempFile("", "test-daemon-ipc-config-restart") + c.Assert(err, checker.IsNil) + file := f.Name() + defer os.Remove(file) + c.Assert(f.Close(), checker.IsNil) + + config := []byte(`{"default-ipc-mode": "private"}`) + c.Assert(ioutil.WriteFile(file, config, 0644), checker.IsNil) + s.d.StartWithBusybox(c, "--config-file", file) + + // check the container is created with private ipc mode as per daemon default + name := "ipc1" + _, err = s.d.Cmd("run", "-d", "--name", name, "--restart=always", "busybox", "top") + c.Assert(err, checker.IsNil) + m, err := s.d.InspectField(name, ".HostConfig.IpcMode") + c.Assert(err, check.IsNil) + c.Assert(m, checker.Equals, "private") + + // restart the daemon with shareable default ipc mode + config = []byte(`{"default-ipc-mode": "shareable"}`) + c.Assert(ioutil.WriteFile(file, config, 0644), checker.IsNil) + s.d.Restart(c, "--config-file", file) + + // check the container is still having private ipc mode + m, err = s.d.InspectField(name, ".HostConfig.IpcMode") + c.Assert(err, check.IsNil) + c.Assert(m, checker.Equals, "private") + + // check a new container is created with shareable ipc mode as per new daemon default + name = "ipc2" + _, err = s.d.Cmd("run", "-d", "--name", name, "busybox", "top") + c.Assert(err, checker.IsNil) + m, err = s.d.InspectField(name, ".HostConfig.IpcMode") + c.Assert(err, check.IsNil) + c.Assert(m, checker.Equals, "shareable") +} + // TestFailedPluginRemove makes sure that a failed plugin remove does not block // the daemon from starting func (s *DockerDaemonSuite) TestFailedPluginRemove(c *check.C) { diff --git a/integration-cli/docker_cli_events_unix_test.go b/integration-cli/docker_cli_events_unix_test.go index 1f87d5fe8c..a2d67069de 100644 --- a/integration-cli/docker_cli_events_unix_test.go +++ b/integration-cli/docker_cli_events_unix_test.go @@ -428,7 +428,32 @@ func (s *DockerDaemonSuite) TestDaemonEvents(c *check.C) { out, err = s.d.Cmd("events", "--since=0", "--until", daemonUnixTime(c)) c.Assert(err, checker.IsNil) - c.Assert(out, checker.Contains, fmt.Sprintf("daemon reload %s (allow-nondistributable-artifacts=[], cluster-advertise=, cluster-store=, cluster-store-opts={}, debug=true, default-runtime=runc, default-shm-size=67108864, insecure-registries=[], labels=[\"bar=foo\"], live-restore=false, max-concurrent-downloads=1, max-concurrent-uploads=5, name=%s, registry-mirrors=[], runtimes=runc:{docker-runc []}, shutdown-timeout=10)", daemonID, daemonName)) + // only check for values known (daemon ID/name) or explicitly set above, + // otherwise just check for names being present. + expectedSubstrings := []string{ + " daemon reload " + daemonID + " ", + "(allow-nondistributable-artifacts=[", + " cluster-advertise=, ", + " cluster-store=, ", + " cluster-store-opts={", + " debug=true, ", + " default-ipc-mode=", + " default-runtime=", + " default-shm-size=", + " insecure-registries=[", + " labels=[\"bar=foo\"], ", + " live-restore=", + " max-concurrent-downloads=1, ", + " max-concurrent-uploads=5, ", + " name=" + daemonName, + " registry-mirrors=[", + " runtimes=", + " shutdown-timeout=10)", + } + + for _, s := range expectedSubstrings { + c.Assert(out, checker.Contains, s) + } } func (s *DockerDaemonSuite) TestDaemonEventsWithFilters(c *check.C) {