-
Notifications
You must be signed in to change notification settings - Fork 1.2k
/
Copy pathcache.go
459 lines (387 loc) · 16.8 KB
/
cache.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
/*
Copyright 2018 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cache
import (
"context"
"fmt"
"net/http"
"time"
corev1 "k8s.io/api/core/v1"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/client-go/rest"
toolscache "k8s.io/client-go/tools/cache"
"k8s.io/utils/pointer"
"sigs.k8s.io/controller-runtime/pkg/cache/internal"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/client/apiutil"
logf "sigs.k8s.io/controller-runtime/pkg/internal/log"
)
var (
log = logf.RuntimeLog.WithName("object-cache")
defaultSyncPeriod = 10 * time.Hour
)
// InformerGetOptions defines the behavior of how informers are retrieved.
type InformerGetOptions internal.GetOptions
// InformerGetOption defines an option that alters the behavior of how informers are retrieved.
type InformerGetOption func(*InformerGetOptions)
// BlockUntilSynced determines whether a get request for an informer should block
// until the informer's cache has synced.
func BlockUntilSynced(shouldBlock bool) InformerGetOption {
return func(opts *InformerGetOptions) {
opts.BlockUntilSynced = &shouldBlock
}
}
// Cache knows how to load Kubernetes objects, fetch informers to request
// to receive events for Kubernetes objects (at a low-level),
// and add indices to fields on the objects stored in the cache.
type Cache interface {
// Reader acts as a client to objects stored in the cache.
client.Reader
// Informers loads informers and adds field indices.
Informers
}
// Informers knows how to create or fetch informers for different
// group-version-kinds, and add indices to those informers. It's safe to call
// GetInformer from multiple threads.
type Informers interface {
// GetInformer fetches or constructs an informer for the given object that corresponds to a single
// API kind and resource.
GetInformer(ctx context.Context, obj client.Object, opts ...InformerGetOption) (Informer, error)
// GetInformerForKind is similar to GetInformer, except that it takes a group-version-kind, instead
// of the underlying object.
GetInformerForKind(ctx context.Context, gvk schema.GroupVersionKind, opts ...InformerGetOption) (Informer, error)
// Start runs all the informers known to this cache until the context is closed.
// It blocks.
Start(ctx context.Context) error
// WaitForCacheSync waits for all the caches to sync. Returns false if it could not sync a cache.
WaitForCacheSync(ctx context.Context) bool
// FieldIndexer adds indices to the managed informers.
client.FieldIndexer
}
// Informer allows you to interact with the underlying informer.
type Informer interface {
// AddEventHandler adds an event handler to the shared informer using the shared informer's resync
// period. Events to a single handler are delivered sequentially, but there is no coordination
// between different handlers.
// It returns a registration handle for the handler that can be used to remove
// the handler again and an error if the handler cannot be added.
AddEventHandler(handler toolscache.ResourceEventHandler) (toolscache.ResourceEventHandlerRegistration, error)
// AddEventHandlerWithResyncPeriod adds an event handler to the shared informer using the
// specified resync period. Events to a single handler are delivered sequentially, but there is
// no coordination between different handlers.
// It returns a registration handle for the handler that can be used to remove
// the handler again and an error if the handler cannot be added.
AddEventHandlerWithResyncPeriod(handler toolscache.ResourceEventHandler, resyncPeriod time.Duration) (toolscache.ResourceEventHandlerRegistration, error)
// RemoveEventHandler removes a previously added event handler given by
// its registration handle.
// This function is guaranteed to be idempotent and thread-safe.
RemoveEventHandler(handle toolscache.ResourceEventHandlerRegistration) error
// AddIndexers adds indexers to this store. If this is called after there is already data
// in the store, the results are undefined.
AddIndexers(indexers toolscache.Indexers) error
// HasSynced return true if the informers underlying store has synced.
HasSynced() bool
}
// Options are the optional arguments for creating a new Cache object.
type Options struct {
// HTTPClient is the http client to use for the REST client
HTTPClient *http.Client
// Scheme is the scheme to use for mapping objects to GroupVersionKinds
Scheme *runtime.Scheme
// Mapper is the RESTMapper to use for mapping GroupVersionKinds to Resources
Mapper meta.RESTMapper
// SyncPeriod determines the minimum frequency at which watched resources are
// reconciled. A lower period will correct entropy more quickly, but reduce
// responsiveness to change if there are many watched resources. Change this
// value only if you know what you are doing. Defaults to 10 hours if unset.
// there will a 10 percent jitter between the SyncPeriod of all controllers
// so that all controllers will not send list requests simultaneously.
//
// This applies to all controllers.
//
// A period sync happens for two reasons:
// 1. To insure against a bug in the controller that causes an object to not
// be requeued, when it otherwise should be requeued.
// 2. To insure against an unknown bug in controller-runtime, or its dependencies,
// that causes an object to not be requeued, when it otherwise should be
// requeued, or to be removed from the queue, when it otherwise should not
// be removed.
//
// If you want
// 1. to insure against missed watch events, or
// 2. to poll services that cannot be watched,
// then we recommend that, instead of changing the default period, the
// controller requeue, with a constant duration `t`, whenever the controller
// is "done" with an object, and would otherwise not requeue it, i.e., we
// recommend the `Reconcile` function return `reconcile.Result{RequeueAfter: t}`,
// instead of `reconcile.Result{}`.
SyncPeriod *time.Duration
// ReaderFailOnMissingInformer configures the cache to return a ErrResourceNotCached error when a user
// requests, using Get() and List(), a resource the cache does not already have an informer for.
//
// This error is distinct from an errors.NotFound.
//
// Defaults to false, which means that the cache will start a new informer
// for every new requested resource.
ReaderFailOnMissingInformer bool
// DefaultNamespaces maps namespace names to cache configs. If set, only
// the namespaces in here will be watched and it will by used to default
// ByObject.Namespaces for all objects if that is nil.
//
// The options in the Config that are nil will be defaulted from
// the respective Default* settings.
DefaultNamespaces map[string]Config
// DefaultLabelSelector will be used as a label selector for all objects
// unless there is already one set in ByObject or DefaultNamespaces.
DefaultLabelSelector labels.Selector
// DefaultFieldSelector will be used as a field selector for all object types
// unless there is already one set in ByObject or DefaultNamespaces.
DefaultFieldSelector fields.Selector
// DefaultTransform will be used as transform for all object types
// unless there is already one set in ByObject or DefaultNamespaces.
DefaultTransform toolscache.TransformFunc
// DefaultUnsafeDisableDeepCopy is the default for UnsafeDisableDeepCopy
// for everything that doesn't specify this.
//
// Be very careful with this, when enabled you must DeepCopy any object before mutating it,
// otherwise you will mutate the object in the cache.
//
// This will be used for all object types, unless it is set in ByObject or
// DefaultNamespaces.
DefaultUnsafeDisableDeepCopy *bool
// ByObject restricts the cache's ListWatch to the desired fields per GVK at the specified object.
// object, this will fall through to Default* settings.
ByObject map[client.Object]ByObject
// newInformer allows overriding of NewSharedIndexInformer for testing.
newInformer *func(toolscache.ListerWatcher, runtime.Object, time.Duration, toolscache.Indexers) toolscache.SharedIndexInformer
}
// ByObject offers more fine-grained control over the cache's ListWatch by object.
type ByObject struct {
// Namespaces maps a namespace name to cache configs. If set, only the
// namespaces in this map will be cached.
//
// Settings in the map value that are unset will be defaulted.
// Use an empty value for the specific setting to prevent that.
//
// A nil map allows to default this to the cache's DefaultNamespaces setting.
// An empty map prevents this and means that all namespaces will be cached.
//
// The defaulting follows the following precedence order:
// 1. ByObject
// 2. DefaultNamespaces[namespace]
// 3. Default*
//
// This must be unset for cluster-scoped objects.
Namespaces map[string]Config
// Label represents a label selector for the object.
Label labels.Selector
// Field represents a field selector for the object.
Field fields.Selector
// Transform is a transformer function for the object which gets applied
// when objects of the transformation are about to be committed to the cache.
//
// This function is called both for new objects to enter the cache,
// and for updated objects.
Transform toolscache.TransformFunc
// UnsafeDisableDeepCopy indicates not to deep copy objects during get or
// list objects per GVK at the specified object.
// Be very careful with this, when enabled you must DeepCopy any object before mutating it,
// otherwise you will mutate the object in the cache.
UnsafeDisableDeepCopy *bool
}
// Config describes all potential options for a given watch.
type Config struct {
// LabelSelector specifies a label selector. A nil value allows to
// default this.
//
// Set to labels.Everything() if you don't want this defaulted.
LabelSelector labels.Selector
// FieldSelector specifics a field selector. A nil value allows to
// default this.
//
// Set to fields.Everything() if you don't want this defaulted.
FieldSelector fields.Selector
// Transform specifies a transform func. A nil value allows to default
// this.
//
// Set to an empty func to prevent this:
// func(in interface{}) (interface{}, error) { return in, nil }
Transform toolscache.TransformFunc
// UnsafeDisableDeepCopy specifies if List and Get requests against the
// cache should not DeepCopy. A nil value allows to default this.
UnsafeDisableDeepCopy *bool
}
// NewCacheFunc - Function for creating a new cache from the options and a rest config.
type NewCacheFunc func(config *rest.Config, opts Options) (Cache, error)
// New initializes and returns a new Cache.
func New(cfg *rest.Config, opts Options) (Cache, error) {
opts, err := defaultOpts(cfg, opts)
if err != nil {
return nil, err
}
newCacheFunc := newCache(cfg, opts)
var defaultCache Cache
if len(opts.DefaultNamespaces) > 0 {
defaultConfig := optionDefaultsToConfig(&opts)
defaultCache = newMultiNamespaceCache(newCacheFunc, opts.Scheme, opts.Mapper, opts.DefaultNamespaces, &defaultConfig)
} else {
defaultCache = newCacheFunc(optionDefaultsToConfig(&opts), corev1.NamespaceAll)
}
if len(opts.ByObject) == 0 {
return defaultCache, nil
}
delegating := &delegatingByGVKCache{
scheme: opts.Scheme,
caches: make(map[schema.GroupVersionKind]Cache, len(opts.ByObject)),
defaultCache: defaultCache,
}
for obj, config := range opts.ByObject {
gvk, err := apiutil.GVKForObject(obj, opts.Scheme)
if err != nil {
return nil, fmt.Errorf("failed to get GVK for type %T: %w", obj, err)
}
var cache Cache
if len(config.Namespaces) > 0 {
cache = newMultiNamespaceCache(newCacheFunc, opts.Scheme, opts.Mapper, config.Namespaces, nil)
} else {
cache = newCacheFunc(byObjectToConfig(config), corev1.NamespaceAll)
}
delegating.caches[gvk] = cache
}
return delegating, nil
}
func optionDefaultsToConfig(opts *Options) Config {
return Config{
LabelSelector: opts.DefaultLabelSelector,
FieldSelector: opts.DefaultFieldSelector,
Transform: opts.DefaultTransform,
UnsafeDisableDeepCopy: opts.DefaultUnsafeDisableDeepCopy,
}
}
func byObjectToConfig(byObject ByObject) Config {
return Config{
LabelSelector: byObject.Label,
FieldSelector: byObject.Field,
Transform: byObject.Transform,
UnsafeDisableDeepCopy: byObject.UnsafeDisableDeepCopy,
}
}
type newCacheFunc func(config Config, namespace string) Cache
func newCache(restConfig *rest.Config, opts Options) newCacheFunc {
return func(config Config, namespace string) Cache {
return &informerCache{
scheme: opts.Scheme,
Informers: internal.NewInformers(restConfig, &internal.InformersOpts{
HTTPClient: opts.HTTPClient,
Scheme: opts.Scheme,
Mapper: opts.Mapper,
ResyncPeriod: *opts.SyncPeriod,
Namespace: namespace,
Selector: internal.Selector{
Label: config.LabelSelector,
Field: config.FieldSelector,
},
Transform: config.Transform,
UnsafeDisableDeepCopy: pointer.BoolDeref(config.UnsafeDisableDeepCopy, false),
NewInformer: opts.newInformer,
}),
readerFailOnMissingInformer: opts.ReaderFailOnMissingInformer,
}
}
}
func defaultOpts(config *rest.Config, opts Options) (Options, error) {
config = rest.CopyConfig(config)
if config.UserAgent == "" {
config.UserAgent = rest.DefaultKubernetesUserAgent()
}
// Use the rest HTTP client for the provided config if unset
if opts.HTTPClient == nil {
var err error
opts.HTTPClient, err = rest.HTTPClientFor(config)
if err != nil {
return Options{}, fmt.Errorf("could not create HTTP client from config: %w", err)
}
}
// Use the default Kubernetes Scheme if unset
if opts.Scheme == nil {
opts.Scheme = scheme.Scheme
}
// Construct a new Mapper if unset
if opts.Mapper == nil {
var err error
opts.Mapper, err = apiutil.NewDiscoveryRESTMapper(config, opts.HTTPClient)
if err != nil {
return Options{}, fmt.Errorf("could not create RESTMapper from config: %w", err)
}
}
for namespace, cfg := range opts.DefaultNamespaces {
cfg = defaultConfig(cfg, optionDefaultsToConfig(&opts))
opts.DefaultNamespaces[namespace] = cfg
}
for obj, byObject := range opts.ByObject {
isNamespaced, err := apiutil.IsObjectNamespaced(obj, opts.Scheme, opts.Mapper)
if err != nil {
return opts, fmt.Errorf("failed to determine if %T is namespaced: %w", obj, err)
}
if !isNamespaced && byObject.Namespaces != nil {
return opts, fmt.Errorf("type %T is not namespaced, but its ByObject.Namespaces setting is not nil", obj)
}
// Default the namespace-level configs first, because they need to use the undefaulted type-level config.
for namespace, config := range byObject.Namespaces {
// 1. Default from the undefaulted type-level config
config = defaultConfig(config, byObjectToConfig(byObject))
// 2. Default from the namespace-level config. This was defaulted from the global default config earlier, but
// might not have an entry for the current namespace.
if defaultNamespaceSettings, hasDefaultNamespace := opts.DefaultNamespaces[namespace]; hasDefaultNamespace {
config = defaultConfig(config, defaultNamespaceSettings)
}
// 3. Default from the global defaults
config = defaultConfig(config, optionDefaultsToConfig(&opts))
byObject.Namespaces[namespace] = config
}
defaultedConfig := defaultConfig(byObjectToConfig(byObject), optionDefaultsToConfig(&opts))
byObject.Label = defaultedConfig.LabelSelector
byObject.Field = defaultedConfig.FieldSelector
byObject.Transform = defaultedConfig.Transform
byObject.UnsafeDisableDeepCopy = defaultedConfig.UnsafeDisableDeepCopy
if byObject.Namespaces == nil {
byObject.Namespaces = opts.DefaultNamespaces
}
opts.ByObject[obj] = byObject
}
// Default the resync period to 10 hours if unset
if opts.SyncPeriod == nil {
opts.SyncPeriod = &defaultSyncPeriod
}
return opts, nil
}
func defaultConfig(toDefault, defaultFrom Config) Config {
if toDefault.LabelSelector == nil {
toDefault.LabelSelector = defaultFrom.LabelSelector
}
if toDefault.FieldSelector == nil {
toDefault.FieldSelector = defaultFrom.FieldSelector
}
if toDefault.Transform == nil {
toDefault.Transform = defaultFrom.Transform
}
if toDefault.UnsafeDisableDeepCopy == nil {
toDefault.UnsafeDisableDeepCopy = defaultFrom.UnsafeDisableDeepCopy
}
return toDefault
}