123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327 |
- package container // import "github.com/docker/docker/integration/container"
- import (
- "bufio"
- "context"
- "os"
- "regexp"
- "strings"
- "testing"
- "github.com/docker/docker/api/types"
- containertypes "github.com/docker/docker/api/types/container"
- "github.com/docker/docker/api/types/versions"
- "github.com/docker/docker/client"
- "github.com/docker/docker/integration/internal/container"
- "github.com/docker/docker/testutil/daemon"
- "github.com/docker/docker/testutil/request"
- "gotest.tools/v3/assert"
- is "gotest.tools/v3/assert/cmp"
- "gotest.tools/v3/fs"
- "gotest.tools/v3/skip"
- )
- // 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
- }
- }
- return false, s.Err()
- }
- // testIpcNonePrivateShareable is a helper function to test "none",
- // "private" and "shareable" modes.
- func testIpcNonePrivateShareable(t *testing.T, mode string, mustBeMounted bool, mustBeShared bool) {
- defer setupTest(t)()
- cfg := containertypes.Config{
- Image: "busybox",
- Cmd: []string{"top"},
- }
- hostCfg := containertypes.HostConfig{
- IpcMode: containertypes.IpcMode(mode),
- }
- client := testEnv.APIClient()
- ctx := context.Background()
- resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "")
- assert.NilError(t, err)
- assert.Check(t, is.Equal(len(resp.Warnings), 0))
- err = client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
- assert.NilError(t, err)
- // get major:minor pair for /dev/shm from container's /proc/self/mountinfo
- cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo"
- result, err := container.Exec(ctx, client, resp.ID, []string{"sh", "-c", cmd})
- assert.NilError(t, err)
- mm := result.Combined()
- if !mustBeMounted {
- assert.Check(t, is.Equal(mm, ""))
- // no more checks to perform
- return
- }
- assert.Check(t, is.Equal(true, regexp.MustCompile("^[0-9]+:[0-9]+$").MatchString(mm)))
- shared, err := testIpcCheckDevExists(mm)
- assert.NilError(t, err)
- t.Logf("[testIpcPrivateShareable] ipcmode: %v, ipcdev: %v, shared: %v, mustBeShared: %v\n", mode, mm, shared, mustBeShared)
- assert.Check(t, is.Equal(shared, mustBeShared))
- }
- // TestIpcModeNone checks the container "none" IPC mode
- // (--ipc none) works as expected. It makes sure there is no
- // /dev/shm mount inside the container.
- func TestIpcModeNone(t *testing.T) {
- skip.If(t, testEnv.IsRemoteDaemon)
- testIpcNonePrivateShareable(t, "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 TestIpcModePrivate(t *testing.T) {
- skip.If(t, testEnv.IsRemoteDaemon)
- testIpcNonePrivateShareable(t, "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 TestIpcModeShareable(t *testing.T) {
- skip.If(t, testEnv.IsRemoteDaemon)
- skip.If(t, testEnv.IsRootless, "cannot test /dev/shm in rootless")
- testIpcNonePrivateShareable(t, "shareable", true, true)
- }
- // testIpcContainer is a helper function to test --ipc container:NNN mode in various scenarios
- func testIpcContainer(t *testing.T, donorMode string, mustWork bool) {
- t.Helper()
- defer setupTest(t)()
- cfg := containertypes.Config{
- Image: "busybox",
- Cmd: []string{"top"},
- }
- hostCfg := containertypes.HostConfig{
- IpcMode: containertypes.IpcMode(donorMode),
- }
- ctx := context.Background()
- client := testEnv.APIClient()
- // create and start the "donor" container
- resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "")
- assert.NilError(t, err)
- assert.Check(t, is.Equal(len(resp.Warnings), 0))
- name1 := resp.ID
- err = client.ContainerStart(ctx, name1, types.ContainerStartOptions{})
- assert.NilError(t, err)
- // create and start the second container
- hostCfg.IpcMode = containertypes.IpcMode("container:" + name1)
- resp, err = client.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "")
- assert.NilError(t, err)
- assert.Check(t, is.Equal(len(resp.Warnings), 0))
- name2 := resp.ID
- err = client.ContainerStart(ctx, name2, types.ContainerStartOptions{})
- if !mustWork {
- // start should fail with a specific error
- assert.Check(t, is.ErrorContains(err, "non-shareable IPC"))
- // no more checks to perform here
- return
- }
- // start should succeed
- assert.NilError(t, err)
- // check that IPC is shared
- // 1. create a file in the first container
- _, err = container.Exec(ctx, client, name1, []string{"sh", "-c", "printf covfefe > /dev/shm/bar"})
- assert.NilError(t, err)
- // 2. check it's the same file in the second one
- result, err := container.Exec(ctx, client, name2, []string{"cat", "/dev/shm/bar"})
- assert.NilError(t, err)
- out := result.Combined()
- assert.Check(t, is.Equal(true, regexp.MustCompile("^covfefe$").MatchString(out)))
- }
- // TestAPIIpcModeShareableAndPrivate checks that
- // 1) a container created with --ipc container:ID can use IPC of another shareable container.
- // 2) a container created with --ipc container:ID can NOT use IPC of another private container.
- func TestAPIIpcModeShareableAndContainer(t *testing.T) {
- skip.If(t, testEnv.IsRemoteDaemon)
- testIpcContainer(t, "shareable", true)
- testIpcContainer(t, "private", false)
- }
- /* TestAPIIpcModeHost checks that a container created with --ipc host
- * can use IPC of the host system.
- */
- func TestAPIIpcModeHost(t *testing.T) {
- skip.If(t, testEnv.IsRemoteDaemon)
- skip.If(t, testEnv.IsUserNamespace)
- skip.If(t, testEnv.IsRootless, "cannot test /dev/shm in rootless")
- cfg := containertypes.Config{
- Image: "busybox",
- Cmd: []string{"top"},
- }
- hostCfg := containertypes.HostConfig{
- IpcMode: containertypes.IPCModeHost,
- }
- ctx := context.Background()
- client := testEnv.APIClient()
- resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, nil, "")
- assert.NilError(t, err)
- assert.Check(t, is.Equal(len(resp.Warnings), 0))
- name := resp.ID
- err = client.ContainerStart(ctx, name, types.ContainerStartOptions{})
- assert.NilError(t, err)
- // check that IPC is shared
- // 1. create a file inside container
- _, err = container.Exec(ctx, client, name, []string{"sh", "-c", "printf covfefe > /dev/shm/." + name})
- assert.NilError(t, err)
- // 2. check it's the same on the host
- bytes, err := os.ReadFile("/dev/shm/." + name)
- assert.NilError(t, err)
- assert.Check(t, is.Equal("covfefe", string(bytes)))
- // 3. clean up
- _, err = container.Exec(ctx, client, name, []string{"rm", "-f", "/dev/shm/." + name})
- assert.NilError(t, err)
- }
- // testDaemonIpcPrivateShareable is a helper function to test "private" and "shareable" daemon default ipc modes.
- func testDaemonIpcPrivateShareable(t *testing.T, mustBeShared bool, arg ...string) {
- defer setupTest(t)()
- d := daemon.New(t)
- d.StartWithBusybox(t, arg...)
- defer d.Stop(t)
- c := d.NewClientT(t)
- cfg := containertypes.Config{
- Image: "busybox",
- Cmd: []string{"top"},
- }
- ctx := context.Background()
- resp, err := c.ContainerCreate(ctx, &cfg, &containertypes.HostConfig{}, nil, nil, "")
- assert.NilError(t, err)
- assert.Check(t, is.Equal(len(resp.Warnings), 0))
- err = c.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
- assert.NilError(t, err)
- // get major:minor pair for /dev/shm from container's /proc/self/mountinfo
- cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo"
- result, err := container.Exec(ctx, c, resp.ID, []string{"sh", "-c", cmd})
- assert.NilError(t, err)
- mm := result.Combined()
- assert.Check(t, is.Equal(true, regexp.MustCompile("^[0-9]+:[0-9]+$").MatchString(mm)))
- shared, err := testIpcCheckDevExists(mm)
- assert.NilError(t, err)
- t.Logf("[testDaemonIpcPrivateShareable] ipcdev: %v, shared: %v, mustBeShared: %v\n", mm, shared, mustBeShared)
- assert.Check(t, is.Equal(shared, mustBeShared))
- }
- // TestDaemonIpcModeShareable checks that --default-ipc-mode shareable works as intended.
- func TestDaemonIpcModeShareable(t *testing.T) {
- skip.If(t, testEnv.IsRemoteDaemon)
- skip.If(t, testEnv.IsRootless, "cannot test /dev/shm in rootless")
- testDaemonIpcPrivateShareable(t, true, "--default-ipc-mode", "shareable")
- }
- // TestDaemonIpcModePrivate checks that --default-ipc-mode private works as intended.
- func TestDaemonIpcModePrivate(t *testing.T) {
- skip.If(t, testEnv.IsRemoteDaemon)
- testDaemonIpcPrivateShareable(t, false, "--default-ipc-mode", "private")
- }
- // used to check if an IpcMode given in config works as intended
- func testDaemonIpcFromConfig(t *testing.T, mode string, mustExist bool) {
- skip.If(t, testEnv.IsRootless, "cannot test /dev/shm in rootless")
- config := `{"default-ipc-mode": "` + mode + `"}`
- file := fs.NewFile(t, "test-daemon-ipc-config", fs.WithContent(config))
- defer file.Remove()
- testDaemonIpcPrivateShareable(t, mustExist, "--config-file", file.Path())
- }
- // TestDaemonIpcModePrivateFromConfig checks that "default-ipc-mode: private" config works as intended.
- func TestDaemonIpcModePrivateFromConfig(t *testing.T) {
- skip.If(t, testEnv.IsRemoteDaemon)
- testDaemonIpcFromConfig(t, "private", false)
- }
- // TestDaemonIpcModeShareableFromConfig checks that "default-ipc-mode: shareable" config works as intended.
- func TestDaemonIpcModeShareableFromConfig(t *testing.T) {
- skip.If(t, testEnv.IsRemoteDaemon)
- testDaemonIpcFromConfig(t, "shareable", true)
- }
- // TestIpcModeOlderClient checks that older client gets shareable IPC mode
- // by default, even when the daemon default is private.
- func TestIpcModeOlderClient(t *testing.T) {
- skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "requires a daemon with DefaultIpcMode: private")
- c := testEnv.APIClient()
- skip.If(t, versions.LessThan(c.ClientVersion(), "1.40"), "requires client API >= 1.40")
- t.Parallel()
- ctx := context.Background()
- // pre-check: default ipc mode in daemon is private
- cID := container.Create(ctx, t, c, container.WithAutoRemove)
- inspect, err := c.ContainerInspect(ctx, cID)
- assert.NilError(t, err)
- assert.Check(t, is.Equal(string(inspect.HostConfig.IpcMode), "private"))
- // main check: using older client creates "shareable" container
- c = request.NewAPIClient(t, client.WithVersion("1.39"))
- cID = container.Create(ctx, t, c, container.WithAutoRemove)
- inspect, err = c.ContainerInspect(ctx, cID)
- assert.NilError(t, err)
- assert.Check(t, is.Equal(string(inspect.HostConfig.IpcMode), "shareable"))
- }
|