k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/cmd/kube-scheduler/app/options/options_test.go (about) 1 /* 2 Copyright 2018 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 options 18 19 import ( 20 "context" 21 "fmt" 22 "net/http" 23 "net/http/httptest" 24 "os" 25 "path/filepath" 26 "testing" 27 "time" 28 29 "github.com/google/go-cmp/cmp" 30 "github.com/stretchr/testify/assert" 31 32 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 33 "k8s.io/apimachinery/pkg/runtime" 34 apiserveroptions "k8s.io/apiserver/pkg/server/options" 35 componentbaseconfig "k8s.io/component-base/config" 36 "k8s.io/component-base/logs" 37 "k8s.io/klog/v2/ktesting" 38 v1 "k8s.io/kube-scheduler/config/v1" 39 kubeschedulerconfig "k8s.io/kubernetes/pkg/scheduler/apis/config" 40 "k8s.io/kubernetes/pkg/scheduler/apis/config/latest" 41 configtesting "k8s.io/kubernetes/pkg/scheduler/apis/config/testing" 42 "k8s.io/kubernetes/pkg/scheduler/apis/config/testing/defaults" 43 "k8s.io/kubernetes/pkg/scheduler/framework/plugins/names" 44 "k8s.io/utils/ptr" 45 ) 46 47 func TestSchedulerOptions(t *testing.T) { 48 // temp dir 49 tmpDir, err := os.MkdirTemp("", "scheduler-options") 50 if err != nil { 51 t.Fatal(err) 52 } 53 defer os.RemoveAll(tmpDir) 54 55 // record the username requests were made with 56 username := "" 57 // https server 58 server := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 59 username, _, _ = req.BasicAuth() 60 if username == "" { 61 username = "none, tls" 62 } 63 w.WriteHeader(200) 64 w.Write([]byte(`ok`)) 65 })) 66 defer server.Close() 67 // http server 68 insecureserver := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { 69 username, _, _ = req.BasicAuth() 70 if username == "" { 71 username = "none, http" 72 } 73 w.WriteHeader(200) 74 w.Write([]byte(`ok`)) 75 })) 76 defer insecureserver.Close() 77 78 // config file and kubeconfig 79 configFile := filepath.Join(tmpDir, "scheduler.yaml") 80 configKubeconfig := filepath.Join(tmpDir, "config.kubeconfig") 81 if err := os.WriteFile(configFile, []byte(fmt.Sprintf(` 82 apiVersion: kubescheduler.config.k8s.io/v1 83 kind: KubeSchedulerConfiguration 84 clientConnection: 85 kubeconfig: '%s' 86 leaderElection: 87 leaderElect: true`, configKubeconfig)), os.FileMode(0600)); err != nil { 88 t.Fatal(err) 89 } 90 if err := os.WriteFile(configKubeconfig, []byte(fmt.Sprintf(` 91 apiVersion: v1 92 kind: Config 93 clusters: 94 - cluster: 95 insecure-skip-tls-verify: true 96 server: %s 97 name: default 98 contexts: 99 - context: 100 cluster: default 101 user: default 102 name: default 103 current-context: default 104 users: 105 - name: default 106 user: 107 username: config 108 `, server.URL)), os.FileMode(0600)); err != nil { 109 t.Fatal(err) 110 } 111 112 oldConfigFile := filepath.Join(tmpDir, "scheduler_old.yaml") 113 if err := os.WriteFile(oldConfigFile, []byte(fmt.Sprintf(` 114 apiVersion: componentconfig/v1alpha1 115 kind: KubeSchedulerConfiguration 116 clientConnection: 117 kubeconfig: '%s' 118 leaderElection: 119 leaderElect: true`, configKubeconfig)), os.FileMode(0600)); err != nil { 120 t.Fatal(err) 121 } 122 123 unknownVersionConfig := filepath.Join(tmpDir, "scheduler_invalid_wrong_api_version.yaml") 124 if err := os.WriteFile(unknownVersionConfig, []byte(fmt.Sprintf(` 125 apiVersion: kubescheduler.config.k8s.io/unknown 126 kind: KubeSchedulerConfiguration 127 clientConnection: 128 kubeconfig: '%s' 129 leaderElection: 130 leaderElect: true`, configKubeconfig)), os.FileMode(0600)); err != nil { 131 t.Fatal(err) 132 } 133 134 noVersionConfig := filepath.Join(tmpDir, "scheduler_invalid_no_version.yaml") 135 if err := os.WriteFile(noVersionConfig, []byte(fmt.Sprintf(` 136 kind: KubeSchedulerConfiguration 137 clientConnection: 138 kubeconfig: '%s' 139 leaderElection: 140 leaderElect: true`, configKubeconfig)), os.FileMode(0600)); err != nil { 141 t.Fatal(err) 142 } 143 144 unknownFieldConfig := filepath.Join(tmpDir, "scheduler_invalid_unknown_field.yaml") 145 if err := os.WriteFile(unknownFieldConfig, []byte(fmt.Sprintf(` 146 apiVersion: kubescheduler.config.k8s.io/v1 147 kind: KubeSchedulerConfiguration 148 clientConnection: 149 kubeconfig: '%s' 150 leaderElection: 151 leaderElect: true 152 foo: bar`, configKubeconfig)), os.FileMode(0600)); err != nil { 153 t.Fatal(err) 154 } 155 156 duplicateFieldConfig := filepath.Join(tmpDir, "scheduler_invalid_duplicate_fields.yaml") 157 if err := os.WriteFile(duplicateFieldConfig, []byte(fmt.Sprintf(` 158 apiVersion: kubescheduler.config.k8s.io/v1 159 kind: KubeSchedulerConfiguration 160 clientConnection: 161 kubeconfig: '%s' 162 leaderElection: 163 leaderElect: true 164 leaderElect: false`, configKubeconfig)), os.FileMode(0600)); err != nil { 165 t.Fatal(err) 166 } 167 168 // flag-specified kubeconfig 169 flagKubeconfig := filepath.Join(tmpDir, "flag.kubeconfig") 170 if err := os.WriteFile(flagKubeconfig, []byte(fmt.Sprintf(` 171 apiVersion: v1 172 kind: Config 173 clusters: 174 - cluster: 175 insecure-skip-tls-verify: true 176 server: %s 177 name: default 178 contexts: 179 - context: 180 cluster: default 181 user: default 182 name: default 183 current-context: default 184 users: 185 - name: default 186 user: 187 username: flag 188 `, server.URL)), os.FileMode(0600)); err != nil { 189 t.Fatal(err) 190 } 191 192 // plugin config 193 pluginConfigFile := filepath.Join(tmpDir, "plugin.yaml") 194 if err := os.WriteFile(pluginConfigFile, []byte(fmt.Sprintf(` 195 apiVersion: kubescheduler.config.k8s.io/v1 196 kind: KubeSchedulerConfiguration 197 clientConnection: 198 kubeconfig: '%s' 199 profiles: 200 - plugins: 201 preEnqueue: 202 enabled: 203 - name: foo 204 reserve: 205 enabled: 206 - name: foo 207 - name: bar 208 disabled: 209 - name: VolumeBinding 210 preBind: 211 enabled: 212 - name: foo 213 disabled: 214 - name: VolumeBinding 215 pluginConfig: 216 - name: InterPodAffinity 217 args: 218 hardPodAffinityWeight: 2 219 - name: foo 220 args: 221 bar: baz 222 `, configKubeconfig)), os.FileMode(0600)); err != nil { 223 t.Fatal(err) 224 } 225 226 // multiple profiles config 227 multiProfilesConfig := filepath.Join(tmpDir, "multi-profiles.yaml") 228 if err := os.WriteFile(multiProfilesConfig, []byte(fmt.Sprintf(` 229 apiVersion: kubescheduler.config.k8s.io/v1 230 kind: KubeSchedulerConfiguration 231 clientConnection: 232 kubeconfig: '%s' 233 profiles: 234 - schedulerName: "foo-profile" 235 plugins: 236 reserve: 237 enabled: 238 - name: foo 239 - name: VolumeBinding 240 disabled: 241 - name: VolumeBinding 242 - schedulerName: "bar-profile" 243 plugins: 244 preBind: 245 disabled: 246 - name: VolumeBinding 247 pluginConfig: 248 - name: foo 249 `, configKubeconfig)), os.FileMode(0600)); err != nil { 250 t.Fatal(err) 251 } 252 253 // high throughput profile config 254 highThroughputProfileConfig := filepath.Join(tmpDir, "high-throughput.yaml") 255 if err := os.WriteFile(highThroughputProfileConfig, []byte(fmt.Sprintf(` 256 apiVersion: kubescheduler.config.k8s.io/v1 257 kind: KubeSchedulerConfiguration 258 clientConnection: 259 kubeconfig: '%s' 260 profiles: 261 - schedulerName: "high-throughput-profile" 262 plugins: 263 preScore: 264 enabled: 265 - name: InterPodAffinity 266 pluginConfig: 267 - name: InterPodAffinity 268 args: 269 ignorePreferredTermsOfExistingPods: true 270 `, configKubeconfig)), os.FileMode(0600)); err != nil { 271 t.Fatal(err) 272 } 273 274 // Insulate this test from picking up in-cluster config when run inside a pod 275 // We can't assume we have permissions to write to /var/run/secrets/... from a unit test to mock in-cluster config for testing 276 if len(os.Getenv("KUBERNETES_SERVICE_HOST")) > 0 { 277 t.Setenv("KUBERNETES_SERVICE_HOST", "") 278 } 279 280 defaultPodInitialBackoffSeconds := int64(1) 281 defaultPodMaxBackoffSeconds := int64(10) 282 defaultPercentageOfNodesToScore := ptr.To[int32](0) 283 284 testcases := []struct { 285 name string 286 options *Options 287 expectedUsername string 288 expectedError string 289 expectedConfig kubeschedulerconfig.KubeSchedulerConfiguration 290 checkErrFn func(err error) bool 291 }{ 292 { 293 name: "v1 config file", 294 options: &Options{ 295 ConfigFile: configFile, 296 ComponentConfig: func() *kubeschedulerconfig.KubeSchedulerConfiguration { 297 cfg := configtesting.V1ToInternalWithDefaults(t, v1.KubeSchedulerConfiguration{}) 298 return cfg 299 }(), 300 SecureServing: (&apiserveroptions.SecureServingOptions{ 301 ServerCert: apiserveroptions.GeneratableKeyCert{ 302 CertDirectory: "/a/b/c", 303 PairName: "kube-scheduler", 304 }, 305 HTTP2MaxStreamsPerConnection: 47, 306 }).WithLoopback(), 307 Authentication: &apiserveroptions.DelegatingAuthenticationOptions{ 308 CacheTTL: 10 * time.Second, 309 ClientCert: apiserveroptions.ClientCertAuthenticationOptions{}, 310 RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{ 311 UsernameHeaders: []string{"x-remote-user"}, 312 GroupHeaders: []string{"x-remote-group"}, 313 ExtraHeaderPrefixes: []string{"x-remote-extra-"}, 314 }, 315 RemoteKubeConfigFileOptional: true, 316 }, 317 Authorization: &apiserveroptions.DelegatingAuthorizationOptions{ 318 AllowCacheTTL: 10 * time.Second, 319 DenyCacheTTL: 10 * time.Second, 320 RemoteKubeConfigFileOptional: true, 321 AlwaysAllowPaths: []string{"/healthz", "/readyz", "/livez"}, // note: this does not match /healthz/ or /healthz/* 322 AlwaysAllowGroups: []string{"system:masters"}, 323 }, 324 Logs: logs.NewOptions(), 325 }, 326 expectedUsername: "config", 327 expectedConfig: kubeschedulerconfig.KubeSchedulerConfiguration{ 328 TypeMeta: metav1.TypeMeta{ 329 APIVersion: v1.SchemeGroupVersion.String(), 330 }, 331 Parallelism: 16, 332 DelayCacheUntilActive: false, 333 DebuggingConfiguration: componentbaseconfig.DebuggingConfiguration{ 334 EnableProfiling: true, 335 EnableContentionProfiling: true, 336 }, 337 LeaderElection: componentbaseconfig.LeaderElectionConfiguration{ 338 LeaderElect: true, 339 LeaseDuration: metav1.Duration{Duration: 15 * time.Second}, 340 RenewDeadline: metav1.Duration{Duration: 10 * time.Second}, 341 RetryPeriod: metav1.Duration{Duration: 2 * time.Second}, 342 ResourceLock: "leases", 343 ResourceNamespace: "kube-system", 344 ResourceName: "kube-scheduler", 345 }, 346 ClientConnection: componentbaseconfig.ClientConnectionConfiguration{ 347 Kubeconfig: configKubeconfig, 348 QPS: 50, 349 Burst: 100, 350 ContentType: "application/vnd.kubernetes.protobuf", 351 }, 352 PercentageOfNodesToScore: defaultPercentageOfNodesToScore, 353 PodInitialBackoffSeconds: defaultPodInitialBackoffSeconds, 354 PodMaxBackoffSeconds: defaultPodMaxBackoffSeconds, 355 Profiles: []kubeschedulerconfig.KubeSchedulerProfile{ 356 { 357 SchedulerName: "default-scheduler", 358 Plugins: defaults.PluginsV1, 359 PluginConfig: defaults.PluginConfigsV1, 360 }, 361 }, 362 }, 363 }, 364 { 365 name: "config file in componentconfig/v1alpha1", 366 options: &Options{ 367 ConfigFile: oldConfigFile, 368 ComponentConfig: func() *kubeschedulerconfig.KubeSchedulerConfiguration { 369 cfg, err := latest.Default() 370 if err != nil { 371 t.Fatal(err) 372 } 373 return cfg 374 }(), 375 Logs: logs.NewOptions(), 376 }, 377 expectedError: "no kind \"KubeSchedulerConfiguration\" is registered for version \"componentconfig/v1alpha1\"", 378 }, 379 { 380 name: "unknown version kubescheduler.config.k8s.io/unknown", 381 options: &Options{ 382 ConfigFile: unknownVersionConfig, 383 Logs: logs.NewOptions(), 384 }, 385 expectedError: "no kind \"KubeSchedulerConfiguration\" is registered for version \"kubescheduler.config.k8s.io/unknown\"", 386 }, 387 { 388 name: "config file with no version", 389 options: &Options{ 390 ConfigFile: noVersionConfig, 391 Logs: logs.NewOptions(), 392 }, 393 expectedError: "Object 'apiVersion' is missing", 394 }, 395 { 396 name: "kubeconfig flag", 397 options: &Options{ 398 ComponentConfig: func() *kubeschedulerconfig.KubeSchedulerConfiguration { 399 cfg, _ := latest.Default() 400 cfg.ClientConnection.Kubeconfig = flagKubeconfig 401 return cfg 402 }(), 403 SecureServing: (&apiserveroptions.SecureServingOptions{ 404 ServerCert: apiserveroptions.GeneratableKeyCert{ 405 CertDirectory: "/a/b/c", 406 PairName: "kube-scheduler", 407 }, 408 HTTP2MaxStreamsPerConnection: 47, 409 }).WithLoopback(), 410 Authentication: &apiserveroptions.DelegatingAuthenticationOptions{ 411 CacheTTL: 10 * time.Second, 412 ClientCert: apiserveroptions.ClientCertAuthenticationOptions{}, 413 RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{ 414 UsernameHeaders: []string{"x-remote-user"}, 415 GroupHeaders: []string{"x-remote-group"}, 416 ExtraHeaderPrefixes: []string{"x-remote-extra-"}, 417 }, 418 RemoteKubeConfigFileOptional: true, 419 }, 420 Authorization: &apiserveroptions.DelegatingAuthorizationOptions{ 421 AllowCacheTTL: 10 * time.Second, 422 DenyCacheTTL: 10 * time.Second, 423 RemoteKubeConfigFileOptional: true, 424 AlwaysAllowPaths: []string{"/healthz", "/readyz", "/livez"}, // note: this does not match /healthz/ or /healthz/* 425 AlwaysAllowGroups: []string{"system:masters"}, 426 }, 427 Logs: logs.NewOptions(), 428 }, 429 expectedUsername: "flag", 430 expectedConfig: kubeschedulerconfig.KubeSchedulerConfiguration{ 431 TypeMeta: metav1.TypeMeta{ 432 APIVersion: v1.SchemeGroupVersion.String(), 433 }, 434 Parallelism: 16, 435 DelayCacheUntilActive: false, 436 DebuggingConfiguration: componentbaseconfig.DebuggingConfiguration{ 437 EnableProfiling: true, 438 EnableContentionProfiling: true, 439 }, 440 LeaderElection: componentbaseconfig.LeaderElectionConfiguration{ 441 LeaderElect: true, 442 LeaseDuration: metav1.Duration{Duration: 15 * time.Second}, 443 RenewDeadline: metav1.Duration{Duration: 10 * time.Second}, 444 RetryPeriod: metav1.Duration{Duration: 2 * time.Second}, 445 ResourceLock: "leases", 446 ResourceNamespace: "kube-system", 447 ResourceName: "kube-scheduler", 448 }, 449 ClientConnection: componentbaseconfig.ClientConnectionConfiguration{ 450 Kubeconfig: flagKubeconfig, 451 QPS: 50, 452 Burst: 100, 453 ContentType: "application/vnd.kubernetes.protobuf", 454 }, 455 PercentageOfNodesToScore: defaultPercentageOfNodesToScore, 456 PodInitialBackoffSeconds: defaultPodInitialBackoffSeconds, 457 PodMaxBackoffSeconds: defaultPodMaxBackoffSeconds, 458 Profiles: []kubeschedulerconfig.KubeSchedulerProfile{ 459 { 460 SchedulerName: "default-scheduler", 461 Plugins: defaults.PluginsV1, 462 PluginConfig: defaults.PluginConfigsV1, 463 }, 464 }, 465 }, 466 }, 467 { 468 name: "overridden master", 469 options: &Options{ 470 ComponentConfig: func() *kubeschedulerconfig.KubeSchedulerConfiguration { 471 cfg, _ := latest.Default() 472 cfg.ClientConnection.Kubeconfig = flagKubeconfig 473 return cfg 474 }(), 475 Master: insecureserver.URL, 476 SecureServing: (&apiserveroptions.SecureServingOptions{ 477 ServerCert: apiserveroptions.GeneratableKeyCert{ 478 CertDirectory: "/a/b/c", 479 PairName: "kube-scheduler", 480 }, 481 HTTP2MaxStreamsPerConnection: 47, 482 }).WithLoopback(), 483 Authentication: &apiserveroptions.DelegatingAuthenticationOptions{ 484 CacheTTL: 10 * time.Second, 485 RequestHeader: apiserveroptions.RequestHeaderAuthenticationOptions{ 486 UsernameHeaders: []string{"x-remote-user"}, 487 GroupHeaders: []string{"x-remote-group"}, 488 ExtraHeaderPrefixes: []string{"x-remote-extra-"}, 489 }, 490 RemoteKubeConfigFileOptional: true, 491 }, 492 Authorization: &apiserveroptions.DelegatingAuthorizationOptions{ 493 AllowCacheTTL: 10 * time.Second, 494 DenyCacheTTL: 10 * time.Second, 495 RemoteKubeConfigFileOptional: true, 496 AlwaysAllowPaths: []string{"/healthz", "/readyz", "/livez"}, // note: this does not match /healthz/ or /healthz/* 497 AlwaysAllowGroups: []string{"system:masters"}, 498 }, 499 Logs: logs.NewOptions(), 500 }, 501 expectedConfig: kubeschedulerconfig.KubeSchedulerConfiguration{ 502 TypeMeta: metav1.TypeMeta{ 503 APIVersion: v1.SchemeGroupVersion.String(), 504 }, 505 Parallelism: 16, 506 DelayCacheUntilActive: false, 507 DebuggingConfiguration: componentbaseconfig.DebuggingConfiguration{ 508 EnableProfiling: true, 509 EnableContentionProfiling: true, 510 }, 511 LeaderElection: componentbaseconfig.LeaderElectionConfiguration{ 512 LeaderElect: true, 513 LeaseDuration: metav1.Duration{Duration: 15 * time.Second}, 514 RenewDeadline: metav1.Duration{Duration: 10 * time.Second}, 515 RetryPeriod: metav1.Duration{Duration: 2 * time.Second}, 516 ResourceLock: "leases", 517 ResourceNamespace: "kube-system", 518 ResourceName: "kube-scheduler", 519 }, 520 ClientConnection: componentbaseconfig.ClientConnectionConfiguration{ 521 Kubeconfig: flagKubeconfig, 522 QPS: 50, 523 Burst: 100, 524 ContentType: "application/vnd.kubernetes.protobuf", 525 }, 526 PercentageOfNodesToScore: defaultPercentageOfNodesToScore, 527 PodInitialBackoffSeconds: defaultPodInitialBackoffSeconds, 528 PodMaxBackoffSeconds: defaultPodMaxBackoffSeconds, 529 Profiles: []kubeschedulerconfig.KubeSchedulerProfile{ 530 { 531 SchedulerName: "default-scheduler", 532 Plugins: defaults.PluginsV1, 533 PluginConfig: defaults.PluginConfigsV1, 534 }, 535 }, 536 }, 537 expectedUsername: "none, http", 538 }, 539 { 540 name: "plugin config", 541 options: &Options{ 542 ConfigFile: pluginConfigFile, 543 Logs: logs.NewOptions(), 544 }, 545 expectedUsername: "config", 546 expectedConfig: kubeschedulerconfig.KubeSchedulerConfiguration{ 547 TypeMeta: metav1.TypeMeta{ 548 APIVersion: v1.SchemeGroupVersion.String(), 549 }, 550 Parallelism: 16, 551 DelayCacheUntilActive: false, 552 DebuggingConfiguration: componentbaseconfig.DebuggingConfiguration{ 553 EnableProfiling: true, 554 EnableContentionProfiling: true, 555 }, 556 LeaderElection: componentbaseconfig.LeaderElectionConfiguration{ 557 LeaderElect: true, 558 LeaseDuration: metav1.Duration{Duration: 15 * time.Second}, 559 RenewDeadline: metav1.Duration{Duration: 10 * time.Second}, 560 RetryPeriod: metav1.Duration{Duration: 2 * time.Second}, 561 ResourceLock: "leases", 562 ResourceNamespace: "kube-system", 563 ResourceName: "kube-scheduler", 564 }, 565 ClientConnection: componentbaseconfig.ClientConnectionConfiguration{ 566 Kubeconfig: configKubeconfig, 567 QPS: 50, 568 Burst: 100, 569 ContentType: "application/vnd.kubernetes.protobuf", 570 }, 571 PercentageOfNodesToScore: defaultPercentageOfNodesToScore, 572 PodInitialBackoffSeconds: defaultPodInitialBackoffSeconds, 573 PodMaxBackoffSeconds: defaultPodMaxBackoffSeconds, 574 Profiles: []kubeschedulerconfig.KubeSchedulerProfile{ 575 { 576 SchedulerName: "default-scheduler", 577 Plugins: &kubeschedulerconfig.Plugins{ 578 PreEnqueue: kubeschedulerconfig.PluginSet{ 579 Enabled: []kubeschedulerconfig.Plugin{ 580 {Name: "foo"}, 581 }, 582 }, 583 Reserve: kubeschedulerconfig.PluginSet{ 584 Enabled: []kubeschedulerconfig.Plugin{ 585 {Name: "foo"}, 586 {Name: "bar"}, 587 }, 588 Disabled: []kubeschedulerconfig.Plugin{ 589 {Name: names.VolumeBinding}, 590 }, 591 }, 592 PreBind: kubeschedulerconfig.PluginSet{ 593 Enabled: []kubeschedulerconfig.Plugin{ 594 {Name: "foo"}, 595 }, 596 Disabled: []kubeschedulerconfig.Plugin{ 597 {Name: names.VolumeBinding}, 598 }, 599 }, 600 MultiPoint: defaults.PluginsV1.MultiPoint, 601 }, 602 PluginConfig: []kubeschedulerconfig.PluginConfig{ 603 { 604 Name: "InterPodAffinity", 605 Args: &kubeschedulerconfig.InterPodAffinityArgs{ 606 HardPodAffinityWeight: 2, 607 }, 608 }, 609 { 610 Name: "foo", 611 Args: &runtime.Unknown{ 612 Raw: []byte(`{"bar":"baz"}`), 613 ContentType: "application/json", 614 }, 615 }, 616 { 617 Name: "DefaultPreemption", 618 Args: &kubeschedulerconfig.DefaultPreemptionArgs{ 619 MinCandidateNodesPercentage: 10, 620 MinCandidateNodesAbsolute: 100, 621 }, 622 }, 623 { 624 Name: "NodeAffinity", 625 Args: &kubeschedulerconfig.NodeAffinityArgs{}, 626 }, 627 { 628 Name: "NodeResourcesBalancedAllocation", 629 Args: &kubeschedulerconfig.NodeResourcesBalancedAllocationArgs{ 630 Resources: []kubeschedulerconfig.ResourceSpec{{Name: "cpu", Weight: 1}, {Name: "memory", Weight: 1}}, 631 }, 632 }, 633 { 634 Name: "NodeResourcesFit", 635 Args: &kubeschedulerconfig.NodeResourcesFitArgs{ 636 ScoringStrategy: &kubeschedulerconfig.ScoringStrategy{ 637 Type: kubeschedulerconfig.LeastAllocated, 638 Resources: []kubeschedulerconfig.ResourceSpec{{Name: "cpu", Weight: 1}, {Name: "memory", Weight: 1}}, 639 }, 640 }, 641 }, 642 { 643 Name: "PodTopologySpread", 644 Args: &kubeschedulerconfig.PodTopologySpreadArgs{ 645 DefaultingType: kubeschedulerconfig.SystemDefaulting, 646 }, 647 }, 648 { 649 Name: "VolumeBinding", 650 Args: &kubeschedulerconfig.VolumeBindingArgs{ 651 BindTimeoutSeconds: 600, 652 }, 653 }, 654 }, 655 }, 656 }, 657 }, 658 }, 659 { 660 name: "multiple profiles", 661 options: &Options{ 662 ConfigFile: multiProfilesConfig, 663 Logs: logs.NewOptions(), 664 }, 665 expectedUsername: "config", 666 expectedConfig: kubeschedulerconfig.KubeSchedulerConfiguration{ 667 TypeMeta: metav1.TypeMeta{ 668 APIVersion: v1.SchemeGroupVersion.String(), 669 }, 670 Parallelism: 16, 671 DelayCacheUntilActive: false, 672 DebuggingConfiguration: componentbaseconfig.DebuggingConfiguration{ 673 EnableProfiling: true, 674 EnableContentionProfiling: true, 675 }, 676 LeaderElection: componentbaseconfig.LeaderElectionConfiguration{ 677 LeaderElect: true, 678 LeaseDuration: metav1.Duration{Duration: 15 * time.Second}, 679 RenewDeadline: metav1.Duration{Duration: 10 * time.Second}, 680 RetryPeriod: metav1.Duration{Duration: 2 * time.Second}, 681 ResourceLock: "leases", 682 ResourceNamespace: "kube-system", 683 ResourceName: "kube-scheduler", 684 }, 685 ClientConnection: componentbaseconfig.ClientConnectionConfiguration{ 686 Kubeconfig: configKubeconfig, 687 QPS: 50, 688 Burst: 100, 689 ContentType: "application/vnd.kubernetes.protobuf", 690 }, 691 PercentageOfNodesToScore: defaultPercentageOfNodesToScore, 692 PodInitialBackoffSeconds: defaultPodInitialBackoffSeconds, 693 PodMaxBackoffSeconds: defaultPodMaxBackoffSeconds, 694 Profiles: []kubeschedulerconfig.KubeSchedulerProfile{ 695 { 696 SchedulerName: "foo-profile", 697 Plugins: &kubeschedulerconfig.Plugins{ 698 MultiPoint: defaults.PluginsV1.MultiPoint, 699 Reserve: kubeschedulerconfig.PluginSet{ 700 Enabled: []kubeschedulerconfig.Plugin{ 701 {Name: "foo"}, 702 {Name: names.VolumeBinding}, 703 }, 704 Disabled: []kubeschedulerconfig.Plugin{ 705 {Name: names.VolumeBinding}, 706 }, 707 }, 708 }, 709 PluginConfig: defaults.PluginConfigsV1, 710 }, 711 { 712 SchedulerName: "bar-profile", 713 Plugins: &kubeschedulerconfig.Plugins{ 714 MultiPoint: defaults.PluginsV1.MultiPoint, 715 PreBind: kubeschedulerconfig.PluginSet{ 716 Disabled: []kubeschedulerconfig.Plugin{ 717 {Name: names.VolumeBinding}, 718 }, 719 }, 720 }, 721 PluginConfig: []kubeschedulerconfig.PluginConfig{ 722 { 723 Name: "foo", 724 }, 725 { 726 Name: "DefaultPreemption", 727 Args: &kubeschedulerconfig.DefaultPreemptionArgs{ 728 MinCandidateNodesPercentage: 10, 729 MinCandidateNodesAbsolute: 100, 730 }, 731 }, 732 { 733 Name: "InterPodAffinity", 734 Args: &kubeschedulerconfig.InterPodAffinityArgs{ 735 HardPodAffinityWeight: 1, 736 }, 737 }, 738 { 739 Name: "NodeAffinity", 740 Args: &kubeschedulerconfig.NodeAffinityArgs{}, 741 }, 742 { 743 Name: "NodeResourcesBalancedAllocation", 744 Args: &kubeschedulerconfig.NodeResourcesBalancedAllocationArgs{ 745 Resources: []kubeschedulerconfig.ResourceSpec{{Name: "cpu", Weight: 1}, {Name: "memory", Weight: 1}}, 746 }, 747 }, 748 { 749 Name: "NodeResourcesFit", 750 Args: &kubeschedulerconfig.NodeResourcesFitArgs{ 751 ScoringStrategy: &kubeschedulerconfig.ScoringStrategy{ 752 Type: kubeschedulerconfig.LeastAllocated, 753 Resources: []kubeschedulerconfig.ResourceSpec{{Name: "cpu", Weight: 1}, {Name: "memory", Weight: 1}}, 754 }, 755 }, 756 }, 757 { 758 Name: "PodTopologySpread", 759 Args: &kubeschedulerconfig.PodTopologySpreadArgs{ 760 DefaultingType: kubeschedulerconfig.SystemDefaulting, 761 }, 762 }, 763 { 764 Name: "VolumeBinding", 765 Args: &kubeschedulerconfig.VolumeBindingArgs{ 766 BindTimeoutSeconds: 600, 767 }, 768 }, 769 }, 770 }, 771 }, 772 }, 773 }, 774 { 775 name: "no config", 776 options: &Options{ 777 Logs: logs.NewOptions(), 778 }, 779 expectedError: "no configuration has been provided", 780 }, 781 { 782 name: "unknown field", 783 options: &Options{ 784 ConfigFile: unknownFieldConfig, 785 Logs: logs.NewOptions(), 786 }, 787 expectedError: `unknown field "foo"`, 788 checkErrFn: runtime.IsStrictDecodingError, 789 }, 790 { 791 name: "duplicate fields", 792 options: &Options{ 793 ConfigFile: duplicateFieldConfig, 794 Logs: logs.NewOptions(), 795 }, 796 expectedError: `key "leaderElect" already set`, 797 checkErrFn: runtime.IsStrictDecodingError, 798 }, 799 { 800 name: "high throughput profile", 801 options: &Options{ 802 ConfigFile: highThroughputProfileConfig, 803 Logs: logs.NewOptions(), 804 }, 805 expectedUsername: "config", 806 expectedConfig: kubeschedulerconfig.KubeSchedulerConfiguration{ 807 TypeMeta: metav1.TypeMeta{ 808 APIVersion: v1.SchemeGroupVersion.String(), 809 }, 810 Parallelism: 16, 811 DelayCacheUntilActive: false, 812 DebuggingConfiguration: componentbaseconfig.DebuggingConfiguration{ 813 EnableProfiling: true, 814 EnableContentionProfiling: true, 815 }, 816 LeaderElection: componentbaseconfig.LeaderElectionConfiguration{ 817 LeaderElect: true, 818 LeaseDuration: metav1.Duration{Duration: 15 * time.Second}, 819 RenewDeadline: metav1.Duration{Duration: 10 * time.Second}, 820 RetryPeriod: metav1.Duration{Duration: 2 * time.Second}, 821 ResourceLock: "leases", 822 ResourceNamespace: "kube-system", 823 ResourceName: "kube-scheduler", 824 }, 825 ClientConnection: componentbaseconfig.ClientConnectionConfiguration{ 826 Kubeconfig: configKubeconfig, 827 QPS: 50, 828 Burst: 100, 829 ContentType: "application/vnd.kubernetes.protobuf", 830 }, 831 PercentageOfNodesToScore: defaultPercentageOfNodesToScore, 832 PodInitialBackoffSeconds: defaultPodInitialBackoffSeconds, 833 PodMaxBackoffSeconds: defaultPodMaxBackoffSeconds, 834 Profiles: []kubeschedulerconfig.KubeSchedulerProfile{ 835 { 836 SchedulerName: "high-throughput-profile", 837 Plugins: &kubeschedulerconfig.Plugins{ 838 QueueSort: defaults.PluginsV1.QueueSort, 839 PreFilter: defaults.PluginsV1.PreFilter, 840 Filter: defaults.PluginsV1.Filter, 841 PostFilter: defaults.PluginsV1.PostFilter, 842 PreScore: kubeschedulerconfig.PluginSet{ 843 Enabled: []kubeschedulerconfig.Plugin{ 844 {Name: "InterPodAffinity"}, 845 }, 846 }, 847 Score: defaults.PluginsV1.Score, 848 Bind: defaults.PluginsV1.Bind, 849 PreBind: defaults.PluginsV1.PreBind, 850 Reserve: defaults.PluginsV1.Reserve, 851 MultiPoint: defaults.PluginsV1.MultiPoint, 852 }, 853 PluginConfig: []kubeschedulerconfig.PluginConfig{ 854 { 855 Name: "InterPodAffinity", 856 Args: &kubeschedulerconfig.InterPodAffinityArgs{ 857 HardPodAffinityWeight: 1, 858 IgnorePreferredTermsOfExistingPods: true, 859 }, 860 }, 861 { 862 Name: "DefaultPreemption", 863 Args: &kubeschedulerconfig.DefaultPreemptionArgs{ 864 MinCandidateNodesPercentage: 10, 865 MinCandidateNodesAbsolute: 100, 866 }, 867 }, 868 { 869 Name: "NodeAffinity", 870 Args: &kubeschedulerconfig.NodeAffinityArgs{}, 871 }, 872 { 873 Name: "NodeResourcesBalancedAllocation", 874 Args: &kubeschedulerconfig.NodeResourcesBalancedAllocationArgs{ 875 Resources: []kubeschedulerconfig.ResourceSpec{{Name: "cpu", Weight: 1}, {Name: "memory", Weight: 1}}, 876 }, 877 }, 878 { 879 Name: "NodeResourcesFit", 880 Args: &kubeschedulerconfig.NodeResourcesFitArgs{ 881 ScoringStrategy: &kubeschedulerconfig.ScoringStrategy{ 882 Type: kubeschedulerconfig.LeastAllocated, 883 Resources: []kubeschedulerconfig.ResourceSpec{{Name: "cpu", Weight: 1}, {Name: "memory", Weight: 1}}, 884 }, 885 }, 886 }, 887 { 888 Name: "PodTopologySpread", 889 Args: &kubeschedulerconfig.PodTopologySpreadArgs{ 890 DefaultingType: kubeschedulerconfig.SystemDefaulting, 891 }, 892 }, 893 { 894 Name: "VolumeBinding", 895 Args: &kubeschedulerconfig.VolumeBindingArgs{ 896 BindTimeoutSeconds: 600, 897 }, 898 }, 899 }, 900 }, 901 }, 902 }, 903 }, 904 } 905 906 for _, tc := range testcases { 907 t.Run(tc.name, func(t *testing.T) { 908 if tc.options.ComponentConfig == nil { 909 if cfg, err := latest.Default(); err != nil { 910 t.Fatal(err) 911 } else { 912 tc.options.ComponentConfig = cfg 913 } 914 } 915 // create the config 916 _, ctx := ktesting.NewTestContext(t) 917 config, err := tc.options.Config(ctx) 918 919 // handle errors 920 if err != nil { 921 if tc.expectedError != "" || tc.checkErrFn != nil { 922 if tc.expectedError != "" { 923 assert.Contains(t, err.Error(), tc.expectedError) 924 } 925 if tc.checkErrFn != nil { 926 assert.True(t, tc.checkErrFn(err), "got error: %v", err) 927 } 928 return 929 } 930 t.Errorf("unexpected error creating config: %v", err) 931 return 932 } 933 934 if _, err := encodeConfig(&config.ComponentConfig); err != nil { 935 t.Errorf("unexpected error in encodeConfig: %v", err) 936 } 937 938 if diff := cmp.Diff(tc.expectedConfig, config.ComponentConfig); diff != "" { 939 t.Errorf("incorrect config (-want,+got):\n%s", diff) 940 } 941 942 // ensure we have a client 943 if config.Client == nil { 944 t.Error("unexpected nil client") 945 return 946 } 947 948 // test the client talks to the endpoint we expect with the credentials we expect 949 username = "" 950 _, err = config.Client.Discovery().RESTClient().Get().AbsPath("/").DoRaw(context.TODO()) 951 if err != nil { 952 t.Error(err) 953 return 954 } 955 if username != tc.expectedUsername { 956 t.Errorf("expected server call with user %q, got %q", tc.expectedUsername, username) 957 } 958 }) 959 } 960 }