@@ -17,28 +17,57 @@ limitations under the License.
17
17
package apiutil
18
18
19
19
import (
20
- "sync"
20
+ "fmt"
21
+ "regexp"
22
+ "strconv"
21
23
"time"
22
24
25
+ "golang.org/x/time/rate"
23
26
"k8s.io/apimachinery/pkg/api/meta"
24
27
"k8s.io/apimachinery/pkg/runtime/schema"
25
28
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
26
- "k8s.io/apimachinery/pkg/util/wait"
27
29
"k8s.io/client-go/discovery"
28
30
"k8s.io/client-go/rest"
29
31
"k8s.io/client-go/restmapper"
30
32
)
31
33
32
- // TODO(estroz): do we want to return a wait.ErrWaitTimeout if backoff duration
33
- // reaches a maximum?
34
+ // ErrRateLimited is returned by a DynamicRESTMapper method if the number
35
+ // of API calls has exceeded a limit within a certain time period.
36
+ type ErrRateLimited struct {
37
+ // Duration to wait until the next API call can be made.
38
+ Delay time.Duration
39
+ }
40
+
41
+ const errRLMsg = "too many API calls to the DynamicRESTMapper within a timeframe"
42
+
43
+ func (e ErrRateLimited ) Error () string {
44
+ return fmt .Sprintf ("%s (%dns)" , errRLMsg , int64 (e .Delay ))
45
+ }
46
+
47
+ var errRLRe = regexp .MustCompile (fmt .Sprintf (".*%s \\ (([0-9]+)ns\\ ).*" , errRLMsg ))
48
+
49
+ func IsRateLimited (err error ) (time.Duration , bool ) {
50
+ if e , ok := err .(ErrRateLimited ); ok {
51
+ return e .Delay , true
52
+ }
53
+ if matches := errRLRe .FindStringSubmatch (err .Error ()); len (matches ) > 1 {
54
+ d , err := strconv .Atoi (matches [1 ])
55
+ if err == nil {
56
+ return time .Duration (d ), true
57
+ }
58
+ }
59
+ return 0 , false
60
+ }
34
61
35
62
var (
36
- // BackoffDuration is the initial duration that the DynamicRESTMapper
37
- // waits to reload its REST mapping.
38
- BackoffDuration time.Duration = time .Millisecond * 10
39
- // BackoffDuration is the number of times that the DynamicRESTMapper
40
- // will perform an exponential backoff before maxing out.
41
- BackoffSteps = 10
63
+ // LimitRate is the number of DynamicRESTMapper API calls allowed per second
64
+ // assuming the rate of API calls <= LimitRate.
65
+ // There is likely no need to change the default value.
66
+ LimitRate = 600
67
+ // LimitSize is the maximum number of simultaneous DynamicRESTMapper API
68
+ // calls allowed.
69
+ // There is likely no need to change the default value.
70
+ LimitSize = 5
42
71
)
43
72
44
73
// DynamicRESTMapper is a RESTMapper that dynamically discovers resource
49
78
type DynamicRESTMapper struct {
50
79
client discovery.DiscoveryInterface
51
80
delegate meta.RESTMapper
52
- backoff * backoff
81
+ limiter * limiter
53
82
}
54
83
55
84
// NewDynamicRESTMapper returns a DynamicRESTMapper for cfg.
@@ -61,27 +90,17 @@ func NewDynamicRESTMapper(cfg *rest.Config) (meta.RESTMapper, error) {
61
90
62
91
drm := & DynamicRESTMapper {
63
92
client : client ,
64
- backoff : & backoff {
65
- Backoff : wait.Backoff {
66
- Duration : BackoffDuration ,
67
- Steps : BackoffSteps ,
68
- Factor : 2 ,
69
- },
93
+ limiter : & limiter {
94
+ rate .NewLimiter (rate .Limit (LimitRate ), LimitSize ),
70
95
},
71
96
}
72
- // Substitute the default backoff error handler for our exponential one.
73
- if len (utilruntime .ErrorHandlers ) > 1 {
74
- utilruntime .ErrorHandlers [1 ] = func (error ) {
75
- time .Sleep (drm .backoff .step ())
76
- }
77
- }
78
- if err := drm .reload (); err != nil {
97
+ if err := drm .setDelegate (); err != nil {
79
98
return nil , err
80
99
}
81
100
return drm , nil
82
101
}
83
102
84
- func (drm * DynamicRESTMapper ) reload () error {
103
+ func (drm * DynamicRESTMapper ) setDelegate () error {
85
104
gr , err := restmapper .GetAPIGroupResources (drm .client )
86
105
if err != nil {
87
106
return err
@@ -90,110 +109,108 @@ func (drm *DynamicRESTMapper) reload() error {
90
109
return nil
91
110
}
92
111
93
- // reloadOnError checks if an error indicates that the delegated RESTMapper
94
- // needs to be reloaded, and if so, reloads it and returns true.
95
- // reloadOnError uses an exponential backoff mechanism to rate limit reloads.
96
- func (drm * DynamicRESTMapper ) reloadOnError (err error ) bool {
97
- if _ , matches := err .(* meta.NoKindMatchError ); ! matches {
98
- drm .backoff .reset ()
99
- return false
112
+ func noKindMatchError (err error ) bool {
113
+ _ , ok := err .(* meta.NoKindMatchError )
114
+ return ok
115
+ }
116
+
117
+ // reload reloads the delegated RESTMapper, and will return an error only
118
+ // if a rate limit has been hit.
119
+ func (drm * DynamicRESTMapper ) reload () error {
120
+ if err := drm .limiter .checkRate (); err != nil {
121
+ return err
100
122
}
101
- err = drm .reload ()
102
- if err != nil {
103
- // TODO(estroz): HandleError uses a rudimentary backoff by default.
104
- // Should we remove it completely or substitute the default backoff
105
- // for our exponential one, as we do above?
123
+ if err := drm .setDelegate (); err != nil {
106
124
utilruntime .HandleError (err )
107
125
}
108
- return err == nil
126
+ return nil
109
127
}
110
128
111
129
func (drm * DynamicRESTMapper ) KindFor (resource schema.GroupVersionResource ) (schema.GroupVersionKind , error ) {
112
130
gvk , err := drm .delegate .KindFor (resource )
113
- if drm .reloadOnError (err ) {
131
+ if noKindMatchError (err ) {
132
+ if rerr := drm .reload (); rerr != nil {
133
+ return schema.GroupVersionKind {}, rerr
134
+ }
114
135
gvk , err = drm .delegate .KindFor (resource )
115
136
}
116
137
return gvk , err
117
138
}
118
139
119
140
func (drm * DynamicRESTMapper ) KindsFor (resource schema.GroupVersionResource ) ([]schema.GroupVersionKind , error ) {
120
141
gvks , err := drm .delegate .KindsFor (resource )
121
- if drm .reloadOnError (err ) {
142
+ if noKindMatchError (err ) {
143
+ if rerr := drm .reload (); rerr != nil {
144
+ return nil , rerr
145
+ }
122
146
gvks , err = drm .delegate .KindsFor (resource )
123
147
}
124
148
return gvks , err
125
149
}
126
150
127
151
func (drm * DynamicRESTMapper ) ResourceFor (input schema.GroupVersionResource ) (schema.GroupVersionResource , error ) {
128
152
gvr , err := drm .delegate .ResourceFor (input )
129
- if drm .reloadOnError (err ) {
153
+ if noKindMatchError (err ) {
154
+ if rerr := drm .reload (); rerr != nil {
155
+ return schema.GroupVersionResource {}, rerr
156
+ }
130
157
gvr , err = drm .delegate .ResourceFor (input )
131
158
}
132
159
return gvr , err
133
160
}
134
161
135
162
func (drm * DynamicRESTMapper ) ResourcesFor (input schema.GroupVersionResource ) ([]schema.GroupVersionResource , error ) {
136
163
gvrs , err := drm .delegate .ResourcesFor (input )
137
- if drm .reloadOnError (err ) {
164
+ if noKindMatchError (err ) {
165
+ if rerr := drm .reload (); rerr != nil {
166
+ return nil , rerr
167
+ }
138
168
gvrs , err = drm .delegate .ResourcesFor (input )
139
169
}
140
170
return gvrs , err
141
171
}
142
172
143
173
func (drm * DynamicRESTMapper ) RESTMapping (gk schema.GroupKind , versions ... string ) (* meta.RESTMapping , error ) {
144
174
m , err := drm .delegate .RESTMapping (gk , versions ... )
145
- if drm .reloadOnError (err ) {
175
+ if noKindMatchError (err ) {
176
+ if rerr := drm .reload (); rerr != nil {
177
+ return nil , rerr
178
+ }
146
179
m , err = drm .delegate .RESTMapping (gk , versions ... )
147
180
}
148
181
return m , err
149
182
}
150
183
151
184
func (drm * DynamicRESTMapper ) RESTMappings (gk schema.GroupKind , versions ... string ) ([]* meta.RESTMapping , error ) {
152
185
ms , err := drm .delegate .RESTMappings (gk , versions ... )
153
- if drm .reloadOnError (err ) {
186
+ if noKindMatchError (err ) {
187
+ if rerr := drm .reload (); rerr != nil {
188
+ return nil , rerr
189
+ }
154
190
ms , err = drm .delegate .RESTMappings (gk , versions ... )
155
191
}
156
192
return ms , err
157
193
}
158
194
159
195
func (drm * DynamicRESTMapper ) ResourceSingularizer (resource string ) (singular string , err error ) {
160
196
s , err := drm .delegate .ResourceSingularizer (resource )
161
- if drm .reloadOnError (err ) {
197
+ if noKindMatchError (err ) {
198
+ if rerr := drm .reload (); rerr != nil {
199
+ return "" , rerr
200
+ }
162
201
s , err = drm .delegate .ResourceSingularizer (resource )
163
202
}
164
203
return s , err
165
204
}
166
205
167
- type backoff struct {
168
- wait.Backoff
169
- mu sync.Mutex
206
+ type limiter struct {
207
+ * rate.Limiter
170
208
}
171
209
172
- // Copied and pared down from
173
- // https://github.com/kubernetes/apimachinery/blob/6a84e37a896db9780c75367af8d2ed2bb944022e/pkg/util/wait/wait.go#L227
174
- // TODO(estroz) call Backoff.Step() once we bump to kubernetes-1.14.1
175
- func (b * backoff ) step () time.Duration {
176
- b .mu .Lock ()
177
- defer b .mu .Unlock ()
178
-
179
- if b .Steps < 1 {
180
- return b .Duration
181
- }
182
- b .Steps --
183
-
184
- duration := b .Duration
185
-
186
- // calculate the next step
187
- if b .Factor != 0 {
188
- b .Duration = time .Duration (float64 (b .Duration ) * b .Factor )
210
+ func (b * limiter ) checkRate () error {
211
+ res := b .Reserve ()
212
+ if res .Delay () == 0 {
213
+ return nil
189
214
}
190
-
191
- return duration
192
- }
193
-
194
- func (b * backoff ) reset () {
195
- b .mu .Lock ()
196
- b .Duration = BackoffDuration
197
- b .Steps = BackoffSteps
198
- b .mu .Unlock ()
215
+ return ErrRateLimited {res .Delay ()}
199
216
}
0 commit comments