gvisor.dev/gvisor@v0.0.0-20240520182842-f9d4d51c7e0f/runsc/config/config_test.go (about) 1 // Copyright 2020 The gVisor Authors. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package config 16 17 import ( 18 "fmt" 19 "reflect" 20 "strings" 21 "testing" 22 23 "github.com/google/go-cmp/cmp" 24 "gvisor.dev/gvisor/runsc/flag" 25 ) 26 27 func TestDefault(t *testing.T) { 28 testFlags := flag.NewFlagSet("test", flag.ContinueOnError) 29 RegisterFlags(testFlags) 30 c, err := NewFromFlags(testFlags) 31 if err != nil { 32 t.Fatal(err) 33 } 34 // "--root" is always set to something different than the default. Reset it 35 // to make it easier to test that default values do not generate flags. 36 c.RootDir = "" 37 38 // All defaults doesn't require setting flags. 39 flags := c.ToFlags() 40 if len(flags) > 0 { 41 t.Errorf("default flags not set correctly for: %s", flags) 42 } 43 } 44 45 func TestFromFlags(t *testing.T) { 46 testFlags := flag.NewFlagSet("test", flag.ContinueOnError) 47 RegisterFlags(testFlags) 48 if err := testFlags.Lookup("root").Value.Set("some-path"); err != nil { 49 t.Errorf("Flag set: %v", err) 50 } 51 if err := testFlags.Lookup("debug").Value.Set("true"); err != nil { 52 t.Errorf("Flag set: %v", err) 53 } 54 if err := testFlags.Lookup("num-network-channels").Value.Set("123"); err != nil { 55 t.Errorf("Flag set: %v", err) 56 } 57 if err := testFlags.Lookup("network").Value.Set("none"); err != nil { 58 t.Errorf("Flag set: %v", err) 59 } 60 61 c, err := NewFromFlags(testFlags) 62 if err != nil { 63 t.Fatal(err) 64 } 65 if want := "some-path"; c.RootDir != want { 66 t.Errorf("RootDir=%v, want: %v", c.RootDir, want) 67 } 68 if want := true; c.Debug != want { 69 t.Errorf("Debug=%v, want: %v", c.Debug, want) 70 } 71 if want := 123; c.NumNetworkChannels != want { 72 t.Errorf("NumNetworkChannels=%v, want: %v", c.NumNetworkChannels, want) 73 } 74 if want := NetworkNone; c.Network != want { 75 t.Errorf("Network=%v, want: %v", c.Network, want) 76 } 77 } 78 79 func TestToFlagsFromFlags(t *testing.T) { 80 testFlags := flag.NewFlagSet("test", flag.ContinueOnError) 81 RegisterFlags(testFlags) 82 testFlags.Set("root", "some-path") 83 testFlags.Set("debug", "true") 84 testFlags.Set("profile", "false") // Matches default value. 85 testFlags.Set("num-network-channels", "123") 86 testFlags.Set("network", "none") 87 c, err := NewFromFlags(testFlags) 88 if err != nil { 89 t.Fatal(err) 90 } 91 92 flags := c.ToFlags() 93 if len(flags) != 5 { 94 t.Errorf("wrong number of flags set, want: 5, got: %d: %s", len(flags), flags) 95 } 96 t.Logf("Flags: %s", flags) 97 fm := map[string]string{} 98 for _, f := range flags { 99 kv := strings.Split(f, "=") 100 fm[kv[0]] = kv[1] 101 } 102 for name, want := range map[string]string{ 103 "--root": "some-path", 104 "--debug": "true", 105 "--profile": "false", 106 "--num-network-channels": "123", 107 "--network": "none", 108 } { 109 if got, ok := fm[name]; ok { 110 if got != want { 111 t.Errorf("flag %q, want: %q, got: %q", name, want, got) 112 } 113 } else { 114 t.Errorf("flag %q not set", name) 115 } 116 } 117 } 118 119 func TestToFlagsFromManual(t *testing.T) { 120 c := &Config{ 121 RootDir: "some-path", 122 Debug: true, 123 ProfileEnable: false, // Matches default flag value. 124 NumNetworkChannels: 123, 125 Network: NetworkNone, 126 } 127 128 // Create a second config with flag-default values that we'll copy from. 129 testFlags := flag.NewFlagSet("test", flag.ContinueOnError) 130 RegisterFlags(testFlags) 131 cfgDefault, err := NewFromFlags(testFlags) 132 if err != nil { 133 t.Fatal(err) 134 } 135 136 // Set all the unset fields of c to their flag-default value from cfgDefault. 137 cfgReflect := reflect.ValueOf(c).Elem() 138 cfgDefaultReflect := reflect.ValueOf(cfgDefault).Elem() 139 cfgType := cfgReflect.Type() 140 for i := 0; i < cfgType.NumField(); i++ { 141 f := cfgType.Field(i) 142 name, ok := f.Tag.Lookup("flag") 143 if !ok { 144 // No flag set for this field. 145 continue 146 } 147 if name == "root" || name == "debug" || name == "profile" || name == "num-network-channels" || name == "network" { 148 continue 149 } 150 cfgReflect.Field(i).Set(cfgDefaultReflect.Field(i)) 151 } 152 153 flags := c.ToFlags() 154 if len(flags) != 4 { 155 t.Errorf("wrong number of flags set, want: 4, got: %d: %s", len(flags), flags) 156 } 157 t.Logf("Flags: %s", flags) 158 fm := map[string]string{} 159 for _, f := range flags { 160 kv := strings.Split(f, "=") 161 fm[kv[0]] = kv[1] 162 } 163 for name, want := range map[string]string{ 164 "--root": "some-path", 165 "--debug": "true", 166 "--num-network-channels": "123", 167 "--network": "none", 168 } { 169 if got, ok := fm[name]; ok { 170 if got != want { 171 t.Errorf("flag %q, want: %q, got: %q", name, want, got) 172 } 173 } else { 174 t.Errorf("flag %q not set", name) 175 } 176 } 177 if _, hasProfile := fm["--profile"]; hasProfile { 178 t.Error("--profile flag unexpectedly set") 179 } 180 } 181 182 // TestInvalidFlags checks that enum flags fail when value is not in enum set. 183 func TestInvalidFlags(t *testing.T) { 184 for _, tc := range []struct { 185 name string 186 value string 187 error string 188 }{ 189 { 190 name: "file-access", 191 value: "invalid", 192 error: "invalid file access type", 193 }, 194 { 195 name: "network", 196 value: "invalid", 197 error: "invalid network type", 198 }, 199 { 200 name: "qdisc", 201 value: "invalid", 202 error: "invalid qdisc", 203 }, 204 { 205 name: "watchdog-action", 206 value: "invalid", 207 error: "invalid watchdog action", 208 }, 209 { 210 name: "ref-leak-mode", 211 value: "invalid", 212 error: "invalid ref leak mode", 213 }, 214 { 215 name: "host-uds", 216 value: "invalid", 217 error: "invalid host UDS", 218 }, 219 { 220 name: "host-fifo", 221 value: "invalid", 222 error: "invalid host fifo", 223 }, 224 { 225 name: "overlay2", 226 value: "root:/tmp", 227 error: "unexpected medium: \"/tmp\"", 228 }, 229 { 230 name: "overlay2", 231 value: "root:dir=tmp", 232 error: "overlay host file directory should be an absolute path, got \"tmp\"", 233 }, 234 } { 235 t.Run(tc.name, func(t *testing.T) { 236 testFlags := flag.NewFlagSet("test", flag.ContinueOnError) 237 RegisterFlags(testFlags) 238 if err := testFlags.Lookup(tc.name).Value.Set(tc.value); err == nil || !strings.Contains(err.Error(), tc.error) { 239 t.Errorf("flag.Value.Set(invalid) wrong error reported: %v", err) 240 } 241 }) 242 } 243 } 244 245 func TestValidationFail(t *testing.T) { 246 for _, tc := range []struct { 247 name string 248 flags map[string]string 249 error string 250 }{ 251 { 252 name: "shared+overlay", 253 flags: map[string]string{ 254 "file-access": "shared", 255 "overlay2": "root:self", 256 }, 257 error: "overlay flag is incompatible", 258 }, 259 { 260 name: "network-channels", 261 flags: map[string]string{ 262 "num-network-channels": "-1", 263 }, 264 error: "num_network_channels must be > 0", 265 }, 266 { 267 name: "fsgofer-host-uds+host-uds:open", 268 flags: map[string]string{ 269 "fsgofer-host-uds": "true", 270 "host-uds": "open", 271 }, 272 error: "fsgofer-host-uds has been replaced with host-uds flag", 273 }, 274 { 275 name: "fsgofer-host-uds+host-uds:create", 276 flags: map[string]string{ 277 "fsgofer-host-uds": "true", 278 "host-uds": "create", 279 }, 280 error: "fsgofer-host-uds has been replaced with host-uds flag", 281 }, 282 { 283 name: "fsgofer-host-uds+host-uds:all", 284 flags: map[string]string{ 285 "fsgofer-host-uds": "true", 286 "host-uds": "all", 287 }, 288 error: "fsgofer-host-uds has been replaced with host-uds flag", 289 }, 290 { 291 name: "overlay+overlay2:root", 292 flags: map[string]string{ 293 "overlay": "true", 294 "overlay2": "root:memory", 295 }, 296 error: "overlay flag has been replaced with overlay2 flag", 297 }, 298 { 299 name: "overlay+overlay2:all", 300 flags: map[string]string{ 301 "overlay": "true", 302 "overlay2": "all:memory", 303 }, 304 error: "overlay flag has been replaced with overlay2 flag", 305 }, 306 } { 307 t.Run(tc.name, func(t *testing.T) { 308 testFlags := flag.NewFlagSet("test", flag.ContinueOnError) 309 RegisterFlags(testFlags) 310 for name, val := range tc.flags { 311 if err := testFlags.Lookup(name).Value.Set(val); err != nil { 312 t.Errorf("%s=%q: %v", name, val, err) 313 } 314 } 315 if _, err := NewFromFlags(testFlags); err == nil || !strings.Contains(err.Error(), tc.error) { 316 t.Errorf("NewFromFlags() wrong error reported: %v", err) 317 } 318 }) 319 } 320 } 321 322 func TestOverride(t *testing.T) { 323 testFlags := flag.NewFlagSet("test", flag.ContinueOnError) 324 RegisterFlags(testFlags) 325 c, err := NewFromFlags(testFlags) 326 if err != nil { 327 t.Fatal(err) 328 } 329 c.AllowFlagOverride = true 330 331 t.Run("string", func(t *testing.T) { 332 c.RootDir = "foobar" 333 if err := c.Override(testFlags, "root", "bar", false); err != nil { 334 t.Fatalf("Override(root, bar) failed: %v", err) 335 } 336 if c.RootDir != "bar" { 337 t.Errorf("Override(root, bar) didn't work: %+v", c) 338 } 339 }) 340 341 t.Run("bool", func(t *testing.T) { 342 c.Debug = true 343 if err := c.Override(testFlags, "debug", "false", false); err != nil { 344 t.Fatalf("Override(debug, false) failed: %v", err) 345 } 346 if c.Debug { 347 t.Errorf("Override(debug, false) didn't work: %+v", c) 348 } 349 }) 350 351 t.Run("enum", func(t *testing.T) { 352 c.FileAccess = FileAccessShared 353 if err := c.Override(testFlags, "file-access", "exclusive", false); err != nil { 354 t.Fatalf("Override(file-access, exclusive) failed: %v", err) 355 } 356 if c.FileAccess != FileAccessExclusive { 357 t.Errorf("Override(file-access, exclusive) didn't work: %+v", c) 358 } 359 }) 360 } 361 362 func TestOverrideDisabled(t *testing.T) { 363 testFlags := flag.NewFlagSet("test", flag.ContinueOnError) 364 RegisterFlags(testFlags) 365 c, err := NewFromFlags(testFlags) 366 if err != nil { 367 t.Fatal(err) 368 } 369 const errMsg = "flag override disabled" 370 if err := c.Override(testFlags, "root", "path", false); err == nil || !strings.Contains(err.Error(), errMsg) { 371 t.Errorf("Override() wrong error: %v", err) 372 } 373 } 374 375 func TestOverrideError(t *testing.T) { 376 testFlags := flag.NewFlagSet("test", flag.ContinueOnError) 377 RegisterFlags(testFlags) 378 c, err := NewFromFlags(testFlags) 379 if err != nil { 380 t.Fatal(err) 381 } 382 c.AllowFlagOverride = true 383 for _, tc := range []struct { 384 name string 385 value string 386 error string 387 }{ 388 { 389 name: "invalid", 390 value: "valid", 391 error: `flag "invalid" not found`, 392 }, 393 { 394 name: "debug", 395 value: "invalid", 396 error: "error setting flag debug", 397 }, 398 { 399 name: "file-access", 400 value: "invalid", 401 error: "invalid file access type", 402 }, 403 } { 404 t.Run(tc.name, func(t *testing.T) { 405 if err := c.Override(testFlags, tc.name, tc.value, false); err == nil || !strings.Contains(err.Error(), tc.error) { 406 t.Errorf("Override(%q, %q) wrong error: %v", tc.name, tc.value, err) 407 } 408 }) 409 } 410 } 411 412 func TestOverrideAllowlist(t *testing.T) { 413 testFlags := flag.NewFlagSet("test", flag.ContinueOnError) 414 RegisterFlags(testFlags) 415 c, err := NewFromFlags(testFlags) 416 if err != nil { 417 t.Fatal(err) 418 } 419 for _, tc := range []struct { 420 flag string 421 value string 422 force bool 423 error string 424 }{ 425 { 426 flag: "debug", 427 value: "true", 428 }, 429 { 430 flag: "debug", 431 value: "123", 432 error: "error setting flag", 433 }, 434 { 435 flag: "oci-seccomp", 436 value: "true", 437 }, 438 { 439 flag: "oci-seccomp", 440 value: "false", 441 error: `disabling "oci-seccomp" requires flag`, 442 }, 443 { 444 flag: "oci-seccomp", 445 value: "123", 446 error: "invalid syntax", 447 }, 448 { 449 flag: "profile", 450 value: "true", 451 error: "flag override disabled", 452 }, 453 { 454 flag: "profile", 455 value: "true", 456 force: true, 457 }, 458 { 459 flag: "profile", 460 value: "123", 461 error: "flag override disabled", 462 }, 463 } { 464 t.Run(tc.flag, func(t *testing.T) { 465 err := c.Override(testFlags, tc.flag, tc.value, tc.force) 466 if len(tc.error) == 0 { 467 if err != nil { 468 t.Errorf("Unexpected error: %v", err) 469 } 470 } else if err == nil || !strings.Contains(err.Error(), tc.error) { 471 t.Errorf("Override(%q, %q) wrong error: %v", tc.flag, tc.value, err) 472 } 473 }) 474 } 475 } 476 477 func TestBundles(t *testing.T) { 478 noChange := func(t *testing.T, old, new *Config) { 479 t.Helper() 480 if diff := cmp.Diff(old, new, cmp.AllowUnexported(Config{}, Overlay2{})); diff != "" { 481 t.Errorf("different configs:\n%+v\nvs\n%+v\nDiff:\n%s", old, new, diff) 482 } 483 } 484 for _, test := range []struct { 485 // Name of the test. 486 Name string 487 488 // List of bundles that exist for the purpose of this test. 489 BundleConfig map[BundleName]Bundle 490 491 // Command-line arguments passed as explicit flags. 492 CommandLine []string 493 494 // Names of the bundles to apply. 495 Bundles []BundleName 496 497 // Whether we expect applying bundles to fail. 498 WantErr bool 499 500 // If bundles were successfully applied, this function is called to compare 501 // pre-bundle-application and post-bundle-application configs. 502 Verify func(t *testing.T, old, new *Config) 503 }{ 504 { 505 Name: "empty bundle", 506 BundleConfig: map[BundleName]Bundle{"empty": {}}, 507 Bundles: []BundleName{"empty"}, 508 Verify: noChange, 509 }, 510 { 511 Name: "no-op bundle", 512 BundleConfig: map[BundleName]Bundle{ 513 "no-debug": { 514 "debug": "false", 515 }, 516 }, 517 Bundles: []BundleName{"no-debug"}, 518 Verify: noChange, 519 }, 520 { 521 Name: "invalid flag", 522 BundleConfig: map[BundleName]Bundle{ 523 "invalid-flag": { 524 "not-a-real-flag": "nope.avi", 525 }, 526 }, 527 Bundles: []BundleName{"invalid-flag"}, 528 WantErr: true, 529 }, 530 { 531 Name: "duplicate no-op bundles", 532 BundleConfig: map[BundleName]Bundle{ 533 "empty": {}, 534 "no-debug": { 535 "debug": "false", 536 }, 537 }, 538 Bundles: []BundleName{"no-debug", "no-debug"}, 539 Verify: noChange, 540 }, 541 { 542 Name: "simple bundle", 543 BundleConfig: map[BundleName]Bundle{ 544 "empty": {}, 545 "debug": { 546 "debug": "true", 547 }, 548 "no-debug": { 549 "debug": "false", 550 }, 551 }, 552 Bundles: []BundleName{"debug"}, 553 Verify: func(t *testing.T, old, new *Config) { 554 t.Helper() 555 if old.Debug { 556 t.Error("debug was previously set to true") 557 } 558 if !new.Debug { 559 t.Error("debug was not set to true") 560 } 561 }, 562 }, 563 { 564 Name: "incompatible bundles", 565 BundleConfig: map[BundleName]Bundle{ 566 "debug": { 567 "debug": "true", 568 }, 569 "no-debug": { 570 "debug": "false", 571 }, 572 }, 573 Bundles: []BundleName{"debug", "no-debug"}, 574 WantErr: true, 575 }, 576 { 577 Name: "compatible bundles", 578 BundleConfig: map[BundleName]Bundle{ 579 "debug": { 580 "debug": "true", 581 }, 582 "debug-and-profile": { 583 "debug": "true", 584 "profile": "true", 585 }, 586 }, 587 Bundles: []BundleName{"debug", "debug-and-profile"}, 588 Verify: func(t *testing.T, old, new *Config) { 589 t.Helper() 590 if old.Debug || old.ProfileEnable { 591 t.Error("debug/profiling was previously set to true") 592 } 593 if !new.Debug { 594 t.Error("debug was not set to true") 595 } 596 if !new.ProfileEnable { 597 t.Error("profiling was not set to true") 598 } 599 }, 600 }, 601 { 602 Name: "bundle takes precedence over command-line value", 603 BundleConfig: map[BundleName]Bundle{ 604 "no-debug": { 605 "debug": "false", 606 }, 607 }, 608 CommandLine: []string{"-debug=true"}, 609 Bundles: []BundleName{"no-debug"}, 610 Verify: func(t *testing.T, old, new *Config) { 611 t.Helper() 612 if new.Debug { 613 t.Error("debug is still true") 614 } 615 }, 616 }, 617 { 618 Name: "command line matching bundle value", 619 BundleConfig: map[BundleName]Bundle{ 620 "debug": { 621 "debug": "true", 622 }, 623 }, 624 CommandLine: []string{"-debug=true"}, 625 Bundles: []BundleName{"debug"}, 626 Verify: func(t *testing.T, old, new *Config) { 627 t.Helper() 628 noChange(t, old, new) 629 if !new.Debug { 630 t.Error("debug was set to false") 631 } 632 }, 633 }, 634 } { 635 t.Run(test.Name, func(t *testing.T) { 636 oldBundles := Bundles 637 defer func() { 638 Bundles = oldBundles 639 }() 640 Bundles = test.BundleConfig 641 flagSet := flag.NewFlagSet(test.Name, flag.ContinueOnError) 642 RegisterFlags(flagSet) 643 if err := flagSet.Parse(test.CommandLine); err != nil { 644 t.Fatalf("cannot parse command line %q: %v", test.CommandLine, err) 645 } 646 cfg, err := NewFromFlags(flagSet) 647 if err != nil { 648 t.Fatalf("cannot generate config from flags: %v", err) 649 } 650 oldCfg := *cfg 651 err = cfg.ApplyBundles(flagSet, test.Bundles...) 652 if test.WantErr && err == nil { 653 t.Error("got no error, but expected one") 654 } 655 if !test.WantErr && err != nil { 656 t.Errorf("got unexpected error: %v", err) 657 } 658 if t.Failed() { 659 return 660 } 661 if err != nil && test.Verify != nil { 662 t.Error("cannot specify Verify function for erroring tests") 663 } 664 if err == nil && test.Verify != nil { 665 test.Verify(t, &oldCfg, cfg) 666 } 667 }) 668 } 669 } 670 671 func TestBundleValidate(t *testing.T) { 672 defaultVerify := func(err error) error { return err } 673 for _, tc := range []struct { 674 name string 675 bundle Bundle 676 verify func(err error) error 677 }{ 678 { 679 name: "empty bundle", 680 bundle: Bundle(map[string]string{}), 681 verify: defaultVerify, 682 }, 683 { 684 name: "invalid flag bundle", 685 bundle: Bundle(map[string]string{"not-a-real-flag": "true"}), 686 verify: func(err error) error { 687 want := `unknown flag "not-a-real-flag"` 688 if !strings.Contains(err.Error(), want) { 689 return fmt.Errorf("mismatch error: got: %q want: %q", err.Error(), want) 690 } 691 return nil 692 }, 693 }, 694 { 695 name: "invalid value", 696 bundle: Bundle(map[string]string{"debug": "invalid"}), 697 verify: func(err error) error { 698 want := `parsing "invalid": invalid syntax` 699 if !strings.Contains(err.Error(), want) { 700 return fmt.Errorf("mismatch error: got: %q want: %q", err.Error(), want) 701 } 702 return nil 703 }, 704 }, 705 { 706 name: "valid flag bundle", 707 bundle: Bundle(map[string]string{"debug": "true"}), 708 verify: defaultVerify, 709 }, 710 } { 711 t.Run(tc.name, func(t *testing.T) { 712 if err := tc.verify(tc.bundle.Validate()); err != nil { 713 t.Fatalf("Validate failed: %v", err) 714 } 715 }) 716 } 717 } 718 719 func TestToContinerdConfigTOML(t *testing.T) { 720 header := `binary_name = "%s" 721 root = "%s" 722 ` 723 opt := ContainerdConfigOptions{ 724 BinaryPath: "/path/to/runsc", 725 RootPath: "/path/to/root", 726 } 727 header = fmt.Sprintf(header, opt.BinaryPath, opt.RootPath) 728 729 for _, tc := range []struct { 730 name string 731 bundle Bundle 732 want string 733 createError error 734 }{ 735 { 736 name: "empty bundle", 737 want: header, 738 }, 739 { 740 name: "valid flag bundle", 741 bundle: Bundle(map[string]string{"debug": "true"}), 742 want: func() string { 743 flagStr := "[runsc_config]\n debug = \"true\"\n" 744 return strings.Join([]string{header, flagStr}, "") 745 }(), 746 }, 747 { 748 name: "invalid flag bundle", 749 bundle: Bundle(map[string]string{"not-a-real-flag": "true"}), 750 createError: fmt.Errorf("unknown flag \"not-a-real-flag\""), 751 }, 752 } { 753 t.Run(tc.name, func(t *testing.T) { 754 cfg, err := NewFromBundle(tc.bundle) 755 if tc.createError != nil { 756 if err == nil { 757 t.Fatalf("got no error, but expected one") 758 } 759 if !strings.Contains(err.Error(), tc.createError.Error()) { 760 t.Fatalf("mismatch error: got: %q want: %q", err.Error(), tc.createError.Error()) 761 } 762 return 763 } 764 765 if err != nil { 766 t.Fatalf("NewFromBundle failed: %v", err) 767 } 768 769 toml, err := cfg.ToContainerdConfigTOML(opt) 770 if err != nil { 771 t.Fatalf("ToContainerdConfigTOML failed: %v", err) 772 } 773 if diff := cmp.Diff(tc.want, toml); diff != "" { 774 t.Fatalf("mismatch strings: %s", diff) 775 } 776 777 }) 778 } 779 780 }