@@ -18,6 +18,7 @@ package core
18
18
19
19
import (
20
20
"bytes"
21
+ stdcontext "context"
21
22
"flag"
22
23
"fmt"
23
24
"os"
@@ -459,7 +460,7 @@ func TestStaticAutoscalerRunOnce(t *testing.T) {
459
460
mock .AssertExpectationsForObjects (t , allPodListerMock ,
460
461
podDisruptionBudgetListerMock , daemonSetListerMock , onScaleUpMock , onScaleDownMock )
461
462
462
- // Scale up to node gorup min size.
463
+ // Scale up to node group min size.
463
464
readyNodeLister .SetNodes ([]* apiv1.Node {n4 })
464
465
allNodeLister .SetNodes ([]* apiv1.Node {n4 })
465
466
allPodListerMock .On ("List" ).Return ([]* apiv1.Pod {}, nil ).Twice ()
@@ -1337,6 +1338,106 @@ func TestStaticAutoscalerRunOnceWithFilteringOnUpcomingNodesEnabledNoScaleUp(t *
1337
1338
podDisruptionBudgetListerMock , daemonSetListerMock , onScaleUpMock , onScaleDownMock )
1338
1339
}
1339
1340
1341
+ // We should not touch taints from unselected node groups.
1342
+ func TestStaticAutoscalerRunOnceWithUnselectedNodeGroups (t * testing.T ) {
1343
+ n1 := BuildTestNode ("n1" , 1000 , 1000 )
1344
+ n1 .Spec .Taints = append (n1 .Spec .Taints , apiv1.Taint {
1345
+ Key : taints .DeletionCandidateTaint ,
1346
+ Value : fmt .Sprint (time .Now ().Unix ()),
1347
+ Effect : apiv1 .TaintEffectPreferNoSchedule ,
1348
+ })
1349
+ SetNodeReadyState (n1 , true , time .Now ())
1350
+ n2 := BuildTestNode ("n2" , 1000 , 1000 )
1351
+ n2 .Spec .Taints = append (n2 .Spec .Taints , apiv1.Taint {
1352
+ Key : taints .DeletionCandidateTaint ,
1353
+ Value : fmt .Sprint (time .Now ().Unix ()),
1354
+ Effect : apiv1 .TaintEffectPreferNoSchedule ,
1355
+ })
1356
+ SetNodeReadyState (n2 , true , time .Now ())
1357
+
1358
+ p1 := BuildTestPod ("p1" , 600 , 100 )
1359
+ p1 .Spec .NodeName = n1 .Name
1360
+
1361
+ // set minimal cloud provider where only ng1 is defined as selected node group
1362
+ provider := testprovider .NewTestCloudProvider (nil , nil )
1363
+ provider .AddNodeGroup ("ng1" , 1 , 10 , 1 )
1364
+ provider .AddNode ("ng1" , n1 )
1365
+ assert .NotNil (t , provider )
1366
+
1367
+ tests := map [string ]struct {
1368
+ node * apiv1.Node
1369
+ pods []* apiv1.Pod
1370
+ expectedTaints []apiv1.Taint
1371
+ }{
1372
+ "Node from selected node groups can get their deletion candidate taints removed" : {
1373
+ node : n1 ,
1374
+ pods : []* apiv1.Pod {p1 },
1375
+ expectedTaints : []apiv1.Taint {},
1376
+ },
1377
+ "Node from non-selected node groups should keep their deletion candidate taints" : {
1378
+ node : n2 ,
1379
+ pods : nil ,
1380
+ expectedTaints : n2 .Spec .Taints ,
1381
+ },
1382
+ }
1383
+
1384
+ for name , test := range tests {
1385
+ // prevent issues with scoping, we should be able to get rid of that with Go 1.22
1386
+ test := test
1387
+ t .Run (name , func (t * testing.T ) {
1388
+ t .Parallel ()
1389
+ // Create fake listers for the generated nodes, nothing returned by the rest (but the ones used in the tested path have to be defined).
1390
+ readyNodeLister := kubernetes .NewTestNodeLister ([]* apiv1.Node {test .node })
1391
+ allNodeLister := kubernetes .NewTestNodeLister ([]* apiv1.Node {test .node })
1392
+ allPodListerMock := kubernetes .NewTestPodLister (test .pods )
1393
+ daemonSetLister , err := kubernetes .NewTestDaemonSetLister (nil )
1394
+ assert .NoError (t , err )
1395
+ listerRegistry := kube_util .NewListerRegistry (allNodeLister , readyNodeLister , allPodListerMock ,
1396
+ kubernetes .NewTestPodDisruptionBudgetLister (nil ), daemonSetLister ,
1397
+ nil , nil , nil , nil )
1398
+
1399
+ // Create context with minimal autoscalingOptions that guarantee we reach the tested logic.
1400
+ autoscalingOptions := config.AutoscalingOptions {
1401
+ ScaleDownEnabled : true ,
1402
+ MaxBulkSoftTaintCount : 10 ,
1403
+ MaxBulkSoftTaintTime : 3 * time .Second ,
1404
+ }
1405
+ processorCallbacks := newStaticAutoscalerProcessorCallbacks ()
1406
+ clientset := fake .NewSimpleClientset (test .node )
1407
+ context , err := NewScaleTestAutoscalingContext (autoscalingOptions , clientset , listerRegistry , provider , processorCallbacks , nil )
1408
+ assert .NoError (t , err )
1409
+
1410
+ // Create CSR with unhealthy cluster protection effectively disabled, to guarantee we reach the tested logic.
1411
+ clusterStateConfig := clusterstate.ClusterStateRegistryConfig {
1412
+ OkTotalUnreadyCount : 1 ,
1413
+ }
1414
+ clusterState := clusterstate .NewClusterStateRegistry (provider , clusterStateConfig , context .LogRecorder , NewBackoff (), nodegroupconfig .NewDefaultNodeGroupConfigProcessor (autoscalingOptions .NodeGroupDefaults ))
1415
+
1416
+ // Setting the Actuator is necessary for testing any scale-down logic, it shouldn't have anything to do in this test.
1417
+ sdActuator := actuation .NewActuator (& context , clusterState , deletiontracker .NewNodeDeletionTracker (0 * time .Second ), options.NodeDeleteOptions {}, nil , NewTestProcessors (& context ).NodeGroupConfigProcessor )
1418
+ context .ScaleDownActuator = sdActuator
1419
+
1420
+ // Fake planner that keeps track of the scale-down candidates passed to UpdateClusterState.
1421
+ sdPlanner := & candidateTrackingFakePlanner {}
1422
+
1423
+ autoscaler := & StaticAutoscaler {
1424
+ AutoscalingContext : & context ,
1425
+ clusterStateRegistry : clusterState ,
1426
+ scaleDownPlanner : sdPlanner ,
1427
+ scaleDownActuator : sdActuator ,
1428
+ processors : NewTestProcessors (& context ),
1429
+ processorCallbacks : processorCallbacks ,
1430
+ }
1431
+
1432
+ err = autoscaler .RunOnce (time .Now ().Add (5 * time .Hour ))
1433
+ assert .NoError (t , err )
1434
+ newNode , err := clientset .CoreV1 ().Nodes ().Get (stdcontext .TODO (), test .node .Name , metav1.GetOptions {})
1435
+ assert .NoError (t , err )
1436
+ assert .Equal (t , test .expectedTaints , newNode .Spec .Taints )
1437
+ })
1438
+ }
1439
+ }
1440
+
1340
1441
func TestStaticAutoscalerRunOnceWithBypassedSchedulers (t * testing.T ) {
1341
1442
bypassedScheduler := "bypassed-scheduler"
1342
1443
nonBypassedScheduler := "non-bypassed-scheduler"
0 commit comments