Skip to content

Commit 87a67e3

Browse files
authored
Merge pull request #7995 from abdelrahman882/cleaningSoftTaintsTesting
Add unit test for cleaning deletion soft taint in scale down cool down
2 parents afc3eaf + dd125d4 commit 87a67e3

File tree

1 file changed

+167
-54
lines changed

1 file changed

+167
-54
lines changed

cluster-autoscaler/core/static_autoscaler_test.go

Lines changed: 167 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -1429,78 +1429,39 @@ func TestStaticAutoscalerRunOnceWithUnselectedNodeGroups(t *testing.T) {
14291429
provider.AddNode("ng1", n1)
14301430
assert.NotNil(t, provider)
14311431

1432-
tests := map[string]struct {
1432+
tests := []struct {
1433+
name string
14331434
node *apiv1.Node
14341435
pods []*apiv1.Pod
14351436
expectedTaints []apiv1.Taint
14361437
}{
1437-
"Node from selected node groups can get their deletion candidate taints removed": {
1438+
{
1439+
name: "Node from selected node groups can get their deletion candidate taints removed",
14381440
node: n1,
14391441
pods: []*apiv1.Pod{p1},
14401442
expectedTaints: []apiv1.Taint{},
14411443
},
1442-
"Node from non-selected node groups should keep their deletion candidate taints": {
1444+
{
1445+
name: "Node from non-selected node groups should keep their deletion candidate taints",
14431446
node: n2,
14441447
pods: nil,
14451448
expectedTaints: n2.Spec.Taints,
14461449
},
14471450
}
14481451

1449-
for name, test := range tests {
1450-
// prevent issues with scoping, we should be able to get rid of that with Go 1.22
1451-
test := test
1452-
t.Run(name, func(t *testing.T) {
1452+
for _, test := range tests {
1453+
t.Run(test.name, func(t *testing.T) {
14531454
t.Parallel()
1454-
// Create fake listers for the generated nodes, nothing returned by the rest (but the ones used in the tested path have to be defined).
1455-
readyNodeLister := kubernetes.NewTestNodeLister([]*apiv1.Node{test.node})
1456-
allNodeLister := kubernetes.NewTestNodeLister([]*apiv1.Node{test.node})
1457-
allPodListerMock := kubernetes.NewTestPodLister(test.pods)
1458-
daemonSetLister, err := kubernetes.NewTestDaemonSetLister(nil)
1459-
assert.NoError(t, err)
1460-
listerRegistry := kube_util.NewListerRegistry(allNodeLister, readyNodeLister, allPodListerMock,
1461-
kubernetes.NewTestPodDisruptionBudgetLister(nil), daemonSetLister,
1462-
nil, nil, nil, nil)
1455+
allNodes := []*apiv1.Node{test.node}
1456+
fakeClient := buildFakeClient(t, allNodes...)
1457+
autoscaler := buildStaticAutoscaler(t, provider, allNodes, allNodes, fakeClient)
14631458

1464-
// Create context with minimal autoscalingOptions that guarantee we reach the tested logic.
1465-
autoscalingOptions := config.AutoscalingOptions{
1466-
ScaleDownEnabled: true,
1467-
MaxBulkSoftTaintCount: 10,
1468-
MaxBulkSoftTaintTime: 3 * time.Second,
1469-
}
1470-
processorCallbacks := newStaticAutoscalerProcessorCallbacks()
1471-
clientset := fake.NewSimpleClientset(test.node)
1472-
context, err := NewScaleTestAutoscalingContext(autoscalingOptions, clientset, listerRegistry, provider, processorCallbacks, nil)
1459+
runningTime := time.Now()
1460+
err := autoscaler.RunOnce(runningTime)
14731461
assert.NoError(t, err)
14741462

1475-
// Create CSR with unhealthy cluster protection effectively disabled, to guarantee we reach the tested logic.
1476-
clusterStateConfig := clusterstate.ClusterStateRegistryConfig{
1477-
OkTotalUnreadyCount: 1,
1478-
}
1479-
processors := processorstest.NewTestProcessors(&context)
1480-
1481-
clusterState := clusterstate.NewClusterStateRegistry(provider, clusterStateConfig, context.LogRecorder, NewBackoff(), nodegroupconfig.NewDefaultNodeGroupConfigProcessor(autoscalingOptions.NodeGroupDefaults), processors.AsyncNodeGroupStateChecker)
1482-
1483-
// Setting the Actuator is necessary for testing any scale-down logic, it shouldn't have anything to do in this test.
1484-
sdActuator := actuation.NewActuator(&context, clusterState, deletiontracker.NewNodeDeletionTracker(0*time.Second), options.NodeDeleteOptions{}, nil, processors.NodeGroupConfigProcessor, nil)
1485-
context.ScaleDownActuator = sdActuator
1486-
1487-
// Fake planner that keeps track of the scale-down candidates passed to UpdateClusterState.
1488-
sdPlanner := &candidateTrackingFakePlanner{}
1489-
1490-
autoscaler := &StaticAutoscaler{
1491-
AutoscalingContext: &context,
1492-
clusterStateRegistry: clusterState,
1493-
scaleDownPlanner: sdPlanner,
1494-
scaleDownActuator: sdActuator,
1495-
processors: processors,
1496-
loopStartNotifier: loopstart.NewObserversList(nil),
1497-
processorCallbacks: processorCallbacks,
1498-
}
1499-
1500-
err = autoscaler.RunOnce(time.Now().Add(5 * time.Hour))
1501-
assert.NoError(t, err)
1502-
newNode, err := clientset.CoreV1().Nodes().Get(stdcontext.TODO(), test.node.Name, metav1.GetOptions{})
1503-
assert.NoError(t, err)
1463+
newNode, clientErr := fakeClient.CoreV1().Nodes().Get(stdcontext.TODO(), test.node.Name, metav1.GetOptions{})
1464+
assert.NoError(t, clientErr)
15041465
assert.Equal(t, test.expectedTaints, newNode.Spec.Taints)
15051466
})
15061467
}
@@ -2773,3 +2734,155 @@ func newEstimatorBuilder() estimator.EstimatorBuilder {
27732734

27742735
return estimatorBuilder
27752736
}
2737+
2738+
func TestCleaningSoftTaintsInScaleDown(t *testing.T) {
2739+
2740+
provider := testprovider.NewTestCloudProvider(nil, nil)
2741+
2742+
minSizeNgName := "ng-min-size"
2743+
nodesToHaveNoTaints := createNodeGroupWithSoftTaintedNodes(provider, minSizeNgName, 2, 10, 2)
2744+
2745+
notSizeNgName := "ng"
2746+
nodesToHaveTaints := createNodeGroupWithSoftTaintedNodes(provider, notSizeNgName, 3, 10, 4)
2747+
2748+
tests := []struct {
2749+
name string
2750+
testNodes []*apiv1.Node
2751+
expectedScaleDownCoolDown bool
2752+
expectedNodesWithSoftTaints []*apiv1.Node
2753+
expectedNodesWithNoSoftTaints []*apiv1.Node
2754+
}{
2755+
{
2756+
name: "Soft tainted nodes are cleaned in case of scale down is in cool down",
2757+
testNodes: nodesToHaveNoTaints,
2758+
expectedScaleDownCoolDown: true,
2759+
expectedNodesWithSoftTaints: []*apiv1.Node{},
2760+
expectedNodesWithNoSoftTaints: nodesToHaveNoTaints,
2761+
},
2762+
{
2763+
name: "Soft tainted nodes are not cleaned in case of scale down isn't in cool down",
2764+
testNodes: nodesToHaveTaints,
2765+
expectedScaleDownCoolDown: false,
2766+
expectedNodesWithSoftTaints: nodesToHaveTaints,
2767+
expectedNodesWithNoSoftTaints: []*apiv1.Node{},
2768+
},
2769+
{
2770+
name: "Soft tainted nodes are cleaned only from min sized node group in case of scale down isn't in cool down",
2771+
testNodes: append(nodesToHaveNoTaints, nodesToHaveTaints...),
2772+
expectedScaleDownCoolDown: false,
2773+
expectedNodesWithSoftTaints: nodesToHaveTaints,
2774+
expectedNodesWithNoSoftTaints: nodesToHaveNoTaints,
2775+
},
2776+
}
2777+
for _, test := range tests {
2778+
t.Run(test.name, func(t *testing.T) {
2779+
t.Parallel()
2780+
fakeClient := buildFakeClient(t, test.testNodes...)
2781+
2782+
autoscaler := buildStaticAutoscaler(t, provider, test.testNodes, test.testNodes, fakeClient)
2783+
2784+
err := autoscaler.RunOnce(time.Now())
2785+
2786+
assert.NoError(t, err)
2787+
candidates, _ := autoscaler.processors.ScaleDownNodeProcessor.GetScaleDownCandidates(autoscaler.AutoscalingContext, test.testNodes)
2788+
assert.Equal(t, test.expectedScaleDownCoolDown, autoscaler.isScaleDownInCooldown(time.Now(), candidates))
2789+
2790+
assertNodesSoftTaintsStatus(t, fakeClient, test.expectedNodesWithSoftTaints, true)
2791+
assertNodesSoftTaintsStatus(t, fakeClient, test.expectedNodesWithNoSoftTaints, false)
2792+
})
2793+
}
2794+
}
2795+
2796+
func buildStaticAutoscaler(t *testing.T, provider cloudprovider.CloudProvider, allNodes []*apiv1.Node, readyNodes []*apiv1.Node, fakeClient *fake.Clientset) *StaticAutoscaler {
2797+
autoscalingOptions := config.AutoscalingOptions{
2798+
NodeGroupDefaults: config.NodeGroupAutoscalingOptions{
2799+
ScaleDownUnneededTime: time.Minute,
2800+
ScaleDownUnreadyTime: time.Minute,
2801+
ScaleDownUtilizationThreshold: 0.5,
2802+
MaxNodeProvisionTime: 10 * time.Second,
2803+
},
2804+
MaxScaleDownParallelism: 10,
2805+
MaxDrainParallelism: 1,
2806+
ScaleDownEnabled: true,
2807+
MaxBulkSoftTaintCount: 20,
2808+
MaxBulkSoftTaintTime: 5 * time.Second,
2809+
NodeDeleteDelayAfterTaint: 5 * time.Minute,
2810+
ScaleDownSimulationTimeout: 10 * time.Second,
2811+
}
2812+
2813+
allNodeLister := kubernetes.NewTestNodeLister(allNodes)
2814+
readyNodeLister := kubernetes.NewTestNodeLister(readyNodes)
2815+
2816+
daemonSetLister, err := kubernetes.NewTestDaemonSetLister(nil)
2817+
assert.NoError(t, err)
2818+
listerRegistry := kube_util.NewListerRegistry(allNodeLister, readyNodeLister,
2819+
kubernetes.NewTestPodLister(nil),
2820+
kubernetes.NewTestPodDisruptionBudgetLister(nil), daemonSetLister, nil, nil, nil, nil)
2821+
2822+
processorCallbacks := newStaticAutoscalerProcessorCallbacks()
2823+
2824+
ctx, err := NewScaleTestAutoscalingContext(autoscalingOptions, fakeClient, listerRegistry, provider, processorCallbacks, nil)
2825+
assert.NoError(t, err)
2826+
2827+
processors := processorstest.NewTestProcessors(&ctx)
2828+
cp := scaledowncandidates.NewCombinedScaleDownCandidatesProcessor()
2829+
cp.Register(scaledowncandidates.NewScaleDownCandidatesSortingProcessor([]scaledowncandidates.CandidatesComparer{}))
2830+
processors.ScaleDownNodeProcessor = cp
2831+
2832+
csr := clusterstate.NewClusterStateRegistry(provider, clusterstate.ClusterStateRegistryConfig{OkTotalUnreadyCount: 1}, ctx.LogRecorder, NewBackoff(), nodegroupconfig.NewDefaultNodeGroupConfigProcessor(config.NodeGroupAutoscalingOptions{MaxNodeProvisionTime: 15 * time.Minute}), processors.AsyncNodeGroupStateChecker)
2833+
actuator := actuation.NewActuator(&ctx, csr, deletiontracker.NewNodeDeletionTracker(0*time.Second), options.NodeDeleteOptions{}, nil, processors.NodeGroupConfigProcessor, nil)
2834+
ctx.ScaleDownActuator = actuator
2835+
2836+
deleteOptions := options.NewNodeDeleteOptions(ctx.AutoscalingOptions)
2837+
drainabilityRules := rules.Default(deleteOptions)
2838+
2839+
sdPlanner := planner.New(&ctx, processors, deleteOptions, drainabilityRules)
2840+
2841+
autoscaler := &StaticAutoscaler{
2842+
AutoscalingContext: &ctx,
2843+
clusterStateRegistry: csr,
2844+
scaleDownActuator: actuator,
2845+
scaleDownPlanner: sdPlanner,
2846+
processors: processors,
2847+
loopStartNotifier: loopstart.NewObserversList(nil),
2848+
processorCallbacks: processorCallbacks,
2849+
}
2850+
return autoscaler
2851+
}
2852+
2853+
func buildFakeClient(t *testing.T, nodes ...*apiv1.Node) *fake.Clientset {
2854+
fakeClient := fake.NewSimpleClientset()
2855+
for _, node := range nodes {
2856+
_, err := fakeClient.CoreV1().Nodes().Create(stdcontext.TODO(), node, metav1.CreateOptions{})
2857+
assert.NoError(t, err)
2858+
}
2859+
return fakeClient
2860+
}
2861+
2862+
func createNodeGroupWithSoftTaintedNodes(provider *testprovider.TestCloudProvider, name string, minSize int, maxSize int, size int) []*apiv1.Node {
2863+
nodesCreationTime := time.Time{}
2864+
var ngNodes []*apiv1.Node
2865+
ng := provider.BuildNodeGroup(name, minSize, maxSize, size, true, false, "", nil)
2866+
provider.InsertNodeGroup(ng)
2867+
for i := range size {
2868+
node := BuildTestNode(fmt.Sprintf("%s-node-%d", name, i), 2000, 1000)
2869+
node.CreationTimestamp = metav1.NewTime(nodesCreationTime)
2870+
node.Spec.Taints = []apiv1.Taint{{
2871+
Key: taints.DeletionCandidateTaint,
2872+
Value: "1",
2873+
Effect: apiv1.TaintEffectNoSchedule,
2874+
}}
2875+
SetNodeReadyState(node, true, nodesCreationTime)
2876+
ngNodes = append(ngNodes, node)
2877+
provider.AddNode(ng.Id(), node)
2878+
}
2879+
return ngNodes
2880+
}
2881+
2882+
func assertNodesSoftTaintsStatus(t *testing.T, fakeClient *fake.Clientset, nodes []*apiv1.Node, tainted bool) {
2883+
for _, node := range nodes {
2884+
newNode, clientErr := fakeClient.CoreV1().Nodes().Get(stdcontext.TODO(), node.Name, metav1.GetOptions{})
2885+
assert.NoError(t, clientErr)
2886+
assert.Equal(t, tainted, taints.HasDeletionCandidateTaint(newNode))
2887+
}
2888+
}

0 commit comments

Comments
 (0)