...

Source file src/k8s.io/kubernetes/pkg/scheduler/apis/config/validation/validation.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  	"fmt"
    21  	"reflect"
    22  
    23  	v1 "k8s.io/api/core/v1"
    24  	apiequality "k8s.io/apimachinery/pkg/api/equality"
    25  	"k8s.io/apimachinery/pkg/runtime"
    26  	utilerrors "k8s.io/apimachinery/pkg/util/errors"
    27  	"k8s.io/apimachinery/pkg/util/sets"
    28  	"k8s.io/apimachinery/pkg/util/validation"
    29  	"k8s.io/apimachinery/pkg/util/validation/field"
    30  	componentbasevalidation "k8s.io/component-base/config/validation"
    31  	v1helper "k8s.io/kubernetes/pkg/apis/core/v1/helper"
    32  	"k8s.io/kubernetes/pkg/scheduler/apis/config"
    33  )
    34  
    35  // ValidateKubeSchedulerConfiguration ensures validation of the KubeSchedulerConfiguration struct
    36  func ValidateKubeSchedulerConfiguration(cc *config.KubeSchedulerConfiguration) utilerrors.Aggregate {
    37  	var errs []error
    38  	errs = append(errs, componentbasevalidation.ValidateClientConnectionConfiguration(&cc.ClientConnection, field.NewPath("clientConnection")).ToAggregate())
    39  	errs = append(errs, componentbasevalidation.ValidateLeaderElectionConfiguration(&cc.LeaderElection, field.NewPath("leaderElection")).ToAggregate())
    40  
    41  	// TODO: This can be removed when ResourceLock is not available
    42  	// Only ResourceLock values with leases are allowed
    43  	if cc.LeaderElection.LeaderElect && cc.LeaderElection.ResourceLock != "leases" {
    44  		leaderElectionPath := field.NewPath("leaderElection")
    45  		errs = append(errs, field.Invalid(leaderElectionPath.Child("resourceLock"), cc.LeaderElection.ResourceLock, `resourceLock value must be "leases"`))
    46  	}
    47  
    48  	profilesPath := field.NewPath("profiles")
    49  	if cc.Parallelism <= 0 {
    50  		errs = append(errs, field.Invalid(field.NewPath("parallelism"), cc.Parallelism, "should be an integer value greater than zero"))
    51  	}
    52  
    53  	if len(cc.Profiles) == 0 {
    54  		errs = append(errs, field.Required(profilesPath, ""))
    55  	} else {
    56  		existingProfiles := make(map[string]int, len(cc.Profiles))
    57  		for i := range cc.Profiles {
    58  			profile := &cc.Profiles[i]
    59  			path := profilesPath.Index(i)
    60  			errs = append(errs, validateKubeSchedulerProfile(path, cc.APIVersion, profile)...)
    61  			if idx, ok := existingProfiles[profile.SchedulerName]; ok {
    62  				errs = append(errs, field.Duplicate(path.Child("schedulerName"), profilesPath.Index(idx).Child("schedulerName")))
    63  			}
    64  			existingProfiles[profile.SchedulerName] = i
    65  		}
    66  		errs = append(errs, validateCommonQueueSort(profilesPath, cc.Profiles)...)
    67  	}
    68  
    69  	errs = append(errs, validatePercentageOfNodesToScore(field.NewPath("percentageOfNodesToScore"), cc.PercentageOfNodesToScore))
    70  
    71  	if cc.PodInitialBackoffSeconds <= 0 {
    72  		errs = append(errs, field.Invalid(field.NewPath("podInitialBackoffSeconds"),
    73  			cc.PodInitialBackoffSeconds, "must be greater than 0"))
    74  	}
    75  	if cc.PodMaxBackoffSeconds < cc.PodInitialBackoffSeconds {
    76  		errs = append(errs, field.Invalid(field.NewPath("podMaxBackoffSeconds"),
    77  			cc.PodMaxBackoffSeconds, "must be greater than or equal to PodInitialBackoffSeconds"))
    78  	}
    79  
    80  	errs = append(errs, validateExtenders(field.NewPath("extenders"), cc.Extenders)...)
    81  	return utilerrors.Flatten(utilerrors.NewAggregate(errs))
    82  }
    83  
    84  func validatePercentageOfNodesToScore(path *field.Path, percentageOfNodesToScore *int32) error {
    85  	if percentageOfNodesToScore != nil {
    86  		if *percentageOfNodesToScore < 0 || *percentageOfNodesToScore > 100 {
    87  			return field.Invalid(path, *percentageOfNodesToScore, "not in valid range [0-100]")
    88  		}
    89  	}
    90  	return nil
    91  }
    92  
    93  type invalidPlugins struct {
    94  	schemeGroupVersion string
    95  	plugins            []string
    96  }
    97  
    98  // invalidPluginsByVersion maintains a list of removed/deprecated plugins in each version.
    99  // Remember to add an entry to that list when creating a new component config
   100  // version (even if the list of invalid plugins is empty).
   101  var invalidPluginsByVersion = []invalidPlugins{
   102  	{
   103  		schemeGroupVersion: v1.SchemeGroupVersion.String(),
   104  		plugins:            []string{},
   105  	},
   106  }
   107  
   108  // isPluginInvalid checks if a given plugin was removed/deprecated in the given component
   109  // config version or earlier.
   110  func isPluginInvalid(apiVersion string, name string) (bool, string) {
   111  	for _, dp := range invalidPluginsByVersion {
   112  		for _, plugin := range dp.plugins {
   113  			if name == plugin {
   114  				return true, dp.schemeGroupVersion
   115  			}
   116  		}
   117  		if apiVersion == dp.schemeGroupVersion {
   118  			break
   119  		}
   120  	}
   121  	return false, ""
   122  }
   123  
   124  func validatePluginSetForInvalidPlugins(path *field.Path, apiVersion string, ps config.PluginSet) []error {
   125  	var errs []error
   126  	for i, plugin := range ps.Enabled {
   127  		if invalid, invalidVersion := isPluginInvalid(apiVersion, plugin.Name); invalid {
   128  			errs = append(errs, field.Invalid(path.Child("enabled").Index(i), plugin.Name, fmt.Sprintf("was invalid in version %q (KubeSchedulerConfiguration is version %q)", invalidVersion, apiVersion)))
   129  		}
   130  	}
   131  	return errs
   132  }
   133  
   134  func validateKubeSchedulerProfile(path *field.Path, apiVersion string, profile *config.KubeSchedulerProfile) []error {
   135  	var errs []error
   136  	if len(profile.SchedulerName) == 0 {
   137  		errs = append(errs, field.Required(path.Child("schedulerName"), ""))
   138  	}
   139  	errs = append(errs, validatePercentageOfNodesToScore(path.Child("percentageOfNodesToScore"), profile.PercentageOfNodesToScore))
   140  	errs = append(errs, validatePluginConfig(path, apiVersion, profile)...)
   141  	return errs
   142  }
   143  
   144  func validatePluginConfig(path *field.Path, apiVersion string, profile *config.KubeSchedulerProfile) []error {
   145  	var errs []error
   146  	m := map[string]interface{}{
   147  		"DefaultPreemption":               ValidateDefaultPreemptionArgs,
   148  		"InterPodAffinity":                ValidateInterPodAffinityArgs,
   149  		"NodeAffinity":                    ValidateNodeAffinityArgs,
   150  		"NodeResourcesBalancedAllocation": ValidateNodeResourcesBalancedAllocationArgs,
   151  		"NodeResourcesFitArgs":            ValidateNodeResourcesFitArgs,
   152  		"PodTopologySpread":               ValidatePodTopologySpreadArgs,
   153  		"VolumeBinding":                   ValidateVolumeBindingArgs,
   154  	}
   155  
   156  	if profile.Plugins != nil {
   157  		stagesToPluginSet := map[string]config.PluginSet{
   158  			"preEnqueue": profile.Plugins.PreEnqueue,
   159  			"queueSort":  profile.Plugins.QueueSort,
   160  			"preFilter":  profile.Plugins.PreFilter,
   161  			"filter":     profile.Plugins.Filter,
   162  			"postFilter": profile.Plugins.PostFilter,
   163  			"preScore":   profile.Plugins.PreScore,
   164  			"score":      profile.Plugins.Score,
   165  			"reserve":    profile.Plugins.Reserve,
   166  			"permit":     profile.Plugins.Permit,
   167  			"preBind":    profile.Plugins.PreBind,
   168  			"bind":       profile.Plugins.Bind,
   169  			"postBind":   profile.Plugins.PostBind,
   170  		}
   171  
   172  		pluginsPath := path.Child("plugins")
   173  		for s, p := range stagesToPluginSet {
   174  			errs = append(errs, validatePluginSetForInvalidPlugins(
   175  				pluginsPath.Child(s), apiVersion, p)...)
   176  		}
   177  	}
   178  
   179  	seenPluginConfig := sets.New[string]()
   180  
   181  	for i := range profile.PluginConfig {
   182  		pluginConfigPath := path.Child("pluginConfig").Index(i)
   183  		name := profile.PluginConfig[i].Name
   184  		args := profile.PluginConfig[i].Args
   185  		if seenPluginConfig.Has(name) {
   186  			errs = append(errs, field.Duplicate(pluginConfigPath, name))
   187  		} else {
   188  			seenPluginConfig.Insert(name)
   189  		}
   190  		if invalid, invalidVersion := isPluginInvalid(apiVersion, name); invalid {
   191  			errs = append(errs, field.Invalid(pluginConfigPath, name, fmt.Sprintf("was invalid in version %q (KubeSchedulerConfiguration is version %q)", invalidVersion, apiVersion)))
   192  		} else if validateFunc, ok := m[name]; ok {
   193  			// type mismatch, no need to validate the `args`.
   194  			if reflect.TypeOf(args) != reflect.ValueOf(validateFunc).Type().In(1) {
   195  				errs = append(errs, field.Invalid(pluginConfigPath.Child("args"), args, "has to match plugin args"))
   196  			} else {
   197  				in := []reflect.Value{reflect.ValueOf(pluginConfigPath.Child("args")), reflect.ValueOf(args)}
   198  				res := reflect.ValueOf(validateFunc).Call(in)
   199  				// It's possible that validation function return a Aggregate, just append here and it will be flattened at the end of CC validation.
   200  				if res[0].Interface() != nil {
   201  					errs = append(errs, res[0].Interface().(error))
   202  				}
   203  			}
   204  		}
   205  	}
   206  	return errs
   207  }
   208  
   209  func validateCommonQueueSort(path *field.Path, profiles []config.KubeSchedulerProfile) []error {
   210  	var errs []error
   211  	var canon config.PluginSet
   212  	var queueSortName string
   213  	var queueSortArgs runtime.Object
   214  	if profiles[0].Plugins != nil {
   215  		canon = profiles[0].Plugins.QueueSort
   216  		if len(profiles[0].Plugins.QueueSort.Enabled) != 0 {
   217  			queueSortName = profiles[0].Plugins.QueueSort.Enabled[0].Name
   218  		}
   219  		length := len(profiles[0].Plugins.QueueSort.Enabled)
   220  		if length > 1 {
   221  			errs = append(errs, field.Invalid(path.Index(0).Child("plugins", "queueSort", "Enabled"), length, "only one queue sort plugin can be enabled"))
   222  		}
   223  	}
   224  	for _, cfg := range profiles[0].PluginConfig {
   225  		if len(queueSortName) > 0 && cfg.Name == queueSortName {
   226  			queueSortArgs = cfg.Args
   227  		}
   228  	}
   229  	for i := 1; i < len(profiles); i++ {
   230  		var curr config.PluginSet
   231  		if profiles[i].Plugins != nil {
   232  			curr = profiles[i].Plugins.QueueSort
   233  		}
   234  		if !apiequality.Semantic.DeepEqual(canon, curr) {
   235  			errs = append(errs, field.Invalid(path.Index(i).Child("plugins", "queueSort"), curr, "queueSort must be the same for all profiles"))
   236  		}
   237  		for _, cfg := range profiles[i].PluginConfig {
   238  			if cfg.Name == queueSortName && !apiequality.Semantic.DeepEqual(queueSortArgs, cfg.Args) {
   239  				errs = append(errs, field.Invalid(path.Index(i).Child("pluginConfig", "args"), cfg.Args, "queueSort must be the same for all profiles"))
   240  			}
   241  		}
   242  	}
   243  	return errs
   244  }
   245  
   246  // validateExtenders validates the configured extenders for the Scheduler
   247  func validateExtenders(fldPath *field.Path, extenders []config.Extender) []error {
   248  	var errs []error
   249  	binders := 0
   250  	extenderManagedResources := sets.New[string]()
   251  	for i, extender := range extenders {
   252  		path := fldPath.Index(i)
   253  		if len(extender.PrioritizeVerb) > 0 && extender.Weight <= 0 {
   254  			errs = append(errs, field.Invalid(path.Child("weight"),
   255  				extender.Weight, "must have a positive weight applied to it"))
   256  		}
   257  		if extender.BindVerb != "" {
   258  			binders++
   259  		}
   260  		for j, resource := range extender.ManagedResources {
   261  			managedResourcesPath := path.Child("managedResources").Index(j)
   262  			validationErrors := validateExtendedResourceName(managedResourcesPath.Child("name"), v1.ResourceName(resource.Name))
   263  			errs = append(errs, validationErrors...)
   264  			if extenderManagedResources.Has(resource.Name) {
   265  				errs = append(errs, field.Invalid(managedResourcesPath.Child("name"),
   266  					resource.Name, "duplicate extender managed resource name"))
   267  			}
   268  			extenderManagedResources.Insert(resource.Name)
   269  		}
   270  	}
   271  	if binders > 1 {
   272  		errs = append(errs, field.Invalid(fldPath, fmt.Sprintf("found %d extenders implementing bind", binders), "only one extender can implement bind"))
   273  	}
   274  	return errs
   275  }
   276  
   277  // validateExtendedResourceName checks whether the specified name is a valid
   278  // extended resource name.
   279  func validateExtendedResourceName(path *field.Path, name v1.ResourceName) []error {
   280  	var validationErrors []error
   281  	for _, msg := range validation.IsQualifiedName(string(name)) {
   282  		validationErrors = append(validationErrors, field.Invalid(path, name, msg))
   283  	}
   284  	if len(validationErrors) != 0 {
   285  		return validationErrors
   286  	}
   287  	if !v1helper.IsExtendedResourceName(name) {
   288  		validationErrors = append(validationErrors, field.Invalid(path, string(name), "is an invalid extended resource name"))
   289  	}
   290  	return validationErrors
   291  }
   292  

View as plain text