k8s.io/kubernetes@v1.29.3/pkg/registry/flowcontrol/ensurer/prioritylevelconfiguration_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 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 // for suggested configurations 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 // for mandatory configurations 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 // must be called after WithLimited 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 }