k8s.io/kubernetes@v1.29.3/pkg/scheduler/apis/config/scheme/scheme_test.go (about) 1 /* 2 Copyright 2020 The Kubernetes 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 scheme 18 19 import ( 20 "bytes" 21 "testing" 22 23 "github.com/google/go-cmp/cmp" 24 corev1 "k8s.io/api/core/v1" 25 "k8s.io/apimachinery/pkg/runtime" 26 "k8s.io/apimachinery/pkg/runtime/schema" 27 v1 "k8s.io/kube-scheduler/config/v1" 28 "k8s.io/kubernetes/pkg/scheduler/apis/config" 29 "k8s.io/kubernetes/pkg/scheduler/apis/config/testing/defaults" 30 "k8s.io/utils/ptr" 31 "sigs.k8s.io/yaml" 32 ) 33 34 // TestCodecsDecodePluginConfig tests that embedded plugin args get decoded 35 // into their appropriate internal types and defaults are applied. 36 func TestCodecsDecodePluginConfig(t *testing.T) { 37 testCases := []struct { 38 name string 39 data []byte 40 wantErr string 41 wantProfiles []config.KubeSchedulerProfile 42 }{ 43 // v1 tests 44 { 45 name: "v1 all plugin args in default profile", 46 data: []byte(` 47 apiVersion: kubescheduler.config.k8s.io/v1 48 kind: KubeSchedulerConfiguration 49 profiles: 50 - pluginConfig: 51 - name: DefaultPreemption 52 args: 53 minCandidateNodesPercentage: 50 54 minCandidateNodesAbsolute: 500 55 - name: InterPodAffinity 56 args: 57 hardPodAffinityWeight: 5 58 - name: NodeResourcesFit 59 args: 60 ignoredResources: ["foo"] 61 - name: PodTopologySpread 62 args: 63 defaultConstraints: 64 - maxSkew: 1 65 topologyKey: zone 66 whenUnsatisfiable: ScheduleAnyway 67 - name: VolumeBinding 68 args: 69 bindTimeoutSeconds: 300 70 - name: NodeAffinity 71 args: 72 addedAffinity: 73 requiredDuringSchedulingIgnoredDuringExecution: 74 nodeSelectorTerms: 75 - matchExpressions: 76 - key: foo 77 operator: In 78 values: ["bar"] 79 - name: NodeResourcesBalancedAllocation 80 args: 81 resources: 82 - name: cpu # default weight(1) will be set. 83 - name: memory # weight 0 will be replaced by 1. 84 weight: 0 85 - name: scalar0 86 weight: 1 87 - name: scalar1 # default weight(1) will be set for scalar1 88 - name: scalar2 # weight 0 will be replaced by 1. 89 weight: 0 90 - name: scalar3 91 weight: 2 92 `), 93 wantProfiles: []config.KubeSchedulerProfile{ 94 { 95 SchedulerName: "default-scheduler", 96 PercentageOfNodesToScore: nil, 97 Plugins: defaults.PluginsV1, 98 PluginConfig: []config.PluginConfig{ 99 { 100 Name: "DefaultPreemption", 101 Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 50, MinCandidateNodesAbsolute: 500}, 102 }, 103 { 104 Name: "InterPodAffinity", 105 Args: &config.InterPodAffinityArgs{HardPodAffinityWeight: 5}, 106 }, 107 { 108 Name: "NodeResourcesFit", 109 Args: &config.NodeResourcesFitArgs{ 110 IgnoredResources: []string{"foo"}, 111 ScoringStrategy: &config.ScoringStrategy{ 112 Type: config.LeastAllocated, 113 Resources: []config.ResourceSpec{ 114 {Name: "cpu", Weight: 1}, 115 {Name: "memory", Weight: 1}, 116 }, 117 }, 118 }, 119 }, 120 { 121 Name: "PodTopologySpread", 122 Args: &config.PodTopologySpreadArgs{ 123 DefaultConstraints: []corev1.TopologySpreadConstraint{ 124 {MaxSkew: 1, TopologyKey: "zone", WhenUnsatisfiable: corev1.ScheduleAnyway}, 125 }, 126 DefaultingType: config.SystemDefaulting, 127 }, 128 }, 129 { 130 Name: "VolumeBinding", 131 Args: &config.VolumeBindingArgs{ 132 BindTimeoutSeconds: 300, 133 }, 134 }, 135 { 136 Name: "NodeAffinity", 137 Args: &config.NodeAffinityArgs{ 138 AddedAffinity: &corev1.NodeAffinity{ 139 RequiredDuringSchedulingIgnoredDuringExecution: &corev1.NodeSelector{ 140 NodeSelectorTerms: []corev1.NodeSelectorTerm{ 141 { 142 MatchExpressions: []corev1.NodeSelectorRequirement{ 143 { 144 Key: "foo", 145 Operator: corev1.NodeSelectorOpIn, 146 Values: []string{"bar"}, 147 }, 148 }, 149 }, 150 }, 151 }, 152 }, 153 }, 154 }, 155 { 156 Name: "NodeResourcesBalancedAllocation", 157 Args: &config.NodeResourcesBalancedAllocationArgs{ 158 Resources: []config.ResourceSpec{ 159 {Name: "cpu", Weight: 1}, 160 {Name: "memory", Weight: 1}, 161 {Name: "scalar0", Weight: 1}, 162 {Name: "scalar1", Weight: 1}, 163 {Name: "scalar2", Weight: 1}, 164 {Name: "scalar3", Weight: 2}}, 165 }, 166 }, 167 }, 168 }, 169 }, 170 }, 171 { 172 name: "v1 with non-default global percentageOfNodesToScore", 173 data: []byte(` 174 apiVersion: kubescheduler.config.k8s.io/v1 175 kind: KubeSchedulerConfiguration 176 percentageOfNodesToScore: 10 177 `), 178 wantProfiles: []config.KubeSchedulerProfile{ 179 { 180 SchedulerName: "default-scheduler", 181 PercentageOfNodesToScore: nil, 182 Plugins: defaults.PluginsV1, 183 PluginConfig: defaults.PluginConfigsV1, 184 }, 185 }, 186 }, 187 { 188 name: "v1 with non-default global and profile percentageOfNodesToScore", 189 data: []byte(` 190 apiVersion: kubescheduler.config.k8s.io/v1 191 kind: KubeSchedulerConfiguration 192 percentageOfNodesToScore: 10 193 profiles: 194 - percentageOfNodesToScore: 20 195 `), 196 wantProfiles: []config.KubeSchedulerProfile{ 197 { 198 SchedulerName: "default-scheduler", 199 PercentageOfNodesToScore: ptr.To[int32](20), 200 Plugins: defaults.PluginsV1, 201 PluginConfig: defaults.PluginConfigsV1, 202 }, 203 }, 204 }, 205 { 206 name: "v1 plugins can include version and kind", 207 data: []byte(` 208 apiVersion: kubescheduler.config.k8s.io/v1 209 kind: KubeSchedulerConfiguration 210 profiles: 211 - pluginConfig: 212 - name: DefaultPreemption 213 args: 214 apiVersion: kubescheduler.config.k8s.io/v1 215 kind: DefaultPreemptionArgs 216 minCandidateNodesPercentage: 50 217 `), 218 wantProfiles: []config.KubeSchedulerProfile{ 219 { 220 SchedulerName: "default-scheduler", 221 Plugins: defaults.PluginsV1, 222 PluginConfig: []config.PluginConfig{ 223 { 224 Name: "DefaultPreemption", 225 Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 50, MinCandidateNodesAbsolute: 100}, 226 }, 227 { 228 Name: "InterPodAffinity", 229 Args: &config.InterPodAffinityArgs{ 230 HardPodAffinityWeight: 1, 231 }, 232 }, 233 { 234 Name: "NodeAffinity", 235 Args: &config.NodeAffinityArgs{}, 236 }, 237 { 238 Name: "NodeResourcesBalancedAllocation", 239 Args: &config.NodeResourcesBalancedAllocationArgs{ 240 Resources: []config.ResourceSpec{{Name: "cpu", Weight: 1}, {Name: "memory", Weight: 1}}, 241 }, 242 }, 243 { 244 Name: "NodeResourcesFit", 245 Args: &config.NodeResourcesFitArgs{ 246 ScoringStrategy: &config.ScoringStrategy{ 247 Type: config.LeastAllocated, 248 Resources: []config.ResourceSpec{ 249 {Name: "cpu", Weight: 1}, 250 {Name: "memory", Weight: 1}, 251 }, 252 }, 253 }, 254 }, 255 { 256 Name: "PodTopologySpread", 257 Args: &config.PodTopologySpreadArgs{ 258 DefaultingType: config.SystemDefaulting, 259 }, 260 }, 261 { 262 Name: "VolumeBinding", 263 Args: &config.VolumeBindingArgs{ 264 BindTimeoutSeconds: 600, 265 }, 266 }, 267 }, 268 }, 269 }, 270 }, 271 { 272 name: "plugin group and kind should match the type", 273 data: []byte(` 274 apiVersion: kubescheduler.config.k8s.io/v1 275 kind: KubeSchedulerConfiguration 276 profiles: 277 - pluginConfig: 278 - name: DefaultPreemption 279 args: 280 apiVersion: kubescheduler.config.k8s.io/v1 281 kind: InterPodAffinityArgs 282 `), 283 wantErr: `decoding .profiles[0].pluginConfig[0]: args for plugin DefaultPreemption were not of type DefaultPreemptionArgs.kubescheduler.config.k8s.io, got InterPodAffinityArgs.kubescheduler.config.k8s.io`, 284 }, 285 { 286 name: "v1 NodResourcesFitArgs shape encoding is strict", 287 data: []byte(` 288 apiVersion: kubescheduler.config.k8s.io/v1 289 kind: KubeSchedulerConfiguration 290 profiles: 291 - pluginConfig: 292 - name: NodeResourcesFit 293 args: 294 scoringStrategy: 295 requestedToCapacityRatio: 296 shape: 297 - Score: 2 298 Utilization: 1 299 `), 300 wantErr: `strict decoding error: decoding .profiles[0].pluginConfig[0]: strict decoding error: decoding args for plugin NodeResourcesFit: strict decoding error: unknown field "scoringStrategy.requestedToCapacityRatio.shape[0].Score", unknown field "scoringStrategy.requestedToCapacityRatio.shape[0].Utilization"`, 301 }, 302 { 303 name: "v1 NodeResourcesFitArgs resources encoding is strict", 304 data: []byte(` 305 apiVersion: kubescheduler.config.k8s.io/v1 306 kind: KubeSchedulerConfiguration 307 profiles: 308 - pluginConfig: 309 - name: NodeResourcesFit 310 args: 311 scoringStrategy: 312 resources: 313 - Name: cpu 314 Weight: 1 315 `), 316 wantErr: `strict decoding error: decoding .profiles[0].pluginConfig[0]: strict decoding error: decoding args for plugin NodeResourcesFit: strict decoding error: unknown field "scoringStrategy.resources[0].Name", unknown field "scoringStrategy.resources[0].Weight"`, 317 }, 318 { 319 name: "out-of-tree plugin args", 320 data: []byte(` 321 apiVersion: kubescheduler.config.k8s.io/v1 322 kind: KubeSchedulerConfiguration 323 profiles: 324 - pluginConfig: 325 - name: OutOfTreePlugin 326 args: 327 foo: bar 328 `), 329 wantProfiles: []config.KubeSchedulerProfile{ 330 { 331 SchedulerName: "default-scheduler", 332 Plugins: defaults.PluginsV1, 333 PluginConfig: append([]config.PluginConfig{ 334 { 335 Name: "OutOfTreePlugin", 336 Args: &runtime.Unknown{ 337 ContentType: "application/json", 338 Raw: []byte(`{"foo":"bar"}`), 339 }, 340 }, 341 }, defaults.PluginConfigsV1...), 342 }, 343 }, 344 }, 345 { 346 name: "empty and no plugin args", 347 data: []byte(` 348 apiVersion: kubescheduler.config.k8s.io/v1 349 kind: KubeSchedulerConfiguration 350 profiles: 351 - pluginConfig: 352 - name: DefaultPreemption 353 args: 354 - name: InterPodAffinity 355 args: 356 - name: NodeResourcesFit 357 - name: OutOfTreePlugin 358 args: 359 - name: VolumeBinding 360 args: 361 - name: PodTopologySpread 362 - name: NodeAffinity 363 - name: NodeResourcesBalancedAllocation 364 `), 365 wantProfiles: []config.KubeSchedulerProfile{ 366 { 367 SchedulerName: "default-scheduler", 368 Plugins: defaults.PluginsV1, 369 PluginConfig: []config.PluginConfig{ 370 { 371 Name: "DefaultPreemption", 372 Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 10, MinCandidateNodesAbsolute: 100}, 373 }, 374 { 375 Name: "InterPodAffinity", 376 Args: &config.InterPodAffinityArgs{ 377 HardPodAffinityWeight: 1, 378 }, 379 }, 380 { 381 Name: "NodeResourcesFit", 382 Args: &config.NodeResourcesFitArgs{ 383 ScoringStrategy: &config.ScoringStrategy{ 384 Type: config.LeastAllocated, 385 Resources: []config.ResourceSpec{ 386 {Name: "cpu", Weight: 1}, 387 {Name: "memory", Weight: 1}, 388 }, 389 }, 390 }, 391 }, 392 {Name: "OutOfTreePlugin"}, 393 { 394 Name: "VolumeBinding", 395 Args: &config.VolumeBindingArgs{ 396 BindTimeoutSeconds: 600, 397 }, 398 }, 399 { 400 Name: "PodTopologySpread", 401 Args: &config.PodTopologySpreadArgs{ 402 DefaultingType: config.SystemDefaulting, 403 }, 404 }, 405 { 406 Name: "NodeAffinity", 407 Args: &config.NodeAffinityArgs{}, 408 }, 409 { 410 Name: "NodeResourcesBalancedAllocation", 411 Args: &config.NodeResourcesBalancedAllocationArgs{ 412 Resources: []config.ResourceSpec{{Name: "cpu", Weight: 1}, {Name: "memory", Weight: 1}}, 413 }, 414 }, 415 }, 416 }, 417 }, 418 }, 419 { 420 name: "ignorePreferredTermsOfExistingPods is enabled", 421 data: []byte(` 422 apiVersion: kubescheduler.config.k8s.io/v1 423 kind: KubeSchedulerConfiguration 424 profiles: 425 - pluginConfig: 426 - name: InterPodAffinity 427 args: 428 ignorePreferredTermsOfExistingPods: true 429 `), 430 wantProfiles: []config.KubeSchedulerProfile{ 431 { 432 SchedulerName: "default-scheduler", 433 Plugins: defaults.PluginsV1, 434 PluginConfig: []config.PluginConfig{ 435 { 436 Name: "InterPodAffinity", 437 Args: &config.InterPodAffinityArgs{ 438 HardPodAffinityWeight: 1, 439 IgnorePreferredTermsOfExistingPods: true, 440 }, 441 }, 442 { 443 Name: "DefaultPreemption", 444 Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 10, MinCandidateNodesAbsolute: 100}, 445 }, 446 { 447 Name: "NodeAffinity", 448 Args: &config.NodeAffinityArgs{}, 449 }, 450 { 451 Name: "NodeResourcesBalancedAllocation", 452 Args: &config.NodeResourcesBalancedAllocationArgs{ 453 Resources: []config.ResourceSpec{{Name: "cpu", Weight: 1}, {Name: "memory", Weight: 1}}, 454 }, 455 }, 456 { 457 Name: "NodeResourcesFit", 458 Args: &config.NodeResourcesFitArgs{ 459 ScoringStrategy: &config.ScoringStrategy{ 460 Type: config.LeastAllocated, 461 Resources: []config.ResourceSpec{ 462 {Name: "cpu", Weight: 1}, 463 {Name: "memory", Weight: 1}, 464 }, 465 }, 466 }, 467 }, 468 { 469 Name: "PodTopologySpread", 470 Args: &config.PodTopologySpreadArgs{ 471 DefaultingType: config.SystemDefaulting, 472 }, 473 }, 474 { 475 Name: "VolumeBinding", 476 Args: &config.VolumeBindingArgs{ 477 BindTimeoutSeconds: 600, 478 }, 479 }, 480 }, 481 }, 482 }, 483 }, 484 } 485 decoder := Codecs.UniversalDecoder() 486 for _, tt := range testCases { 487 t.Run(tt.name, func(t *testing.T) { 488 obj, gvk, err := decoder.Decode(tt.data, nil, nil) 489 if err != nil { 490 if tt.wantErr != err.Error() { 491 t.Fatalf("\ngot err:\n\t%v\nwant:\n\t%s", err, tt.wantErr) 492 } 493 return 494 } 495 if len(tt.wantErr) != 0 { 496 t.Fatalf("no error produced, wanted %v", tt.wantErr) 497 } 498 got, ok := obj.(*config.KubeSchedulerConfiguration) 499 if !ok { 500 t.Fatalf("decoded into %s, want %s", gvk, config.SchemeGroupVersion.WithKind("KubeSchedulerConfiguration")) 501 } 502 if diff := cmp.Diff(tt.wantProfiles, got.Profiles); diff != "" { 503 t.Errorf("unexpected configuration (-want,+got):\n%s", diff) 504 } 505 }) 506 } 507 } 508 509 func TestCodecsEncodePluginConfig(t *testing.T) { 510 testCases := []struct { 511 name string 512 obj runtime.Object 513 version schema.GroupVersion 514 want string 515 }{ 516 //v1 tests 517 { 518 name: "v1 in-tree and out-of-tree plugins", 519 version: v1.SchemeGroupVersion, 520 obj: &v1.KubeSchedulerConfiguration{ 521 Profiles: []v1.KubeSchedulerProfile{ 522 { 523 PluginConfig: []v1.PluginConfig{ 524 { 525 Name: "InterPodAffinity", 526 Args: runtime.RawExtension{ 527 Object: &v1.InterPodAffinityArgs{ 528 HardPodAffinityWeight: ptr.To[int32](5), 529 }, 530 }, 531 }, 532 { 533 Name: "VolumeBinding", 534 Args: runtime.RawExtension{ 535 Object: &v1.VolumeBindingArgs{ 536 BindTimeoutSeconds: ptr.To[int64](300), 537 Shape: []v1.UtilizationShapePoint{ 538 { 539 Utilization: 0, 540 Score: 0, 541 }, 542 { 543 Utilization: 100, 544 Score: 10, 545 }, 546 }, 547 }, 548 }, 549 }, 550 { 551 Name: "NodeResourcesFit", 552 Args: runtime.RawExtension{ 553 Object: &v1.NodeResourcesFitArgs{ 554 ScoringStrategy: &v1.ScoringStrategy{ 555 Type: v1.RequestedToCapacityRatio, 556 Resources: []v1.ResourceSpec{{Name: "cpu", Weight: 1}}, 557 RequestedToCapacityRatio: &v1.RequestedToCapacityRatioParam{ 558 Shape: []v1.UtilizationShapePoint{ 559 {Utilization: 1, Score: 2}, 560 }, 561 }, 562 }, 563 }, 564 }, 565 }, 566 { 567 Name: "PodTopologySpread", 568 Args: runtime.RawExtension{ 569 Object: &v1.PodTopologySpreadArgs{ 570 DefaultConstraints: []corev1.TopologySpreadConstraint{}, 571 }, 572 }, 573 }, 574 { 575 Name: "OutOfTreePlugin", 576 Args: runtime.RawExtension{ 577 Raw: []byte(`{"foo":"bar"}`), 578 }, 579 }, 580 }, 581 }, 582 }, 583 }, 584 want: `apiVersion: kubescheduler.config.k8s.io/v1 585 clientConnection: 586 acceptContentTypes: "" 587 burst: 0 588 contentType: "" 589 kubeconfig: "" 590 qps: 0 591 kind: KubeSchedulerConfiguration 592 leaderElection: 593 leaderElect: null 594 leaseDuration: 0s 595 renewDeadline: 0s 596 resourceLock: "" 597 resourceName: "" 598 resourceNamespace: "" 599 retryPeriod: 0s 600 profiles: 601 - pluginConfig: 602 - args: 603 apiVersion: kubescheduler.config.k8s.io/v1 604 hardPodAffinityWeight: 5 605 ignorePreferredTermsOfExistingPods: false 606 kind: InterPodAffinityArgs 607 name: InterPodAffinity 608 - args: 609 apiVersion: kubescheduler.config.k8s.io/v1 610 bindTimeoutSeconds: 300 611 kind: VolumeBindingArgs 612 shape: 613 - score: 0 614 utilization: 0 615 - score: 10 616 utilization: 100 617 name: VolumeBinding 618 - args: 619 apiVersion: kubescheduler.config.k8s.io/v1 620 kind: NodeResourcesFitArgs 621 scoringStrategy: 622 requestedToCapacityRatio: 623 shape: 624 - score: 2 625 utilization: 1 626 resources: 627 - name: cpu 628 weight: 1 629 type: RequestedToCapacityRatio 630 name: NodeResourcesFit 631 - args: 632 apiVersion: kubescheduler.config.k8s.io/v1 633 kind: PodTopologySpreadArgs 634 name: PodTopologySpread 635 - args: 636 foo: bar 637 name: OutOfTreePlugin 638 `, 639 }, 640 { 641 name: "v1 in-tree and out-of-tree plugins from internal", 642 version: v1.SchemeGroupVersion, 643 obj: &config.KubeSchedulerConfiguration{ 644 Parallelism: 8, 645 DelayCacheUntilActive: true, 646 Profiles: []config.KubeSchedulerProfile{ 647 { 648 PluginConfig: []config.PluginConfig{ 649 { 650 Name: "InterPodAffinity", 651 Args: &config.InterPodAffinityArgs{ 652 HardPodAffinityWeight: 5, 653 }, 654 }, 655 { 656 Name: "NodeResourcesFit", 657 Args: &config.NodeResourcesFitArgs{ 658 ScoringStrategy: &config.ScoringStrategy{ 659 Type: config.LeastAllocated, 660 Resources: []config.ResourceSpec{{Name: "cpu", Weight: 1}}, 661 }, 662 }, 663 }, 664 { 665 Name: "VolumeBinding", 666 Args: &config.VolumeBindingArgs{ 667 BindTimeoutSeconds: 300, 668 }, 669 }, 670 { 671 Name: "PodTopologySpread", 672 Args: &config.PodTopologySpreadArgs{}, 673 }, 674 { 675 Name: "OutOfTreePlugin", 676 Args: &runtime.Unknown{ 677 Raw: []byte(`{"foo":"bar"}`), 678 }, 679 }, 680 }, 681 }, 682 }, 683 }, 684 want: `apiVersion: kubescheduler.config.k8s.io/v1 685 clientConnection: 686 acceptContentTypes: "" 687 burst: 0 688 contentType: "" 689 kubeconfig: "" 690 qps: 0 691 delayCacheUntilActive: true 692 enableContentionProfiling: false 693 enableProfiling: false 694 kind: KubeSchedulerConfiguration 695 leaderElection: 696 leaderElect: false 697 leaseDuration: 0s 698 renewDeadline: 0s 699 resourceLock: "" 700 resourceName: "" 701 resourceNamespace: "" 702 retryPeriod: 0s 703 parallelism: 8 704 podInitialBackoffSeconds: 0 705 podMaxBackoffSeconds: 0 706 profiles: 707 - pluginConfig: 708 - args: 709 apiVersion: kubescheduler.config.k8s.io/v1 710 hardPodAffinityWeight: 5 711 ignorePreferredTermsOfExistingPods: false 712 kind: InterPodAffinityArgs 713 name: InterPodAffinity 714 - args: 715 apiVersion: kubescheduler.config.k8s.io/v1 716 kind: NodeResourcesFitArgs 717 scoringStrategy: 718 resources: 719 - name: cpu 720 weight: 1 721 type: LeastAllocated 722 name: NodeResourcesFit 723 - args: 724 apiVersion: kubescheduler.config.k8s.io/v1 725 bindTimeoutSeconds: 300 726 kind: VolumeBindingArgs 727 name: VolumeBinding 728 - args: 729 apiVersion: kubescheduler.config.k8s.io/v1 730 kind: PodTopologySpreadArgs 731 name: PodTopologySpread 732 - args: 733 foo: bar 734 name: OutOfTreePlugin 735 schedulerName: "" 736 `, 737 }, 738 { 739 name: "v1 ignorePreferredTermsOfExistingPods is enabled", 740 version: v1.SchemeGroupVersion, 741 obj: &config.KubeSchedulerConfiguration{ 742 Parallelism: 8, 743 DelayCacheUntilActive: true, 744 Profiles: []config.KubeSchedulerProfile{ 745 { 746 PluginConfig: []config.PluginConfig{ 747 { 748 Name: "InterPodAffinity", 749 Args: &config.InterPodAffinityArgs{ 750 HardPodAffinityWeight: 5, 751 IgnorePreferredTermsOfExistingPods: true, 752 }, 753 }, 754 }, 755 }, 756 }, 757 }, 758 want: `apiVersion: kubescheduler.config.k8s.io/v1 759 clientConnection: 760 acceptContentTypes: "" 761 burst: 0 762 contentType: "" 763 kubeconfig: "" 764 qps: 0 765 delayCacheUntilActive: true 766 enableContentionProfiling: false 767 enableProfiling: false 768 kind: KubeSchedulerConfiguration 769 leaderElection: 770 leaderElect: false 771 leaseDuration: 0s 772 renewDeadline: 0s 773 resourceLock: "" 774 resourceName: "" 775 resourceNamespace: "" 776 retryPeriod: 0s 777 parallelism: 8 778 podInitialBackoffSeconds: 0 779 podMaxBackoffSeconds: 0 780 profiles: 781 - pluginConfig: 782 - args: 783 apiVersion: kubescheduler.config.k8s.io/v1 784 hardPodAffinityWeight: 5 785 ignorePreferredTermsOfExistingPods: true 786 kind: InterPodAffinityArgs 787 name: InterPodAffinity 788 schedulerName: "" 789 `, 790 }, 791 } 792 yamlInfo, ok := runtime.SerializerInfoForMediaType(Codecs.SupportedMediaTypes(), runtime.ContentTypeYAML) 793 if !ok { 794 t.Fatalf("unable to locate encoder -- %q is not a supported media type", runtime.ContentTypeYAML) 795 } 796 jsonInfo, ok := runtime.SerializerInfoForMediaType(Codecs.SupportedMediaTypes(), runtime.ContentTypeJSON) 797 if !ok { 798 t.Fatalf("unable to locate encoder -- %q is not a supported media type", runtime.ContentTypeJSON) 799 } 800 for _, tt := range testCases { 801 t.Run(tt.name, func(t *testing.T) { 802 encoder := Codecs.EncoderForVersion(yamlInfo.Serializer, tt.version) 803 var buf bytes.Buffer 804 if err := encoder.Encode(tt.obj, &buf); err != nil { 805 t.Fatal(err) 806 } 807 if diff := cmp.Diff(tt.want, buf.String()); diff != "" { 808 t.Errorf("unexpected encoded configuration:\n%s", diff) 809 } 810 encoder = Codecs.EncoderForVersion(jsonInfo.Serializer, tt.version) 811 buf = bytes.Buffer{} 812 if err := encoder.Encode(tt.obj, &buf); err != nil { 813 t.Fatal(err) 814 } 815 out, err := yaml.JSONToYAML(buf.Bytes()) 816 if err != nil { 817 t.Fatal(err) 818 } 819 if diff := cmp.Diff(tt.want, string(out)); diff != "" { 820 t.Errorf("unexpected encoded configuration:\n%s", diff) 821 } 822 }) 823 } 824 }