github.com/demonoid81/moby@v0.0.0-20200517203328-62dd8e17c460/integration/container/create_test.go (about)

     1  package container // import "github.com/demonoid81/moby/integration/container"
     2  
     3  import (
     4  	"context"
     5  	"encoding/json"
     6  	"fmt"
     7  	"strconv"
     8  	"testing"
     9  	"time"
    10  
    11  	"github.com/demonoid81/moby/api/types"
    12  	"github.com/demonoid81/moby/api/types/container"
    13  	"github.com/demonoid81/moby/api/types/network"
    14  	"github.com/demonoid81/moby/api/types/versions"
    15  	"github.com/demonoid81/moby/client"
    16  	"github.com/demonoid81/moby/errdefs"
    17  	ctr "github.com/demonoid81/moby/integration/internal/container"
    18  	"github.com/demonoid81/moby/oci"
    19  	"github.com/demonoid81/moby/testutil/request"
    20  	"gotest.tools/v3/assert"
    21  	is "gotest.tools/v3/assert/cmp"
    22  	"gotest.tools/v3/poll"
    23  	"gotest.tools/v3/skip"
    24  )
    25  
    26  func TestCreateFailsWhenIdentifierDoesNotExist(t *testing.T) {
    27  	defer setupTest(t)()
    28  	client := testEnv.APIClient()
    29  
    30  	testCases := []struct {
    31  		doc           string
    32  		image         string
    33  		expectedError string
    34  	}{
    35  		{
    36  			doc:           "image and tag",
    37  			image:         "test456:v1",
    38  			expectedError: "No such image: test456:v1",
    39  		},
    40  		{
    41  			doc:           "image no tag",
    42  			image:         "test456",
    43  			expectedError: "No such image: test456",
    44  		},
    45  		{
    46  			doc:           "digest",
    47  			image:         "sha256:0cb40641836c461bc97c793971d84d758371ed682042457523e4ae701efeaaaa",
    48  			expectedError: "No such image: sha256:0cb40641836c461bc97c793971d84d758371ed682042457523e4ae701efeaaaa",
    49  		},
    50  	}
    51  
    52  	for _, tc := range testCases {
    53  		tc := tc
    54  		t.Run(tc.doc, func(t *testing.T) {
    55  			t.Parallel()
    56  			_, err := client.ContainerCreate(context.Background(),
    57  				&container.Config{Image: tc.image},
    58  				&container.HostConfig{},
    59  				&network.NetworkingConfig{},
    60  				"",
    61  			)
    62  			assert.Check(t, is.ErrorContains(err, tc.expectedError))
    63  			assert.Check(t, errdefs.IsNotFound(err))
    64  		})
    65  	}
    66  }
    67  
    68  // TestCreateLinkToNonExistingContainer verifies that linking to a non-existing
    69  // container returns an "invalid parameter" (400) status, and not the underlying
    70  // "non exists" (404).
    71  func TestCreateLinkToNonExistingContainer(t *testing.T) {
    72  	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "legacy links are not supported on windows")
    73  	defer setupTest(t)()
    74  	c := testEnv.APIClient()
    75  
    76  	_, err := c.ContainerCreate(context.Background(),
    77  		&container.Config{
    78  			Image: "busybox",
    79  		},
    80  		&container.HostConfig{
    81  			Links: []string{"no-such-container"},
    82  		},
    83  		&network.NetworkingConfig{},
    84  		"",
    85  	)
    86  	assert.Check(t, is.ErrorContains(err, "could not get container for no-such-container"))
    87  	assert.Check(t, errdefs.IsInvalidParameter(err))
    88  }
    89  
    90  func TestCreateWithInvalidEnv(t *testing.T) {
    91  	defer setupTest(t)()
    92  	client := testEnv.APIClient()
    93  
    94  	testCases := []struct {
    95  		env           string
    96  		expectedError string
    97  	}{
    98  		{
    99  			env:           "",
   100  			expectedError: "invalid environment variable:",
   101  		},
   102  		{
   103  			env:           "=",
   104  			expectedError: "invalid environment variable: =",
   105  		},
   106  		{
   107  			env:           "=foo",
   108  			expectedError: "invalid environment variable: =foo",
   109  		},
   110  	}
   111  
   112  	for index, tc := range testCases {
   113  		tc := tc
   114  		t.Run(strconv.Itoa(index), func(t *testing.T) {
   115  			t.Parallel()
   116  			_, err := client.ContainerCreate(context.Background(),
   117  				&container.Config{
   118  					Image: "busybox",
   119  					Env:   []string{tc.env},
   120  				},
   121  				&container.HostConfig{},
   122  				&network.NetworkingConfig{},
   123  				"",
   124  			)
   125  			assert.Check(t, is.ErrorContains(err, tc.expectedError))
   126  			assert.Check(t, errdefs.IsInvalidParameter(err))
   127  		})
   128  	}
   129  }
   130  
   131  // Test case for #30166 (target was not validated)
   132  func TestCreateTmpfsMountsTarget(t *testing.T) {
   133  	skip.If(t, testEnv.DaemonInfo.OSType == "windows")
   134  
   135  	defer setupTest(t)()
   136  	client := testEnv.APIClient()
   137  
   138  	testCases := []struct {
   139  		target        string
   140  		expectedError string
   141  	}{
   142  		{
   143  			target:        ".",
   144  			expectedError: "mount path must be absolute",
   145  		},
   146  		{
   147  			target:        "foo",
   148  			expectedError: "mount path must be absolute",
   149  		},
   150  		{
   151  			target:        "/",
   152  			expectedError: "destination can't be '/'",
   153  		},
   154  		{
   155  			target:        "//",
   156  			expectedError: "destination can't be '/'",
   157  		},
   158  	}
   159  
   160  	for _, tc := range testCases {
   161  		_, err := client.ContainerCreate(context.Background(),
   162  			&container.Config{
   163  				Image: "busybox",
   164  			},
   165  			&container.HostConfig{
   166  				Tmpfs: map[string]string{tc.target: ""},
   167  			},
   168  			&network.NetworkingConfig{},
   169  			"",
   170  		)
   171  		assert.Check(t, is.ErrorContains(err, tc.expectedError))
   172  		assert.Check(t, errdefs.IsInvalidParameter(err))
   173  	}
   174  }
   175  func TestCreateWithCustomMaskedPaths(t *testing.T) {
   176  	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
   177  
   178  	defer setupTest(t)()
   179  	client := testEnv.APIClient()
   180  	ctx := context.Background()
   181  
   182  	testCases := []struct {
   183  		maskedPaths []string
   184  		expected    []string
   185  	}{
   186  		{
   187  			maskedPaths: []string{},
   188  			expected:    []string{},
   189  		},
   190  		{
   191  			maskedPaths: nil,
   192  			expected:    oci.DefaultSpec().Linux.MaskedPaths,
   193  		},
   194  		{
   195  			maskedPaths: []string{"/proc/kcore", "/proc/keys"},
   196  			expected:    []string{"/proc/kcore", "/proc/keys"},
   197  		},
   198  	}
   199  
   200  	checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) {
   201  		_, b, err := client.ContainerInspectWithRaw(ctx, name, false)
   202  		assert.NilError(t, err)
   203  
   204  		var inspectJSON map[string]interface{}
   205  		err = json.Unmarshal(b, &inspectJSON)
   206  		assert.NilError(t, err)
   207  
   208  		cfg, ok := inspectJSON["HostConfig"].(map[string]interface{})
   209  		assert.Check(t, is.Equal(true, ok), name)
   210  
   211  		maskedPaths, ok := cfg["MaskedPaths"].([]interface{})
   212  		assert.Check(t, is.Equal(true, ok), name)
   213  
   214  		mps := []string{}
   215  		for _, mp := range maskedPaths {
   216  			mps = append(mps, mp.(string))
   217  		}
   218  
   219  		assert.DeepEqual(t, expected, mps)
   220  	}
   221  
   222  	for i, tc := range testCases {
   223  		name := fmt.Sprintf("create-masked-paths-%d", i)
   224  		config := container.Config{
   225  			Image: "busybox",
   226  			Cmd:   []string{"true"},
   227  		}
   228  		hc := container.HostConfig{}
   229  		if tc.maskedPaths != nil {
   230  			hc.MaskedPaths = tc.maskedPaths
   231  		}
   232  
   233  		// Create the container.
   234  		c, err := client.ContainerCreate(context.Background(),
   235  			&config,
   236  			&hc,
   237  			&network.NetworkingConfig{},
   238  			name,
   239  		)
   240  		assert.NilError(t, err)
   241  
   242  		checkInspect(t, ctx, name, tc.expected)
   243  
   244  		// Start the container.
   245  		err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{})
   246  		assert.NilError(t, err)
   247  
   248  		poll.WaitOn(t, ctr.IsInState(ctx, client, c.ID, "exited"), poll.WithDelay(100*time.Millisecond))
   249  
   250  		checkInspect(t, ctx, name, tc.expected)
   251  	}
   252  }
   253  
   254  func TestCreateWithCapabilities(t *testing.T) {
   255  	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "FIXME: test should be able to run on LCOW")
   256  	skip.If(t, versions.LessThan(testEnv.DaemonAPIVersion(), "1.40"), "Capabilities was added in API v1.40")
   257  
   258  	defer setupTest(t)()
   259  	ctx := context.Background()
   260  	clientNew := request.NewAPIClient(t)
   261  	clientOld := request.NewAPIClient(t, client.WithVersion("1.39"))
   262  
   263  	testCases := []struct {
   264  		doc           string
   265  		hostConfig    container.HostConfig
   266  		expected      []string
   267  		expectedError string
   268  		oldClient     bool
   269  	}{
   270  		{
   271  			doc:        "no capabilities",
   272  			hostConfig: container.HostConfig{},
   273  		},
   274  		{
   275  			doc: "empty capabilities",
   276  			hostConfig: container.HostConfig{
   277  				Capabilities: []string{},
   278  			},
   279  			expected: []string{},
   280  		},
   281  		{
   282  			doc: "valid capabilities",
   283  			hostConfig: container.HostConfig{
   284  				Capabilities: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"},
   285  			},
   286  			expected: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"},
   287  		},
   288  		{
   289  			doc: "invalid capabilities",
   290  			hostConfig: container.HostConfig{
   291  				Capabilities: []string{"NET_RAW"},
   292  			},
   293  			expectedError: `invalid Capabilities: unknown capability: "NET_RAW"`,
   294  		},
   295  		{
   296  			doc: "duplicate capabilities",
   297  			hostConfig: container.HostConfig{
   298  				Capabilities: []string{"CAP_SYS_NICE", "CAP_SYS_NICE"},
   299  			},
   300  			expected: []string{"CAP_SYS_NICE", "CAP_SYS_NICE"},
   301  		},
   302  		{
   303  			doc: "capabilities API v1.39",
   304  			hostConfig: container.HostConfig{
   305  				Capabilities: []string{"CAP_NET_RAW", "CAP_SYS_CHROOT"},
   306  			},
   307  			expected:  nil,
   308  			oldClient: true,
   309  		},
   310  		{
   311  			doc: "empty capadd",
   312  			hostConfig: container.HostConfig{
   313  				Capabilities: []string{"CAP_NET_ADMIN"},
   314  				CapAdd:       []string{},
   315  			},
   316  			expected: []string{"CAP_NET_ADMIN"},
   317  		},
   318  		{
   319  			doc: "empty capdrop",
   320  			hostConfig: container.HostConfig{
   321  				Capabilities: []string{"CAP_NET_ADMIN"},
   322  				CapDrop:      []string{},
   323  			},
   324  			expected: []string{"CAP_NET_ADMIN"},
   325  		},
   326  		{
   327  			doc: "capadd capdrop",
   328  			hostConfig: container.HostConfig{
   329  				CapAdd:  []string{"SYS_NICE", "CAP_SYS_NICE"},
   330  				CapDrop: []string{"SYS_NICE", "CAP_SYS_NICE"},
   331  			},
   332  		},
   333  		{
   334  			doc: "conflict with capadd",
   335  			hostConfig: container.HostConfig{
   336  				Capabilities: []string{"CAP_NET_ADMIN"},
   337  				CapAdd:       []string{"SYS_NICE"},
   338  			},
   339  			expectedError: `conflicting options: Capabilities and CapAdd`,
   340  		},
   341  		{
   342  			doc: "conflict with capdrop",
   343  			hostConfig: container.HostConfig{
   344  				Capabilities: []string{"CAP_NET_ADMIN"},
   345  				CapDrop:      []string{"NET_RAW"},
   346  			},
   347  			expectedError: `conflicting options: Capabilities and CapDrop`,
   348  		},
   349  	}
   350  
   351  	for _, tc := range testCases {
   352  		tc := tc
   353  		t.Run(tc.doc, func(t *testing.T) {
   354  			t.Parallel()
   355  			client := clientNew
   356  			if tc.oldClient {
   357  				client = clientOld
   358  			}
   359  
   360  			c, err := client.ContainerCreate(context.Background(),
   361  				&container.Config{Image: "busybox"},
   362  				&tc.hostConfig,
   363  				&network.NetworkingConfig{},
   364  				"",
   365  			)
   366  			if tc.expectedError == "" {
   367  				assert.NilError(t, err)
   368  				ci, err := client.ContainerInspect(ctx, c.ID)
   369  				assert.NilError(t, err)
   370  				assert.Check(t, ci.HostConfig != nil)
   371  				assert.DeepEqual(t, tc.expected, ci.HostConfig.Capabilities)
   372  			} else {
   373  				assert.ErrorContains(t, err, tc.expectedError)
   374  				assert.Check(t, errdefs.IsInvalidParameter(err))
   375  			}
   376  		})
   377  	}
   378  }
   379  
   380  func TestCreateWithCustomReadonlyPaths(t *testing.T) {
   381  	skip.If(t, testEnv.DaemonInfo.OSType != "linux")
   382  
   383  	defer setupTest(t)()
   384  	client := testEnv.APIClient()
   385  	ctx := context.Background()
   386  
   387  	testCases := []struct {
   388  		readonlyPaths []string
   389  		expected      []string
   390  	}{
   391  		{
   392  			readonlyPaths: []string{},
   393  			expected:      []string{},
   394  		},
   395  		{
   396  			readonlyPaths: nil,
   397  			expected:      oci.DefaultSpec().Linux.ReadonlyPaths,
   398  		},
   399  		{
   400  			readonlyPaths: []string{"/proc/asound", "/proc/bus"},
   401  			expected:      []string{"/proc/asound", "/proc/bus"},
   402  		},
   403  	}
   404  
   405  	checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) {
   406  		_, b, err := client.ContainerInspectWithRaw(ctx, name, false)
   407  		assert.NilError(t, err)
   408  
   409  		var inspectJSON map[string]interface{}
   410  		err = json.Unmarshal(b, &inspectJSON)
   411  		assert.NilError(t, err)
   412  
   413  		cfg, ok := inspectJSON["HostConfig"].(map[string]interface{})
   414  		assert.Check(t, is.Equal(true, ok), name)
   415  
   416  		readonlyPaths, ok := cfg["ReadonlyPaths"].([]interface{})
   417  		assert.Check(t, is.Equal(true, ok), name)
   418  
   419  		rops := []string{}
   420  		for _, rop := range readonlyPaths {
   421  			rops = append(rops, rop.(string))
   422  		}
   423  		assert.DeepEqual(t, expected, rops)
   424  	}
   425  
   426  	for i, tc := range testCases {
   427  		name := fmt.Sprintf("create-readonly-paths-%d", i)
   428  		config := container.Config{
   429  			Image: "busybox",
   430  			Cmd:   []string{"true"},
   431  		}
   432  		hc := container.HostConfig{}
   433  		if tc.readonlyPaths != nil {
   434  			hc.ReadonlyPaths = tc.readonlyPaths
   435  		}
   436  
   437  		// Create the container.
   438  		c, err := client.ContainerCreate(context.Background(),
   439  			&config,
   440  			&hc,
   441  			&network.NetworkingConfig{},
   442  			name,
   443  		)
   444  		assert.NilError(t, err)
   445  
   446  		checkInspect(t, ctx, name, tc.expected)
   447  
   448  		// Start the container.
   449  		err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{})
   450  		assert.NilError(t, err)
   451  
   452  		poll.WaitOn(t, ctr.IsInState(ctx, client, c.ID, "exited"), poll.WithDelay(100*time.Millisecond))
   453  
   454  		checkInspect(t, ctx, name, tc.expected)
   455  	}
   456  }
   457  
   458  func TestCreateWithInvalidHealthcheckParams(t *testing.T) {
   459  	defer setupTest(t)()
   460  	client := testEnv.APIClient()
   461  	ctx := context.Background()
   462  
   463  	testCases := []struct {
   464  		doc         string
   465  		interval    time.Duration
   466  		timeout     time.Duration
   467  		retries     int
   468  		startPeriod time.Duration
   469  		expectedErr string
   470  	}{
   471  		{
   472  			doc:         "test invalid Interval in Healthcheck: less than 0s",
   473  			interval:    -10 * time.Millisecond,
   474  			timeout:     time.Second,
   475  			retries:     1000,
   476  			expectedErr: fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration),
   477  		},
   478  		{
   479  			doc:         "test invalid Interval in Healthcheck: larger than 0s but less than 1ms",
   480  			interval:    500 * time.Microsecond,
   481  			timeout:     time.Second,
   482  			retries:     1000,
   483  			expectedErr: fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration),
   484  		},
   485  		{
   486  			doc:         "test invalid Timeout in Healthcheck: less than 1ms",
   487  			interval:    time.Second,
   488  			timeout:     -100 * time.Millisecond,
   489  			retries:     1000,
   490  			expectedErr: fmt.Sprintf("Timeout in Healthcheck cannot be less than %s", container.MinimumDuration),
   491  		},
   492  		{
   493  			doc:         "test invalid Retries in Healthcheck: less than 0",
   494  			interval:    time.Second,
   495  			timeout:     time.Second,
   496  			retries:     -10,
   497  			expectedErr: "Retries in Healthcheck cannot be negative",
   498  		},
   499  		{
   500  			doc:         "test invalid StartPeriod in Healthcheck: not 0 and less than 1ms",
   501  			interval:    time.Second,
   502  			timeout:     time.Second,
   503  			retries:     1000,
   504  			startPeriod: 100 * time.Microsecond,
   505  			expectedErr: fmt.Sprintf("StartPeriod in Healthcheck cannot be less than %s", container.MinimumDuration),
   506  		},
   507  	}
   508  
   509  	for _, tc := range testCases {
   510  		tc := tc
   511  		t.Run(tc.doc, func(t *testing.T) {
   512  			t.Parallel()
   513  			cfg := container.Config{
   514  				Image: "busybox",
   515  				Healthcheck: &container.HealthConfig{
   516  					Interval: tc.interval,
   517  					Timeout:  tc.timeout,
   518  					Retries:  tc.retries,
   519  				},
   520  			}
   521  			if tc.startPeriod != 0 {
   522  				cfg.Healthcheck.StartPeriod = tc.startPeriod
   523  			}
   524  
   525  			resp, err := client.ContainerCreate(ctx, &cfg, &container.HostConfig{}, nil, "")
   526  			assert.Check(t, is.Equal(len(resp.Warnings), 0))
   527  
   528  			if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") {
   529  				assert.Check(t, errdefs.IsSystem(err))
   530  			} else {
   531  				assert.Check(t, errdefs.IsInvalidParameter(err))
   532  			}
   533  			assert.ErrorContains(t, err, tc.expectedErr)
   534  		})
   535  	}
   536  }
   537  
   538  // Make sure that anonymous volumes can be overritten by tmpfs
   539  // https://github.com/moby/moby/issues/40446
   540  func TestCreateTmpfsOverrideAnonymousVolume(t *testing.T) {
   541  	skip.If(t, testEnv.DaemonInfo.OSType == "windows", "windows does not support tmpfs")
   542  	defer setupTest(t)()
   543  	client := testEnv.APIClient()
   544  	ctx := context.Background()
   545  
   546  	id := ctr.Create(ctx, t, client,
   547  		ctr.WithVolume("/foo"),
   548  		ctr.WithTmpfs("/foo"),
   549  		ctr.WithVolume("/bar"),
   550  		ctr.WithTmpfs("/bar:size=999"),
   551  		ctr.WithCmd("/bin/sh", "-c", "mount | grep '/foo' | grep tmpfs && mount | grep '/bar' | grep tmpfs"),
   552  	)
   553  
   554  	defer func() {
   555  		err := client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{Force: true})
   556  		assert.NilError(t, err)
   557  	}()
   558  
   559  	inspect, err := client.ContainerInspect(ctx, id)
   560  	assert.NilError(t, err)
   561  	// tmpfs do not currently get added to inspect.Mounts
   562  	// Normally an anoynmous volume would, except now tmpfs should prevent that.
   563  	assert.Assert(t, is.Len(inspect.Mounts, 0))
   564  
   565  	chWait, chErr := client.ContainerWait(ctx, id, container.WaitConditionNextExit)
   566  	assert.NilError(t, client.ContainerStart(ctx, id, types.ContainerStartOptions{}))
   567  
   568  	timeout := time.NewTimer(30 * time.Second)
   569  	defer timeout.Stop()
   570  
   571  	select {
   572  	case <-timeout.C:
   573  		t.Fatal("timeout waiting for container to exit")
   574  	case status := <-chWait:
   575  		var errMsg string
   576  		if status.Error != nil {
   577  			errMsg = status.Error.Message
   578  		}
   579  		assert.Equal(t, int(status.StatusCode), 0, errMsg)
   580  	case err := <-chErr:
   581  		assert.NilError(t, err)
   582  	}
   583  }