@@ -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,162 @@ 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+ scaleDownInCoolDown bool
2655+ expectedNodesWithSoftTaints []* apiv1.Node
2656+ expectedNodesWithNoSoftTaints []* apiv1.Node
2657+ }{
2658+ {
2659+ name : "Soft tainted nodes are cleaned when scale down skipped" ,
2660+ testNodes : nodesToHaveNoTaints ,
2661+ scaleDownInCoolDown : false ,
2662+ expectedNodesWithSoftTaints : []* apiv1.Node {},
2663+ expectedNodesWithNoSoftTaints : nodesToHaveNoTaints ,
2664+ },
2665+ {
2666+ name : "Soft tainted nodes are cleaned when scale down in cooldown" ,
2667+ testNodes : nodesToHaveNoTaints ,
2668+ scaleDownInCoolDown : true ,
2669+ expectedNodesWithSoftTaints : []* apiv1.Node {},
2670+ expectedNodesWithNoSoftTaints : nodesToHaveNoTaints ,
2671+ },
2672+ {
2673+ name : "Soft tainted nodes are not cleaned when scale down requested" ,
2674+ testNodes : nodesToHaveTaints ,
2675+ scaleDownInCoolDown : false ,
2676+ expectedNodesWithSoftTaints : nodesToHaveTaints ,
2677+ expectedNodesWithNoSoftTaints : []* apiv1.Node {},
2678+ },
2679+ {
2680+ name : "Soft tainted nodes are cleaned only from min sized node group when scale down requested" ,
2681+ testNodes : append (nodesToHaveNoTaints , nodesToHaveTaints ... ),
2682+ scaleDownInCoolDown : false ,
2683+ expectedNodesWithSoftTaints : nodesToHaveTaints ,
2684+ expectedNodesWithNoSoftTaints : nodesToHaveNoTaints ,
2685+ },
2686+ }
2687+ for _ , test := range tests {
2688+ t .Run (test .name , func (t * testing.T ) {
2689+ t .Parallel ()
2690+ fakeClient := buildFakeClient (t , test .testNodes ... )
2691+
2692+ autoscaler := buildStaticAutoscaler (t , provider , test .testNodes , test .testNodes , fakeClient )
2693+ autoscaler .processorCallbacks .disableScaleDownForLoop = test .scaleDownInCoolDown
2694+ assert .Equal (t , autoscaler .isScaleDownInCooldown (time .Now ()), test .scaleDownInCoolDown )
2695+
2696+ err := autoscaler .RunOnce (time .Now ())
2697+
2698+ assert .NoError (t , err )
2699+
2700+ assertNodesSoftTaintsStatus (t , fakeClient , test .expectedNodesWithSoftTaints , true )
2701+ assertNodesSoftTaintsStatus (t , fakeClient , test .expectedNodesWithNoSoftTaints , false )
2702+ })
2703+ }
2704+ }
2705+
2706+ func buildStaticAutoscaler (t * testing.T , provider cloudprovider.CloudProvider , allNodes []* apiv1.Node , readyNodes []* apiv1.Node , fakeClient * fake.Clientset ) * StaticAutoscaler {
2707+ autoscalingOptions := config.AutoscalingOptions {
2708+ NodeGroupDefaults : config.NodeGroupAutoscalingOptions {
2709+ ScaleDownUnneededTime : time .Minute ,
2710+ ScaleDownUnreadyTime : time .Minute ,
2711+ ScaleDownUtilizationThreshold : 0.5 ,
2712+ MaxNodeProvisionTime : 10 * time .Second ,
2713+ },
2714+ MaxScaleDownParallelism : 10 ,
2715+ MaxDrainParallelism : 1 ,
2716+ ScaleDownEnabled : true ,
2717+ MaxBulkSoftTaintCount : 20 ,
2718+ MaxBulkSoftTaintTime : 5 * time .Second ,
2719+ NodeDeleteDelayAfterTaint : 5 * time .Minute ,
2720+ ScaleDownSimulationTimeout : 10 * time .Second ,
2721+ }
2722+
2723+ allNodeLister := kubernetes .NewTestNodeLister (allNodes )
2724+ readyNodeLister := kubernetes .NewTestNodeLister (readyNodes )
2725+
2726+ daemonSetLister , err := kubernetes .NewTestDaemonSetLister (nil )
2727+ assert .NoError (t , err )
2728+ listerRegistry := kube_util .NewListerRegistry (allNodeLister , readyNodeLister ,
2729+ kubernetes .NewTestPodLister (nil ),
2730+ kubernetes .NewTestPodDisruptionBudgetLister (nil ), daemonSetLister , nil , nil , nil , nil )
2731+
2732+ processorCallbacks := newStaticAutoscalerProcessorCallbacks ()
2733+
2734+ ctx , err := NewScaleTestAutoscalingContext (autoscalingOptions , fakeClient , listerRegistry , provider , processorCallbacks , nil )
2735+ assert .NoError (t , err )
2736+
2737+ processors := NewTestProcessors (& ctx )
2738+ cp := scaledowncandidates .NewCombinedScaleDownCandidatesProcessor ()
2739+ cp .Register (scaledowncandidates .NewScaleDownCandidatesSortingProcessor ([]scaledowncandidates.CandidatesComparer {}))
2740+ processors .ScaleDownNodeProcessor = cp
2741+
2742+ csr := clusterstate .NewClusterStateRegistry (provider , clusterstate.ClusterStateRegistryConfig {OkTotalUnreadyCount : 1 }, ctx .LogRecorder , NewBackoff (), nodegroupconfig .NewDefaultNodeGroupConfigProcessor (config.NodeGroupAutoscalingOptions {MaxNodeProvisionTime : 15 * time .Minute }), processors .AsyncNodeGroupStateChecker )
2743+ actuator := actuation .NewActuator (& ctx , csr , deletiontracker .NewNodeDeletionTracker (0 * time .Second ), options.NodeDeleteOptions {}, nil , processors .NodeGroupConfigProcessor )
2744+ ctx .ScaleDownActuator = actuator
2745+
2746+ deleteOptions := options .NewNodeDeleteOptions (ctx .AutoscalingOptions )
2747+ drainabilityRules := rules .Default (deleteOptions )
2748+
2749+ sdPlanner := planner .New (& ctx , processors , deleteOptions , drainabilityRules )
2750+
2751+ autoscaler := & StaticAutoscaler {
2752+ AutoscalingContext : & ctx ,
2753+ clusterStateRegistry : csr ,
2754+ scaleDownActuator : actuator ,
2755+ scaleDownPlanner : sdPlanner ,
2756+ processors : processors ,
2757+ loopStartNotifier : loopstart .NewObserversList (nil ),
2758+ processorCallbacks : processorCallbacks ,
2759+ }
2760+ return autoscaler
2761+ }
2762+
2763+ func buildFakeClient (t * testing.T , nodes ... * apiv1.Node ) * fake.Clientset {
2764+ fakeClient := fake .NewSimpleClientset ()
2765+ for _ , node := range nodes {
2766+ _ , err := fakeClient .CoreV1 ().Nodes ().Create (stdcontext .TODO (), node , metav1.CreateOptions {})
2767+ assert .NoError (t , err )
2768+ }
2769+ return fakeClient
2770+ }
2771+
2772+ func createNodeGroupWithSoftTaintedNodes (provider * testprovider.TestCloudProvider , name string , minSize int , maxSize int , size int ) []* apiv1.Node {
2773+ nodesCreationTime := time.Time {}
2774+ var ngNodes []* apiv1.Node
2775+ ng := provider .BuildNodeGroup (name , minSize , maxSize , size , true , false , "" , nil )
2776+ provider .InsertNodeGroup (ng )
2777+ for i := range size {
2778+ node := BuildTestNode (fmt .Sprintf ("%s-node-%d" , name , i ), 2000 , 1000 )
2779+ node .CreationTimestamp = metav1 .NewTime (nodesCreationTime )
2780+ node .Spec .Taints = []apiv1.Taint {{
2781+ Key : taints .DeletionCandidateTaint ,
2782+ Value : "1" ,
2783+ Effect : apiv1 .TaintEffectNoSchedule ,
2784+ }}
2785+ SetNodeReadyState (node , true , nodesCreationTime )
2786+ ngNodes = append (ngNodes , node )
2787+ provider .AddNode (ng .Id (), node )
2788+ }
2789+ return ngNodes
2790+ }
2791+
2792+ func assertNodesSoftTaintsStatus (t * testing.T , fakeClient * fake.Clientset , nodes []* apiv1.Node , tainted bool ) {
2793+ for _ , node := range nodes {
2794+ newNode , clientErr := fakeClient .CoreV1 ().Nodes ().Get (stdcontext .TODO (), node .Name , metav1.GetOptions {})
2795+ assert .NoError (t , clientErr )
2796+ assert .Equal (t , tainted , taints .HasDeletionCandidateTaint (newNode ))
2797+ }
2798+ }
0 commit comments