@@ -18,12 +18,23 @@ package aws
1818
1919import (
2020 "fmt"
21+ "sync"
22+ "time"
2123
2224 "github.com/aws/aws-sdk-go/aws"
2325 "github.com/aws/aws-sdk-go/service/autoscaling"
26+ "k8s.io/apimachinery/pkg/util/clock"
27+ "k8s.io/apimachinery/pkg/util/rand"
28+ "k8s.io/client-go/tools/cache"
2429 "k8s.io/klog"
2530)
2631
32+ const (
33+ launchConfigurationCachedTTL = time .Minute * 20
34+ cacheMinTTL = 120
35+ cacheMaxTTL = 600
36+ )
37+
2738// autoScaling is the interface represents a specific aspect of the auto-scaling service provided by AWS SDK for use in CA
2839type autoScaling interface {
2940 DescribeAutoScalingGroupsPages (input * autoscaling.DescribeAutoScalingGroupsInput , fn func (* autoscaling.DescribeAutoScalingGroupsOutput , bool ) bool ) error
@@ -36,30 +47,94 @@ type autoScaling interface {
3647// autoScalingWrapper provides several utility methods over the auto-scaling service provided by AWS SDK
3748type autoScalingWrapper struct {
3849 autoScaling
39- launchConfigurationInstanceTypeCache map [ string ] string
50+ launchConfigurationInstanceTypeCache * expirationStore
4051}
4152
42- func (m autoScalingWrapper ) getInstanceTypeByLCName (name string ) (string , error ) {
43- if instanceType , found := m .launchConfigurationInstanceTypeCache [name ]; found {
44- return instanceType , nil
53+ // expirationStore cache the launch configuration with their instance type.
54+ // The store expires its keys based on a TTL. This TTL can have a jitter applied to it.
55+ // This allows to get a better repartition of the AWS queries.
56+ type expirationStore struct {
57+ cache.Store
58+ jitterClock * jitterClock
59+ }
60+
61+ type instanceTypeCachedObject struct {
62+ name string
63+ instanceType string
64+ }
65+
66+ type jitterClock struct {
67+ clock.Clock
68+
69+ jitter bool
70+ sync.RWMutex
71+ }
72+
73+ func newLaunchConfigurationInstanceTypeCache () * expirationStore {
74+ jc := & jitterClock {}
75+ return & expirationStore {
76+ cache .NewExpirationStore (func (obj interface {}) (s string , e error ) {
77+ return obj .(instanceTypeCachedObject ).name , nil
78+ }, & cache.TTLPolicy {
79+ TTL : launchConfigurationCachedTTL ,
80+ Clock : jc ,
81+ }),
82+ jc ,
83+ }
84+ }
85+
86+ func (c * jitterClock ) Since (ts time.Time ) time.Duration {
87+ since := time .Since (ts )
88+ c .RLock ()
89+ defer c .RUnlock ()
90+ if c .jitter {
91+ return since + (time .Second * time .Duration (rand .IntnRange (cacheMinTTL , cacheMaxTTL )))
92+ }
93+ return since
94+ }
95+
96+ func (m autoScalingWrapper ) getInstanceTypeByLCNames (launchConfigToQuery []* string ) ([]* autoscaling.LaunchConfiguration , error ) {
97+ var launchConfigurations []* autoscaling.LaunchConfiguration
98+
99+ for i := 0 ; i < len (launchConfigToQuery ); i += 50 {
100+ end := i + 50
101+
102+ if end > len (launchConfigToQuery ) {
103+ end = len (launchConfigToQuery )
104+ }
105+ params := & autoscaling.DescribeLaunchConfigurationsInput {
106+ LaunchConfigurationNames : launchConfigToQuery [i :end ],
107+ MaxRecords : aws .Int64 (50 ),
108+ }
109+ r , err := m .DescribeLaunchConfigurations (params )
110+ if err != nil {
111+ return nil , err
112+ }
113+ launchConfigurations = append (launchConfigurations , r .LaunchConfigurations ... )
114+ for _ , lc := range r .LaunchConfigurations {
115+ _ = m .launchConfigurationInstanceTypeCache .Add (instanceTypeCachedObject {
116+ name : * lc .LaunchConfigurationName ,
117+ instanceType : * lc .InstanceType ,
118+ })
119+ }
45120 }
121+ return launchConfigurations , nil
122+ }
46123
47- params := & autoscaling. DescribeLaunchConfigurationsInput {
48- LaunchConfigurationNames : [] * string { aws . String (name )},
49- MaxRecords : aws . Int64 ( 1 ),
124+ func ( m autoScalingWrapper ) getInstanceTypeByLCName ( name string ) ( string , error ) {
125+ if obj , found , _ := m . launchConfigurationInstanceTypeCache . GetByKey (name ); found {
126+ return obj .( instanceTypeCachedObject ). instanceType , nil
50127 }
51- launchConfigurations , err := m .DescribeLaunchConfigurations (params )
128+
129+ launchConfigs , err := m .getInstanceTypeByLCNames ([]* string {aws .String (name )})
52130 if err != nil {
53- klog .V ( 4 ). Infof ( "Failed LaunchConfiguration info request for %s : %v" , name , err )
131+ klog .Errorf ( "Failed to query the launch configuration %s to get the instance type : %v" , name , err )
54132 return "" , err
55133 }
56- if len (launchConfigurations . LaunchConfigurations ) < 1 {
134+ if len (launchConfigs ) < 1 || launchConfigs [ 0 ]. InstanceType == nil {
57135 return "" , fmt .Errorf ("unable to get first LaunchConfiguration for %s" , name )
58136 }
59-
60- instanceType := * launchConfigurations .LaunchConfigurations [0 ].InstanceType
61- m .launchConfigurationInstanceTypeCache [name ] = instanceType
62- return instanceType , nil
137+ return * launchConfigs [0 ].InstanceType , nil
63138}
64139
65140func (m * autoScalingWrapper ) getAutoscalingGroupsByNames (names []string ) ([]* autoscaling.Group , error ) {
@@ -94,6 +169,48 @@ func (m *autoScalingWrapper) getAutoscalingGroupsByNames(names []string) ([]*aut
94169 return asgs , nil
95170}
96171
172+ func (m autoScalingWrapper ) populateLaunchConfigurationInstanceTypeCache (autoscalingGroups []* autoscaling.Group ) error {
173+ var launchConfigToQuery []* string
174+
175+ m .launchConfigurationInstanceTypeCache .jitterClock .Lock ()
176+ m .launchConfigurationInstanceTypeCache .jitterClock .jitter = true
177+ m .launchConfigurationInstanceTypeCache .jitterClock .Unlock ()
178+ for _ , asg := range autoscalingGroups {
179+ if asg == nil {
180+ continue
181+ }
182+ if asg .LaunchConfigurationName == nil {
183+ continue
184+ }
185+ _ , found , _ := m .launchConfigurationInstanceTypeCache .GetByKey (* asg .LaunchConfigurationName )
186+ if found {
187+ continue
188+ }
189+ launchConfigToQuery = append (launchConfigToQuery , asg .LaunchConfigurationName )
190+ }
191+ m .launchConfigurationInstanceTypeCache .jitterClock .Lock ()
192+ m .launchConfigurationInstanceTypeCache .jitterClock .jitter = false
193+ m .launchConfigurationInstanceTypeCache .jitterClock .Unlock ()
194+
195+ // List expire old entries
196+ _ = m .launchConfigurationInstanceTypeCache .List ()
197+
198+ if len (launchConfigToQuery ) == 0 {
199+ klog .V (4 ).Infof ("%d launch configurations already in cache" , len (autoscalingGroups ))
200+ return nil
201+ }
202+ klog .V (4 ).Infof ("%d launch configurations to query" , len (launchConfigToQuery ))
203+
204+ _ , err := m .getInstanceTypeByLCNames (launchConfigToQuery )
205+ if err != nil {
206+ klog .Errorf ("Failed to query %d launch configurations" , len (launchConfigToQuery ))
207+ return err
208+ }
209+
210+ klog .V (4 ).Infof ("Successfully query %d launch configurations" , len (launchConfigToQuery ))
211+ return nil
212+ }
213+
97214func (m * autoScalingWrapper ) getAutoscalingGroupNamesByTags (kvs map [string ]string ) ([]string , error ) {
98215 // DescribeTags does an OR query when multiple filters on different tags are
99216 // specified. In other words, DescribeTags returns [asg1, asg1] for keys
0 commit comments