1
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
49 tmpDir, err := os.MkdirTemp("", "scheduler-options")
50 if err != nil {
51 t.Fatal(err)
52 }
53 defer os.RemoveAll(tmpDir)
54
55
56 username := ""
57
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
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
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
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
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
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
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
275
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"},
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"},
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"},
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
916 _, ctx := ktesting.NewTestContext(t)
917 config, err := tc.options.Config(ctx)
918
919
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
943 if config.Client == nil {
944 t.Error("unexpected nil client")
945 return
946 }
947
948
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 }
961
View as plain text