k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/pkg/scheduler/apis/config/validation/validation.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 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 "AzureDiskLimits", 106 "CinderLimits", 107 "EBSLimits", 108 "GCEPDLimits", 109 }, 110 }, 111 } 112 113 // isPluginInvalid checks if a given plugin was removed/deprecated in the given component 114 // config version or earlier. 115 func isPluginInvalid(apiVersion string, name string) (bool, string) { 116 for _, dp := range invalidPluginsByVersion { 117 for _, plugin := range dp.plugins { 118 if name == plugin { 119 return true, dp.schemeGroupVersion 120 } 121 } 122 if apiVersion == dp.schemeGroupVersion { 123 break 124 } 125 } 126 return false, "" 127 } 128 129 func validatePluginSetForInvalidPlugins(path *field.Path, apiVersion string, ps config.PluginSet) []error { 130 var errs []error 131 for i, plugin := range ps.Enabled { 132 if invalid, invalidVersion := isPluginInvalid(apiVersion, plugin.Name); invalid { 133 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))) 134 } 135 } 136 return errs 137 } 138 139 func validateKubeSchedulerProfile(path *field.Path, apiVersion string, profile *config.KubeSchedulerProfile) []error { 140 var errs []error 141 if len(profile.SchedulerName) == 0 { 142 errs = append(errs, field.Required(path.Child("schedulerName"), "")) 143 } 144 errs = append(errs, validatePercentageOfNodesToScore(path.Child("percentageOfNodesToScore"), profile.PercentageOfNodesToScore)) 145 errs = append(errs, validatePluginConfig(path, apiVersion, profile)...) 146 return errs 147 } 148 149 func validatePluginConfig(path *field.Path, apiVersion string, profile *config.KubeSchedulerProfile) []error { 150 var errs []error 151 m := map[string]interface{}{ 152 "DefaultPreemption": ValidateDefaultPreemptionArgs, 153 "InterPodAffinity": ValidateInterPodAffinityArgs, 154 "NodeAffinity": ValidateNodeAffinityArgs, 155 "NodeResourcesBalancedAllocation": ValidateNodeResourcesBalancedAllocationArgs, 156 "NodeResourcesFitArgs": ValidateNodeResourcesFitArgs, 157 "PodTopologySpread": ValidatePodTopologySpreadArgs, 158 "VolumeBinding": ValidateVolumeBindingArgs, 159 } 160 161 if profile.Plugins != nil { 162 stagesToPluginSet := map[string]config.PluginSet{ 163 "preEnqueue": profile.Plugins.PreEnqueue, 164 "queueSort": profile.Plugins.QueueSort, 165 "preFilter": profile.Plugins.PreFilter, 166 "filter": profile.Plugins.Filter, 167 "postFilter": profile.Plugins.PostFilter, 168 "preScore": profile.Plugins.PreScore, 169 "score": profile.Plugins.Score, 170 "reserve": profile.Plugins.Reserve, 171 "permit": profile.Plugins.Permit, 172 "preBind": profile.Plugins.PreBind, 173 "bind": profile.Plugins.Bind, 174 "postBind": profile.Plugins.PostBind, 175 } 176 177 pluginsPath := path.Child("plugins") 178 for s, p := range stagesToPluginSet { 179 errs = append(errs, validatePluginSetForInvalidPlugins( 180 pluginsPath.Child(s), apiVersion, p)...) 181 } 182 } 183 184 seenPluginConfig := sets.New[string]() 185 186 for i := range profile.PluginConfig { 187 pluginConfigPath := path.Child("pluginConfig").Index(i) 188 name := profile.PluginConfig[i].Name 189 args := profile.PluginConfig[i].Args 190 if seenPluginConfig.Has(name) { 191 errs = append(errs, field.Duplicate(pluginConfigPath, name)) 192 } else { 193 seenPluginConfig.Insert(name) 194 } 195 if invalid, invalidVersion := isPluginInvalid(apiVersion, name); invalid { 196 errs = append(errs, field.Invalid(pluginConfigPath, name, fmt.Sprintf("was invalid in version %q (KubeSchedulerConfiguration is version %q)", invalidVersion, apiVersion))) 197 } else if validateFunc, ok := m[name]; ok { 198 // type mismatch, no need to validate the `args`. 199 if reflect.TypeOf(args) != reflect.ValueOf(validateFunc).Type().In(1) { 200 errs = append(errs, field.Invalid(pluginConfigPath.Child("args"), args, "has to match plugin args")) 201 } else { 202 in := []reflect.Value{reflect.ValueOf(pluginConfigPath.Child("args")), reflect.ValueOf(args)} 203 res := reflect.ValueOf(validateFunc).Call(in) 204 // It's possible that validation function return a Aggregate, just append here and it will be flattened at the end of CC validation. 205 if res[0].Interface() != nil { 206 errs = append(errs, res[0].Interface().(error)) 207 } 208 } 209 } 210 } 211 return errs 212 } 213 214 func validateCommonQueueSort(path *field.Path, profiles []config.KubeSchedulerProfile) []error { 215 var errs []error 216 var canon config.PluginSet 217 var queueSortName string 218 var queueSortArgs runtime.Object 219 if profiles[0].Plugins != nil { 220 canon = profiles[0].Plugins.QueueSort 221 if len(profiles[0].Plugins.QueueSort.Enabled) != 0 { 222 queueSortName = profiles[0].Plugins.QueueSort.Enabled[0].Name 223 } 224 length := len(profiles[0].Plugins.QueueSort.Enabled) 225 if length > 1 { 226 errs = append(errs, field.Invalid(path.Index(0).Child("plugins", "queueSort", "Enabled"), length, "only one queue sort plugin can be enabled")) 227 } 228 } 229 for _, cfg := range profiles[0].PluginConfig { 230 if len(queueSortName) > 0 && cfg.Name == queueSortName { 231 queueSortArgs = cfg.Args 232 } 233 } 234 for i := 1; i < len(profiles); i++ { 235 var curr config.PluginSet 236 if profiles[i].Plugins != nil { 237 curr = profiles[i].Plugins.QueueSort 238 } 239 if !apiequality.Semantic.DeepEqual(canon, curr) { 240 errs = append(errs, field.Invalid(path.Index(i).Child("plugins", "queueSort"), curr, "queueSort must be the same for all profiles")) 241 } 242 for _, cfg := range profiles[i].PluginConfig { 243 if cfg.Name == queueSortName && !apiequality.Semantic.DeepEqual(queueSortArgs, cfg.Args) { 244 errs = append(errs, field.Invalid(path.Index(i).Child("pluginConfig", "args"), cfg.Args, "queueSort must be the same for all profiles")) 245 } 246 } 247 } 248 return errs 249 } 250 251 // validateExtenders validates the configured extenders for the Scheduler 252 func validateExtenders(fldPath *field.Path, extenders []config.Extender) []error { 253 var errs []error 254 binders := 0 255 extenderManagedResources := sets.New[string]() 256 for i, extender := range extenders { 257 path := fldPath.Index(i) 258 if len(extender.PrioritizeVerb) > 0 && extender.Weight <= 0 { 259 errs = append(errs, field.Invalid(path.Child("weight"), 260 extender.Weight, "must have a positive weight applied to it")) 261 } 262 if extender.BindVerb != "" { 263 binders++ 264 } 265 for j, resource := range extender.ManagedResources { 266 managedResourcesPath := path.Child("managedResources").Index(j) 267 validationErrors := validateExtendedResourceName(managedResourcesPath.Child("name"), v1.ResourceName(resource.Name)) 268 errs = append(errs, validationErrors...) 269 if extenderManagedResources.Has(resource.Name) { 270 errs = append(errs, field.Invalid(managedResourcesPath.Child("name"), 271 resource.Name, "duplicate extender managed resource name")) 272 } 273 extenderManagedResources.Insert(resource.Name) 274 } 275 } 276 if binders > 1 { 277 errs = append(errs, field.Invalid(fldPath, fmt.Sprintf("found %d extenders implementing bind", binders), "only one extender can implement bind")) 278 } 279 return errs 280 } 281 282 // validateExtendedResourceName checks whether the specified name is a valid 283 // extended resource name. 284 func validateExtendedResourceName(path *field.Path, name v1.ResourceName) []error { 285 var validationErrors []error 286 for _, msg := range validation.IsQualifiedName(string(name)) { 287 validationErrors = append(validationErrors, field.Invalid(path, name, msg)) 288 } 289 if len(validationErrors) != 0 { 290 return validationErrors 291 } 292 if !v1helper.IsExtendedResourceName(name) { 293 validationErrors = append(validationErrors, field.Invalid(path, string(name), "is an invalid extended resource name")) 294 } 295 return validationErrors 296 }