1
+ /*
2
+ Copyright 2024 The Aibrix Team.
3
+
4
+ Licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use this file except in compliance with the License.
6
+ You may obtain a copy of the License at
7
+
8
+ http://www.apache.org/licenses/LICENSE-2.0
9
+
10
+ Unless required by applicable law or agreed to in writing, software
11
+ distributed under the License is distributed on an "AS IS" BASIS,
12
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
+ See the License for the specific language governing permissions and
14
+ limitations under the License.
15
+ */
16
+
17
+ package algorithm
18
+
19
+ import (
20
+ "testing"
21
+
22
+ "github.com/vllm-project/aibrix/pkg/controller/podautoscaler/common"
23
+ )
24
+
25
+ func TestApaScalingAlgorithm_ComputeTargetReplicas (t * testing.T ) {
26
+ algorithm := & ApaScalingAlgorithm {}
27
+
28
+ tests := []struct {
29
+ name string
30
+ currentPodCount float64
31
+ context * common.MockScalingContext
32
+ expected int32
33
+ description string
34
+ }{
35
+ {
36
+ name : "no_scaling_within_tolerance" ,
37
+ currentPodCount : 3.0 ,
38
+ context : & common.MockScalingContext {
39
+ TargetValue : 50.0 ,
40
+ UpFluctuationTolerance : 0.1 , // 10% tolerance
41
+ DownFluctuationTolerance : 0.2 , // 20% tolerance
42
+ MaxScaleUpRate : 2.0 , // Can scale up by 100%
43
+ MaxScaleDownRate : 2.0 , // Can scale down by 50%
44
+ CurrentUsePerPod : 50.0 , // Exactly at target
45
+ MinReplicas : 1 ,
46
+ MaxReplicas : 10 ,
47
+ },
48
+ expected : 3 ,
49
+ description : "Should not scale when current use per pod equals target value" ,
50
+ },
51
+ {
52
+ name : "no_scaling_within_up_tolerance" ,
53
+ currentPodCount : 3.0 ,
54
+ context : & common.MockScalingContext {
55
+ TargetValue : 50.0 ,
56
+ UpFluctuationTolerance : 0.1 , // 10% tolerance
57
+ DownFluctuationTolerance : 0.2 , // 20% tolerance
58
+ MaxScaleUpRate : 2.0 ,
59
+ MaxScaleDownRate : 2.0 ,
60
+ CurrentUsePerPod : 54.0 , // 54/50 = 1.08, within 1+0.1=1.1 tolerance
61
+ MinReplicas : 1 ,
62
+ MaxReplicas : 10 ,
63
+ },
64
+ expected : 3 ,
65
+ description : "Should not scale when within up tolerance (8% above target)" ,
66
+ },
67
+ {
68
+ name : "no_scaling_within_down_tolerance" ,
69
+ currentPodCount : 3.0 ,
70
+ context : & common.MockScalingContext {
71
+ TargetValue : 50.0 ,
72
+ UpFluctuationTolerance : 0.1 , // 10% tolerance
73
+ DownFluctuationTolerance : 0.2 , // 20% tolerance
74
+ MaxScaleUpRate : 2.0 ,
75
+ MaxScaleDownRate : 2.0 ,
76
+ CurrentUsePerPod : 42.0 , // 42/50 = 0.84, within 1-0.2=0.8 tolerance
77
+ MinReplicas : 1 ,
78
+ MaxReplicas : 10 ,
79
+ },
80
+ expected : 3 ,
81
+ description : "Should not scale when within down tolerance (16% below target)" ,
82
+ },
83
+ {
84
+ name : "scale_up_beyond_tolerance" ,
85
+ currentPodCount : 3.0 ,
86
+ context : & common.MockScalingContext {
87
+ TargetValue : 50.0 ,
88
+ UpFluctuationTolerance : 0.1 , // 10% tolerance
89
+ DownFluctuationTolerance : 0.2 , // 20% tolerance
90
+ MaxScaleUpRate : 2.0 , // Can scale up by 100%
91
+ MaxScaleDownRate : 2.0 ,
92
+ CurrentUsePerPod : 60.0 , // 60/50 = 1.2, exceeds 1+0.1=1.1 tolerance
93
+ MinReplicas : 1 ,
94
+ MaxReplicas : 10 ,
95
+ },
96
+ expected : 4 , // ceil(3 * (60/50)) = ceil(3.6) = 4
97
+ description : "Should scale up when current use exceeds up tolerance threshold" ,
98
+ },
99
+ {
100
+ name : "scale_down_beyond_tolerance" ,
101
+ currentPodCount : 5.0 ,
102
+ context : & common.MockScalingContext {
103
+ TargetValue : 50.0 ,
104
+ UpFluctuationTolerance : 0.1 , // 10% tolerance
105
+ DownFluctuationTolerance : 0.2 , // 20% tolerance
106
+ MaxScaleUpRate : 2.0 ,
107
+ MaxScaleDownRate : 2.0 , // Can scale down by 50%
108
+ CurrentUsePerPod : 30.0 , // 30/50 = 0.6, below 1-0.2=0.8 tolerance
109
+ MinReplicas : 1 ,
110
+ MaxReplicas : 10 ,
111
+ },
112
+ expected : 3 , // ceil(5 * (30/50)) = ceil(3.0) = 3
113
+ description : "Should scale down when current use falls below down tolerance threshold" ,
114
+ },
115
+ {
116
+ name : "scale_up_limited_by_max_scale_up_rate" ,
117
+ currentPodCount : 3.0 ,
118
+ context : & common.MockScalingContext {
119
+ TargetValue : 50.0 ,
120
+ UpFluctuationTolerance : 0.1 , // 10% tolerance
121
+ DownFluctuationTolerance : 0.2 , // 20% tolerance
122
+ MaxScaleUpRate : 1.5 , // Can only scale up by 50%
123
+ MaxScaleDownRate : 2.0 ,
124
+ CurrentUsePerPod : 100.0 , // 100/50 = 2.0, would need 6 pods but limited
125
+ MinReplicas : 1 ,
126
+ MaxReplicas : 10 ,
127
+ },
128
+ expected : 5 , // ceil(1.5 * 3) = ceil(4.5) = 5 (limited by max scale up rate)
129
+ description : "Should be limited by max scale up rate" ,
130
+ },
131
+ {
132
+ name : "scale_down_limited_by_max_scale_down_rate" ,
133
+ currentPodCount : 6.0 ,
134
+ context : & common.MockScalingContext {
135
+ TargetValue : 50.0 ,
136
+ UpFluctuationTolerance : 0.1 , // 10% tolerance
137
+ DownFluctuationTolerance : 0.2 , // 20% tolerance
138
+ MaxScaleUpRate : 2.0 ,
139
+ MaxScaleDownRate : 3.0 , // Can scale down to 1/3 = 33% of current (6/3=2 minimum)
140
+ CurrentUsePerPod : 10.0 , // 10/50 = 0.2, would need 1.2 pods but limited
141
+ MinReplicas : 1 ,
142
+ MaxReplicas : 10 ,
143
+ },
144
+ expected : 2 , // floor(6/3) = floor(2) = 2
145
+ description : "Should be limited by max scale down rate" ,
146
+ },
147
+ {
148
+ name : "extreme_scale_up_case" ,
149
+ currentPodCount : 2.0 ,
150
+ context : & common.MockScalingContext {
151
+ TargetValue : 10.0 ,
152
+ UpFluctuationTolerance : 0.1 , // 10% tolerance
153
+ DownFluctuationTolerance : 0.2 , // 20% tolerance
154
+ MaxScaleUpRate : 3.0 , // Can scale up by 200%
155
+ MaxScaleDownRate : 2.0 ,
156
+ CurrentUsePerPod : 50.0 , // 50/10 = 5.0, would need 10 pods
157
+ MinReplicas : 1 ,
158
+ MaxReplicas : 20 ,
159
+ },
160
+ expected : 6 , // ceil(3.0 * 2) = 6 (limited by max scale up rate)
161
+ description : "Should handle extreme scale up scenarios with rate limiting" ,
162
+ },
163
+ }
164
+
165
+ for _ , tt := range tests {
166
+ t .Run (tt .name , func (t * testing.T ) {
167
+ result := algorithm .ComputeTargetReplicas (tt .currentPodCount , tt .context )
168
+ if result != tt .expected {
169
+ t .Errorf ("ComputeTargetReplicas() = %d, expected %d. %s" , result , tt .expected , tt .description )
170
+ t .Logf ("Current pod count: %.1f" , tt .currentPodCount )
171
+ t .Logf ("Current use per pod: %.1f" , tt .context .CurrentUsePerPod )
172
+ t .Logf ("Target value: %.1f" , tt .context .TargetValue )
173
+ t .Logf ("Up tolerance: %.3f" , tt .context .UpFluctuationTolerance )
174
+ t .Logf ("Down tolerance: %.3f" , tt .context .DownFluctuationTolerance )
175
+ t .Logf ("Ratio (current/expected): %.6f" , tt .context .CurrentUsePerPod / tt .context .TargetValue )
176
+ t .Logf ("Up threshold: %.6f" , 1 + tt .context .UpFluctuationTolerance )
177
+ t .Logf ("Down threshold: %.6f" , 1 - tt .context .DownFluctuationTolerance )
178
+ }
179
+ })
180
+ }
181
+ }
0 commit comments