k8s.io/kubernetes@v1.29.3/pkg/registry/flowcontrol/ensurer/flowschema_test.go (about) 1 /* 2 Copyright 2021 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 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 // for suggested configurations 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 // for mandatory configurations 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 }