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 }