...

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

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

     1  /*
     2  Copyright 2020 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  	"strings"
    22  
    23  	v1 "k8s.io/api/core/v1"
    24  	metav1validation "k8s.io/apimachinery/pkg/apis/meta/v1/validation"
    25  	"k8s.io/apimachinery/pkg/util/errors"
    26  	"k8s.io/apimachinery/pkg/util/sets"
    27  	"k8s.io/apimachinery/pkg/util/validation/field"
    28  	utilfeature "k8s.io/apiserver/pkg/util/feature"
    29  	"k8s.io/component-helpers/scheduling/corev1/nodeaffinity"
    30  	"k8s.io/kubernetes/pkg/features"
    31  	"k8s.io/kubernetes/pkg/scheduler/apis/config"
    32  )
    33  
    34  // supportedScoringStrategyTypes has to be a set of strings for use with field.Unsupported
    35  var supportedScoringStrategyTypes = sets.New(
    36  	string(config.LeastAllocated),
    37  	string(config.MostAllocated),
    38  	string(config.RequestedToCapacityRatio),
    39  )
    40  
    41  // ValidateDefaultPreemptionArgs validates that DefaultPreemptionArgs are correct.
    42  func ValidateDefaultPreemptionArgs(path *field.Path, args *config.DefaultPreemptionArgs) error {
    43  	var allErrs field.ErrorList
    44  	percentagePath := path.Child("minCandidateNodesPercentage")
    45  	absolutePath := path.Child("minCandidateNodesAbsolute")
    46  	if err := validateMinCandidateNodesPercentage(args.MinCandidateNodesPercentage, percentagePath); err != nil {
    47  		allErrs = append(allErrs, err)
    48  	}
    49  	if err := validateMinCandidateNodesAbsolute(args.MinCandidateNodesAbsolute, absolutePath); err != nil {
    50  		allErrs = append(allErrs, err)
    51  	}
    52  	if args.MinCandidateNodesPercentage == 0 && args.MinCandidateNodesAbsolute == 0 {
    53  		allErrs = append(allErrs,
    54  			field.Invalid(percentagePath, args.MinCandidateNodesPercentage, "cannot be zero at the same time as minCandidateNodesAbsolute"),
    55  			field.Invalid(absolutePath, args.MinCandidateNodesAbsolute, "cannot be zero at the same time as minCandidateNodesPercentage"))
    56  	}
    57  	return allErrs.ToAggregate()
    58  }
    59  
    60  // validateMinCandidateNodesPercentage validates that
    61  // minCandidateNodesPercentage is within the allowed range.
    62  func validateMinCandidateNodesPercentage(minCandidateNodesPercentage int32, p *field.Path) *field.Error {
    63  	if minCandidateNodesPercentage < 0 || minCandidateNodesPercentage > 100 {
    64  		return field.Invalid(p, minCandidateNodesPercentage, "not in valid range [0, 100]")
    65  	}
    66  	return nil
    67  }
    68  
    69  // validateMinCandidateNodesAbsolute validates that minCandidateNodesAbsolute
    70  // is within the allowed range.
    71  func validateMinCandidateNodesAbsolute(minCandidateNodesAbsolute int32, p *field.Path) *field.Error {
    72  	if minCandidateNodesAbsolute < 0 {
    73  		return field.Invalid(p, minCandidateNodesAbsolute, "not in valid range [0, inf)")
    74  	}
    75  	return nil
    76  }
    77  
    78  // ValidateInterPodAffinityArgs validates that InterPodAffinityArgs are correct.
    79  func ValidateInterPodAffinityArgs(path *field.Path, args *config.InterPodAffinityArgs) error {
    80  	return validateHardPodAffinityWeight(path.Child("hardPodAffinityWeight"), args.HardPodAffinityWeight)
    81  }
    82  
    83  // validateHardPodAffinityWeight validates that weight is within allowed range.
    84  func validateHardPodAffinityWeight(path *field.Path, w int32) error {
    85  	const (
    86  		minHardPodAffinityWeight = 0
    87  		maxHardPodAffinityWeight = 100
    88  	)
    89  
    90  	if w < minHardPodAffinityWeight || w > maxHardPodAffinityWeight {
    91  		msg := fmt.Sprintf("not in valid range [%d, %d]", minHardPodAffinityWeight, maxHardPodAffinityWeight)
    92  		return field.Invalid(path, w, msg)
    93  	}
    94  	return nil
    95  }
    96  
    97  // ValidatePodTopologySpreadArgs validates that PodTopologySpreadArgs are correct.
    98  // It replicates the validation from pkg/apis/core/validation.validateTopologySpreadConstraints
    99  // with an additional check for .labelSelector to be nil.
   100  func ValidatePodTopologySpreadArgs(path *field.Path, args *config.PodTopologySpreadArgs) error {
   101  	var allErrs field.ErrorList
   102  	if err := validateDefaultingType(path.Child("defaultingType"), args.DefaultingType, args.DefaultConstraints); err != nil {
   103  		allErrs = append(allErrs, err)
   104  	}
   105  
   106  	defaultConstraintsPath := path.Child("defaultConstraints")
   107  	for i, c := range args.DefaultConstraints {
   108  		p := defaultConstraintsPath.Index(i)
   109  		if c.MaxSkew <= 0 {
   110  			f := p.Child("maxSkew")
   111  			allErrs = append(allErrs, field.Invalid(f, c.MaxSkew, "not in valid range (0, inf)"))
   112  		}
   113  		allErrs = append(allErrs, validateTopologyKey(p.Child("topologyKey"), c.TopologyKey)...)
   114  		if err := validateWhenUnsatisfiable(p.Child("whenUnsatisfiable"), c.WhenUnsatisfiable); err != nil {
   115  			allErrs = append(allErrs, err)
   116  		}
   117  		if c.LabelSelector != nil {
   118  			f := field.Forbidden(p.Child("labelSelector"), "constraint must not define a selector, as they deduced for each pod")
   119  			allErrs = append(allErrs, f)
   120  		}
   121  		if err := validateConstraintNotRepeat(defaultConstraintsPath, args.DefaultConstraints, i); err != nil {
   122  			allErrs = append(allErrs, err)
   123  		}
   124  	}
   125  	if len(allErrs) == 0 {
   126  		return nil
   127  	}
   128  	return allErrs.ToAggregate()
   129  }
   130  
   131  func validateDefaultingType(p *field.Path, v config.PodTopologySpreadConstraintsDefaulting, constraints []v1.TopologySpreadConstraint) *field.Error {
   132  	if v != config.SystemDefaulting && v != config.ListDefaulting {
   133  		return field.NotSupported(p, v, []string{string(config.SystemDefaulting), string(config.ListDefaulting)})
   134  	}
   135  	if v == config.SystemDefaulting && len(constraints) > 0 {
   136  		return field.Invalid(p, v, "when .defaultConstraints are not empty")
   137  	}
   138  	return nil
   139  }
   140  
   141  func validateTopologyKey(p *field.Path, v string) field.ErrorList {
   142  	var allErrs field.ErrorList
   143  	if len(v) == 0 {
   144  		allErrs = append(allErrs, field.Required(p, "can not be empty"))
   145  	} else {
   146  		allErrs = append(allErrs, metav1validation.ValidateLabelName(v, p)...)
   147  	}
   148  	return allErrs
   149  }
   150  
   151  func validateWhenUnsatisfiable(p *field.Path, v v1.UnsatisfiableConstraintAction) *field.Error {
   152  	supportedScheduleActions := sets.New(string(v1.DoNotSchedule), string(v1.ScheduleAnyway))
   153  
   154  	if len(v) == 0 {
   155  		return field.Required(p, "can not be empty")
   156  	}
   157  	if !supportedScheduleActions.Has(string(v)) {
   158  		return field.NotSupported(p, v, sets.List(supportedScheduleActions))
   159  	}
   160  	return nil
   161  }
   162  
   163  func validateConstraintNotRepeat(path *field.Path, constraints []v1.TopologySpreadConstraint, idx int) *field.Error {
   164  	c := &constraints[idx]
   165  	for i := range constraints[:idx] {
   166  		other := &constraints[i]
   167  		if c.TopologyKey == other.TopologyKey && c.WhenUnsatisfiable == other.WhenUnsatisfiable {
   168  			return field.Duplicate(path.Index(idx), fmt.Sprintf("{%v, %v}", c.TopologyKey, c.WhenUnsatisfiable))
   169  		}
   170  	}
   171  	return nil
   172  }
   173  
   174  func validateFunctionShape(shape []config.UtilizationShapePoint, path *field.Path) field.ErrorList {
   175  	const (
   176  		minUtilization = 0
   177  		maxUtilization = 100
   178  		minScore       = 0
   179  		maxScore       = int32(config.MaxCustomPriorityScore)
   180  	)
   181  
   182  	var allErrs field.ErrorList
   183  
   184  	if len(shape) == 0 {
   185  		allErrs = append(allErrs, field.Required(path, "at least one point must be specified"))
   186  		return allErrs
   187  	}
   188  
   189  	for i := 1; i < len(shape); i++ {
   190  		if shape[i-1].Utilization >= shape[i].Utilization {
   191  			allErrs = append(allErrs, field.Invalid(path.Index(i).Child("utilization"), shape[i].Utilization, "utilization values must be sorted in increasing order"))
   192  			break
   193  		}
   194  	}
   195  
   196  	for i, point := range shape {
   197  		if point.Utilization < minUtilization || point.Utilization > maxUtilization {
   198  			msg := fmt.Sprintf("not in valid range [%d, %d]", minUtilization, maxUtilization)
   199  			allErrs = append(allErrs, field.Invalid(path.Index(i).Child("utilization"), point.Utilization, msg))
   200  		}
   201  
   202  		if point.Score < minScore || point.Score > maxScore {
   203  			msg := fmt.Sprintf("not in valid range [%d, %d]", minScore, maxScore)
   204  			allErrs = append(allErrs, field.Invalid(path.Index(i).Child("score"), point.Score, msg))
   205  		}
   206  	}
   207  
   208  	return allErrs
   209  }
   210  
   211  func validateResources(resources []config.ResourceSpec, p *field.Path) field.ErrorList {
   212  	var allErrs field.ErrorList
   213  	for i, resource := range resources {
   214  		if resource.Weight <= 0 || resource.Weight > 100 {
   215  			msg := fmt.Sprintf("resource weight of %v not in valid range (0, 100]", resource.Name)
   216  			allErrs = append(allErrs, field.Invalid(p.Index(i).Child("weight"), resource.Weight, msg))
   217  		}
   218  	}
   219  	return allErrs
   220  }
   221  
   222  // ValidateNodeResourcesBalancedAllocationArgs validates that NodeResourcesBalancedAllocationArgs are set correctly.
   223  func ValidateNodeResourcesBalancedAllocationArgs(path *field.Path, args *config.NodeResourcesBalancedAllocationArgs) error {
   224  	var allErrs field.ErrorList
   225  	seenResources := sets.New[string]()
   226  	for i, resource := range args.Resources {
   227  		if seenResources.Has(resource.Name) {
   228  			allErrs = append(allErrs, field.Duplicate(path.Child("resources").Index(i).Child("name"), resource.Name))
   229  		} else {
   230  			seenResources.Insert(resource.Name)
   231  		}
   232  		if resource.Weight != 1 {
   233  			allErrs = append(allErrs, field.Invalid(path.Child("resources").Index(i).Child("weight"), resource.Weight, "must be 1"))
   234  		}
   235  	}
   236  	return allErrs.ToAggregate()
   237  }
   238  
   239  // ValidateNodeAffinityArgs validates that NodeAffinityArgs are correct.
   240  func ValidateNodeAffinityArgs(path *field.Path, args *config.NodeAffinityArgs) error {
   241  	if args.AddedAffinity == nil {
   242  		return nil
   243  	}
   244  	affinity := args.AddedAffinity
   245  	var errs []error
   246  	if ns := affinity.RequiredDuringSchedulingIgnoredDuringExecution; ns != nil {
   247  		_, err := nodeaffinity.NewNodeSelector(ns, field.WithPath(path.Child("addedAffinity", "requiredDuringSchedulingIgnoredDuringExecution")))
   248  		if err != nil {
   249  			errs = append(errs, err)
   250  		}
   251  	}
   252  	// TODO: Add validation for requiredDuringSchedulingRequiredDuringExecution when it gets added to the API.
   253  	if terms := affinity.PreferredDuringSchedulingIgnoredDuringExecution; len(terms) != 0 {
   254  		_, err := nodeaffinity.NewPreferredSchedulingTerms(terms, field.WithPath(path.Child("addedAffinity", "preferredDuringSchedulingIgnoredDuringExecution")))
   255  		if err != nil {
   256  			errs = append(errs, err)
   257  		}
   258  	}
   259  	return errors.Flatten(errors.NewAggregate(errs))
   260  }
   261  
   262  // VolumeBindingArgsValidationOptions contains the different settings for validation.
   263  type VolumeBindingArgsValidationOptions struct {
   264  	AllowVolumeCapacityPriority bool
   265  }
   266  
   267  // ValidateVolumeBindingArgs validates that VolumeBindingArgs are set correctly.
   268  func ValidateVolumeBindingArgs(path *field.Path, args *config.VolumeBindingArgs) error {
   269  	return ValidateVolumeBindingArgsWithOptions(path, args, VolumeBindingArgsValidationOptions{
   270  		AllowVolumeCapacityPriority: utilfeature.DefaultFeatureGate.Enabled(features.VolumeCapacityPriority),
   271  	})
   272  }
   273  
   274  // ValidateVolumeBindingArgsWithOptions validates that VolumeBindingArgs and VolumeBindingArgsValidationOptions with scheduler features.
   275  func ValidateVolumeBindingArgsWithOptions(path *field.Path, args *config.VolumeBindingArgs, opts VolumeBindingArgsValidationOptions) error {
   276  	var allErrs field.ErrorList
   277  
   278  	if args.BindTimeoutSeconds < 0 {
   279  		allErrs = append(allErrs, field.Invalid(path.Child("bindTimeoutSeconds"), args.BindTimeoutSeconds, "invalid BindTimeoutSeconds, should not be a negative value"))
   280  	}
   281  
   282  	if opts.AllowVolumeCapacityPriority {
   283  		allErrs = append(allErrs, validateFunctionShape(args.Shape, path.Child("shape"))...)
   284  	} else if args.Shape != nil {
   285  		// When the feature is off, return an error if the config is not nil.
   286  		// This prevents unexpected configuration from taking effect when the
   287  		// feature turns on in the future.
   288  		allErrs = append(allErrs, field.Invalid(path.Child("shape"), args.Shape, "unexpected field `shape`, remove it or turn on the feature gate VolumeCapacityPriority"))
   289  	}
   290  	return allErrs.ToAggregate()
   291  }
   292  
   293  func ValidateNodeResourcesFitArgs(path *field.Path, args *config.NodeResourcesFitArgs) error {
   294  	var allErrs field.ErrorList
   295  	resPath := path.Child("ignoredResources")
   296  	for i, res := range args.IgnoredResources {
   297  		path := resPath.Index(i)
   298  		if errs := metav1validation.ValidateLabelName(res, path); len(errs) != 0 {
   299  			allErrs = append(allErrs, errs...)
   300  		}
   301  	}
   302  
   303  	groupPath := path.Child("ignoredResourceGroups")
   304  	for i, group := range args.IgnoredResourceGroups {
   305  		path := groupPath.Index(i)
   306  		if strings.Contains(group, "/") {
   307  			allErrs = append(allErrs, field.Invalid(path, group, "resource group name can't contain '/'"))
   308  		}
   309  		if errs := metav1validation.ValidateLabelName(group, path); len(errs) != 0 {
   310  			allErrs = append(allErrs, errs...)
   311  		}
   312  	}
   313  
   314  	strategyPath := path.Child("scoringStrategy")
   315  	if args.ScoringStrategy != nil {
   316  		if !supportedScoringStrategyTypes.Has(string(args.ScoringStrategy.Type)) {
   317  			allErrs = append(allErrs, field.NotSupported(strategyPath.Child("type"), args.ScoringStrategy.Type, sets.List(supportedScoringStrategyTypes)))
   318  		}
   319  		allErrs = append(allErrs, validateResources(args.ScoringStrategy.Resources, strategyPath.Child("resources"))...)
   320  		if args.ScoringStrategy.RequestedToCapacityRatio != nil {
   321  			allErrs = append(allErrs, validateFunctionShape(args.ScoringStrategy.RequestedToCapacityRatio.Shape, strategyPath.Child("shape"))...)
   322  		}
   323  	}
   324  
   325  	if len(allErrs) == 0 {
   326  		return nil
   327  	}
   328  	return allErrs.ToAggregate()
   329  }
   330  

View as plain text