@@ -38,6 +38,7 @@ import (
3838 "k8s.io/autoscaler/cluster-autoscaler/core/scaledown/actuation"
3939 "k8s.io/autoscaler/cluster-autoscaler/core/scaledown/deletiontracker"
4040 "k8s.io/autoscaler/cluster-autoscaler/core/scaledown/legacy"
41+ "k8s.io/autoscaler/cluster-autoscaler/core/scaledown/planner"
4142 "k8s.io/autoscaler/cluster-autoscaler/core/scaledown/status"
4243 "k8s.io/autoscaler/cluster-autoscaler/core/scaleup/orchestrator"
4344 . "k8s.io/autoscaler/cluster-autoscaler/core/test"
@@ -1393,78 +1394,37 @@ func TestStaticAutoscalerRunOnceWithUnselectedNodeGroups(t *testing.T) {
13931394 provider .AddNode ("ng1" , n1 )
13941395 assert .NotNil (t , provider )
13951396
1396- tests := map [string ]struct {
1397+ tests := []struct {
1398+ name string
13971399 node * apiv1.Node
13981400 pods []* apiv1.Pod
13991401 expectedTaints []apiv1.Taint
14001402 }{
1401- "Node from selected node groups can get their deletion candidate taints removed" : {
1403+ {
1404+ name : "Node from selected node groups can get their deletion candidate taints removed" ,
14021405 node : n1 ,
14031406 pods : []* apiv1.Pod {p1 },
14041407 expectedTaints : []apiv1.Taint {},
14051408 },
1406- "Node from non-selected node groups should keep their deletion candidate taints" : {
1409+ {
1410+ name : "Node from non-selected node groups should keep their deletion candidate taints" ,
14071411 node : n2 ,
14081412 pods : nil ,
14091413 expectedTaints : n2 .Spec .Taints ,
14101414 },
14111415 }
14121416
1413- for name , test := range tests {
1414- // prevent issues with scoping, we should be able to get rid of that with Go 1.22
1415- test := test
1416- t .Run (name , func (t * testing.T ) {
1417+ for _ , test := range tests {
1418+ t .Run (test .name , func (t * testing.T ) {
14171419 t .Parallel ()
1418- // Create fake listers for the generated nodes, nothing returned by the rest (but the ones used in the tested path have to be defined).
1419- readyNodeLister := kubernetes .NewTestNodeLister ([]* apiv1.Node {test .node })
1420- allNodeLister := kubernetes .NewTestNodeLister ([]* apiv1.Node {test .node })
1421- allPodListerMock := kubernetes .NewTestPodLister (test .pods )
1422- daemonSetLister , err := kubernetes .NewTestDaemonSetLister (nil )
1423- assert .NoError (t , err )
1424- listerRegistry := kube_util .NewListerRegistry (allNodeLister , readyNodeLister , allPodListerMock ,
1425- kubernetes .NewTestPodDisruptionBudgetLister (nil ), daemonSetLister ,
1426- nil , nil , nil , nil )
1427-
1428- // Create context with minimal autoscalingOptions that guarantee we reach the tested logic.
1429- autoscalingOptions := config.AutoscalingOptions {
1430- ScaleDownEnabled : true ,
1431- MaxBulkSoftTaintCount : 10 ,
1432- MaxBulkSoftTaintTime : 3 * time .Second ,
1433- }
1434- processorCallbacks := newStaticAutoscalerProcessorCallbacks ()
1435- clientset := fake .NewSimpleClientset (test .node )
1436- context , err := NewScaleTestAutoscalingContext (autoscalingOptions , clientset , listerRegistry , provider , processorCallbacks , nil )
1437- assert .NoError (t , err )
1438-
1439- // Create CSR with unhealthy cluster protection effectively disabled, to guarantee we reach the tested logic.
1440- clusterStateConfig := clusterstate.ClusterStateRegistryConfig {
1441- OkTotalUnreadyCount : 1 ,
1442- }
1443- processors := NewTestProcessors (& context )
1444-
1445- clusterState := clusterstate .NewClusterStateRegistry (provider , clusterStateConfig , context .LogRecorder , NewBackoff (), nodegroupconfig .NewDefaultNodeGroupConfigProcessor (autoscalingOptions .NodeGroupDefaults ), processors .AsyncNodeGroupStateChecker )
1446-
1447- // Setting the Actuator is necessary for testing any scale-down logic, it shouldn't have anything to do in this test.
1448- sdActuator := actuation .NewActuator (& context , clusterState , deletiontracker .NewNodeDeletionTracker (0 * time .Second ), options.NodeDeleteOptions {}, nil , processors .NodeGroupConfigProcessor )
1449- context .ScaleDownActuator = sdActuator
1450-
1451- // Fake planner that keeps track of the scale-down candidates passed to UpdateClusterState.
1452- sdPlanner := & candidateTrackingFakePlanner {}
1453-
1454- autoscaler := & StaticAutoscaler {
1455- AutoscalingContext : & context ,
1456- clusterStateRegistry : clusterState ,
1457- scaleDownPlanner : sdPlanner ,
1458- scaleDownActuator : sdActuator ,
1459- processors : processors ,
1460- loopStartNotifier : loopstart .NewObserversList (nil ),
1461- processorCallbacks : processorCallbacks ,
1462- }
1463-
1464- err = autoscaler .RunOnce (time .Now ().Add (5 * time .Hour ))
1465- assert .NoError (t , err )
1466- newNode , err := clientset .CoreV1 ().Nodes ().Get (stdcontext .TODO (), test .node .Name , metav1.GetOptions {})
1420+ allNodes := []* apiv1.Node {test .node }
1421+ fakeClient := buildFakeClient (t , allNodes ... )
1422+ autoscaler := buildStaticAutoscaler (t , provider , allNodes , allNodes , fakeClient )
1423+ runningTime := time .Now ()
1424+ err := autoscaler .RunOnce (runningTime )
14671425 assert .NoError (t , err )
1426+ newNode , clientErr := fakeClient .CoreV1 ().Nodes ().Get (stdcontext .TODO (), test .node .Name , metav1.GetOptions {})
1427+ assert .NoError (t , clientErr )
14681428 assert .Equal (t , test .expectedTaints , newNode .Spec .Taints )
14691429 })
14701430 }
@@ -2677,3 +2637,155 @@ func newEstimatorBuilder() estimator.EstimatorBuilder {
26772637
26782638 return estimatorBuilder
26792639}
2640+
2641+ func TestCleaningSoftTaintsInScaleDown (t * testing.T ) {
2642+
2643+ provider := testprovider .NewTestCloudProvider (nil , nil )
2644+
2645+ minSizeNgName := "ng-min-size"
2646+ nodesToHaveNoTaints := createNodeGroupWithSoftTaintedNodes (provider , minSizeNgName , 2 , 10 , 2 )
2647+
2648+ notSizeNgName := "ng"
2649+ nodesToHaveTaints := createNodeGroupWithSoftTaintedNodes (provider , notSizeNgName , 3 , 10 , 4 )
2650+
2651+ tests := []struct {
2652+ name string
2653+ testNodes []* apiv1.Node
2654+ expectedScaleDownCoolDown bool
2655+ expectedNodesWithSoftTaints []* apiv1.Node
2656+ expectedNodesWithNoSoftTaints []* apiv1.Node
2657+ }{
2658+ {
2659+ name : "Soft tainted nodes are cleaned in case of scale down is in cool down" ,
2660+ testNodes : nodesToHaveNoTaints ,
2661+ expectedScaleDownCoolDown : true ,
2662+ expectedNodesWithSoftTaints : []* apiv1.Node {},
2663+ expectedNodesWithNoSoftTaints : nodesToHaveNoTaints ,
2664+ },
2665+ {
2666+ name : "Soft tainted nodes are not cleaned in case of scale down isn't in cool down" ,
2667+ testNodes : nodesToHaveTaints ,
2668+ expectedScaleDownCoolDown : false ,
2669+ expectedNodesWithSoftTaints : nodesToHaveTaints ,
2670+ expectedNodesWithNoSoftTaints : []* apiv1.Node {},
2671+ },
2672+ {
2673+ name : "Soft tainted nodes are cleaned only from min sized node group in case of scale down isn't in cool down" ,
2674+ testNodes : append (nodesToHaveNoTaints , nodesToHaveTaints ... ),
2675+ expectedScaleDownCoolDown : false ,
2676+ expectedNodesWithSoftTaints : nodesToHaveTaints ,
2677+ expectedNodesWithNoSoftTaints : nodesToHaveNoTaints ,
2678+ },
2679+ }
2680+ for _ , test := range tests {
2681+ t .Run (test .name , func (t * testing.T ) {
2682+ t .Parallel ()
2683+ fakeClient := buildFakeClient (t , test .testNodes ... )
2684+
2685+ autoscaler := buildStaticAutoscaler (t , provider , test .testNodes , test .testNodes , fakeClient )
2686+
2687+ err := autoscaler .RunOnce (time .Now ())
2688+
2689+ assert .NoError (t , err )
2690+ candidates , _ := autoscaler .processors .ScaleDownNodeProcessor .GetScaleDownCandidates (autoscaler .AutoscalingContext , test .testNodes )
2691+ assert .Equal (t , test .expectedScaleDownCoolDown , autoscaler .isScaleDownInCooldown (time .Now (), candidates ))
2692+
2693+ assertNodesSoftTaintsStatus (t , fakeClient , test .expectedNodesWithSoftTaints , true )
2694+ assertNodesSoftTaintsStatus (t , fakeClient , test .expectedNodesWithNoSoftTaints , false )
2695+ })
2696+ }
2697+ }
2698+
2699+ func buildStaticAutoscaler (t * testing.T , provider cloudprovider.CloudProvider , allNodes []* apiv1.Node , readyNodes []* apiv1.Node , fakeClient * fake.Clientset ) * StaticAutoscaler {
2700+ autoscalingOptions := config.AutoscalingOptions {
2701+ NodeGroupDefaults : config.NodeGroupAutoscalingOptions {
2702+ ScaleDownUnneededTime : time .Minute ,
2703+ ScaleDownUnreadyTime : time .Minute ,
2704+ ScaleDownUtilizationThreshold : 0.5 ,
2705+ MaxNodeProvisionTime : 10 * time .Second ,
2706+ },
2707+ MaxScaleDownParallelism : 10 ,
2708+ MaxDrainParallelism : 1 ,
2709+ ScaleDownEnabled : true ,
2710+ MaxBulkSoftTaintCount : 20 ,
2711+ MaxBulkSoftTaintTime : 5 * time .Second ,
2712+ NodeDeleteDelayAfterTaint : 5 * time .Minute ,
2713+ ScaleDownSimulationTimeout : 10 * time .Second ,
2714+ }
2715+
2716+ allNodeLister := kubernetes .NewTestNodeLister (allNodes )
2717+ readyNodeLister := kubernetes .NewTestNodeLister (readyNodes )
2718+
2719+ daemonSetLister , err := kubernetes .NewTestDaemonSetLister (nil )
2720+ assert .NoError (t , err )
2721+ listerRegistry := kube_util .NewListerRegistry (allNodeLister , readyNodeLister ,
2722+ kubernetes .NewTestPodLister (nil ),
2723+ kubernetes .NewTestPodDisruptionBudgetLister (nil ), daemonSetLister , nil , nil , nil , nil )
2724+
2725+ processorCallbacks := newStaticAutoscalerProcessorCallbacks ()
2726+
2727+ ctx , err := NewScaleTestAutoscalingContext (autoscalingOptions , fakeClient , listerRegistry , provider , processorCallbacks , nil )
2728+ assert .NoError (t , err )
2729+
2730+ processors := NewTestProcessors (& ctx )
2731+ cp := scaledowncandidates .NewCombinedScaleDownCandidatesProcessor ()
2732+ cp .Register (scaledowncandidates .NewScaleDownCandidatesSortingProcessor ([]scaledowncandidates.CandidatesComparer {}))
2733+ processors .ScaleDownNodeProcessor = cp
2734+
2735+ csr := clusterstate .NewClusterStateRegistry (provider , clusterstate.ClusterStateRegistryConfig {OkTotalUnreadyCount : 1 }, ctx .LogRecorder , NewBackoff (), nodegroupconfig .NewDefaultNodeGroupConfigProcessor (config.NodeGroupAutoscalingOptions {MaxNodeProvisionTime : 15 * time .Minute }), processors .AsyncNodeGroupStateChecker )
2736+ actuator := actuation .NewActuator (& ctx , csr , deletiontracker .NewNodeDeletionTracker (0 * time .Second ), options.NodeDeleteOptions {}, nil , processors .NodeGroupConfigProcessor )
2737+ ctx .ScaleDownActuator = actuator
2738+
2739+ deleteOptions := options .NewNodeDeleteOptions (ctx .AutoscalingOptions )
2740+ drainabilityRules := rules .Default (deleteOptions )
2741+
2742+ sdPlanner := planner .New (& ctx , processors , deleteOptions , drainabilityRules )
2743+
2744+ autoscaler := & StaticAutoscaler {
2745+ AutoscalingContext : & ctx ,
2746+ clusterStateRegistry : csr ,
2747+ scaleDownActuator : actuator ,
2748+ scaleDownPlanner : sdPlanner ,
2749+ processors : processors ,
2750+ loopStartNotifier : loopstart .NewObserversList (nil ),
2751+ processorCallbacks : processorCallbacks ,
2752+ }
2753+ return autoscaler
2754+ }
2755+
2756+ func buildFakeClient (t * testing.T , nodes ... * apiv1.Node ) * fake.Clientset {
2757+ fakeClient := fake .NewSimpleClientset ()
2758+ for _ , node := range nodes {
2759+ _ , err := fakeClient .CoreV1 ().Nodes ().Create (stdcontext .TODO (), node , metav1.CreateOptions {})
2760+ assert .NoError (t , err )
2761+ }
2762+ return fakeClient
2763+ }
2764+
2765+ func createNodeGroupWithSoftTaintedNodes (provider * testprovider.TestCloudProvider , name string , minSize int , maxSize int , size int ) []* apiv1.Node {
2766+ nodesCreationTime := time.Time {}
2767+ var ngNodes []* apiv1.Node
2768+ ng := provider .BuildNodeGroup (name , minSize , maxSize , size , true , false , "" , nil )
2769+ provider .InsertNodeGroup (ng )
2770+ for i := range size {
2771+ node := BuildTestNode (fmt .Sprintf ("%s-node-%d" , name , i ), 2000 , 1000 )
2772+ node .CreationTimestamp = metav1 .NewTime (nodesCreationTime )
2773+ node .Spec .Taints = []apiv1.Taint {{
2774+ Key : taints .DeletionCandidateTaint ,
2775+ Value : "1" ,
2776+ Effect : apiv1 .TaintEffectNoSchedule ,
2777+ }}
2778+ SetNodeReadyState (node , true , nodesCreationTime )
2779+ ngNodes = append (ngNodes , node )
2780+ provider .AddNode (ng .Id (), node )
2781+ }
2782+ return ngNodes
2783+ }
2784+
2785+ func assertNodesSoftTaintsStatus (t * testing.T , fakeClient * fake.Clientset , nodes []* apiv1.Node , tainted bool ) {
2786+ for _ , node := range nodes {
2787+ newNode , clientErr := fakeClient .CoreV1 ().Nodes ().Get (stdcontext .TODO (), node .Name , metav1.GetOptions {})
2788+ assert .NoError (t , clientErr )
2789+ assert .Equal (t , tainted , taints .HasDeletionCandidateTaint (newNode ))
2790+ }
2791+ }
0 commit comments