1
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