@@ -17,81 +17,349 @@ limitations under the License.
1717package test
1818
1919import (
20+ "fmt"
2021 apiv1 "k8s.io/api/core/v1"
2122 "k8s.io/apimachinery/pkg/api/resource"
2223 "k8s.io/autoscaler/cluster-autoscaler/cloudprovider"
24+ "k8s.io/autoscaler/cluster-autoscaler/config"
25+ "k8s.io/autoscaler/cluster-autoscaler/simulator/framework"
2326 "k8s.io/autoscaler/cluster-autoscaler/utils/errors"
27+ fakek8s "k8s.io/autoscaler/cluster-autoscaler/utils/fake"
2428 "sync"
2529)
2630
31+ const (
32+ defaultMinSize = 0
33+ defaultMaxSize = 1000
34+ )
35+
2736// CloudProvider is a fake implementation of the cloudprovider interface for testing.
2837type CloudProvider struct {
29- sync.Mutex
38+ sync.RWMutex
39+ groups map [string ]cloudprovider.NodeGroup
40+ minLimits map [string ]int64
41+ maxLimits map [string ]int64
42+ // nodeToGroup tracks which node name belongs to which group ID.
43+ nodeToGroup map [string ]string
44+ k8s * fakek8s.Kubernetes
3045}
3146
47+ // CloudProviderOption defines a function to configure the CloudProvider.
48+ type CloudProviderOption func (* CloudProvider )
49+
3250// NewCloudProvider creates a new instance of the fake CloudProvider.
33- func NewCloudProvider () * CloudProvider {
34- return & CloudProvider {}
51+ func NewCloudProvider (k8s * fakek8s.Kubernetes ) * CloudProvider {
52+ return & CloudProvider {
53+ groups : make (map [string ]cloudprovider.NodeGroup ),
54+ nodeToGroup : make (map [string ]string ),
55+ minLimits : map [string ]int64 {
56+ cloudprovider .ResourceNameCores : 0 ,
57+ cloudprovider .ResourceNameMemory : 0 ,
58+ },
59+ maxLimits : map [string ]int64 {
60+ // Set to a effectively infinite number for tests.
61+ cloudprovider .ResourceNameCores : 1000000 ,
62+ cloudprovider .ResourceNameMemory : 1000000 ,
63+ },
64+ k8s : k8s ,
65+ }
3566}
3667
3768// NodeGroups returns all node groups configured in the fake CloudProvider.
3869func (c * CloudProvider ) NodeGroups () []cloudprovider.NodeGroup {
39- panic ("not implemented" )
70+ c .Lock ()
71+ defer c .Unlock ()
72+ var res []cloudprovider.NodeGroup
73+ for _ , g := range c .groups {
74+ res = append (res , g )
75+ }
76+ return res
4077}
4178
4279// NodeGroupForNode returns the node group that a given node belongs to.
4380func (c * CloudProvider ) NodeGroupForNode (node * apiv1.Node ) (cloudprovider.NodeGroup , error ) {
44- panic ("not implemented" )
81+ c .Lock ()
82+ defer c .Unlock ()
83+ groupId , ok := c .nodeToGroup [node .Name ]
84+ if ! ok {
85+ return nil , nil
86+ }
87+ return c .groups [groupId ], nil
4588}
4689
4790// HasInstance returns true if the given node is managed by this cloud provider.
4891func (c * CloudProvider ) HasInstance (node * apiv1.Node ) (bool , error ) {
49- panic ("not implemented" )
92+ c .Lock ()
93+ defer c .Unlock ()
94+ _ , found := c .nodeToGroup [node .Name ]
95+ return found , nil
5096}
5197
52- // GetResourceLimiter generates a NEW limiter based on our current internal maps.
98+ // GetResourceLimiter generates a new limiter based on our current internal maps.
5399func (c * CloudProvider ) GetResourceLimiter () (* cloudprovider.ResourceLimiter , error ) {
54- panic ("not implemented" )
100+ c .Lock ()
101+ defer c .Unlock ()
102+ return cloudprovider .NewResourceLimiter (c .minLimits , c .maxLimits ), nil
55103}
56104
57105// GPULabel returns the label used to identify GPU types in this provider.
58- func (c * CloudProvider ) GPULabel () string {
59- panic ("not implemented" )
60- }
106+ func (c * CloudProvider ) GPULabel () string { return "gpu-label" }
61107
62108// GetAvailableGPUTypes returns a map of all GPU types available in this provider.
63- func (c * CloudProvider ) GetAvailableGPUTypes () map [string ]struct {} {
64- panic ("not implemented" )
65- }
109+ func (c * CloudProvider ) GetAvailableGPUTypes () map [string ]struct {} { return nil }
66110
67111// GetNodeGpuConfig returns the GPU configuration for a specific node.
68- func (c * CloudProvider ) GetNodeGpuConfig (node * apiv1.Node ) * cloudprovider.GpuConfig {
69- panic ("not implemented" )
70- }
112+ func (c * CloudProvider ) GetNodeGpuConfig (node * apiv1.Node ) * cloudprovider.GpuConfig { return nil }
71113
72114// Cleanup performs any necessary teardown of the CloudProvider.
73- func (c * CloudProvider ) Cleanup () error {
74- panic ("not implemented" )
75- }
115+ func (c * CloudProvider ) Cleanup () error { return nil }
76116
77117// Refresh updates the internal state of the CloudProvider.
78- func (c * CloudProvider ) Refresh () error { panic ( "not implemented" ) }
118+ func (c * CloudProvider ) Refresh () error { return nil }
79119
80120// Name returns the name of the cloud provider.
81- func (c * CloudProvider ) Name () string { panic ( "not implemented" ) }
121+ func (c * CloudProvider ) Name () string { return "Provider" }
82122
83123// Pricing returns the pricing model associated with the provider.
84124func (c * CloudProvider ) Pricing () (cloudprovider.PricingModel , errors.AutoscalerError ) {
85- panic ( "not implemented" )
125+ return nil , cloudprovider . ErrNotImplemented
86126}
87127
88128// GetAvailableMachineTypes returns the machine types supported by the provider.
89129func (c * CloudProvider ) GetAvailableMachineTypes () ([]string , error ) {
90- panic ( "not implemented" )
130+ return nil , cloudprovider . ErrNotImplemented
91131}
92132
93133// NewNodeGroup creates a new node group based on the provided specifications.
94134func (c * CloudProvider ) NewNodeGroup (machineType string , labels map [string ]string , systemLabels map [string ]string ,
95135 taints []apiv1.Taint , extraResources map [string ]resource.Quantity ) (cloudprovider.NodeGroup , error ) {
96- panic ( "not implemented" )
136+ return nil , cloudprovider . ErrNotImplemented
97137}
138+
139+ // NodeGroupOption is a function that configures a NodeGroup during creation.
140+ type NodeGroupOption func (* NodeGroup )
141+
142+ // WithNode adds a single initial node to the group and
143+ // automatically sets the group's template based on that node.
144+ func WithNode (node * apiv1.Node ) NodeGroupOption {
145+ return func (n * NodeGroup ) {
146+ n .provider .nodeToGroup [node .Name ] = n .id
147+ n .instances [node .Name ] = cloudprovider .InstanceRunning
148+ n .targetSize = 1
149+ n .template = framework .NewTestNodeInfo (node .DeepCopy ())
150+ if n .provider .k8s != nil {
151+ n .provider .k8s .AddNode (node )
152+ }
153+ }
154+ }
155+
156+ // AddNodeGroup is a helper for tests to add a group with its template.
157+ func (c * CloudProvider ) AddNodeGroup (id string , opts ... NodeGroupOption ) {
158+ c .Lock ()
159+ defer c .Unlock ()
160+
161+ group := & NodeGroup {
162+ id : id ,
163+ minSize : defaultMinSize ,
164+ maxSize : defaultMaxSize ,
165+ targetSize : 0 ,
166+ instances : make (map [string ]cloudprovider.InstanceState ),
167+ provider : c ,
168+ }
169+
170+ for _ , opt := range opts {
171+ opt (group )
172+ }
173+ c .groups [id ] = group
174+ }
175+
176+ // GetNodeGroup is a helper for tests to get a node group.
177+ func (c * CloudProvider ) GetNodeGroup (id string ) cloudprovider.NodeGroup {
178+ c .Lock ()
179+ defer c .Unlock ()
180+ return c .groups [id ]
181+ }
182+
183+ // AddNode connects a node name to a group ID.
184+ func (c * CloudProvider ) AddNode (groupId string , node * apiv1.Node ) {
185+ c .Lock ()
186+ defer c .Unlock ()
187+ c .nodeToGroup [node .Name ] = groupId
188+ }
189+
190+ // SetResourceLimit allows the test to reach in and change the limits.
191+ func (c * CloudProvider ) SetResourceLimit (resource string , min , max int64 ) {
192+ c .Lock ()
193+ defer c .Unlock ()
194+ c .minLimits [resource ] = min
195+ c .maxLimits [resource ] = max
196+ }
197+
198+ // NodeGroup is a fake implementation of the cloudprovider.NodeGroup interface for testing.
199+ type NodeGroup struct {
200+ sync.RWMutex
201+ id string
202+ minSize int
203+ maxSize int
204+ targetSize int
205+ template * framework.NodeInfo
206+ // instances maps instanceID -> state.
207+ instances map [string ]cloudprovider.InstanceState
208+ provider * CloudProvider
209+ }
210+
211+ // MaxSize returns the maximum size of the node group.
212+ func (n * NodeGroup ) MaxSize () int {
213+ return n .maxSize
214+ }
215+
216+ // MinSize returns the minimum size of the node group.
217+ func (n * NodeGroup ) MinSize () int {
218+ return n .minSize
219+ }
220+
221+ // AtomicIncreaseSize is a version of IncreaseSize that increases the size of the node group atomically.
222+ func (n * NodeGroup ) AtomicIncreaseSize (delta int ) error {
223+ return n .IncreaseSize (delta )
224+ }
225+
226+ // DeleteNodes removes specific nodes from the node group and updates the internal mapping.
227+ func (n * NodeGroup ) DeleteNodes (nodes []* apiv1.Node ) error {
228+ n .Lock ()
229+ defer n .Unlock ()
230+
231+ n .provider .Lock ()
232+ defer n .provider .Unlock ()
233+
234+ deletedCount := 0
235+ for _ , node := range nodes {
236+ if groupId , exists := n .provider .nodeToGroup [node .Name ]; exists && groupId == n .id {
237+ delete (n .provider .nodeToGroup , node .Name )
238+ delete (n .instances , node .Name )
239+ if n .provider .k8s != nil {
240+ n .provider .k8s .DeleteNode (node .Name )
241+ }
242+ deletedCount ++
243+ } else {
244+ fmt .Printf ("Warning: node %s not found in group %s or already deleted." , node .Name , n .id )
245+ }
246+ }
247+
248+ if n .targetSize >= deletedCount {
249+ n .targetSize -= deletedCount
250+ } else {
251+ n .targetSize = 0
252+ }
253+
254+ return nil
255+ }
256+
257+ // ForceDeleteNodes deletes nodes without checking for specific conditions (fake implementation).
258+ func (n * NodeGroup ) ForceDeleteNodes (nodes []* apiv1.Node ) error {
259+ return n .DeleteNodes (nodes )
260+ }
261+
262+ // DecreaseTargetSize reduces the target size of the node group by the specified delta.
263+ func (n * NodeGroup ) DecreaseTargetSize (delta int ) error {
264+ n .Lock ()
265+ defer n .Unlock ()
266+ n .targetSize -= delta
267+ return nil
268+ }
269+
270+ // Id returns the unique identifier of the node group.
271+ func (n * NodeGroup ) Id () string {
272+ return n .id
273+ }
274+
275+ // Debug returns a string representation of the node group's current state.
276+ func (n * NodeGroup ) Debug () string {
277+ return fmt .Sprintf ("NodeGroup{id: %s, targetSize: %d}" , n .id , n .targetSize )
278+ }
279+
280+ // Nodes returns a list of all instances currently existing in this node group.
281+ func (n * NodeGroup ) Nodes () ([]cloudprovider.Instance , error ) {
282+ n .provider .Lock ()
283+ defer n .provider .Unlock ()
284+
285+ var instances []cloudprovider.Instance
286+ for id , state := range n .instances {
287+ instances = append (instances , cloudprovider.Instance {
288+ Id : id ,
289+ Status : & cloudprovider.InstanceStatus {
290+ State : state ,
291+ },
292+ })
293+ }
294+ return instances , nil
295+ }
296+
297+ // Exist returns true if the node group currently exists in the cloud provider.
298+ func (n * NodeGroup ) Exist () bool {
299+ return true
300+ }
301+
302+ // Create creates the node group in the cloud provider (not implemented).
303+ func (n * NodeGroup ) Create () (cloudprovider.NodeGroup , error ) {
304+ return nil , cloudprovider .ErrNotImplemented
305+ }
306+
307+ // Delete deletes the node group from the cloud provider (not implemented).
308+ func (n * NodeGroup ) Delete () error {
309+ return cloudprovider .ErrNotImplemented
310+ }
311+
312+ // Autoprovisioned returns true if the node group is autoprovisioned.
313+ func (n * NodeGroup ) Autoprovisioned () bool {
314+ return false
315+ }
316+
317+ // GetOptions returns autoscaling options specific to this node group.
318+ func (n * NodeGroup ) GetOptions (defaults config.NodeGroupAutoscalingOptions ) (* config.NodeGroupAutoscalingOptions , error ) {
319+ return nil , nil
320+ }
321+
322+ // TargetSize returns the current target size of the node group.
323+ func (n * NodeGroup ) TargetSize () (int , error ) { return n .targetSize , nil }
324+
325+ // IncreaseSize adds nodes to the node group and updates internal instance mapping.
326+ func (n * NodeGroup ) IncreaseSize (delta int ) error {
327+ n .Lock ()
328+ defer n .Unlock ()
329+ if n .targetSize + delta > n .maxSize {
330+ return fmt .Errorf ("size too large" )
331+ }
332+
333+ n .provider .Lock ()
334+ defer n .provider .Unlock ()
335+
336+ for i := 0 ; i < delta ; i ++ {
337+ instanceNum := n .targetSize + i
338+ instanceId := fmt .Sprintf ("%s-node-%d" , n .id , instanceNum )
339+
340+ if n .template == nil || n .template .Node () == nil {
341+ return fmt .Errorf ("node group %s has no template to create new nodes" , n .id )
342+ }
343+ newNode := n .template .Node ().DeepCopy ()
344+ newNode .Name = instanceId
345+
346+ n .instances [instanceId ] = cloudprovider .InstanceRunning
347+ n .provider .nodeToGroup [instanceId ] = n .id
348+ if n .provider .k8s != nil {
349+ n .provider .k8s .AddNode (newNode )
350+ }
351+ }
352+ n .targetSize += delta
353+ return nil
354+ }
355+
356+ // TemplateNodeInfo returns the template node information for this node group.
357+ func (n * NodeGroup ) TemplateNodeInfo () (* framework.NodeInfo , error ) {
358+ if n .template == nil {
359+ return nil , cloudprovider .ErrNotImplemented
360+ }
361+ return n .template , nil
362+ }
363+
364+ // GetTargetSize returns the target size as a raw integer (helper method).
365+ func (n * NodeGroup ) GetTargetSize () int { return n .targetSize }
0 commit comments