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  }