Skip to content

Commit 72f9592

Browse files
committed
Add unit test for cleaning deletion soft taints in scale down cool down
Signed-off-by: Jack Francis <[email protected]>
1 parent 9c3fdea commit 72f9592

File tree

1 file changed

+168
-56
lines changed

1 file changed

+168
-56
lines changed

cluster-autoscaler/core/static_autoscaler_test.go

+168-56
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)