1
16
17 package ensurer
18
19 import (
20 "context"
21 "reflect"
22 "testing"
23
24 flowcontrolv1 "k8s.io/api/flowcontrol/v1"
25 apierrors "k8s.io/apimachinery/pkg/api/errors"
26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 "k8s.io/apiserver/pkg/apis/flowcontrol/bootstrap"
28 "k8s.io/client-go/kubernetes/fake"
29 flowcontrollisters "k8s.io/client-go/listers/flowcontrol/v1"
30 toolscache "k8s.io/client-go/tools/cache"
31 flowcontrolapisv1 "k8s.io/kubernetes/pkg/apis/flowcontrol/v1"
32 "k8s.io/utils/pointer"
33 "k8s.io/utils/ptr"
34
35 "github.com/google/go-cmp/cmp"
36 )
37
38 func TestEnsurePriorityLevel(t *testing.T) {
39 validExemptPL := func() *flowcontrolv1.PriorityLevelConfiguration {
40 copy := bootstrap.MandatoryPriorityLevelConfigurationExempt.DeepCopy()
41 copy.Annotations[flowcontrolv1.AutoUpdateAnnotationKey] = "true"
42 copy.Spec.Exempt.NominalConcurrencyShares = pointer.Int32(10)
43 copy.Spec.Exempt.LendablePercent = pointer.Int32(50)
44 return copy
45 }()
46
47 tests := []struct {
48 name string
49 strategy func() EnsureStrategy[*flowcontrolv1.PriorityLevelConfiguration]
50 current *flowcontrolv1.PriorityLevelConfiguration
51 bootstrap *flowcontrolv1.PriorityLevelConfiguration
52 expected *flowcontrolv1.PriorityLevelConfiguration
53 }{
54
55 {
56 name: "suggested priority level configuration does not exist - the object should always be re-created",
57 strategy: NewSuggestedEnsureStrategy[*flowcontrolv1.PriorityLevelConfiguration],
58 bootstrap: newPLConfiguration("pl1").WithLimited(10).Object(),
59 current: nil,
60 expected: newPLConfiguration("pl1").WithLimited(10).Object(),
61 },
62 {
63 name: "suggested priority level configuration exists, auto update is enabled, spec does not match - current object should be updated",
64 strategy: NewSuggestedEnsureStrategy[*flowcontrolv1.PriorityLevelConfiguration],
65 bootstrap: newPLConfiguration("pl1").WithLimited(20).Object(),
66 current: newPLConfiguration("pl1").WithAutoUpdateAnnotation("true").WithLimited(10).Object(),
67 expected: newPLConfiguration("pl1").WithAutoUpdateAnnotation("true").WithLimited(20).Object(),
68 },
69 {
70 name: "suggested priority level configuration exists, auto update is disabled, spec does not match - current object should not be updated",
71 strategy: NewSuggestedEnsureStrategy[*flowcontrolv1.PriorityLevelConfiguration],
72 bootstrap: newPLConfiguration("pl1").WithLimited(20).Object(),
73 current: newPLConfiguration("pl1").WithAutoUpdateAnnotation("false").WithLimited(10).Object(),
74 expected: newPLConfiguration("pl1").WithAutoUpdateAnnotation("false").WithLimited(10).Object(),
75 },
76
77
78 {
79 name: "mandatory priority level configuration does not exist - new object should be created",
80 strategy: NewMandatoryEnsureStrategy[*flowcontrolv1.PriorityLevelConfiguration],
81 bootstrap: newPLConfiguration("pl1").WithLimited(10).WithAutoUpdateAnnotation("true").Object(),
82 current: nil,
83 expected: newPLConfiguration("pl1").WithLimited(10).WithAutoUpdateAnnotation("true").Object(),
84 },
85 {
86 name: "mandatory priority level configuration exists, annotation is missing - annotation is added",
87 strategy: NewMandatoryEnsureStrategy[*flowcontrolv1.PriorityLevelConfiguration],
88 bootstrap: newPLConfiguration("pl1").WithLimited(20).Object(),
89 current: newPLConfiguration("pl1").WithLimited(20).Object(),
90 expected: newPLConfiguration("pl1").WithAutoUpdateAnnotation("true").WithLimited(20).Object(),
91 },
92 {
93 name: "mandatory priority level configuration exists, auto update is disabled, spec does not match - current object should be updated",
94 strategy: NewMandatoryEnsureStrategy[*flowcontrolv1.PriorityLevelConfiguration],
95 bootstrap: newPLConfiguration("pl1").WithLimited(20).Object(),
96 current: newPLConfiguration("pl1").WithAutoUpdateAnnotation("false").WithLimited(10).Object(),
97 expected: newPLConfiguration("pl1").WithAutoUpdateAnnotation("true").WithLimited(20).Object(),
98 },
99 {
100 name: "admin changes the Exempt field of the exempt priority level configuration",
101 strategy: NewMandatoryEnsureStrategy[*flowcontrolv1.PriorityLevelConfiguration],
102 bootstrap: func() *flowcontrolv1.PriorityLevelConfiguration {
103 return bootstrap.MandatoryPriorityLevelConfigurationExempt
104 }(),
105 current: validExemptPL,
106 expected: validExemptPL,
107 },
108 }
109
110 for _, test := range tests {
111 t.Run(test.name, func(t *testing.T) {
112 client := fake.NewSimpleClientset().FlowcontrolV1().PriorityLevelConfigurations()
113 indexer := toolscache.NewIndexer(toolscache.MetaNamespaceKeyFunc, toolscache.Indexers{})
114 if test.current != nil {
115 client.Create(context.TODO(), test.current, metav1.CreateOptions{})
116 indexer.Add(test.current)
117 }
118
119 ops := NewPriorityLevelConfigurationOps(client, flowcontrollisters.NewPriorityLevelConfigurationLister(indexer))
120 boots := []*flowcontrolv1.PriorityLevelConfiguration{test.bootstrap}
121 strategy := test.strategy()
122
123 err := EnsureConfigurations(context.Background(), ops, boots, strategy)
124 if err != nil {
125 t.Fatalf("Expected no error, but got: %v", err)
126 }
127
128 plGot, err := client.Get(context.TODO(), test.bootstrap.Name, metav1.GetOptions{})
129 switch {
130 case test.expected == nil:
131 if !apierrors.IsNotFound(err) {
132 t.Fatalf("Expected GET to return an %q error, but got: %v", metav1.StatusReasonNotFound, err)
133 }
134 case err != nil:
135 t.Fatalf("Expected GET to return no error, but got: %v", err)
136 }
137
138 if !reflect.DeepEqual(test.expected, plGot) {
139 t.Errorf("PriorityLevelConfiguration does not match - diff: %s", cmp.Diff(test.expected, plGot))
140 }
141 })
142 }
143 }
144
145 func TestSuggestedPLEnsureStrategy_ShouldUpdate(t *testing.T) {
146 tests := []struct {
147 name string
148 current *flowcontrolv1.PriorityLevelConfiguration
149 bootstrap *flowcontrolv1.PriorityLevelConfiguration
150 newObjectExpected *flowcontrolv1.PriorityLevelConfiguration
151 }{
152 {
153 name: "auto update is enabled, first generation, spec does not match - spec update expected",
154 current: newPLConfiguration("foo").WithAutoUpdateAnnotation("true").WithGeneration(1).WithLimited(5).Object(),
155 bootstrap: newPLConfiguration("foo").WithLimited(10).Object(),
156 newObjectExpected: newPLConfiguration("foo").WithAutoUpdateAnnotation("true").WithGeneration(1).WithLimited(10).Object(),
157 },
158 {
159 name: "auto update is enabled, first generation, spec matches - no update expected",
160 current: newPLConfiguration("foo").WithAutoUpdateAnnotation("true").WithGeneration(1).WithLimited(5).Object(),
161 bootstrap: newPLConfiguration("foo").WithGeneration(1).WithLimited(5).Object(),
162 newObjectExpected: nil,
163 },
164 {
165 name: "auto update is enabled, second generation, spec does not match - spec update expected",
166 current: newPLConfiguration("foo").WithAutoUpdateAnnotation("true").WithGeneration(2).WithLimited(5).Object(),
167 bootstrap: newPLConfiguration("foo").WithLimited(10).Object(),
168 newObjectExpected: newPLConfiguration("foo").WithAutoUpdateAnnotation("true").WithGeneration(2).WithLimited(10).Object(),
169 },
170 {
171 name: "auto update is enabled, second generation, spec matches - no update expected",
172 current: newPLConfiguration("foo").WithAutoUpdateAnnotation("true").WithGeneration(2).WithLimited(5).Object(),
173 bootstrap: newPLConfiguration("foo").WithLimited(5).Object(),
174 newObjectExpected: nil,
175 },
176 {
177 name: "auto update is disabled, first generation, spec does not match - no update expected",
178 current: newPLConfiguration("foo").WithAutoUpdateAnnotation("false").WithGeneration(1).WithLimited(5).Object(),
179 bootstrap: newPLConfiguration("foo").WithLimited(10).Object(),
180 newObjectExpected: nil,
181 },
182 {
183 name: "auto update is disabled, first generation, spec matches - no update expected",
184 current: newPLConfiguration("foo").WithAutoUpdateAnnotation("false").WithGeneration(1).WithLimited(5).Object(),
185 bootstrap: newPLConfiguration("foo").WithLimited(5).Object(),
186 newObjectExpected: nil,
187 },
188 {
189 name: "auto update is disabled, second generation, spec does not match - no update expected",
190 current: newPLConfiguration("foo").WithAutoUpdateAnnotation("false").WithGeneration(2).WithLimited(5).Object(),
191 bootstrap: newPLConfiguration("foo").WithLimited(10).Object(),
192 newObjectExpected: nil,
193 },
194 {
195 name: "auto update is disabled, second generation, spec matches - no update expected",
196 current: newPLConfiguration("foo").WithAutoUpdateAnnotation("false").WithGeneration(2).WithLimited(5).Object(),
197 bootstrap: newPLConfiguration("foo").WithLimited(5).Object(),
198 newObjectExpected: nil,
199 },
200 {
201 name: "annotation is missing, first generation, spec does not match - both annotation and spec update expected",
202 current: newPLConfiguration("foo").WithGeneration(1).WithLimited(5).Object(),
203 bootstrap: newPLConfiguration("foo").WithLimited(10).Object(),
204 newObjectExpected: newPLConfiguration("foo").WithAutoUpdateAnnotation("true").WithGeneration(1).WithLimited(10).Object(),
205 },
206 {
207 name: "annotation is missing, first generation, spec matches - annotation update is expected",
208 current: newPLConfiguration("foo").WithGeneration(1).WithLimited(5).Object(),
209 bootstrap: newPLConfiguration("foo").WithLimited(5).Object(),
210 newObjectExpected: newPLConfiguration("foo").WithAutoUpdateAnnotation("true").WithGeneration(1).WithLimited(5).Object(),
211 },
212 {
213 name: "annotation is missing, second generation, spec does not match - annotation update is expected",
214 current: newPLConfiguration("foo").WithGeneration(2).WithLimited(5).Object(),
215 bootstrap: newPLConfiguration("foo").WithLimited(10).Object(),
216 newObjectExpected: newPLConfiguration("foo").WithAutoUpdateAnnotation("false").WithGeneration(2).WithLimited(5).Object(),
217 },
218 {
219 name: "annotation is missing, second generation, spec matches - annotation update is expected",
220 current: newPLConfiguration("foo").WithGeneration(2).WithLimited(5).Object(),
221 bootstrap: newPLConfiguration("foo").WithLimited(5).Object(),
222 newObjectExpected: newPLConfiguration("foo").WithAutoUpdateAnnotation("false").WithGeneration(2).WithLimited(5).Object(),
223 },
224 }
225
226 ops := NewPriorityLevelConfigurationOps(nil, nil)
227 for _, test := range tests {
228 t.Run(test.name, func(t *testing.T) {
229 strategy := NewSuggestedEnsureStrategy[*flowcontrolv1.PriorityLevelConfiguration]()
230 updatableGot, updateGot, err := strategy.ReviseIfNeeded(ops, test.current, test.bootstrap)
231 if err != nil {
232 t.Errorf("Expected no error, but got: %v", err)
233 }
234 if test.newObjectExpected == nil {
235 if updatableGot != nil {
236 t.Errorf("Expected a nil object, but got: %#v", updatableGot)
237 }
238 if updateGot {
239 t.Errorf("Expected update=%t but got: %t", false, updateGot)
240 }
241 return
242 }
243
244 if !updateGot {
245 t.Errorf("Expected update=%t but got: %t", true, updateGot)
246 }
247 if !reflect.DeepEqual(test.newObjectExpected, updatableGot) {
248 t.Errorf("Expected the object to be updated to match - diff: %s", cmp.Diff(test.newObjectExpected, updatableGot))
249 }
250 })
251 }
252 }
253
254 func TestPriorityLevelSpecChanged(t *testing.T) {
255 pl1 := &flowcontrolv1.PriorityLevelConfiguration{
256 Spec: flowcontrolv1.PriorityLevelConfigurationSpec{
257 Type: flowcontrolv1.PriorityLevelEnablementLimited,
258 Limited: &flowcontrolv1.LimitedPriorityLevelConfiguration{
259 LimitResponse: flowcontrolv1.LimitResponse{
260 Type: flowcontrolv1.LimitResponseTypeReject,
261 },
262 },
263 },
264 }
265 pl2 := &flowcontrolv1.PriorityLevelConfiguration{
266 Spec: flowcontrolv1.PriorityLevelConfigurationSpec{
267 Type: flowcontrolv1.PriorityLevelEnablementLimited,
268 Limited: &flowcontrolv1.LimitedPriorityLevelConfiguration{
269 NominalConcurrencyShares: ptr.To(int32(1)),
270 },
271 },
272 }
273 pl1Defaulted := &flowcontrolv1.PriorityLevelConfiguration{
274 Spec: flowcontrolv1.PriorityLevelConfigurationSpec{
275 Type: flowcontrolv1.PriorityLevelEnablementLimited,
276 Limited: &flowcontrolv1.LimitedPriorityLevelConfiguration{
277 NominalConcurrencyShares: ptr.To(flowcontrolapisv1.PriorityLevelConfigurationDefaultNominalConcurrencyShares),
278 LendablePercent: pointer.Int32(0),
279 LimitResponse: flowcontrolv1.LimitResponse{
280 Type: flowcontrolv1.LimitResponseTypeReject,
281 },
282 },
283 },
284 }
285 ple1 := &flowcontrolv1.PriorityLevelConfiguration{
286 ObjectMeta: metav1.ObjectMeta{Name: "exempt"},
287 Spec: flowcontrolv1.PriorityLevelConfigurationSpec{
288 Type: flowcontrolv1.PriorityLevelEnablementExempt,
289 Exempt: &flowcontrolv1.ExemptPriorityLevelConfiguration{
290 NominalConcurrencyShares: pointer.Int32(42),
291 LendablePercent: pointer.Int32(33),
292 },
293 },
294 }
295 ple2 := &flowcontrolv1.PriorityLevelConfiguration{
296 ObjectMeta: metav1.ObjectMeta{Name: "exempt"},
297 Spec: flowcontrolv1.PriorityLevelConfigurationSpec{
298 Type: flowcontrolv1.PriorityLevelEnablementExempt,
299 Exempt: &flowcontrolv1.ExemptPriorityLevelConfiguration{
300 NominalConcurrencyShares: pointer.Int32(24),
301 LendablePercent: pointer.Int32(86),
302 },
303 },
304 }
305 pleWrong := &flowcontrolv1.PriorityLevelConfiguration{
306 ObjectMeta: metav1.ObjectMeta{Name: "exempt"},
307 Spec: flowcontrolv1.PriorityLevelConfigurationSpec{
308 Type: flowcontrolv1.PriorityLevelEnablementLimited,
309 Limited: &flowcontrolv1.LimitedPriorityLevelConfiguration{
310 NominalConcurrencyShares: ptr.To(int32(1)),
311 },
312 },
313 }
314 pleInvalid := &flowcontrolv1.PriorityLevelConfiguration{
315 ObjectMeta: metav1.ObjectMeta{Name: "exempt"},
316 Spec: flowcontrolv1.PriorityLevelConfigurationSpec{
317 Type: "widget",
318 },
319 }
320 testCases := []struct {
321 name string
322 expected *flowcontrolv1.PriorityLevelConfiguration
323 actual *flowcontrolv1.PriorityLevelConfiguration
324 specChanged bool
325 }{
326 {
327 name: "identical priority-level should work",
328 expected: bootstrap.MandatoryPriorityLevelConfigurationCatchAll,
329 actual: bootstrap.MandatoryPriorityLevelConfigurationCatchAll,
330 specChanged: false,
331 },
332 {
333 name: "defaulted priority-level should work",
334 expected: pl1,
335 actual: pl1Defaulted,
336 specChanged: false,
337 },
338 {
339 name: "non-defaulted priority-level has wrong spec",
340 expected: pl1,
341 actual: pl2,
342 specChanged: true,
343 },
344 {
345 name: "tweaked exempt config",
346 expected: ple1,
347 actual: ple2,
348 specChanged: false,
349 },
350 {
351 name: "exempt with wrong tag",
352 expected: ple1,
353 actual: pleWrong,
354 specChanged: true,
355 },
356 {
357 name: "exempt with invalid tag",
358 expected: ple1,
359 actual: pleInvalid,
360 specChanged: true,
361 },
362 }
363 for _, testCase := range testCases {
364 t.Run(testCase.name, func(t *testing.T) {
365 w := !plcSpecEqualish(testCase.expected, testCase.actual)
366 if testCase.specChanged != w {
367 t.Errorf("Expected priorityLevelSpecChanged to return %t, but got: %t - diff: %s", testCase.specChanged, w,
368 cmp.Diff(testCase.expected, testCase.actual))
369 }
370 })
371 }
372 }
373
374 func TestRemovePriorityLevelConfiguration(t *testing.T) {
375 tests := []struct {
376 name string
377 current *flowcontrolv1.PriorityLevelConfiguration
378 bootstrapName string
379 removeExpected bool
380 }{
381 {
382 name: "no priority level configuration objects exist",
383 bootstrapName: "pl1",
384 current: nil,
385 },
386 {
387 name: "priority level configuration not wanted, auto update is enabled",
388 bootstrapName: "pl0",
389 current: newPLConfiguration("pl1").WithAutoUpdateAnnotation("true").Object(),
390 removeExpected: true,
391 },
392 {
393 name: "priority level configuration not wanted, auto update is disabled",
394 bootstrapName: "pl0",
395 current: newPLConfiguration("pl1").WithAutoUpdateAnnotation("false").Object(),
396 removeExpected: false,
397 },
398 {
399 name: "priority level configuration not wanted, the auto-update annotation is malformed",
400 bootstrapName: "pl0",
401 current: newPLConfiguration("pl1").WithAutoUpdateAnnotation("invalid").Object(),
402 removeExpected: false,
403 },
404 {
405 name: "priority level configuration wanted, auto update is enabled",
406 bootstrapName: "pl1",
407 current: newPLConfiguration("pl1").WithAutoUpdateAnnotation("true").Object(),
408 removeExpected: false,
409 },
410 {
411 name: "priority level configuration wanted, auto update is disabled",
412 bootstrapName: "pl1",
413 current: newPLConfiguration("pl1").WithAutoUpdateAnnotation("false").Object(),
414 removeExpected: false,
415 },
416 {
417 name: "priority level configuration wanted, the auto-update annotation is malformed",
418 bootstrapName: "pl1",
419 current: newPLConfiguration("pl1").WithAutoUpdateAnnotation("invalid").Object(),
420 removeExpected: false,
421 },
422 }
423
424 for _, test := range tests {
425 t.Run(test.name, func(t *testing.T) {
426 client := fake.NewSimpleClientset().FlowcontrolV1().PriorityLevelConfigurations()
427 indexer := toolscache.NewIndexer(toolscache.MetaNamespaceKeyFunc, toolscache.Indexers{})
428 if test.current != nil {
429 client.Create(context.TODO(), test.current, metav1.CreateOptions{})
430 indexer.Add(test.current)
431 }
432
433 boot := newPLConfiguration(test.bootstrapName).Object()
434 boots := []*flowcontrolv1.PriorityLevelConfiguration{boot}
435 ops := NewPriorityLevelConfigurationOps(client, flowcontrollisters.NewPriorityLevelConfigurationLister(indexer))
436 err := RemoveUnwantedObjects(context.Background(), ops, boots)
437 if err != nil {
438 t.Fatalf("Expected no error, but got: %v", err)
439 }
440
441 if test.current == nil {
442 return
443 }
444 _, err = client.Get(context.TODO(), test.current.Name, metav1.GetOptions{})
445 switch {
446 case test.removeExpected:
447 if !apierrors.IsNotFound(err) {
448 t.Errorf("Expected error: %q, but got: %v", metav1.StatusReasonNotFound, err)
449 }
450 default:
451 if err != nil {
452 t.Errorf("Expected no error, but got: %v", err)
453 }
454 }
455 })
456 }
457 }
458
459 type plBuilder struct {
460 object *flowcontrolv1.PriorityLevelConfiguration
461 }
462
463 func newPLConfiguration(name string) *plBuilder {
464 return &plBuilder{
465 object: &flowcontrolv1.PriorityLevelConfiguration{
466 ObjectMeta: metav1.ObjectMeta{
467 Name: name,
468 },
469 },
470 }
471 }
472
473 func (b *plBuilder) Object() *flowcontrolv1.PriorityLevelConfiguration {
474 return b.object
475 }
476
477 func (b *plBuilder) WithGeneration(value int64) *plBuilder {
478 b.object.SetGeneration(value)
479 return b
480 }
481
482 func (b *plBuilder) WithAutoUpdateAnnotation(value string) *plBuilder {
483 setAnnotation(b.object, value)
484 return b
485 }
486
487 func (b *plBuilder) WithLimited(nominalConcurrencyShares int32) *plBuilder {
488 b.object.Spec.Type = flowcontrolv1.PriorityLevelEnablementLimited
489 b.object.Spec.Limited = &flowcontrolv1.LimitedPriorityLevelConfiguration{
490 NominalConcurrencyShares: ptr.To(nominalConcurrencyShares),
491 LendablePercent: pointer.Int32(0),
492 LimitResponse: flowcontrolv1.LimitResponse{
493 Type: flowcontrolv1.LimitResponseTypeReject,
494 },
495 }
496 return b
497 }
498
499
500 func (b *plBuilder) WithQueuing(queues, handSize, queueLengthLimit int32) *plBuilder {
501 limited := b.object.Spec.Limited
502 if limited == nil {
503 return b
504 }
505
506 limited.LimitResponse.Type = flowcontrolv1.LimitResponseTypeQueue
507 limited.LimitResponse.Queuing = &flowcontrolv1.QueuingConfiguration{
508 Queues: queues,
509 HandSize: handSize,
510 QueueLengthLimit: queueLengthLimit,
511 }
512
513 return b
514 }
515
View as plain text