github.com/demonoid81/containerd@v1.3.4/oci/spec_opts_test.go (about) 1 /* 2 Copyright The containerd Authors. 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package oci 18 19 import ( 20 "context" 21 "encoding/json" 22 "errors" 23 "fmt" 24 "io" 25 "io/ioutil" 26 "log" 27 "os" 28 "reflect" 29 "runtime" 30 "strings" 31 "testing" 32 33 "github.com/containerd/containerd/content" 34 "github.com/opencontainers/go-digest" 35 ocispec "github.com/opencontainers/image-spec/specs-go/v1" 36 37 "github.com/containerd/containerd/containers" 38 "github.com/containerd/containerd/namespaces" 39 specs "github.com/opencontainers/runtime-spec/specs-go" 40 ) 41 42 type blob []byte 43 44 func (b blob) ReadAt(p []byte, off int64) (int, error) { 45 if off >= int64(len(b)) { 46 return 0, io.EOF 47 } 48 return copy(p, b[off:]), nil 49 } 50 51 func (b blob) Close() error { 52 return nil 53 } 54 55 func (b blob) Size() int64 { 56 return int64(len(b)) 57 } 58 59 type fakeImage struct { 60 config ocispec.Descriptor 61 blobs map[string]blob 62 } 63 64 func newFakeImage(config ocispec.Image) (Image, error) { 65 configBlob, err := json.Marshal(config) 66 if err != nil { 67 return nil, err 68 } 69 configDescriptor := ocispec.Descriptor{ 70 MediaType: ocispec.MediaTypeImageConfig, 71 Digest: digest.NewDigestFromBytes(digest.SHA256, configBlob), 72 } 73 74 return fakeImage{ 75 config: configDescriptor, 76 blobs: map[string]blob{ 77 configDescriptor.Digest.String(): configBlob, 78 }, 79 }, nil 80 } 81 82 func (i fakeImage) Config(ctx context.Context) (ocispec.Descriptor, error) { 83 return i.config, nil 84 } 85 86 func (i fakeImage) ContentStore() content.Store { 87 return i 88 } 89 90 func (i fakeImage) ReaderAt(ctx context.Context, dec ocispec.Descriptor) (content.ReaderAt, error) { 91 blob, found := i.blobs[dec.Digest.String()] 92 if !found { 93 return nil, errors.New("not found") 94 } 95 return blob, nil 96 } 97 98 func (i fakeImage) Info(ctx context.Context, dgst digest.Digest) (content.Info, error) { 99 return content.Info{}, errors.New("not implemented") 100 } 101 102 func (i fakeImage) Update(ctx context.Context, info content.Info, fieldpaths ...string) (content.Info, error) { 103 return content.Info{}, errors.New("not implemented") 104 } 105 106 func (i fakeImage) Walk(ctx context.Context, fn content.WalkFunc, filters ...string) error { 107 return errors.New("not implemented") 108 } 109 110 func (i fakeImage) Delete(ctx context.Context, dgst digest.Digest) error { 111 return errors.New("not implemented") 112 } 113 114 func (i fakeImage) Status(ctx context.Context, ref string) (content.Status, error) { 115 return content.Status{}, errors.New("not implemented") 116 } 117 118 func (i fakeImage) ListStatuses(ctx context.Context, filters ...string) ([]content.Status, error) { 119 return nil, errors.New("not implemented") 120 } 121 122 func (i fakeImage) Abort(ctx context.Context, ref string) error { 123 return errors.New("not implemented") 124 } 125 126 func (i fakeImage) Writer(ctx context.Context, opts ...content.WriterOpt) (content.Writer, error) { 127 return nil, errors.New("not implemented") 128 } 129 130 func TestReplaceOrAppendEnvValues(t *testing.T) { 131 t.Parallel() 132 133 defaults := []string{ 134 "o=ups", "p=$e", "x=foo", "y=boo", "z", "t=", 135 } 136 overrides := []string{ 137 "x=bar", "y", "a=42", "o=", "e", "s=", 138 } 139 expected := []string{ 140 "o=", "p=$e", "x=bar", "z", "t=", "a=42", "s=", 141 } 142 143 defaultsOrig := make([]string, len(defaults)) 144 copy(defaultsOrig, defaults) 145 overridesOrig := make([]string, len(overrides)) 146 copy(overridesOrig, overrides) 147 148 results := replaceOrAppendEnvValues(defaults, overrides) 149 150 if err := assertEqualsStringArrays(defaults, defaultsOrig); err != nil { 151 t.Fatal(err) 152 } 153 if err := assertEqualsStringArrays(overrides, overridesOrig); err != nil { 154 t.Fatal(err) 155 } 156 157 if err := assertEqualsStringArrays(results, expected); err != nil { 158 t.Fatal(err) 159 } 160 } 161 162 func TestWithEnv(t *testing.T) { 163 t.Parallel() 164 165 s := Spec{} 166 s.Process = &specs.Process{ 167 Env: []string{"DEFAULT=test"}, 168 } 169 170 WithEnv([]string{"env=1"})(context.Background(), nil, nil, &s) 171 172 if len(s.Process.Env) != 2 { 173 t.Fatal("didn't append") 174 } 175 176 WithEnv([]string{"env2=1"})(context.Background(), nil, nil, &s) 177 178 if len(s.Process.Env) != 3 { 179 t.Fatal("didn't append") 180 } 181 182 WithEnv([]string{"env2=2"})(context.Background(), nil, nil, &s) 183 184 if s.Process.Env[2] != "env2=2" { 185 t.Fatal("couldn't update") 186 } 187 188 WithEnv([]string{"env2"})(context.Background(), nil, nil, &s) 189 190 if len(s.Process.Env) != 2 { 191 t.Fatal("couldn't unset") 192 } 193 } 194 195 func TestWithMounts(t *testing.T) { 196 197 t.Parallel() 198 199 s := Spec{ 200 Mounts: []specs.Mount{ 201 { 202 Source: "default-source", 203 Destination: "default-dest", 204 }, 205 }, 206 } 207 208 WithMounts([]specs.Mount{ 209 { 210 Source: "new-source", 211 Destination: "new-dest", 212 }, 213 })(nil, nil, nil, &s) 214 215 if len(s.Mounts) != 2 { 216 t.Fatal("didn't append") 217 } 218 219 if s.Mounts[1].Source != "new-source" { 220 t.Fatal("invalid mount") 221 } 222 223 if s.Mounts[1].Destination != "new-dest" { 224 t.Fatal("invalid mount") 225 } 226 } 227 228 func TestWithDefaultSpec(t *testing.T) { 229 t.Parallel() 230 var ( 231 s Spec 232 c = containers.Container{ID: "TestWithDefaultSpec"} 233 ctx = namespaces.WithNamespace(context.Background(), "test") 234 ) 235 236 if err := ApplyOpts(ctx, nil, &c, &s, WithDefaultSpec()); err != nil { 237 t.Fatal(err) 238 } 239 240 var expected Spec 241 var err error 242 if runtime.GOOS == "windows" { 243 err = populateDefaultWindowsSpec(ctx, &expected, c.ID) 244 } else { 245 err = populateDefaultUnixSpec(ctx, &expected, c.ID) 246 } 247 if err != nil { 248 t.Fatal(err) 249 } 250 251 if reflect.DeepEqual(s, Spec{}) { 252 t.Fatalf("spec should not be empty") 253 } 254 255 if !reflect.DeepEqual(&s, &expected) { 256 t.Fatalf("spec from option differs from default: \n%#v != \n%#v", &s, expected) 257 } 258 } 259 260 func TestWithSpecFromFile(t *testing.T) { 261 t.Parallel() 262 var ( 263 s Spec 264 c = containers.Container{ID: "TestWithDefaultSpec"} 265 ctx = namespaces.WithNamespace(context.Background(), "test") 266 ) 267 268 fp, err := ioutil.TempFile("", "testwithdefaultspec.json") 269 if err != nil { 270 t.Fatal(err) 271 } 272 defer func() { 273 if err := os.Remove(fp.Name()); err != nil { 274 log.Printf("failed to remove tempfile %v: %v", fp.Name(), err) 275 } 276 }() 277 defer fp.Close() 278 279 expected, err := GenerateSpec(ctx, nil, &c) 280 if err != nil { 281 t.Fatal(err) 282 } 283 284 p, err := json.Marshal(expected) 285 if err != nil { 286 t.Fatal(err) 287 } 288 289 if _, err := fp.Write(p); err != nil { 290 t.Fatal(err) 291 } 292 293 if err := ApplyOpts(ctx, nil, &c, &s, WithSpecFromFile(fp.Name())); err != nil { 294 t.Fatal(err) 295 } 296 297 if reflect.DeepEqual(s, Spec{}) { 298 t.Fatalf("spec should not be empty") 299 } 300 301 if !reflect.DeepEqual(&s, expected) { 302 t.Fatalf("spec from option differs from default: \n%#v != \n%#v", &s, expected) 303 } 304 } 305 306 func TestWithMemoryLimit(t *testing.T) { 307 var ( 308 ctx = namespaces.WithNamespace(context.Background(), "testing") 309 c = containers.Container{ID: t.Name()} 310 m = uint64(768 * 1024 * 1024) 311 o = WithMemoryLimit(m) 312 ) 313 // Test with all three supported scenarios 314 platforms := []string{"", "linux/amd64", "windows/amd64"} 315 for _, p := range platforms { 316 var spec *Spec 317 var err error 318 if p == "" { 319 t.Log("Testing GenerateSpec default platform") 320 spec, err = GenerateSpec(ctx, nil, &c, o) 321 322 // Convert the platform to the default based on GOOS like 323 // GenerateSpec does. 324 switch runtime.GOOS { 325 case "linux": 326 p = "linux/amd64" 327 case "windows": 328 p = "windows/amd64" 329 } 330 } else { 331 t.Logf("Testing GenerateSpecWithPlatform with platform: '%s'", p) 332 spec, err = GenerateSpecWithPlatform(ctx, nil, p, &c, o) 333 } 334 if err != nil { 335 t.Fatalf("failed to generate spec with: %v", err) 336 } 337 switch p { 338 case "linux/amd64": 339 if *spec.Linux.Resources.Memory.Limit != int64(m) { 340 t.Fatalf("spec.Linux.Resources.Memory.Limit expected: %v, got: %v", m, *spec.Linux.Resources.Memory.Limit) 341 } 342 // If we are linux/amd64 on Windows GOOS it is LCOW 343 if runtime.GOOS == "windows" { 344 // Verify that we also set the Windows section. 345 if *spec.Windows.Resources.Memory.Limit != m { 346 t.Fatalf("for LCOW spec.Windows.Resources.Memory.Limit is also expected: %v, got: %v", m, *spec.Windows.Resources.Memory.Limit) 347 } 348 } else { 349 if spec.Windows != nil { 350 t.Fatalf("spec.Windows section should not be set for linux/amd64 spec on non-windows platform") 351 } 352 } 353 case "windows/amd64": 354 if *spec.Windows.Resources.Memory.Limit != m { 355 t.Fatalf("spec.Windows.Resources.Memory.Limit expected: %v, got: %v", m, *spec.Windows.Resources.Memory.Limit) 356 } 357 if spec.Linux != nil { 358 t.Fatalf("spec.Linux section should not be set for windows/amd64 spec ever") 359 } 360 } 361 } 362 } 363 364 func isEqualStringArrays(values, expected []string) bool { 365 if len(values) != len(expected) { 366 return false 367 } 368 369 for i, x := range expected { 370 if values[i] != x { 371 return false 372 } 373 } 374 return true 375 } 376 377 func assertEqualsStringArrays(values, expected []string) error { 378 if !isEqualStringArrays(values, expected) { 379 return fmt.Errorf("expected %s, but found %s", expected, values) 380 } 381 return nil 382 } 383 384 func TestWithImageConfigArgs(t *testing.T) { 385 t.Parallel() 386 387 img, err := newFakeImage(ocispec.Image{ 388 Config: ocispec.ImageConfig{ 389 Env: []string{"z=bar", "y=baz"}, 390 Entrypoint: []string{"create", "--namespace=test"}, 391 Cmd: []string{"", "--debug"}, 392 }, 393 }) 394 if err != nil { 395 t.Fatal(err) 396 } 397 398 s := Spec{ 399 Version: specs.Version, 400 Root: &specs.Root{}, 401 Windows: &specs.Windows{}, 402 } 403 404 opts := []SpecOpts{ 405 WithEnv([]string{"x=foo", "y=boo"}), 406 WithProcessArgs("run", "--foo", "xyz", "--bar"), 407 WithImageConfigArgs(img, []string{"--boo", "bar"}), 408 } 409 410 expectedEnv := []string{"z=bar", "y=boo", "x=foo"} 411 expectedArgs := []string{"create", "--namespace=test", "--boo", "bar"} 412 413 for _, opt := range opts { 414 if err := opt(nil, nil, nil, &s); err != nil { 415 t.Fatal(err) 416 } 417 } 418 419 if err := assertEqualsStringArrays(s.Process.Env, expectedEnv); err != nil { 420 t.Fatal(err) 421 } 422 if err := assertEqualsStringArrays(s.Process.Args, expectedArgs); err != nil { 423 t.Fatal(err) 424 } 425 } 426 427 func TestAddCaps(t *testing.T) { 428 t.Parallel() 429 430 var s specs.Spec 431 432 if err := WithAddedCapabilities([]string{"CAP_CHOWN"})(context.Background(), nil, nil, &s); err != nil { 433 t.Fatal(err) 434 } 435 for i, cl := range [][]string{ 436 s.Process.Capabilities.Bounding, 437 s.Process.Capabilities.Effective, 438 s.Process.Capabilities.Permitted, 439 s.Process.Capabilities.Inheritable, 440 } { 441 if !capsContain(cl, "CAP_CHOWN") { 442 t.Errorf("cap list %d does not contain added cap", i) 443 } 444 } 445 } 446 447 func TestDropCaps(t *testing.T) { 448 t.Parallel() 449 450 var s specs.Spec 451 452 if err := WithAllCapabilities(context.Background(), nil, nil, &s); err != nil { 453 t.Fatal(err) 454 } 455 if err := WithDroppedCapabilities([]string{"CAP_CHOWN"})(context.Background(), nil, nil, &s); err != nil { 456 t.Fatal(err) 457 } 458 459 for i, cl := range [][]string{ 460 s.Process.Capabilities.Bounding, 461 s.Process.Capabilities.Effective, 462 s.Process.Capabilities.Permitted, 463 s.Process.Capabilities.Inheritable, 464 } { 465 if capsContain(cl, "CAP_CHOWN") { 466 t.Errorf("cap list %d contains dropped cap", i) 467 } 468 } 469 470 // Add all capabilities back and drop a different cap. 471 if err := WithAllCapabilities(context.Background(), nil, nil, &s); err != nil { 472 t.Fatal(err) 473 } 474 if err := WithDroppedCapabilities([]string{"CAP_FOWNER"})(context.Background(), nil, nil, &s); err != nil { 475 t.Fatal(err) 476 } 477 478 for i, cl := range [][]string{ 479 s.Process.Capabilities.Bounding, 480 s.Process.Capabilities.Effective, 481 s.Process.Capabilities.Permitted, 482 s.Process.Capabilities.Inheritable, 483 } { 484 if capsContain(cl, "CAP_FOWNER") { 485 t.Errorf("cap list %d contains dropped cap", i) 486 } 487 if !capsContain(cl, "CAP_CHOWN") { 488 t.Errorf("cap list %d doesn't contain non-dropped cap", i) 489 } 490 } 491 492 // Drop all duplicated caps. 493 if err := WithCapabilities([]string{"CAP_CHOWN", "CAP_CHOWN"})(context.Background(), nil, nil, &s); err != nil { 494 t.Fatal(err) 495 } 496 if err := WithDroppedCapabilities([]string{"CAP_CHOWN"})(context.Background(), nil, nil, &s); err != nil { 497 t.Fatal(err) 498 } 499 for i, cl := range [][]string{ 500 s.Process.Capabilities.Bounding, 501 s.Process.Capabilities.Effective, 502 s.Process.Capabilities.Permitted, 503 s.Process.Capabilities.Inheritable, 504 } { 505 if len(cl) != 0 { 506 t.Errorf("cap list %d is not empty", i) 507 } 508 } 509 } 510 511 func TestDevShmSize(t *testing.T) { 512 t.Parallel() 513 var ( 514 s Spec 515 c = containers.Container{ID: t.Name()} 516 ctx = namespaces.WithNamespace(context.Background(), "test") 517 ) 518 519 err := populateDefaultUnixSpec(ctx, &s, c.ID) 520 if err != nil { 521 t.Fatal(err) 522 } 523 524 expected := "1024k" 525 if err := WithDevShmSize(1024)(nil, nil, nil, &s); err != nil { 526 t.Fatal(err) 527 } 528 m := getShmMount(&s) 529 if m == nil { 530 t.Fatal("no shm mount found") 531 } 532 o := getShmSize(m.Options) 533 if o == "" { 534 t.Fatal("shm size not specified") 535 } 536 parts := strings.Split(o, "=") 537 if len(parts) != 2 { 538 t.Fatal("invalid size format") 539 } 540 size := parts[1] 541 if size != expected { 542 t.Fatalf("size %s not equal %s", size, expected) 543 } 544 } 545 546 func getShmMount(s *Spec) *specs.Mount { 547 for _, m := range s.Mounts { 548 if m.Source == "shm" && m.Type == "tmpfs" { 549 return &m 550 } 551 } 552 return nil 553 } 554 555 func getShmSize(opts []string) string { 556 for _, o := range opts { 557 if strings.HasPrefix(o, "size=") { 558 return o 559 } 560 } 561 return "" 562 }