github.com/devdivbcp/moby@v17.12.0-ce-rc1.0.20200726071732-2d4bfdc789ad+incompatible/integration/container/create_test.go (about) 1 package container // import "github.com/docker/docker/integration/container" 2 3 import ( 4 "context" 5 "encoding/json" 6 "fmt" 7 "strconv" 8 "testing" 9 "time" 10 11 "github.com/docker/docker/api/types" 12 "github.com/docker/docker/api/types/container" 13 "github.com/docker/docker/api/types/network" 14 "github.com/docker/docker/api/types/versions" 15 "github.com/docker/docker/client" 16 "github.com/docker/docker/errdefs" 17 ctr "github.com/docker/docker/integration/internal/container" 18 "github.com/docker/docker/internal/test/request" 19 "github.com/docker/docker/oci" 20 "gotest.tools/assert" 21 is "gotest.tools/assert/cmp" 22 "gotest.tools/poll" 23 "gotest.tools/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 doc string 389 readonlyPaths []string 390 expected []string 391 }{ 392 { 393 readonlyPaths: []string{}, 394 expected: []string{}, 395 }, 396 { 397 readonlyPaths: nil, 398 expected: oci.DefaultSpec().Linux.ReadonlyPaths, 399 }, 400 { 401 readonlyPaths: []string{"/proc/asound", "/proc/bus"}, 402 expected: []string{"/proc/asound", "/proc/bus"}, 403 }, 404 } 405 406 checkInspect := func(t *testing.T, ctx context.Context, name string, expected []string) { 407 _, b, err := client.ContainerInspectWithRaw(ctx, name, false) 408 assert.NilError(t, err) 409 410 var inspectJSON map[string]interface{} 411 err = json.Unmarshal(b, &inspectJSON) 412 assert.NilError(t, err) 413 414 cfg, ok := inspectJSON["HostConfig"].(map[string]interface{}) 415 assert.Check(t, is.Equal(true, ok), name) 416 417 readonlyPaths, ok := cfg["ReadonlyPaths"].([]interface{}) 418 assert.Check(t, is.Equal(true, ok), name) 419 420 rops := []string{} 421 for _, rop := range readonlyPaths { 422 rops = append(rops, rop.(string)) 423 } 424 assert.DeepEqual(t, expected, rops) 425 } 426 427 for i, tc := range testCases { 428 name := fmt.Sprintf("create-readonly-paths-%d", i) 429 config := container.Config{ 430 Image: "busybox", 431 Cmd: []string{"true"}, 432 } 433 hc := container.HostConfig{} 434 if tc.readonlyPaths != nil { 435 hc.ReadonlyPaths = tc.readonlyPaths 436 } 437 438 // Create the container. 439 c, err := client.ContainerCreate(context.Background(), 440 &config, 441 &hc, 442 &network.NetworkingConfig{}, 443 name, 444 ) 445 assert.NilError(t, err) 446 447 checkInspect(t, ctx, name, tc.expected) 448 449 // Start the container. 450 err = client.ContainerStart(ctx, c.ID, types.ContainerStartOptions{}) 451 assert.NilError(t, err) 452 453 poll.WaitOn(t, ctr.IsInState(ctx, client, c.ID, "exited"), poll.WithDelay(100*time.Millisecond)) 454 455 checkInspect(t, ctx, name, tc.expected) 456 } 457 } 458 459 func TestCreateWithInvalidHealthcheckParams(t *testing.T) { 460 defer setupTest(t)() 461 client := testEnv.APIClient() 462 ctx := context.Background() 463 464 testCases := []struct { 465 doc string 466 interval time.Duration 467 timeout time.Duration 468 retries int 469 startPeriod time.Duration 470 expectedErr string 471 }{ 472 { 473 doc: "test invalid Interval in Healthcheck: less than 0s", 474 interval: -10 * time.Millisecond, 475 timeout: time.Second, 476 retries: 1000, 477 expectedErr: fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration), 478 }, 479 { 480 doc: "test invalid Interval in Healthcheck: larger than 0s but less than 1ms", 481 interval: 500 * time.Microsecond, 482 timeout: time.Second, 483 retries: 1000, 484 expectedErr: fmt.Sprintf("Interval in Healthcheck cannot be less than %s", container.MinimumDuration), 485 }, 486 { 487 doc: "test invalid Timeout in Healthcheck: less than 1ms", 488 interval: time.Second, 489 timeout: -100 * time.Millisecond, 490 retries: 1000, 491 expectedErr: fmt.Sprintf("Timeout in Healthcheck cannot be less than %s", container.MinimumDuration), 492 }, 493 { 494 doc: "test invalid Retries in Healthcheck: less than 0", 495 interval: time.Second, 496 timeout: time.Second, 497 retries: -10, 498 expectedErr: "Retries in Healthcheck cannot be negative", 499 }, 500 { 501 doc: "test invalid StartPeriod in Healthcheck: not 0 and less than 1ms", 502 interval: time.Second, 503 timeout: time.Second, 504 retries: 1000, 505 startPeriod: 100 * time.Microsecond, 506 expectedErr: fmt.Sprintf("StartPeriod in Healthcheck cannot be less than %s", container.MinimumDuration), 507 }, 508 } 509 510 for _, tc := range testCases { 511 tc := tc 512 t.Run(tc.doc, func(t *testing.T) { 513 t.Parallel() 514 cfg := container.Config{ 515 Image: "busybox", 516 Healthcheck: &container.HealthConfig{ 517 Interval: tc.interval, 518 Timeout: tc.timeout, 519 Retries: tc.retries, 520 }, 521 } 522 if tc.startPeriod != 0 { 523 cfg.Healthcheck.StartPeriod = tc.startPeriod 524 } 525 526 resp, err := client.ContainerCreate(ctx, &cfg, &container.HostConfig{}, nil, "") 527 assert.Check(t, is.Equal(len(resp.Warnings), 0)) 528 529 if versions.LessThan(testEnv.DaemonAPIVersion(), "1.32") { 530 assert.Check(t, errdefs.IsSystem(err)) 531 } else { 532 assert.Check(t, errdefs.IsInvalidParameter(err)) 533 } 534 assert.ErrorContains(t, err, tc.expectedErr) 535 }) 536 } 537 } 538 539 // Make sure that anonymous volumes can be overritten by tmpfs 540 // https://github.com/moby/moby/issues/40446 541 func TestCreateTmpfsOverrideAnonymousVolume(t *testing.T) { 542 skip.If(t, testEnv.DaemonInfo.OSType == "windows", "windows does not support tmpfs") 543 defer setupTest(t)() 544 client := testEnv.APIClient() 545 ctx := context.Background() 546 547 id := ctr.Create(ctx, t, client, 548 ctr.WithVolume("/foo"), 549 ctr.WithTmpfs("/foo"), 550 ctr.WithVolume("/bar"), 551 ctr.WithTmpfs("/bar:size=999"), 552 ctr.WithCmd("/bin/sh", "-c", "mount | grep '/foo' | grep tmpfs && mount | grep '/bar' | grep tmpfs"), 553 ) 554 555 defer func() { 556 err := client.ContainerRemove(ctx, id, types.ContainerRemoveOptions{Force: true}) 557 assert.NilError(t, err) 558 }() 559 560 inspect, err := client.ContainerInspect(ctx, id) 561 assert.NilError(t, err) 562 // tmpfs do not currently get added to inspect.Mounts 563 // Normally an anoynmous volume would, except now tmpfs should prevent that. 564 assert.Assert(t, is.Len(inspect.Mounts, 0)) 565 566 chWait, chErr := client.ContainerWait(ctx, id, container.WaitConditionNextExit) 567 assert.NilError(t, client.ContainerStart(ctx, id, types.ContainerStartOptions{})) 568 569 timeout := time.NewTimer(30 * time.Second) 570 defer timeout.Stop() 571 572 select { 573 case <-timeout.C: 574 t.Fatal("timeout waiting for container to exit") 575 case status := <-chWait: 576 var errMsg string 577 if status.Error != nil { 578 errMsg = status.Error.Message 579 } 580 assert.Equal(t, int(status.StatusCode), 0, errMsg) 581 case err := <-chErr: 582 assert.NilError(t, err) 583 } 584 }