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 "k8s.io/klog/v2"
32 flowcontrolapisv1 "k8s.io/kubernetes/pkg/apis/flowcontrol/v1"
33
34 "github.com/google/go-cmp/cmp"
35 "github.com/stretchr/testify/assert"
36 )
37
38 func init() {
39 klog.InitFlags(nil)
40 }
41
42 func TestEnsureFlowSchema(t *testing.T) {
43 tests := []struct {
44 name string
45 strategy func() EnsureStrategy[*flowcontrolv1.FlowSchema]
46 current *flowcontrolv1.FlowSchema
47 bootstrap *flowcontrolv1.FlowSchema
48 expected *flowcontrolv1.FlowSchema
49 }{
50
51 {
52 name: "suggested flow schema does not exist - the object should always be re-created",
53 strategy: NewSuggestedEnsureStrategy[*flowcontrolv1.FlowSchema],
54 bootstrap: newFlowSchema("fs1", "pl1", 100).Object(),
55 current: nil,
56 expected: newFlowSchema("fs1", "pl1", 100).Object(),
57 },
58 {
59 name: "suggested flow schema exists, auto update is enabled, spec does not match - current object should be updated",
60 strategy: NewSuggestedEnsureStrategy[*flowcontrolv1.FlowSchema],
61 bootstrap: newFlowSchema("fs1", "pl1", 100).Object(),
62 current: newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("true").Object(),
63 expected: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").Object(),
64 },
65 {
66 name: "suggested flow schema exists, auto update is disabled, spec does not match - current object should not be updated",
67 strategy: NewSuggestedEnsureStrategy[*flowcontrolv1.FlowSchema],
68 bootstrap: newFlowSchema("fs1", "pl1", 100).Object(),
69 current: newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("false").Object(),
70 expected: newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("false").Object(),
71 },
72
73
74 {
75 name: "mandatory flow schema does not exist - new object should be created",
76 strategy: NewMandatoryEnsureStrategy[*flowcontrolv1.FlowSchema],
77 bootstrap: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").Object(),
78 current: nil,
79 expected: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").Object(),
80 },
81 {
82 name: "mandatory flow schema exists, annotation is missing - annotation should be added",
83 strategy: NewMandatoryEnsureStrategy[*flowcontrolv1.FlowSchema],
84 bootstrap: newFlowSchema("fs1", "pl1", 100).Object(),
85 current: newFlowSchema("fs1", "pl1", 100).Object(),
86 expected: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").Object(),
87 },
88 {
89 name: "mandatory flow schema exists, auto update is disabled, spec does not match - current object should be updated",
90 strategy: NewMandatoryEnsureStrategy[*flowcontrolv1.FlowSchema],
91 bootstrap: newFlowSchema("fs1", "pl1", 100).Object(),
92 current: newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("false").Object(),
93 expected: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").Object(),
94 },
95 }
96
97 for _, test := range tests {
98 t.Run(test.name, func(t *testing.T) {
99 client := fake.NewSimpleClientset().FlowcontrolV1().FlowSchemas()
100 indexer := toolscache.NewIndexer(toolscache.MetaNamespaceKeyFunc, toolscache.Indexers{})
101 if test.current != nil {
102 client.Create(context.TODO(), test.current, metav1.CreateOptions{})
103 indexer.Add(test.current)
104 }
105
106 ops := NewFlowSchemaOps(client, flowcontrollisters.NewFlowSchemaLister(indexer))
107 boots := []*flowcontrolv1.FlowSchema{test.bootstrap}
108 strategy := test.strategy()
109 err := EnsureConfigurations(context.Background(), ops, boots, strategy)
110 if err != nil {
111 t.Fatalf("Expected no error, but got: %v", err)
112 }
113
114 fsGot, err := client.Get(context.TODO(), test.bootstrap.Name, metav1.GetOptions{})
115 switch {
116 case test.expected == nil:
117 if !apierrors.IsNotFound(err) {
118 t.Fatalf("Expected GET to return an %q error, but got: %v", metav1.StatusReasonNotFound, err)
119 }
120 case err != nil:
121 t.Fatalf("Expected GET to return no error, but got: %v", err)
122 }
123
124 if !reflect.DeepEqual(test.expected, fsGot) {
125 t.Errorf("FlowSchema does not match - diff: %s", cmp.Diff(test.expected, fsGot))
126 }
127 })
128 }
129 }
130
131 func TestSuggestedFSEnsureStrategy_ShouldUpdate(t *testing.T) {
132 tests := []struct {
133 name string
134 current *flowcontrolv1.FlowSchema
135 bootstrap *flowcontrolv1.FlowSchema
136 newObjectExpected *flowcontrolv1.FlowSchema
137 }{
138 {
139 name: "auto update is enabled, first generation, spec does not match - spec update expected",
140 current: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").WithGeneration(1).Object(),
141 bootstrap: newFlowSchema("fs1", "pl1", 200).Object(),
142 newObjectExpected: newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("true").WithGeneration(1).Object(),
143 },
144 {
145 name: "auto update is enabled, first generation, spec matches - no update expected",
146 current: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").WithGeneration(1).Object(),
147 bootstrap: newFlowSchema("fs1", "pl1", 100).Object(),
148 newObjectExpected: nil,
149 },
150 {
151 name: "auto update is enabled, second generation, spec does not match - spec update expected",
152 current: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").WithGeneration(2).Object(),
153 bootstrap: newFlowSchema("fs1", "pl2", 200).Object(),
154 newObjectExpected: newFlowSchema("fs1", "pl2", 200).WithAutoUpdateAnnotation("true").WithGeneration(2).Object(),
155 },
156 {
157 name: "auto update is enabled, second generation, spec matches - no update expected",
158 current: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").WithGeneration(2).Object(),
159 bootstrap: newFlowSchema("fs1", "pl1", 100).Object(),
160 newObjectExpected: nil,
161 },
162 {
163 name: "auto update is disabled, first generation, spec does not match - no update expected",
164 current: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("false").WithGeneration(1).Object(),
165 bootstrap: newFlowSchema("fs1", "pl1", 200).Object(),
166 newObjectExpected: nil,
167 },
168 {
169 name: "auto update is disabled, first generation, spec matches - no update expected",
170 current: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("false").WithGeneration(1).Object(),
171 bootstrap: newFlowSchema("fs1", "pl1", 100).Object(),
172 newObjectExpected: nil,
173 },
174 {
175 name: "auto update is disabled, second generation, spec does not match - no update expected",
176 current: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("false").WithGeneration(2).Object(),
177 bootstrap: newFlowSchema("fs1", "pl2", 200).Object(),
178 newObjectExpected: nil,
179 },
180 {
181 name: "auto update is disabled, second generation, spec matches - no update expected",
182 current: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("false").WithGeneration(2).Object(),
183 bootstrap: newFlowSchema("fs1", "pl1", 100).Object(),
184 newObjectExpected: nil,
185 },
186 {
187 name: "annotation is missing, first generation, spec does not match - both annotation and spec update expected",
188 current: newFlowSchema("fs1", "pl1", 100).WithGeneration(1).Object(),
189 bootstrap: newFlowSchema("fs1", "pl2", 200).Object(),
190 newObjectExpected: newFlowSchema("fs1", "pl2", 200).WithAutoUpdateAnnotation("true").WithGeneration(1).Object(),
191 },
192 {
193 name: "annotation is missing, first generation, spec matches - annotation update is expected",
194 current: newFlowSchema("fs1", "pl1", 100).WithGeneration(1).Object(),
195 bootstrap: newFlowSchema("fs1", "pl1", 100).Object(),
196 newObjectExpected: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("true").WithGeneration(1).Object(),
197 },
198 {
199 name: "annotation is missing, second generation, spec does not match - annotation update is expected",
200 current: newFlowSchema("fs1", "pl1", 100).WithGeneration(2).Object(),
201 bootstrap: newFlowSchema("fs1", "pl2", 200).Object(),
202 newObjectExpected: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("false").WithGeneration(2).Object(),
203 },
204 {
205 name: "annotation is missing, second generation, spec matches - annotation update is expected",
206 current: newFlowSchema("fs1", "pl1", 100).WithGeneration(2).Object(),
207 bootstrap: newFlowSchema("fs1", "pl1", 100).Object(),
208 newObjectExpected: newFlowSchema("fs1", "pl1", 100).WithAutoUpdateAnnotation("false").WithGeneration(2).Object(),
209 },
210 }
211
212 ops := NewFlowSchemaOps(nil, nil)
213 for _, test := range tests {
214 t.Run(test.name, func(t *testing.T) {
215 strategy := NewSuggestedEnsureStrategy[*flowcontrolv1.FlowSchema]()
216 updatableGot, updateGot, err := strategy.ReviseIfNeeded(ops, test.current, test.bootstrap)
217 if err != nil {
218 t.Errorf("Expected no error, but got: %v", err)
219 }
220 if test.newObjectExpected == nil {
221 if updatableGot != nil {
222 t.Errorf("Expected a nil object, but got: %#v", updatableGot)
223 }
224 if updateGot {
225 t.Errorf("Expected update=%t but got: %t", false, updateGot)
226 }
227 return
228 }
229
230 if !updateGot {
231 t.Errorf("Expected update=%t but got: %t", true, updateGot)
232 }
233 if !reflect.DeepEqual(test.newObjectExpected, updatableGot) {
234 t.Errorf("Expected the object to be updated to match - diff: %s", cmp.Diff(test.newObjectExpected, updatableGot))
235 }
236 })
237 }
238 }
239
240 func TestFlowSchemaSpecChanged(t *testing.T) {
241 fs1 := &flowcontrolv1.FlowSchema{
242 Spec: flowcontrolv1.FlowSchemaSpec{},
243 }
244 fs2 := &flowcontrolv1.FlowSchema{
245 Spec: flowcontrolv1.FlowSchemaSpec{
246 MatchingPrecedence: 1,
247 },
248 }
249 fs1Defaulted := &flowcontrolv1.FlowSchema{
250 Spec: flowcontrolv1.FlowSchemaSpec{
251 MatchingPrecedence: flowcontrolapisv1.FlowSchemaDefaultMatchingPrecedence,
252 },
253 }
254 testCases := []struct {
255 name string
256 expected *flowcontrolv1.FlowSchema
257 actual *flowcontrolv1.FlowSchema
258 specChanged bool
259 }{
260 {
261 name: "identical flow-schemas should work",
262 expected: bootstrap.MandatoryFlowSchemaCatchAll,
263 actual: bootstrap.MandatoryFlowSchemaCatchAll,
264 specChanged: false,
265 },
266 {
267 name: "defaulted flow-schemas should work",
268 expected: fs1,
269 actual: fs1Defaulted,
270 specChanged: false,
271 },
272 {
273 name: "non-defaulted flow-schema has wrong spec",
274 expected: fs1,
275 actual: fs2,
276 specChanged: true,
277 },
278 }
279 for _, testCase := range testCases {
280 t.Run(testCase.name, func(t *testing.T) {
281 w := !flowSchemaSpecEqual(testCase.expected, testCase.actual)
282 assert.Equal(t, testCase.specChanged, w)
283 })
284 }
285 }
286
287 func TestRemoveFlowSchema(t *testing.T) {
288 tests := []struct {
289 name string
290 current *flowcontrolv1.FlowSchema
291 bootstrapName string
292 removeExpected bool
293 }{
294 {
295 name: "no flow schema objects exist",
296 bootstrapName: "fs1",
297 current: nil,
298 },
299 {
300 name: "flow schema unwanted, auto update is enabled",
301 bootstrapName: "fs0",
302 current: newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("true").Object(),
303 removeExpected: true,
304 },
305 {
306 name: "flow schema unwanted, auto update is disabled",
307 bootstrapName: "fs0",
308 current: newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("false").Object(),
309 removeExpected: false,
310 },
311 {
312 name: "flow schema unwanted, the auto-update annotation is malformed",
313 bootstrapName: "fs0",
314 current: newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("invalid").Object(),
315 removeExpected: false,
316 },
317 {
318 name: "flow schema wanted, auto update is enabled",
319 bootstrapName: "fs1",
320 current: newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("true").Object(),
321 removeExpected: false,
322 },
323 {
324 name: "flow schema wanted, auto update is disabled",
325 bootstrapName: "fs1",
326 current: newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("false").Object(),
327 removeExpected: false,
328 },
329 {
330 name: "flow schema wanted, the auto-update annotation is malformed",
331 bootstrapName: "fs1",
332 current: newFlowSchema("fs1", "pl1", 200).WithAutoUpdateAnnotation("invalid").Object(),
333 removeExpected: false,
334 },
335 }
336
337 for _, test := range tests {
338 t.Run(test.name, func(t *testing.T) {
339 client := fake.NewSimpleClientset().FlowcontrolV1().FlowSchemas()
340 indexer := toolscache.NewIndexer(toolscache.MetaNamespaceKeyFunc, toolscache.Indexers{})
341 if test.current != nil {
342 client.Create(context.TODO(), test.current, metav1.CreateOptions{})
343 indexer.Add(test.current)
344 }
345 bootFS := newFlowSchema(test.bootstrapName, "pl", 100).Object()
346 ops := NewFlowSchemaOps(client, flowcontrollisters.NewFlowSchemaLister(indexer))
347 boots := []*flowcontrolv1.FlowSchema{bootFS}
348 err := RemoveUnwantedObjects(context.Background(), ops, boots)
349
350 if err != nil {
351 t.Fatalf("Expected no error, but got: %v", err)
352 }
353
354 if test.current == nil {
355 return
356 }
357 _, err = client.Get(context.TODO(), test.current.Name, metav1.GetOptions{})
358 switch {
359 case test.removeExpected:
360 if !apierrors.IsNotFound(err) {
361 t.Errorf("Expected error from Get after Delete: %q, but got: %v", metav1.StatusReasonNotFound, err)
362 }
363 default:
364 if err != nil {
365 t.Errorf("Expected no error from Get after Delete, but got: %v", err)
366 }
367 }
368 })
369 }
370 }
371
372 type fsBuilder struct {
373 object *flowcontrolv1.FlowSchema
374 }
375
376 func newFlowSchema(name, plName string, matchingPrecedence int32) *fsBuilder {
377 return &fsBuilder{
378 object: &flowcontrolv1.FlowSchema{
379 ObjectMeta: metav1.ObjectMeta{
380 Name: name,
381 },
382 Spec: flowcontrolv1.FlowSchemaSpec{
383 PriorityLevelConfiguration: flowcontrolv1.PriorityLevelConfigurationReference{
384 Name: plName,
385 },
386 MatchingPrecedence: matchingPrecedence,
387 },
388 },
389 }
390 }
391
392 func (b *fsBuilder) Object() *flowcontrolv1.FlowSchema {
393 return b.object
394 }
395
396 func (b *fsBuilder) WithGeneration(value int64) *fsBuilder {
397 b.object.SetGeneration(value)
398 return b
399 }
400
401 func (b *fsBuilder) WithAutoUpdateAnnotation(value string) *fsBuilder {
402 setAnnotation(b.object, value)
403 return b
404 }
405
406 func setAnnotation(accessor metav1.Object, value string) {
407 if accessor.GetAnnotations() == nil {
408 accessor.SetAnnotations(map[string]string{})
409 }
410
411 accessor.GetAnnotations()[flowcontrolv1.AutoUpdateAnnotationKey] = value
412 }
413
View as plain text