k8s.io/kubernetes@v1.29.3/pkg/scheduler/apis/config/validation/validation_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 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 metricsBindAddrInvalid := validConfig.DeepCopy() 107 metricsBindAddrInvalid.MetricsBindAddress = "0.0.0.0:9090" 108 109 healthzBindAddrInvalid := validConfig.DeepCopy() 110 healthzBindAddrInvalid.HealthzBindAddress = "0.0.0.0:9090" 111 112 percentageOfNodesToScore101 := validConfig.DeepCopy() 113 percentageOfNodesToScore101.PercentageOfNodesToScore = ptr.To[int32](101) 114 115 percentageOfNodesToScoreNegative := validConfig.DeepCopy() 116 percentageOfNodesToScoreNegative.PercentageOfNodesToScore = ptr.To[int32](-1) 117 118 schedulerNameNotSet := validConfig.DeepCopy() 119 schedulerNameNotSet.Profiles[1].SchedulerName = "" 120 121 repeatedSchedulerName := validConfig.DeepCopy() 122 repeatedSchedulerName.Profiles[0].SchedulerName = "other" 123 124 profilePercentageOfNodesToScore101 := validConfig.DeepCopy() 125 profilePercentageOfNodesToScore101.Profiles[1].PercentageOfNodesToScore = ptr.To[int32](101) 126 127 profilePercentageOfNodesToScoreNegative := validConfig.DeepCopy() 128 profilePercentageOfNodesToScoreNegative.Profiles[1].PercentageOfNodesToScore = ptr.To[int32](-1) 129 130 differentQueueSort := validConfig.DeepCopy() 131 differentQueueSort.Profiles[1].Plugins.QueueSort.Enabled[0].Name = "AnotherSort" 132 133 oneEmptyQueueSort := validConfig.DeepCopy() 134 oneEmptyQueueSort.Profiles[0].Plugins = nil 135 136 extenderNegativeWeight := validConfig.DeepCopy() 137 extenderNegativeWeight.Extenders[0].Weight = -1 138 139 invalidNodePercentage := validConfig.DeepCopy() 140 invalidNodePercentage.Profiles[0].PluginConfig = []config.PluginConfig{{ 141 Name: "DefaultPreemption", 142 Args: &config.DefaultPreemptionArgs{MinCandidateNodesPercentage: 200, MinCandidateNodesAbsolute: 100}, 143 }} 144 145 invalidPluginArgs := validConfig.DeepCopy() 146 invalidPluginArgs.Profiles[0].PluginConfig = []config.PluginConfig{{ 147 Name: "DefaultPreemption", 148 Args: &config.InterPodAffinityArgs{}, 149 }} 150 151 duplicatedPluginConfig := validConfig.DeepCopy() 152 duplicatedPluginConfig.Profiles[0].PluginConfig = []config.PluginConfig{{ 153 Name: "config", 154 }, { 155 Name: "config", 156 }} 157 158 mismatchQueueSort := validConfig.DeepCopy() 159 mismatchQueueSort.Profiles = []config.KubeSchedulerProfile{{ 160 SchedulerName: "me", 161 Plugins: &config.Plugins{ 162 QueueSort: config.PluginSet{ 163 Enabled: []config.Plugin{{Name: "PrioritySort"}}, 164 }, 165 }, 166 PluginConfig: []config.PluginConfig{{ 167 Name: "PrioritySort", 168 }}, 169 }, { 170 SchedulerName: "other", 171 Plugins: &config.Plugins{ 172 QueueSort: config.PluginSet{ 173 Enabled: []config.Plugin{{Name: "CustomSort"}}, 174 }, 175 }, 176 PluginConfig: []config.PluginConfig{{ 177 Name: "CustomSort", 178 }}, 179 }} 180 181 extenderDuplicateManagedResource := validConfig.DeepCopy() 182 extenderDuplicateManagedResource.Extenders[0].ManagedResources = []config.ExtenderManagedResource{ 183 {Name: "example.com/foo", IgnoredByScheduler: false}, 184 {Name: "example.com/foo", IgnoredByScheduler: false}, 185 } 186 187 extenderDuplicateBind := validConfig.DeepCopy() 188 extenderDuplicateBind.Extenders[0].BindVerb = "foo" 189 extenderDuplicateBind.Extenders = append(extenderDuplicateBind.Extenders, config.Extender{ 190 PrioritizeVerb: "prioritize", 191 BindVerb: "bar", 192 Weight: 1, 193 }) 194 195 validPlugins := validConfig.DeepCopy() 196 validPlugins.Profiles[0].Plugins.Score.Enabled = append(validPlugins.Profiles[0].Plugins.Score.Enabled, config.Plugin{Name: "PodTopologySpread", Weight: 2}) 197 198 scenarios := map[string]struct { 199 config *config.KubeSchedulerConfiguration 200 wantErrs field.ErrorList 201 }{ 202 "good": { 203 config: validConfig, 204 }, 205 "bad-parallelism-invalid-value": { 206 config: invalidParallelismValue, 207 wantErrs: field.ErrorList{ 208 &field.Error{ 209 Type: field.ErrorTypeInvalid, 210 Field: "parallelism", 211 }, 212 }, 213 }, 214 "bad-resource-name-not-set": { 215 config: resourceNameNotSet, 216 wantErrs: field.ErrorList{ 217 &field.Error{ 218 Type: field.ErrorTypeInvalid, 219 Field: "leaderElection.resourceName", 220 }, 221 }, 222 }, 223 "bad-resource-namespace-not-set": { 224 config: resourceNamespaceNotSet, 225 wantErrs: field.ErrorList{ 226 &field.Error{ 227 Type: field.ErrorTypeInvalid, 228 Field: "leaderElection.resourceNamespace", 229 }, 230 }, 231 }, 232 "bad-resource-lock-not-leases": { 233 config: resourceLockNotLeases, 234 wantErrs: field.ErrorList{ 235 &field.Error{ 236 Type: field.ErrorTypeInvalid, 237 Field: "leaderElection.resourceLock", 238 }, 239 }, 240 }, 241 "non-empty-metrics-bind-addr": { 242 config: metricsBindAddrInvalid, 243 wantErrs: field.ErrorList{ 244 &field.Error{ 245 Type: field.ErrorTypeInvalid, 246 Field: "metricsBindAddress", 247 }, 248 }, 249 }, 250 "non-empty-healthz-bind-addr": { 251 config: healthzBindAddrInvalid, 252 wantErrs: field.ErrorList{ 253 &field.Error{ 254 Type: field.ErrorTypeInvalid, 255 Field: "healthzBindAddress", 256 }, 257 }, 258 }, 259 "bad-percentage-of-nodes-to-score": { 260 config: percentageOfNodesToScore101, 261 wantErrs: field.ErrorList{ 262 &field.Error{ 263 Type: field.ErrorTypeInvalid, 264 Field: "percentageOfNodesToScore", 265 }, 266 }, 267 }, 268 "negative-percentage-of-nodes-to-score": { 269 config: percentageOfNodesToScoreNegative, 270 wantErrs: field.ErrorList{ 271 &field.Error{ 272 Type: field.ErrorTypeInvalid, 273 Field: "percentageOfNodesToScore", 274 }, 275 }, 276 }, 277 "scheduler-name-not-set": { 278 config: schedulerNameNotSet, 279 wantErrs: field.ErrorList{ 280 &field.Error{ 281 Type: field.ErrorTypeRequired, 282 Field: "profiles[1].schedulerName", 283 }, 284 }, 285 }, 286 "repeated-scheduler-name": { 287 config: repeatedSchedulerName, 288 wantErrs: field.ErrorList{ 289 &field.Error{ 290 Type: field.ErrorTypeDuplicate, 291 Field: "profiles[1].schedulerName", 292 }, 293 }, 294 }, 295 "greater-than-100-profile-percentage-of-nodes-to-score": { 296 config: profilePercentageOfNodesToScore101, 297 wantErrs: field.ErrorList{ 298 &field.Error{ 299 Type: field.ErrorTypeInvalid, 300 Field: "profiles[1].percentageOfNodesToScore", 301 }, 302 }, 303 }, 304 "negative-profile-percentage-of-nodes-to-score": { 305 config: profilePercentageOfNodesToScoreNegative, 306 wantErrs: field.ErrorList{ 307 &field.Error{ 308 Type: field.ErrorTypeInvalid, 309 Field: "profiles[1].percentageOfNodesToScore", 310 }, 311 }, 312 }, 313 "different-queue-sort": { 314 config: differentQueueSort, 315 wantErrs: field.ErrorList{ 316 &field.Error{ 317 Type: field.ErrorTypeInvalid, 318 Field: "profiles[1].plugins.queueSort", 319 }, 320 }, 321 }, 322 "one-empty-queue-sort": { 323 config: oneEmptyQueueSort, 324 wantErrs: field.ErrorList{ 325 &field.Error{ 326 Type: field.ErrorTypeInvalid, 327 Field: "profiles[1].plugins.queueSort", 328 }, 329 }, 330 }, 331 "extender-negative-weight": { 332 config: extenderNegativeWeight, 333 wantErrs: field.ErrorList{ 334 &field.Error{ 335 Type: field.ErrorTypeInvalid, 336 Field: "extenders[0].weight", 337 }, 338 }, 339 }, 340 "extender-duplicate-managed-resources": { 341 config: extenderDuplicateManagedResource, 342 wantErrs: field.ErrorList{ 343 &field.Error{ 344 Type: field.ErrorTypeInvalid, 345 Field: "extenders[0].managedResources[1].name", 346 }, 347 }, 348 }, 349 "extender-duplicate-bind": { 350 config: extenderDuplicateBind, 351 wantErrs: field.ErrorList{ 352 &field.Error{ 353 Type: field.ErrorTypeInvalid, 354 Field: "extenders", 355 }, 356 }, 357 }, 358 "invalid-node-percentage": { 359 config: invalidNodePercentage, 360 wantErrs: field.ErrorList{ 361 &field.Error{ 362 Type: field.ErrorTypeInvalid, 363 Field: "profiles[0].pluginConfig[0].args.minCandidateNodesPercentage", 364 }, 365 }, 366 }, 367 "invalid-plugin-args": { 368 config: invalidPluginArgs, 369 wantErrs: field.ErrorList{ 370 &field.Error{ 371 Type: field.ErrorTypeInvalid, 372 Field: "profiles[0].pluginConfig[0].args", 373 }, 374 }, 375 }, 376 "duplicated-plugin-config": { 377 config: duplicatedPluginConfig, 378 wantErrs: field.ErrorList{ 379 &field.Error{ 380 Type: field.ErrorTypeDuplicate, 381 Field: "profiles[0].pluginConfig[1]", 382 }, 383 }, 384 }, 385 "mismatch-queue-sort": { 386 config: mismatchQueueSort, 387 wantErrs: field.ErrorList{ 388 &field.Error{ 389 Type: field.ErrorTypeInvalid, 390 Field: "profiles[1].plugins.queueSort", 391 }, 392 }, 393 }, 394 "valid-plugins": { 395 config: validPlugins, 396 }, 397 } 398 399 for name, scenario := range scenarios { 400 t.Run(name, func(t *testing.T) { 401 errs := ValidateKubeSchedulerConfiguration(scenario.config) 402 diff := cmp.Diff(scenario.wantErrs.ToAggregate(), errs, ignoreBadValueDetail) 403 if diff != "" { 404 t.Errorf("KubeSchedulerConfiguration returned err (-want,+got):\n%s", diff) 405 } 406 }) 407 } 408 }