Skip to content

Commit b0e0692

Browse files
authored
Merge pull request kubernetes#91446 from pancernik/scheduler-plugin-args-validation-nr
Move Node Resources scheduler plugin args validation to apis/config/validation
2 parents eff6105 + 3ffd71c commit b0e0692

File tree

7 files changed

+369
-113
lines changed

7 files changed

+369
-113
lines changed

pkg/scheduler/apis/config/validation/validation_pluginargs.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,3 +133,82 @@ func validateConstraintNotRepeat(path *field.Path, constraints []v1.TopologySpre
133133
}
134134
return nil
135135
}
136+
137+
// ValidateRequestedToCapacityRatioArgs validates that RequestedToCapacityRatioArgs are correct.
138+
func ValidateRequestedToCapacityRatioArgs(args config.RequestedToCapacityRatioArgs) error {
139+
if err := validateFunctionShape(args.Shape); err != nil {
140+
return err
141+
}
142+
if err := validateResourcesNoMax(args.Resources); err != nil {
143+
return err
144+
}
145+
return nil
146+
}
147+
148+
func validateFunctionShape(shape []config.UtilizationShapePoint) error {
149+
const (
150+
minUtilization = 0
151+
maxUtilization = 100
152+
minScore = 0
153+
maxScore = int32(config.MaxCustomPriorityScore)
154+
)
155+
156+
if len(shape) == 0 {
157+
return fmt.Errorf("at least one point must be specified")
158+
}
159+
160+
for i := 1; i < len(shape); i++ {
161+
if shape[i-1].Utilization >= shape[i].Utilization {
162+
return fmt.Errorf("utilization values must be sorted. Utilization[%d]==%d >= Utilization[%d]==%d", i-1, shape[i-1].Utilization, i, shape[i].Utilization)
163+
}
164+
}
165+
166+
for i, point := range shape {
167+
if point.Utilization < minUtilization {
168+
return fmt.Errorf("utilization values must not be less than %d. Utilization[%d]==%d", minUtilization, i, point.Utilization)
169+
}
170+
if point.Utilization > maxUtilization {
171+
return fmt.Errorf("utilization values must not be greater than %d. Utilization[%d]==%d", maxUtilization, i, point.Utilization)
172+
}
173+
if point.Score < minScore {
174+
return fmt.Errorf("score values must not be less than %d. Score[%d]==%d", minScore, i, point.Score)
175+
}
176+
if point.Score > maxScore {
177+
return fmt.Errorf("score values must not be greater than %d. Score[%d]==%d", maxScore, i, point.Score)
178+
}
179+
}
180+
181+
return nil
182+
}
183+
184+
// TODO potentially replace with validateResources
185+
func validateResourcesNoMax(resources []config.ResourceSpec) error {
186+
for _, r := range resources {
187+
if r.Weight < 1 {
188+
return fmt.Errorf("resource %s weight %d must not be less than 1", string(r.Name), r.Weight)
189+
}
190+
}
191+
return nil
192+
}
193+
194+
// ValidateNodeResourcesLeastAllocatedArgs validates that NodeResourcesLeastAllocatedArgs are correct.
195+
func ValidateNodeResourcesLeastAllocatedArgs(args *config.NodeResourcesLeastAllocatedArgs) error {
196+
return validateResources(args.Resources)
197+
}
198+
199+
// ValidateNodeResourcesMostAllocatedArgs validates that NodeResourcesMostAllocatedArgs are correct.
200+
func ValidateNodeResourcesMostAllocatedArgs(args *config.NodeResourcesMostAllocatedArgs) error {
201+
return validateResources(args.Resources)
202+
}
203+
204+
func validateResources(resources []config.ResourceSpec) error {
205+
for _, resource := range resources {
206+
if resource.Weight <= 0 {
207+
return fmt.Errorf("resource Weight of %v should be a positive value, got %v", resource.Name, resource.Weight)
208+
}
209+
if resource.Weight > 100 {
210+
return fmt.Errorf("resource Weight of %v should be less than 100, got %v", resource.Name, resource.Weight)
211+
}
212+
}
213+
return nil
214+
}

pkg/scheduler/apis/config/validation/validation_pluginargs_test.go

Lines changed: 274 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -114,7 +114,7 @@ func TestValidatePodTopologySpreadArgs(t *testing.T) {
114114
},
115115
},
116116
},
117-
"max skew less than zero": {
117+
"maxSkew less than zero": {
118118
args: &config.PodTopologySpreadArgs{
119119
DefaultConstraints: []v1.TopologySpreadConstraint{
120120
{
@@ -138,7 +138,7 @@ func TestValidatePodTopologySpreadArgs(t *testing.T) {
138138
},
139139
wantErr: `defaultConstraints[0].topologyKey: Required value: can not be empty`,
140140
},
141-
"WhenUnsatisfiable is empty": {
141+
"whenUnsatisfiable is empty": {
142142
args: &config.PodTopologySpreadArgs{
143143
DefaultConstraints: []v1.TopologySpreadConstraint{
144144
{
@@ -150,7 +150,7 @@ func TestValidatePodTopologySpreadArgs(t *testing.T) {
150150
},
151151
wantErr: `defaultConstraints[0].whenUnsatisfiable: Required value: can not be empty`,
152152
},
153-
"WhenUnsatisfiable contains unsupported action": {
153+
"whenUnsatisfiable contains unsupported action": {
154154
args: &config.PodTopologySpreadArgs{
155155
DefaultConstraints: []v1.TopologySpreadConstraint{
156156
{
@@ -206,17 +206,285 @@ func TestValidatePodTopologySpreadArgs(t *testing.T) {
206206
}
207207
}
208208

209+
func TestValidateRequestedToCapacityRatioArgs(t *testing.T) {
210+
cases := map[string]struct {
211+
args config.RequestedToCapacityRatioArgs
212+
wantErr string
213+
}{
214+
"valid config": {
215+
args: config.RequestedToCapacityRatioArgs{
216+
Shape: []config.UtilizationShapePoint{
217+
{
218+
Utilization: 20,
219+
Score: 5,
220+
},
221+
{
222+
Utilization: 30,
223+
Score: 3,
224+
},
225+
{
226+
Utilization: 50,
227+
Score: 2,
228+
},
229+
},
230+
Resources: []config.ResourceSpec{
231+
{
232+
Name: "custom-resource",
233+
Weight: 5,
234+
},
235+
},
236+
},
237+
},
238+
"no shape points": {
239+
args: config.RequestedToCapacityRatioArgs{
240+
Shape: []config.UtilizationShapePoint{},
241+
Resources: []config.ResourceSpec{
242+
{
243+
Name: "custom",
244+
Weight: 5,
245+
},
246+
},
247+
},
248+
wantErr: `at least one point must be specified`,
249+
},
250+
"utilization less than min": {
251+
args: config.RequestedToCapacityRatioArgs{
252+
Shape: []config.UtilizationShapePoint{
253+
{
254+
Utilization: -10,
255+
Score: 3,
256+
},
257+
{
258+
Utilization: 10,
259+
Score: 2,
260+
},
261+
},
262+
},
263+
wantErr: `utilization values must not be less than 0. Utilization[0]==-10`,
264+
},
265+
"utilization greater than max": {
266+
args: config.RequestedToCapacityRatioArgs{
267+
Shape: []config.UtilizationShapePoint{
268+
{
269+
Utilization: 10,
270+
Score: 3,
271+
},
272+
{
273+
Utilization: 110,
274+
Score: 2,
275+
},
276+
},
277+
},
278+
wantErr: `utilization values must not be greater than 100. Utilization[1]==110`,
279+
},
280+
"Utilization values in non-increasing order": {
281+
args: config.RequestedToCapacityRatioArgs{
282+
Shape: []config.UtilizationShapePoint{
283+
{
284+
Utilization: 30,
285+
Score: 3,
286+
},
287+
{
288+
Utilization: 20,
289+
Score: 2,
290+
},
291+
{
292+
Utilization: 10,
293+
Score: 1,
294+
},
295+
},
296+
},
297+
wantErr: `utilization values must be sorted. Utilization[0]==30 >= Utilization[1]==20`,
298+
},
299+
"duplicated utilization values": {
300+
args: config.RequestedToCapacityRatioArgs{
301+
Shape: []config.UtilizationShapePoint{
302+
{
303+
Utilization: 10,
304+
Score: 3,
305+
},
306+
{
307+
Utilization: 20,
308+
Score: 2,
309+
},
310+
{
311+
Utilization: 20,
312+
Score: 1,
313+
},
314+
},
315+
},
316+
wantErr: `utilization values must be sorted. Utilization[1]==20 >= Utilization[2]==20`,
317+
},
318+
"score less than min": {
319+
args: config.RequestedToCapacityRatioArgs{
320+
Shape: []config.UtilizationShapePoint{
321+
{
322+
Utilization: 10,
323+
Score: -1,
324+
},
325+
{
326+
Utilization: 20,
327+
Score: 2,
328+
},
329+
},
330+
},
331+
wantErr: `score values must not be less than 0. Score[0]==-1`,
332+
},
333+
"score greater than max": {
334+
args: config.RequestedToCapacityRatioArgs{
335+
Shape: []config.UtilizationShapePoint{
336+
{
337+
Utilization: 10,
338+
Score: 3,
339+
},
340+
{
341+
Utilization: 20,
342+
Score: 11,
343+
},
344+
},
345+
},
346+
wantErr: `score values must not be greater than 10. Score[1]==11`,
347+
},
348+
"resources weight less than 1": {
349+
args: config.RequestedToCapacityRatioArgs{
350+
Shape: []config.UtilizationShapePoint{
351+
{
352+
Utilization: 10,
353+
Score: 1,
354+
},
355+
},
356+
Resources: []config.ResourceSpec{
357+
{
358+
Name: "custom",
359+
Weight: 0,
360+
},
361+
},
362+
},
363+
wantErr: `resource custom weight 0 must not be less than 1`,
364+
},
365+
}
366+
367+
for name, tc := range cases {
368+
t.Run(name, func(t *testing.T) {
369+
err := ValidateRequestedToCapacityRatioArgs(tc.args)
370+
assertErr(t, tc.wantErr, err)
371+
})
372+
}
373+
}
374+
375+
func TestValidateNodeResourcesLeastAllocatedArgs(t *testing.T) {
376+
cases := map[string]struct {
377+
args *config.NodeResourcesLeastAllocatedArgs
378+
wantErr string
379+
}{
380+
"valid config": {
381+
args: &config.NodeResourcesLeastAllocatedArgs{
382+
Resources: []config.ResourceSpec{
383+
{
384+
Name: "cpu",
385+
Weight: 50,
386+
},
387+
{
388+
Name: "memory",
389+
Weight: 30,
390+
},
391+
},
392+
},
393+
},
394+
"weight less than min": {
395+
args: &config.NodeResourcesLeastAllocatedArgs{
396+
Resources: []config.ResourceSpec{
397+
{
398+
Name: "cpu",
399+
Weight: 0,
400+
},
401+
},
402+
},
403+
wantErr: `resource Weight of cpu should be a positive value, got 0`,
404+
},
405+
"weight more than max": {
406+
args: &config.NodeResourcesLeastAllocatedArgs{
407+
Resources: []config.ResourceSpec{
408+
{
409+
Name: "memory",
410+
Weight: 101,
411+
},
412+
},
413+
},
414+
wantErr: `resource Weight of memory should be less than 100, got 101`,
415+
},
416+
}
417+
418+
for name, tc := range cases {
419+
t.Run(name, func(t *testing.T) {
420+
err := ValidateNodeResourcesLeastAllocatedArgs(tc.args)
421+
assertErr(t, tc.wantErr, err)
422+
})
423+
}
424+
}
425+
426+
func TestValidateNodeResourcesMostAllocatedArgs(t *testing.T) {
427+
cases := map[string]struct {
428+
args *config.NodeResourcesMostAllocatedArgs
429+
wantErr string
430+
}{
431+
"valid config": {
432+
args: &config.NodeResourcesMostAllocatedArgs{
433+
Resources: []config.ResourceSpec{
434+
{
435+
Name: "cpu",
436+
Weight: 70,
437+
},
438+
{
439+
Name: "memory",
440+
Weight: 40,
441+
},
442+
},
443+
},
444+
},
445+
"weight less than min": {
446+
args: &config.NodeResourcesMostAllocatedArgs{
447+
Resources: []config.ResourceSpec{
448+
{
449+
Name: "cpu",
450+
Weight: -1,
451+
},
452+
},
453+
},
454+
wantErr: `resource Weight of cpu should be a positive value, got -1`,
455+
},
456+
"weight more than max": {
457+
args: &config.NodeResourcesMostAllocatedArgs{
458+
Resources: []config.ResourceSpec{
459+
{
460+
Name: "memory",
461+
Weight: 110,
462+
},
463+
},
464+
},
465+
wantErr: `resource Weight of memory should be less than 100, got 110`,
466+
},
467+
}
468+
469+
for name, tc := range cases {
470+
t.Run(name, func(t *testing.T) {
471+
err := ValidateNodeResourcesMostAllocatedArgs(tc.args)
472+
assertErr(t, tc.wantErr, err)
473+
})
474+
}
475+
}
476+
209477
func assertErr(t *testing.T, wantErr string, gotErr error) {
210478
if wantErr == "" {
211479
if gotErr != nil {
212-
t.Fatalf("wanted err to be: 'nil', got: '%s'", gotErr.Error())
480+
t.Fatalf("\nwant err to be:\n\tnil\ngot:\n\t%s", gotErr.Error())
213481
}
214482
} else {
215483
if gotErr == nil {
216-
t.Fatalf("wanted err to be: '%s', got: nil", wantErr)
484+
t.Fatalf("\nwant err to be:\n\t%s\ngot:\n\tnil", wantErr)
217485
}
218486
if gotErr.Error() != wantErr {
219-
t.Errorf("wanted err to be: '%s', got '%s'", wantErr, gotErr.Error())
487+
t.Errorf("\nwant err to be:\n\t%s\ngot:\n\t%s", wantErr, gotErr.Error())
220488
}
221489
}
222490
}

0 commit comments

Comments
 (0)