...

Source file src/k8s.io/kubernetes/pkg/scheduler/apis/config/validation/validation_test.go

Documentation: k8s.io/kubernetes/pkg/scheduler/apis/config/validation

     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 validation
    18  
    19  import (
    20  	"testing"
    21  	"time"
    22  
    23  	"github.com/google/go-cmp/cmp"
    24  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    25  	"k8s.io/apimachinery/pkg/util/validation/field"
    26  	componentbaseconfig "k8s.io/component-base/config"
    27  	"k8s.io/kubernetes/pkg/scheduler/apis/config"
    28  	configv1 "k8s.io/kubernetes/pkg/scheduler/apis/config/v1"
    29  	"k8s.io/utils/ptr"
    30  )
    31  
    32  func TestValidateKubeSchedulerConfigurationV1(t *testing.T) {
    33  	podInitialBackoffSeconds := int64(1)
    34  	podMaxBackoffSeconds := int64(1)
    35  	validConfig := &config.KubeSchedulerConfiguration{
    36  		TypeMeta: metav1.TypeMeta{
    37  			APIVersion: configv1.SchemeGroupVersion.String(),
    38  		},
    39  		Parallelism: 8,
    40  		ClientConnection: componentbaseconfig.ClientConnectionConfiguration{
    41  			AcceptContentTypes: "application/json",
    42  			ContentType:        "application/json",
    43  			QPS:                10,
    44  			Burst:              10,
    45  		},
    46  		LeaderElection: componentbaseconfig.LeaderElectionConfiguration{
    47  			ResourceLock:      "leases",
    48  			LeaderElect:       true,
    49  			LeaseDuration:     metav1.Duration{Duration: 30 * time.Second},
    50  			RenewDeadline:     metav1.Duration{Duration: 15 * time.Second},
    51  			RetryPeriod:       metav1.Duration{Duration: 5 * time.Second},
    52  			ResourceNamespace: "name",
    53  			ResourceName:      "name",
    54  		},
    55  		PodInitialBackoffSeconds: podInitialBackoffSeconds,
    56  		PodMaxBackoffSeconds:     podMaxBackoffSeconds,
    57  		Profiles: []config.KubeSchedulerProfile{{
    58  			SchedulerName:            "me",
    59  			PercentageOfNodesToScore: ptr.To[int32](35),
    60  			Plugins: &config.Plugins{
    61  				QueueSort: config.PluginSet{
    62  					Enabled: []config.Plugin{{Name: "CustomSort"}},
    63  				},
    64  				Score: config.PluginSet{
    65  					Disabled: []config.Plugin{{Name: "*"}},
    66  				},
    67  			},
    68  			PluginConfig: []config.PluginConfig{{
    69  				Name: "DefaultPreemption",
    70  				Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 10, MinCandidateNodesAbsolute: 100},
    71  			}},
    72  		}, {
    73  			SchedulerName:            "other",
    74  			PercentageOfNodesToScore: ptr.To[int32](35),
    75  			Plugins: &config.Plugins{
    76  				QueueSort: config.PluginSet{
    77  					Enabled: []config.Plugin{{Name: "CustomSort"}},
    78  				},
    79  				Bind: config.PluginSet{
    80  					Enabled: []config.Plugin{{Name: "CustomBind"}},
    81  				},
    82  			},
    83  		}},
    84  		Extenders: []config.Extender{{
    85  			PrioritizeVerb: "prioritize",
    86  			Weight:         1,
    87  		}},
    88  	}
    89  
    90  	invalidParallelismValue := validConfig.DeepCopy()
    91  	invalidParallelismValue.Parallelism = 0
    92  
    93  	resourceNameNotSet := validConfig.DeepCopy()
    94  	resourceNameNotSet.LeaderElection.ResourceName = ""
    95  
    96  	resourceNamespaceNotSet := validConfig.DeepCopy()
    97  	resourceNamespaceNotSet.LeaderElection.ResourceNamespace = ""
    98  
    99  	resourceLockNotLeases := validConfig.DeepCopy()
   100  	resourceLockNotLeases.LeaderElection.ResourceLock = "configmap"
   101  
   102  	enableContentProfilingSetWithoutEnableProfiling := validConfig.DeepCopy()
   103  	enableContentProfilingSetWithoutEnableProfiling.EnableProfiling = false
   104  	enableContentProfilingSetWithoutEnableProfiling.EnableContentionProfiling = true
   105  
   106  	percentageOfNodesToScore101 := validConfig.DeepCopy()
   107  	percentageOfNodesToScore101.PercentageOfNodesToScore = ptr.To[int32](101)
   108  
   109  	percentageOfNodesToScoreNegative := validConfig.DeepCopy()
   110  	percentageOfNodesToScoreNegative.PercentageOfNodesToScore = ptr.To[int32](-1)
   111  
   112  	schedulerNameNotSet := validConfig.DeepCopy()
   113  	schedulerNameNotSet.Profiles[1].SchedulerName = ""
   114  
   115  	repeatedSchedulerName := validConfig.DeepCopy()
   116  	repeatedSchedulerName.Profiles[0].SchedulerName = "other"
   117  
   118  	profilePercentageOfNodesToScore101 := validConfig.DeepCopy()
   119  	profilePercentageOfNodesToScore101.Profiles[1].PercentageOfNodesToScore = ptr.To[int32](101)
   120  
   121  	profilePercentageOfNodesToScoreNegative := validConfig.DeepCopy()
   122  	profilePercentageOfNodesToScoreNegative.Profiles[1].PercentageOfNodesToScore = ptr.To[int32](-1)
   123  
   124  	differentQueueSort := validConfig.DeepCopy()
   125  	differentQueueSort.Profiles[1].Plugins.QueueSort.Enabled[0].Name = "AnotherSort"
   126  
   127  	oneEmptyQueueSort := validConfig.DeepCopy()
   128  	oneEmptyQueueSort.Profiles[0].Plugins = nil
   129  
   130  	extenderNegativeWeight := validConfig.DeepCopy()
   131  	extenderNegativeWeight.Extenders[0].Weight = -1
   132  
   133  	invalidNodePercentage := validConfig.DeepCopy()
   134  	invalidNodePercentage.Profiles[0].PluginConfig = []config.PluginConfig{{
   135  		Name: "DefaultPreemption",
   136  		Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 200, MinCandidateNodesAbsolute: 100},
   137  	}}
   138  
   139  	invalidPluginArgs := validConfig.DeepCopy()
   140  	invalidPluginArgs.Profiles[0].PluginConfig = []config.PluginConfig{{
   141  		Name: "DefaultPreemption",
   142  		Args: &config.InterPodAffinityArgs{},
   143  	}}
   144  
   145  	duplicatedPluginConfig := validConfig.DeepCopy()
   146  	duplicatedPluginConfig.Profiles[0].PluginConfig = []config.PluginConfig{{
   147  		Name: "config",
   148  	}, {
   149  		Name: "config",
   150  	}}
   151  
   152  	mismatchQueueSort := validConfig.DeepCopy()
   153  	mismatchQueueSort.Profiles = []config.KubeSchedulerProfile{{
   154  		SchedulerName: "me",
   155  		Plugins: &config.Plugins{
   156  			QueueSort: config.PluginSet{
   157  				Enabled: []config.Plugin{{Name: "PrioritySort"}},
   158  			},
   159  		},
   160  		PluginConfig: []config.PluginConfig{{
   161  			Name: "PrioritySort",
   162  		}},
   163  	}, {
   164  		SchedulerName: "other",
   165  		Plugins: &config.Plugins{
   166  			QueueSort: config.PluginSet{
   167  				Enabled: []config.Plugin{{Name: "CustomSort"}},
   168  			},
   169  		},
   170  		PluginConfig: []config.PluginConfig{{
   171  			Name: "CustomSort",
   172  		}},
   173  	}}
   174  
   175  	extenderDuplicateManagedResource := validConfig.DeepCopy()
   176  	extenderDuplicateManagedResource.Extenders[0].ManagedResources = []config.ExtenderManagedResource{
   177  		{Name: "example.com/foo", IgnoredByScheduler: false},
   178  		{Name: "example.com/foo", IgnoredByScheduler: false},
   179  	}
   180  
   181  	extenderDuplicateBind := validConfig.DeepCopy()
   182  	extenderDuplicateBind.Extenders[0].BindVerb = "foo"
   183  	extenderDuplicateBind.Extenders = append(extenderDuplicateBind.Extenders, config.Extender{
   184  		PrioritizeVerb: "prioritize",
   185  		BindVerb:       "bar",
   186  		Weight:         1,
   187  	})
   188  
   189  	validPlugins := validConfig.DeepCopy()
   190  	validPlugins.Profiles[0].Plugins.Score.Enabled = append(validPlugins.Profiles[0].Plugins.Score.Enabled, config.Plugin{Name: "PodTopologySpread", Weight: 2})
   191  
   192  	scenarios := map[string]struct {
   193  		config   *config.KubeSchedulerConfiguration
   194  		wantErrs field.ErrorList
   195  	}{
   196  		"good": {
   197  			config: validConfig,
   198  		},
   199  		"bad-parallelism-invalid-value": {
   200  			config: invalidParallelismValue,
   201  			wantErrs: field.ErrorList{
   202  				&field.Error{
   203  					Type:  field.ErrorTypeInvalid,
   204  					Field: "parallelism",
   205  				},
   206  			},
   207  		},
   208  		"bad-resource-name-not-set": {
   209  			config: resourceNameNotSet,
   210  			wantErrs: field.ErrorList{
   211  				&field.Error{
   212  					Type:  field.ErrorTypeInvalid,
   213  					Field: "leaderElection.resourceName",
   214  				},
   215  			},
   216  		},
   217  		"bad-resource-namespace-not-set": {
   218  			config: resourceNamespaceNotSet,
   219  			wantErrs: field.ErrorList{
   220  				&field.Error{
   221  					Type:  field.ErrorTypeInvalid,
   222  					Field: "leaderElection.resourceNamespace",
   223  				},
   224  			},
   225  		},
   226  		"bad-resource-lock-not-leases": {
   227  			config: resourceLockNotLeases,
   228  			wantErrs: field.ErrorList{
   229  				&field.Error{
   230  					Type:  field.ErrorTypeInvalid,
   231  					Field: "leaderElection.resourceLock",
   232  				},
   233  			},
   234  		},
   235  		"bad-percentage-of-nodes-to-score": {
   236  			config: percentageOfNodesToScore101,
   237  			wantErrs: field.ErrorList{
   238  				&field.Error{
   239  					Type:  field.ErrorTypeInvalid,
   240  					Field: "percentageOfNodesToScore",
   241  				},
   242  			},
   243  		},
   244  		"negative-percentage-of-nodes-to-score": {
   245  			config: percentageOfNodesToScoreNegative,
   246  			wantErrs: field.ErrorList{
   247  				&field.Error{
   248  					Type:  field.ErrorTypeInvalid,
   249  					Field: "percentageOfNodesToScore",
   250  				},
   251  			},
   252  		},
   253  		"scheduler-name-not-set": {
   254  			config: schedulerNameNotSet,
   255  			wantErrs: field.ErrorList{
   256  				&field.Error{
   257  					Type:  field.ErrorTypeRequired,
   258  					Field: "profiles[1].schedulerName",
   259  				},
   260  			},
   261  		},
   262  		"repeated-scheduler-name": {
   263  			config: repeatedSchedulerName,
   264  			wantErrs: field.ErrorList{
   265  				&field.Error{
   266  					Type:  field.ErrorTypeDuplicate,
   267  					Field: "profiles[1].schedulerName",
   268  				},
   269  			},
   270  		},
   271  		"greater-than-100-profile-percentage-of-nodes-to-score": {
   272  			config: profilePercentageOfNodesToScore101,
   273  			wantErrs: field.ErrorList{
   274  				&field.Error{
   275  					Type:  field.ErrorTypeInvalid,
   276  					Field: "profiles[1].percentageOfNodesToScore",
   277  				},
   278  			},
   279  		},
   280  		"negative-profile-percentage-of-nodes-to-score": {
   281  			config: profilePercentageOfNodesToScoreNegative,
   282  			wantErrs: field.ErrorList{
   283  				&field.Error{
   284  					Type:  field.ErrorTypeInvalid,
   285  					Field: "profiles[1].percentageOfNodesToScore",
   286  				},
   287  			},
   288  		},
   289  		"different-queue-sort": {
   290  			config: differentQueueSort,
   291  			wantErrs: field.ErrorList{
   292  				&field.Error{
   293  					Type:  field.ErrorTypeInvalid,
   294  					Field: "profiles[1].plugins.queueSort",
   295  				},
   296  			},
   297  		},
   298  		"one-empty-queue-sort": {
   299  			config: oneEmptyQueueSort,
   300  			wantErrs: field.ErrorList{
   301  				&field.Error{
   302  					Type:  field.ErrorTypeInvalid,
   303  					Field: "profiles[1].plugins.queueSort",
   304  				},
   305  			},
   306  		},
   307  		"extender-negative-weight": {
   308  			config: extenderNegativeWeight,
   309  			wantErrs: field.ErrorList{
   310  				&field.Error{
   311  					Type:  field.ErrorTypeInvalid,
   312  					Field: "extenders[0].weight",
   313  				},
   314  			},
   315  		},
   316  		"extender-duplicate-managed-resources": {
   317  			config: extenderDuplicateManagedResource,
   318  			wantErrs: field.ErrorList{
   319  				&field.Error{
   320  					Type:  field.ErrorTypeInvalid,
   321  					Field: "extenders[0].managedResources[1].name",
   322  				},
   323  			},
   324  		},
   325  		"extender-duplicate-bind": {
   326  			config: extenderDuplicateBind,
   327  			wantErrs: field.ErrorList{
   328  				&field.Error{
   329  					Type:  field.ErrorTypeInvalid,
   330  					Field: "extenders",
   331  				},
   332  			},
   333  		},
   334  		"invalid-node-percentage": {
   335  			config: invalidNodePercentage,
   336  			wantErrs: field.ErrorList{
   337  				&field.Error{
   338  					Type:  field.ErrorTypeInvalid,
   339  					Field: "profiles[0].pluginConfig[0].args.minCandidateNodesPercentage",
   340  				},
   341  			},
   342  		},
   343  		"invalid-plugin-args": {
   344  			config: invalidPluginArgs,
   345  			wantErrs: field.ErrorList{
   346  				&field.Error{
   347  					Type:  field.ErrorTypeInvalid,
   348  					Field: "profiles[0].pluginConfig[0].args",
   349  				},
   350  			},
   351  		},
   352  		"duplicated-plugin-config": {
   353  			config: duplicatedPluginConfig,
   354  			wantErrs: field.ErrorList{
   355  				&field.Error{
   356  					Type:  field.ErrorTypeDuplicate,
   357  					Field: "profiles[0].pluginConfig[1]",
   358  				},
   359  			},
   360  		},
   361  		"mismatch-queue-sort": {
   362  			config: mismatchQueueSort,
   363  			wantErrs: field.ErrorList{
   364  				&field.Error{
   365  					Type:  field.ErrorTypeInvalid,
   366  					Field: "profiles[1].plugins.queueSort",
   367  				},
   368  			},
   369  		},
   370  		"valid-plugins": {
   371  			config: validPlugins,
   372  		},
   373  	}
   374  
   375  	for name, scenario := range scenarios {
   376  		t.Run(name, func(t *testing.T) {
   377  			errs := ValidateKubeSchedulerConfiguration(scenario.config)
   378  			diff := cmp.Diff(scenario.wantErrs.ToAggregate(), errs, ignoreBadValueDetail)
   379  			if diff != "" {
   380  				t.Errorf("KubeSchedulerConfiguration returned err (-want,+got):\n%s", diff)
   381  			}
   382  		})
   383  	}
   384  }
   385  

View as plain text