github.com/zhouyu0/docker-note@v0.0.0-20190722021225-b8d3825084db/integration/container/ipcmode_linux_test.go (about)

     1  package container // import "github.com/docker/docker/integration/container"
     2  
     3  import (
     4  	"bufio"
     5  	"context"
     6  	"io/ioutil"
     7  	"os"
     8  	"regexp"
     9  	"strings"
    10  	"testing"
    11  
    12  	"github.com/docker/docker/api/types"
    13  	containertypes "github.com/docker/docker/api/types/container"
    14  	"github.com/docker/docker/integration/internal/container"
    15  	"github.com/docker/docker/internal/test/daemon"
    16  	"gotest.tools/assert"
    17  	is "gotest.tools/assert/cmp"
    18  	"gotest.tools/fs"
    19  	"gotest.tools/skip"
    20  )
    21  
    22  // testIpcCheckDevExists checks whether a given mount (identified by its
    23  // major:minor pair from /proc/self/mountinfo) exists on the host system.
    24  //
    25  // The format of /proc/self/mountinfo is like:
    26  //
    27  // 29 23 0:24 / /dev/shm rw,nosuid,nodev shared:4 - tmpfs tmpfs rw
    28  //       ^^^^\
    29  //            - this is the minor:major we look for
    30  func testIpcCheckDevExists(mm string) (bool, error) {
    31  	f, err := os.Open("/proc/self/mountinfo")
    32  	if err != nil {
    33  		return false, err
    34  	}
    35  	defer f.Close()
    36  
    37  	s := bufio.NewScanner(f)
    38  	for s.Scan() {
    39  		fields := strings.Fields(s.Text())
    40  		if len(fields) < 7 {
    41  			continue
    42  		}
    43  		if fields[2] == mm {
    44  			return true, nil
    45  		}
    46  	}
    47  
    48  	return false, s.Err()
    49  }
    50  
    51  // testIpcNonePrivateShareable is a helper function to test "none",
    52  // "private" and "shareable" modes.
    53  func testIpcNonePrivateShareable(t *testing.T, mode string, mustBeMounted bool, mustBeShared bool) {
    54  	defer setupTest(t)()
    55  
    56  	cfg := containertypes.Config{
    57  		Image: "busybox",
    58  		Cmd:   []string{"top"},
    59  	}
    60  	hostCfg := containertypes.HostConfig{
    61  		IpcMode: containertypes.IpcMode(mode),
    62  	}
    63  	client := testEnv.APIClient()
    64  	ctx := context.Background()
    65  
    66  	resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
    67  	assert.NilError(t, err)
    68  	assert.Check(t, is.Equal(len(resp.Warnings), 0))
    69  
    70  	err = client.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
    71  	assert.NilError(t, err)
    72  
    73  	// get major:minor pair for /dev/shm from container's /proc/self/mountinfo
    74  	cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo"
    75  	result, err := container.Exec(ctx, client, resp.ID, []string{"sh", "-c", cmd})
    76  	assert.NilError(t, err)
    77  	mm := result.Combined()
    78  	if !mustBeMounted {
    79  		assert.Check(t, is.Equal(mm, ""))
    80  		// no more checks to perform
    81  		return
    82  	}
    83  	assert.Check(t, is.Equal(true, regexp.MustCompile("^[0-9]+:[0-9]+$").MatchString(mm)))
    84  
    85  	shared, err := testIpcCheckDevExists(mm)
    86  	assert.NilError(t, err)
    87  	t.Logf("[testIpcPrivateShareable] ipcmode: %v, ipcdev: %v, shared: %v, mustBeShared: %v\n", mode, mm, shared, mustBeShared)
    88  	assert.Check(t, is.Equal(shared, mustBeShared))
    89  }
    90  
    91  // TestIpcModeNone checks the container "none" IPC mode
    92  // (--ipc none) works as expected. It makes sure there is no
    93  // /dev/shm mount inside the container.
    94  func TestIpcModeNone(t *testing.T) {
    95  	skip.If(t, testEnv.IsRemoteDaemon)
    96  
    97  	testIpcNonePrivateShareable(t, "none", false, false)
    98  }
    99  
   100  // TestAPIIpcModePrivate checks the container private IPC mode
   101  // (--ipc private) works as expected. It gets the minor:major pair
   102  // of /dev/shm mount from the container, and makes sure there is no
   103  // such pair on the host.
   104  func TestIpcModePrivate(t *testing.T) {
   105  	skip.If(t, testEnv.IsRemoteDaemon)
   106  
   107  	testIpcNonePrivateShareable(t, "private", true, false)
   108  }
   109  
   110  // TestAPIIpcModeShareable checks the container shareable IPC mode
   111  // (--ipc shareable) works as expected. It gets the minor:major pair
   112  // of /dev/shm mount from the container, and makes sure such pair
   113  // also exists on the host.
   114  func TestIpcModeShareable(t *testing.T) {
   115  	skip.If(t, testEnv.IsRemoteDaemon)
   116  
   117  	testIpcNonePrivateShareable(t, "shareable", true, true)
   118  }
   119  
   120  // testIpcContainer is a helper function to test --ipc container:NNN mode in various scenarios
   121  func testIpcContainer(t *testing.T, donorMode string, mustWork bool) {
   122  	t.Helper()
   123  
   124  	defer setupTest(t)()
   125  
   126  	cfg := containertypes.Config{
   127  		Image: "busybox",
   128  		Cmd:   []string{"top"},
   129  	}
   130  	hostCfg := containertypes.HostConfig{
   131  		IpcMode: containertypes.IpcMode(donorMode),
   132  	}
   133  	ctx := context.Background()
   134  	client := testEnv.APIClient()
   135  
   136  	// create and start the "donor" container
   137  	resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
   138  	assert.NilError(t, err)
   139  	assert.Check(t, is.Equal(len(resp.Warnings), 0))
   140  	name1 := resp.ID
   141  
   142  	err = client.ContainerStart(ctx, name1, types.ContainerStartOptions{})
   143  	assert.NilError(t, err)
   144  
   145  	// create and start the second container
   146  	hostCfg.IpcMode = containertypes.IpcMode("container:" + name1)
   147  	resp, err = client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
   148  	assert.NilError(t, err)
   149  	assert.Check(t, is.Equal(len(resp.Warnings), 0))
   150  	name2 := resp.ID
   151  
   152  	err = client.ContainerStart(ctx, name2, types.ContainerStartOptions{})
   153  	if !mustWork {
   154  		// start should fail with a specific error
   155  		assert.Check(t, is.ErrorContains(err, "non-shareable IPC"))
   156  		// no more checks to perform here
   157  		return
   158  	}
   159  
   160  	// start should succeed
   161  	assert.NilError(t, err)
   162  
   163  	// check that IPC is shared
   164  	// 1. create a file in the first container
   165  	_, err = container.Exec(ctx, client, name1, []string{"sh", "-c", "printf covfefe > /dev/shm/bar"})
   166  	assert.NilError(t, err)
   167  	// 2. check it's the same file in the second one
   168  	result, err := container.Exec(ctx, client, name2, []string{"cat", "/dev/shm/bar"})
   169  	assert.NilError(t, err)
   170  	out := result.Combined()
   171  	assert.Check(t, is.Equal(true, regexp.MustCompile("^covfefe$").MatchString(out)))
   172  }
   173  
   174  // TestAPIIpcModeShareableAndPrivate checks that
   175  // 1) a container created with --ipc container:ID can use IPC of another shareable container.
   176  // 2) a container created with --ipc container:ID can NOT use IPC of another private container.
   177  func TestAPIIpcModeShareableAndContainer(t *testing.T) {
   178  	skip.If(t, testEnv.IsRemoteDaemon)
   179  
   180  	testIpcContainer(t, "shareable", true)
   181  
   182  	testIpcContainer(t, "private", false)
   183  }
   184  
   185  /* TestAPIIpcModeHost checks that a container created with --ipc host
   186   * can use IPC of the host system.
   187   */
   188  func TestAPIIpcModeHost(t *testing.T) {
   189  	skip.If(t, testEnv.IsRemoteDaemon)
   190  	skip.If(t, testEnv.IsUserNamespace)
   191  
   192  	cfg := containertypes.Config{
   193  		Image: "busybox",
   194  		Cmd:   []string{"top"},
   195  	}
   196  	hostCfg := containertypes.HostConfig{
   197  		IpcMode: containertypes.IpcMode("host"),
   198  	}
   199  	ctx := context.Background()
   200  
   201  	client := testEnv.APIClient()
   202  	resp, err := client.ContainerCreate(ctx, &cfg, &hostCfg, nil, "")
   203  	assert.NilError(t, err)
   204  	assert.Check(t, is.Equal(len(resp.Warnings), 0))
   205  	name := resp.ID
   206  
   207  	err = client.ContainerStart(ctx, name, types.ContainerStartOptions{})
   208  	assert.NilError(t, err)
   209  
   210  	// check that IPC is shared
   211  	// 1. create a file inside container
   212  	_, err = container.Exec(ctx, client, name, []string{"sh", "-c", "printf covfefe > /dev/shm/." + name})
   213  	assert.NilError(t, err)
   214  	// 2. check it's the same on the host
   215  	bytes, err := ioutil.ReadFile("/dev/shm/." + name)
   216  	assert.NilError(t, err)
   217  	assert.Check(t, is.Equal("covfefe", string(bytes)))
   218  	// 3. clean up
   219  	_, err = container.Exec(ctx, client, name, []string{"rm", "-f", "/dev/shm/." + name})
   220  	assert.NilError(t, err)
   221  }
   222  
   223  // testDaemonIpcPrivateShareable is a helper function to test "private" and "shareable" daemon default ipc modes.
   224  func testDaemonIpcPrivateShareable(t *testing.T, mustBeShared bool, arg ...string) {
   225  	defer setupTest(t)()
   226  
   227  	d := daemon.New(t)
   228  	d.StartWithBusybox(t, arg...)
   229  	defer d.Stop(t)
   230  
   231  	c := d.NewClientT(t)
   232  
   233  	cfg := containertypes.Config{
   234  		Image: "busybox",
   235  		Cmd:   []string{"top"},
   236  	}
   237  	ctx := context.Background()
   238  
   239  	resp, err := c.ContainerCreate(ctx, &cfg, &containertypes.HostConfig{}, nil, "")
   240  	assert.NilError(t, err)
   241  	assert.Check(t, is.Equal(len(resp.Warnings), 0))
   242  
   243  	err = c.ContainerStart(ctx, resp.ID, types.ContainerStartOptions{})
   244  	assert.NilError(t, err)
   245  
   246  	// get major:minor pair for /dev/shm from container's /proc/self/mountinfo
   247  	cmd := "awk '($5 == \"/dev/shm\") {printf $3}' /proc/self/mountinfo"
   248  	result, err := container.Exec(ctx, c, resp.ID, []string{"sh", "-c", cmd})
   249  	assert.NilError(t, err)
   250  	mm := result.Combined()
   251  	assert.Check(t, is.Equal(true, regexp.MustCompile("^[0-9]+:[0-9]+$").MatchString(mm)))
   252  
   253  	shared, err := testIpcCheckDevExists(mm)
   254  	assert.NilError(t, err)
   255  	t.Logf("[testDaemonIpcPrivateShareable] ipcdev: %v, shared: %v, mustBeShared: %v\n", mm, shared, mustBeShared)
   256  	assert.Check(t, is.Equal(shared, mustBeShared))
   257  }
   258  
   259  // TestDaemonIpcModeShareable checks that --default-ipc-mode shareable works as intended.
   260  func TestDaemonIpcModeShareable(t *testing.T) {
   261  	skip.If(t, testEnv.IsRemoteDaemon)
   262  
   263  	testDaemonIpcPrivateShareable(t, true, "--default-ipc-mode", "shareable")
   264  }
   265  
   266  // TestDaemonIpcModePrivate checks that --default-ipc-mode private works as intended.
   267  func TestDaemonIpcModePrivate(t *testing.T) {
   268  	skip.If(t, testEnv.IsRemoteDaemon)
   269  
   270  	testDaemonIpcPrivateShareable(t, false, "--default-ipc-mode", "private")
   271  }
   272  
   273  // used to check if an IpcMode given in config works as intended
   274  func testDaemonIpcFromConfig(t *testing.T, mode string, mustExist bool) {
   275  	config := `{"default-ipc-mode": "` + mode + `"}`
   276  	file := fs.NewFile(t, "test-daemon-ipc-config", fs.WithContent(config))
   277  	defer file.Remove()
   278  
   279  	testDaemonIpcPrivateShareable(t, mustExist, "--config-file", file.Path())
   280  }
   281  
   282  // TestDaemonIpcModePrivateFromConfig checks that "default-ipc-mode: private" config works as intended.
   283  func TestDaemonIpcModePrivateFromConfig(t *testing.T) {
   284  	skip.If(t, testEnv.IsRemoteDaemon)
   285  
   286  	testDaemonIpcFromConfig(t, "private", false)
   287  }
   288  
   289  // TestDaemonIpcModeShareableFromConfig checks that "default-ipc-mode: shareable" config works as intended.
   290  func TestDaemonIpcModeShareableFromConfig(t *testing.T) {
   291  	skip.If(t, testEnv.IsRemoteDaemon)
   292  
   293  	testDaemonIpcFromConfig(t, "shareable", true)
   294  }