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