Skip to content

Commit 8e7f5b0

Browse files
miladbarazandehMartin Linkhorst
andauthored
Prevent scaling up stacks without traffic (#542)
* Add e2e test for HPA deletion at downscaling fix updated parameter list of test factory adapt test cases to new struct field * turn on inactive HPA deletion to pass the tests * drop the feature flag to simplify the code * extend e2e test to include scale down of first stack --------- Co-authored-by: Martin Linkhorst <[email protected]>
1 parent 0acab4a commit 8e7f5b0

File tree

3 files changed

+112
-4
lines changed

3 files changed

+112
-4
lines changed

cmd/e2e/generated_autoscaler_test.go

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package main
33
import (
44
"fmt"
55
"testing"
6+
"time"
67

78
"github.com/stretchr/testify/require"
89
zv1 "github.com/zalando-incubator/stackset-controller/pkg/apis/zalando.org/v1"
@@ -75,3 +76,81 @@ func TestGenerateAutoscaler(t *testing.T) {
7576
}
7677
}
7778
}
79+
80+
func TestAutoscalerWithoutTraffic(t *testing.T) {
81+
t.Parallel()
82+
83+
// Create a stackset with two stacks and an autoscaler for each stack
84+
stacksetName := "autoscaler-without-traffic"
85+
metrics := []zv1.AutoscalerMetrics{
86+
makeCPUAutoscalerMetrics(50),
87+
}
88+
factory := NewTestStacksetSpecFactory(stacksetName).Ingress().Autoscaler(1, 3, metrics).StackGC(1, 30)
89+
firstStack := "v1"
90+
fullFirstStack := fmt.Sprintf("%s-%s", stacksetName, firstStack)
91+
spec := factory.Create(t, firstStack)
92+
err := createStackSet(stacksetName, 0, spec)
93+
require.NoError(t, err)
94+
_, err = waitForStack(t, stacksetName, firstStack)
95+
require.NoError(t, err)
96+
_, err = waitForHPA(t, fullFirstStack)
97+
require.NoError(t, err)
98+
99+
secondStack := "v2"
100+
fullSecondStack := fmt.Sprintf("%s-%s", stacksetName, secondStack)
101+
spec = factory.Create(t, secondStack)
102+
err = updateStackset(stacksetName, spec)
103+
require.NoError(t, err)
104+
_, err = waitForStack(t, stacksetName, secondStack)
105+
require.NoError(t, err)
106+
_, err = waitForHPA(t, fullSecondStack)
107+
require.NoError(t, err)
108+
109+
// Switch traffic 100% to the first stack
110+
desiredTraffic := map[string]float64{
111+
fullFirstStack: 100,
112+
fullSecondStack: 0,
113+
}
114+
err = setDesiredTrafficWeightsStackset(stacksetName, desiredTraffic)
115+
require.NoError(t, err)
116+
err = trafficWeightsUpdatedStackset(t, stacksetName, weightKindActual, desiredTraffic, nil).withTimeout(time.Minute * 1).await()
117+
require.NoError(t, err)
118+
119+
// ensure that the HPA for the first stack is still there and that the HPA for the second stack is deleted
120+
err = resourceDeleted(t, "hpa", fullSecondStack, hpaInterface()).withTimeout(time.Minute * 1).await()
121+
require.NoError(t, err)
122+
_, err = waitForHPA(t, fullFirstStack)
123+
require.NoError(t, err)
124+
125+
// Switch traffic 50% to each stack
126+
desiredTraffic = map[string]float64{
127+
fullFirstStack: 50,
128+
fullSecondStack: 50,
129+
}
130+
err = setDesiredTrafficWeightsStackset(stacksetName, desiredTraffic)
131+
require.NoError(t, err)
132+
err = trafficWeightsUpdatedStackset(t, stacksetName, weightKindActual, desiredTraffic, nil).withTimeout(time.Minute * 1).await()
133+
require.NoError(t, err)
134+
135+
// ensure that the HPAs for both stacks are still there
136+
_, err = waitForHPA(t, fullFirstStack)
137+
require.NoError(t, err)
138+
_, err = waitForHPA(t, fullSecondStack)
139+
require.NoError(t, err)
140+
141+
// Switch traffic 100% to the second stack
142+
desiredTraffic = map[string]float64{
143+
fullFirstStack: 0,
144+
fullSecondStack: 100,
145+
}
146+
err = setDesiredTrafficWeightsStackset(stacksetName, desiredTraffic)
147+
require.NoError(t, err)
148+
err = trafficWeightsUpdatedStackset(t, stacksetName, weightKindActual, desiredTraffic, nil).withTimeout(time.Minute * 1).await()
149+
require.NoError(t, err)
150+
151+
// ensure that the HPA for the first stack is deleted and that the HPA for the second stack is still there
152+
err = resourceDeleted(t, "hpa", fullFirstStack, hpaInterface()).withTimeout(time.Minute * 1).await()
153+
require.NoError(t, err)
154+
_, err = waitForHPA(t, fullSecondStack)
155+
require.NoError(t, err)
156+
}

pkg/core/stack_resources.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -253,6 +253,10 @@ func (sc *StackContainer) generateHPA(toSegment bool) (
253253
return nil, nil
254254
}
255255

256+
if sc.ScaledDown() {
257+
return nil, nil
258+
}
259+
256260
result := &autoscaling.HorizontalPodAutoscaler{
257261
ObjectMeta: sc.resourceMeta(),
258262
TypeMeta: metav1.TypeMeta{

pkg/core/stack_resources_test.go

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1349,6 +1349,7 @@ func TestGenerateHPA(t *testing.T) {
13491349
autoscaler *zv1.Autoscaler
13501350
expectedMinReplicas *int32
13511351
expectedMaxReplicas int32
1352+
noTrafficSince time.Time
13521353
expectedMetrics []autoscaling.MetricSpec
13531354
expectedBehavior *autoscaling.HorizontalPodAutoscalerBehavior
13541355
}{
@@ -1382,6 +1383,24 @@ func TestGenerateHPA(t *testing.T) {
13821383
},
13831384
expectedBehavior: exampleBehavior,
13841385
},
1386+
{
1387+
name: "HPA when stack scaled down",
1388+
autoscaler: &zv1.Autoscaler{
1389+
MinReplicas: &min,
1390+
MaxReplicas: max,
1391+
1392+
Metrics: []zv1.AutoscalerMetrics{
1393+
{
1394+
Type: zv1.CPUAutoscalerMetric,
1395+
AverageUtilization: &utilization,
1396+
},
1397+
},
1398+
Behavior: exampleBehavior,
1399+
},
1400+
noTrafficSince: time.Now().Add(-time.Hour),
1401+
expectedMetrics: nil,
1402+
expectedBehavior: nil,
1403+
},
13851404
} {
13861405
t.Run(tc.name, func(t *testing.T) {
13871406
podTemplate := zv1.PodTemplateSpec{
@@ -1409,14 +1428,20 @@ func TestGenerateHPA(t *testing.T) {
14091428
},
14101429
},
14111430
},
1431+
noTrafficSince: tc.noTrafficSince,
1432+
scaledownTTL: time.Minute,
14121433
}
14131434

14141435
hpa, err := autoscalerContainer.GenerateHPA()
14151436
require.NoError(t, err)
1416-
require.Equal(t, tc.expectedMinReplicas, hpa.Spec.MinReplicas)
1417-
require.Equal(t, tc.expectedMaxReplicas, hpa.Spec.MaxReplicas)
1418-
require.Equal(t, tc.expectedMetrics, hpa.Spec.Metrics)
1419-
require.Equal(t, tc.expectedBehavior, hpa.Spec.Behavior)
1437+
if tc.expectedBehavior == nil {
1438+
require.Nil(t, hpa)
1439+
} else {
1440+
require.Equal(t, tc.expectedMinReplicas, hpa.Spec.MinReplicas)
1441+
require.Equal(t, tc.expectedMaxReplicas, hpa.Spec.MaxReplicas)
1442+
require.Equal(t, tc.expectedMetrics, hpa.Spec.Metrics)
1443+
require.Equal(t, tc.expectedBehavior, hpa.Spec.Behavior)
1444+
}
14201445
})
14211446
}
14221447
}

0 commit comments

Comments
 (0)