Add vendor dependencies as part git repo
This commit is contained in:
28
vendor/k8s.io/client-go/tools/cache/OWNERS
generated
vendored
Normal file
28
vendor/k8s.io/client-go/tools/cache/OWNERS
generated
vendored
Normal file
@ -0,0 +1,28 @@
|
||||
# See the OWNERS docs at https://go.k8s.io/owners
|
||||
|
||||
approvers:
|
||||
- thockin
|
||||
- lavalamp
|
||||
- smarterclayton
|
||||
- wojtek-t
|
||||
- deads2k
|
||||
- caesarxuchao
|
||||
- liggitt
|
||||
- ncdc
|
||||
reviewers:
|
||||
- thockin
|
||||
- lavalamp
|
||||
- smarterclayton
|
||||
- wojtek-t
|
||||
- deads2k
|
||||
- derekwaynecarr
|
||||
- caesarxuchao
|
||||
- mikedanese
|
||||
- liggitt
|
||||
- janetkuo
|
||||
- justinsb
|
||||
- soltysh
|
||||
- jsafrane
|
||||
- dims
|
||||
- ingvagabund
|
||||
- ncdc
|
498
vendor/k8s.io/client-go/tools/cache/controller.go
generated
vendored
Normal file
498
vendor/k8s.io/client-go/tools/cache/controller.go
generated
vendored
Normal file
@ -0,0 +1,498 @@
|
||||
/*
|
||||
Copyright 2015 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 (
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
// This file implements a low-level controller that is used in
|
||||
// sharedIndexInformer, which is an implementation of
|
||||
// SharedIndexInformer. Such informers, in turn, are key components
|
||||
// in the high level controllers that form the backbone of the
|
||||
// Kubernetes control plane. Look at those for examples, or the
|
||||
// example in
|
||||
// https://github.com/kubernetes/client-go/tree/master/examples/workqueue
|
||||
// .
|
||||
|
||||
// Config contains all the settings for one of these low-level controllers.
|
||||
type Config struct {
|
||||
// The queue for your objects - has to be a DeltaFIFO due to
|
||||
// assumptions in the implementation. Your Process() function
|
||||
// should accept the output of this Queue's Pop() method.
|
||||
Queue
|
||||
|
||||
// Something that can list and watch your objects.
|
||||
ListerWatcher
|
||||
|
||||
// Something that can process a popped Deltas.
|
||||
Process ProcessFunc
|
||||
|
||||
// ObjectType is an example object of the type this controller is
|
||||
// expected to handle. Only the type needs to be right, except
|
||||
// that when that is `unstructured.Unstructured` the object's
|
||||
// `"apiVersion"` and `"kind"` must also be right.
|
||||
ObjectType runtime.Object
|
||||
|
||||
// FullResyncPeriod is the period at which ShouldResync is considered.
|
||||
FullResyncPeriod time.Duration
|
||||
|
||||
// ShouldResync is periodically used by the reflector to determine
|
||||
// whether to Resync the Queue. If ShouldResync is `nil` or
|
||||
// returns true, it means the reflector should proceed with the
|
||||
// resync.
|
||||
ShouldResync ShouldResyncFunc
|
||||
|
||||
// If true, when Process() returns an error, re-enqueue the object.
|
||||
// TODO: add interface to let you inject a delay/backoff or drop
|
||||
// the object completely if desired. Pass the object in
|
||||
// question to this interface as a parameter. This is probably moot
|
||||
// now that this functionality appears at a higher level.
|
||||
RetryOnError bool
|
||||
|
||||
// Called whenever the ListAndWatch drops the connection with an error.
|
||||
WatchErrorHandler WatchErrorHandler
|
||||
|
||||
// WatchListPageSize is the requested chunk size of initial and relist watch lists.
|
||||
WatchListPageSize int64
|
||||
}
|
||||
|
||||
// ShouldResyncFunc is a type of function that indicates if a reflector should perform a
|
||||
// resync or not. It can be used by a shared informer to support multiple event handlers with custom
|
||||
// resync periods.
|
||||
type ShouldResyncFunc func() bool
|
||||
|
||||
// ProcessFunc processes a single object.
|
||||
type ProcessFunc func(obj interface{}) error
|
||||
|
||||
// `*controller` implements Controller
|
||||
type controller struct {
|
||||
config Config
|
||||
reflector *Reflector
|
||||
reflectorMutex sync.RWMutex
|
||||
clock clock.Clock
|
||||
}
|
||||
|
||||
// Controller is a low-level controller that is parameterized by a
|
||||
// Config and used in sharedIndexInformer.
|
||||
type Controller interface {
|
||||
// Run does two things. One is to construct and run a Reflector
|
||||
// to pump objects/notifications from the Config's ListerWatcher
|
||||
// to the Config's Queue and possibly invoke the occasional Resync
|
||||
// on that Queue. The other is to repeatedly Pop from the Queue
|
||||
// and process with the Config's ProcessFunc. Both of these
|
||||
// continue until `stopCh` is closed.
|
||||
Run(stopCh <-chan struct{})
|
||||
|
||||
// HasSynced delegates to the Config's Queue
|
||||
HasSynced() bool
|
||||
|
||||
// LastSyncResourceVersion delegates to the Reflector when there
|
||||
// is one, otherwise returns the empty string
|
||||
LastSyncResourceVersion() string
|
||||
}
|
||||
|
||||
// New makes a new Controller from the given Config.
|
||||
func New(c *Config) Controller {
|
||||
ctlr := &controller{
|
||||
config: *c,
|
||||
clock: &clock.RealClock{},
|
||||
}
|
||||
return ctlr
|
||||
}
|
||||
|
||||
// Run begins processing items, and will continue until a value is sent down stopCh or it is closed.
|
||||
// It's an error to call Run more than once.
|
||||
// Run blocks; call via go.
|
||||
func (c *controller) Run(stopCh <-chan struct{}) {
|
||||
defer utilruntime.HandleCrash()
|
||||
go func() {
|
||||
<-stopCh
|
||||
c.config.Queue.Close()
|
||||
}()
|
||||
r := NewReflector(
|
||||
c.config.ListerWatcher,
|
||||
c.config.ObjectType,
|
||||
c.config.Queue,
|
||||
c.config.FullResyncPeriod,
|
||||
)
|
||||
r.ShouldResync = c.config.ShouldResync
|
||||
r.WatchListPageSize = c.config.WatchListPageSize
|
||||
r.clock = c.clock
|
||||
if c.config.WatchErrorHandler != nil {
|
||||
r.watchErrorHandler = c.config.WatchErrorHandler
|
||||
}
|
||||
|
||||
c.reflectorMutex.Lock()
|
||||
c.reflector = r
|
||||
c.reflectorMutex.Unlock()
|
||||
|
||||
var wg wait.Group
|
||||
|
||||
wg.StartWithChannel(stopCh, r.Run)
|
||||
|
||||
wait.Until(c.processLoop, time.Second, stopCh)
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
// Returns true once this controller has completed an initial resource listing
|
||||
func (c *controller) HasSynced() bool {
|
||||
return c.config.Queue.HasSynced()
|
||||
}
|
||||
|
||||
func (c *controller) LastSyncResourceVersion() string {
|
||||
c.reflectorMutex.RLock()
|
||||
defer c.reflectorMutex.RUnlock()
|
||||
if c.reflector == nil {
|
||||
return ""
|
||||
}
|
||||
return c.reflector.LastSyncResourceVersion()
|
||||
}
|
||||
|
||||
// processLoop drains the work queue.
|
||||
// TODO: Consider doing the processing in parallel. This will require a little thought
|
||||
// to make sure that we don't end up processing the same object multiple times
|
||||
// concurrently.
|
||||
//
|
||||
// TODO: Plumb through the stopCh here (and down to the queue) so that this can
|
||||
// actually exit when the controller is stopped. Or just give up on this stuff
|
||||
// ever being stoppable. Converting this whole package to use Context would
|
||||
// also be helpful.
|
||||
func (c *controller) processLoop() {
|
||||
for {
|
||||
obj, err := c.config.Queue.Pop(PopProcessFunc(c.config.Process))
|
||||
if err != nil {
|
||||
if err == ErrFIFOClosed {
|
||||
return
|
||||
}
|
||||
if c.config.RetryOnError {
|
||||
// This is the safe way to re-enqueue.
|
||||
c.config.Queue.AddIfNotPresent(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ResourceEventHandler can handle notifications for events that
|
||||
// happen to a resource. The events are informational only, so you
|
||||
// can't return an error. The handlers MUST NOT modify the objects
|
||||
// received; this concerns not only the top level of structure but all
|
||||
// the data structures reachable from it.
|
||||
// * OnAdd is called when an object is added.
|
||||
// * OnUpdate is called when an object is modified. Note that oldObj is the
|
||||
// last known state of the object-- it is possible that several changes
|
||||
// were combined together, so you can't use this to see every single
|
||||
// change. OnUpdate is also called when a re-list happens, and it will
|
||||
// get called even if nothing changed. This is useful for periodically
|
||||
// evaluating or syncing something.
|
||||
// * OnDelete will get the final state of the item if it is known, otherwise
|
||||
// it will get an object of type DeletedFinalStateUnknown. This can
|
||||
// happen if the watch is closed and misses the delete event and we don't
|
||||
// notice the deletion until the subsequent re-list.
|
||||
type ResourceEventHandler interface {
|
||||
OnAdd(obj interface{})
|
||||
OnUpdate(oldObj, newObj interface{})
|
||||
OnDelete(obj interface{})
|
||||
}
|
||||
|
||||
// ResourceEventHandlerFuncs is an adaptor to let you easily specify as many or
|
||||
// as few of the notification functions as you want while still implementing
|
||||
// ResourceEventHandler. This adapter does not remove the prohibition against
|
||||
// modifying the objects.
|
||||
type ResourceEventHandlerFuncs struct {
|
||||
AddFunc func(obj interface{})
|
||||
UpdateFunc func(oldObj, newObj interface{})
|
||||
DeleteFunc func(obj interface{})
|
||||
}
|
||||
|
||||
// OnAdd calls AddFunc if it's not nil.
|
||||
func (r ResourceEventHandlerFuncs) OnAdd(obj interface{}) {
|
||||
if r.AddFunc != nil {
|
||||
r.AddFunc(obj)
|
||||
}
|
||||
}
|
||||
|
||||
// OnUpdate calls UpdateFunc if it's not nil.
|
||||
func (r ResourceEventHandlerFuncs) OnUpdate(oldObj, newObj interface{}) {
|
||||
if r.UpdateFunc != nil {
|
||||
r.UpdateFunc(oldObj, newObj)
|
||||
}
|
||||
}
|
||||
|
||||
// OnDelete calls DeleteFunc if it's not nil.
|
||||
func (r ResourceEventHandlerFuncs) OnDelete(obj interface{}) {
|
||||
if r.DeleteFunc != nil {
|
||||
r.DeleteFunc(obj)
|
||||
}
|
||||
}
|
||||
|
||||
// FilteringResourceEventHandler applies the provided filter to all events coming
|
||||
// in, ensuring the appropriate nested handler method is invoked. An object
|
||||
// that starts passing the filter after an update is considered an add, and an
|
||||
// object that stops passing the filter after an update is considered a delete.
|
||||
// Like the handlers, the filter MUST NOT modify the objects it is given.
|
||||
type FilteringResourceEventHandler struct {
|
||||
FilterFunc func(obj interface{}) bool
|
||||
Handler ResourceEventHandler
|
||||
}
|
||||
|
||||
// OnAdd calls the nested handler only if the filter succeeds
|
||||
func (r FilteringResourceEventHandler) OnAdd(obj interface{}) {
|
||||
if !r.FilterFunc(obj) {
|
||||
return
|
||||
}
|
||||
r.Handler.OnAdd(obj)
|
||||
}
|
||||
|
||||
// OnUpdate ensures the proper handler is called depending on whether the filter matches
|
||||
func (r FilteringResourceEventHandler) OnUpdate(oldObj, newObj interface{}) {
|
||||
newer := r.FilterFunc(newObj)
|
||||
older := r.FilterFunc(oldObj)
|
||||
switch {
|
||||
case newer && older:
|
||||
r.Handler.OnUpdate(oldObj, newObj)
|
||||
case newer && !older:
|
||||
r.Handler.OnAdd(newObj)
|
||||
case !newer && older:
|
||||
r.Handler.OnDelete(oldObj)
|
||||
default:
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
// OnDelete calls the nested handler only if the filter succeeds
|
||||
func (r FilteringResourceEventHandler) OnDelete(obj interface{}) {
|
||||
if !r.FilterFunc(obj) {
|
||||
return
|
||||
}
|
||||
r.Handler.OnDelete(obj)
|
||||
}
|
||||
|
||||
// DeletionHandlingMetaNamespaceKeyFunc checks for
|
||||
// DeletedFinalStateUnknown objects before calling
|
||||
// MetaNamespaceKeyFunc.
|
||||
func DeletionHandlingMetaNamespaceKeyFunc(obj interface{}) (string, error) {
|
||||
if d, ok := obj.(DeletedFinalStateUnknown); ok {
|
||||
return d.Key, nil
|
||||
}
|
||||
return MetaNamespaceKeyFunc(obj)
|
||||
}
|
||||
|
||||
// NewInformer returns a Store and a controller for populating the store
|
||||
// while also providing event notifications. You should only used the returned
|
||||
// Store for Get/List operations; Add/Modify/Deletes will cause the event
|
||||
// notifications to be faulty.
|
||||
//
|
||||
// Parameters:
|
||||
// * lw is list and watch functions for the source of the resource you want to
|
||||
// be informed of.
|
||||
// * objType is an object of the type that you expect to receive.
|
||||
// * resyncPeriod: if non-zero, will re-list this often (you will get OnUpdate
|
||||
// calls, even if nothing changed). Otherwise, re-list will be delayed as
|
||||
// long as possible (until the upstream source closes the watch or times out,
|
||||
// or you stop the controller).
|
||||
// * h is the object you want notifications sent to.
|
||||
//
|
||||
func NewInformer(
|
||||
lw ListerWatcher,
|
||||
objType runtime.Object,
|
||||
resyncPeriod time.Duration,
|
||||
h ResourceEventHandler,
|
||||
) (Store, Controller) {
|
||||
// This will hold the client state, as we know it.
|
||||
clientState := NewStore(DeletionHandlingMetaNamespaceKeyFunc)
|
||||
|
||||
return clientState, newInformer(lw, objType, resyncPeriod, h, clientState, nil)
|
||||
}
|
||||
|
||||
// NewIndexerInformer returns an Indexer and a Controller for populating the index
|
||||
// while also providing event notifications. You should only used the returned
|
||||
// Index for Get/List operations; Add/Modify/Deletes will cause the event
|
||||
// notifications to be faulty.
|
||||
//
|
||||
// Parameters:
|
||||
// * lw is list and watch functions for the source of the resource you want to
|
||||
// be informed of.
|
||||
// * objType is an object of the type that you expect to receive.
|
||||
// * resyncPeriod: if non-zero, will re-list this often (you will get OnUpdate
|
||||
// calls, even if nothing changed). Otherwise, re-list will be delayed as
|
||||
// long as possible (until the upstream source closes the watch or times out,
|
||||
// or you stop the controller).
|
||||
// * h is the object you want notifications sent to.
|
||||
// * indexers is the indexer for the received object type.
|
||||
//
|
||||
func NewIndexerInformer(
|
||||
lw ListerWatcher,
|
||||
objType runtime.Object,
|
||||
resyncPeriod time.Duration,
|
||||
h ResourceEventHandler,
|
||||
indexers Indexers,
|
||||
) (Indexer, Controller) {
|
||||
// This will hold the client state, as we know it.
|
||||
clientState := NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers)
|
||||
|
||||
return clientState, newInformer(lw, objType, resyncPeriod, h, clientState, nil)
|
||||
}
|
||||
|
||||
// TransformFunc allows for transforming an object before it will be processed
|
||||
// and put into the controller cache and before the corresponding handlers will
|
||||
// be called on it.
|
||||
// TransformFunc (similarly to ResourceEventHandler functions) should be able
|
||||
// to correctly handle the tombstone of type cache.DeletedFinalStateUnknown
|
||||
//
|
||||
// The most common usage pattern is to clean-up some parts of the object to
|
||||
// reduce component memory usage if a given component doesn't care about them.
|
||||
// given controller doesn't care for them
|
||||
type TransformFunc func(interface{}) (interface{}, error)
|
||||
|
||||
// NewTransformingInformer returns a Store and a controller for populating
|
||||
// the store while also providing event notifications. You should only used
|
||||
// the returned Store for Get/List operations; Add/Modify/Deletes will cause
|
||||
// the event notifications to be faulty.
|
||||
// The given transform function will be called on all objects before they will
|
||||
// put into the Store and corresponding Add/Modify/Delete handlers will
|
||||
// be invoked for them.
|
||||
func NewTransformingInformer(
|
||||
lw ListerWatcher,
|
||||
objType runtime.Object,
|
||||
resyncPeriod time.Duration,
|
||||
h ResourceEventHandler,
|
||||
transformer TransformFunc,
|
||||
) (Store, Controller) {
|
||||
// This will hold the client state, as we know it.
|
||||
clientState := NewStore(DeletionHandlingMetaNamespaceKeyFunc)
|
||||
|
||||
return clientState, newInformer(lw, objType, resyncPeriod, h, clientState, transformer)
|
||||
}
|
||||
|
||||
// NewTransformingIndexerInformer returns an Indexer and a controller for
|
||||
// populating the index while also providing event notifications. You should
|
||||
// only used the returned Index for Get/List operations; Add/Modify/Deletes
|
||||
// will cause the event notifications to be faulty.
|
||||
// The given transform function will be called on all objects before they will
|
||||
// be put into the Index and corresponding Add/Modify/Delete handlers will
|
||||
// be invoked for them.
|
||||
func NewTransformingIndexerInformer(
|
||||
lw ListerWatcher,
|
||||
objType runtime.Object,
|
||||
resyncPeriod time.Duration,
|
||||
h ResourceEventHandler,
|
||||
indexers Indexers,
|
||||
transformer TransformFunc,
|
||||
) (Indexer, Controller) {
|
||||
// This will hold the client state, as we know it.
|
||||
clientState := NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers)
|
||||
|
||||
return clientState, newInformer(lw, objType, resyncPeriod, h, clientState, transformer)
|
||||
}
|
||||
|
||||
// Multiplexes updates in the form of a list of Deltas into a Store, and informs
|
||||
// a given handler of events OnUpdate, OnAdd, OnDelete
|
||||
func processDeltas(
|
||||
// Object which receives event notifications from the given deltas
|
||||
handler ResourceEventHandler,
|
||||
clientState Store,
|
||||
transformer TransformFunc,
|
||||
deltas Deltas,
|
||||
) error {
|
||||
// from oldest to newest
|
||||
for _, d := range deltas {
|
||||
obj := d.Object
|
||||
if transformer != nil {
|
||||
var err error
|
||||
obj, err = transformer(obj)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
switch d.Type {
|
||||
case Sync, Replaced, Added, Updated:
|
||||
if old, exists, err := clientState.Get(obj); err == nil && exists {
|
||||
if err := clientState.Update(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
handler.OnUpdate(old, obj)
|
||||
} else {
|
||||
if err := clientState.Add(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
handler.OnAdd(obj)
|
||||
}
|
||||
case Deleted:
|
||||
if err := clientState.Delete(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
handler.OnDelete(obj)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// newInformer returns a controller for populating the store while also
|
||||
// providing event notifications.
|
||||
//
|
||||
// Parameters
|
||||
// * lw is list and watch functions for the source of the resource you want to
|
||||
// be informed of.
|
||||
// * objType is an object of the type that you expect to receive.
|
||||
// * resyncPeriod: if non-zero, will re-list this often (you will get OnUpdate
|
||||
// calls, even if nothing changed). Otherwise, re-list will be delayed as
|
||||
// long as possible (until the upstream source closes the watch or times out,
|
||||
// or you stop the controller).
|
||||
// * h is the object you want notifications sent to.
|
||||
// * clientState is the store you want to populate
|
||||
//
|
||||
func newInformer(
|
||||
lw ListerWatcher,
|
||||
objType runtime.Object,
|
||||
resyncPeriod time.Duration,
|
||||
h ResourceEventHandler,
|
||||
clientState Store,
|
||||
transformer TransformFunc,
|
||||
) Controller {
|
||||
// This will hold incoming changes. Note how we pass clientState in as a
|
||||
// KeyLister, that way resync operations will result in the correct set
|
||||
// of update/delete deltas.
|
||||
fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{
|
||||
KnownObjects: clientState,
|
||||
EmitDeltaTypeReplaced: true,
|
||||
})
|
||||
|
||||
cfg := &Config{
|
||||
Queue: fifo,
|
||||
ListerWatcher: lw,
|
||||
ObjectType: objType,
|
||||
FullResyncPeriod: resyncPeriod,
|
||||
RetryOnError: false,
|
||||
|
||||
Process: func(obj interface{}) error {
|
||||
if deltas, ok := obj.(Deltas); ok {
|
||||
return processDeltas(h, clientState, transformer, deltas)
|
||||
}
|
||||
return errors.New("object given as Process argument is not Deltas")
|
||||
},
|
||||
}
|
||||
return New(cfg)
|
||||
}
|
757
vendor/k8s.io/client-go/tools/cache/delta_fifo.go
generated
vendored
Normal file
757
vendor/k8s.io/client-go/tools/cache/delta_fifo.go
generated
vendored
Normal file
@ -0,0 +1,757 @@
|
||||
/*
|
||||
Copyright 2014 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
utiltrace "k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
// DeltaFIFOOptions is the configuration parameters for DeltaFIFO. All are
|
||||
// optional.
|
||||
type DeltaFIFOOptions struct {
|
||||
|
||||
// KeyFunction is used to figure out what key an object should have. (It's
|
||||
// exposed in the returned DeltaFIFO's KeyOf() method, with additional
|
||||
// handling around deleted objects and queue state).
|
||||
// Optional, the default is MetaNamespaceKeyFunc.
|
||||
KeyFunction KeyFunc
|
||||
|
||||
// KnownObjects is expected to return a list of keys that the consumer of
|
||||
// this queue "knows about". It is used to decide which items are missing
|
||||
// when Replace() is called; 'Deleted' deltas are produced for the missing items.
|
||||
// KnownObjects may be nil if you can tolerate missing deletions on Replace().
|
||||
KnownObjects KeyListerGetter
|
||||
|
||||
// EmitDeltaTypeReplaced indicates that the queue consumer
|
||||
// understands the Replaced DeltaType. Before the `Replaced` event type was
|
||||
// added, calls to Replace() were handled the same as Sync(). For
|
||||
// backwards-compatibility purposes, this is false by default.
|
||||
// When true, `Replaced` events will be sent for items passed to a Replace() call.
|
||||
// When false, `Sync` events will be sent instead.
|
||||
EmitDeltaTypeReplaced bool
|
||||
}
|
||||
|
||||
// DeltaFIFO is like FIFO, but differs in two ways. One is that the
|
||||
// accumulator associated with a given object's key is not that object
|
||||
// but rather a Deltas, which is a slice of Delta values for that
|
||||
// object. Applying an object to a Deltas means to append a Delta
|
||||
// except when the potentially appended Delta is a Deleted and the
|
||||
// Deltas already ends with a Deleted. In that case the Deltas does
|
||||
// not grow, although the terminal Deleted will be replaced by the new
|
||||
// Deleted if the older Deleted's object is a
|
||||
// DeletedFinalStateUnknown.
|
||||
//
|
||||
// The other difference is that DeltaFIFO has two additional ways that
|
||||
// an object can be applied to an accumulator: Replaced and Sync.
|
||||
// If EmitDeltaTypeReplaced is not set to true, Sync will be used in
|
||||
// replace events for backwards compatibility. Sync is used for periodic
|
||||
// resync events.
|
||||
//
|
||||
// DeltaFIFO is a producer-consumer queue, where a Reflector is
|
||||
// intended to be the producer, and the consumer is whatever calls
|
||||
// the Pop() method.
|
||||
//
|
||||
// DeltaFIFO solves this use case:
|
||||
// * You want to process every object change (delta) at most once.
|
||||
// * When you process an object, you want to see everything
|
||||
// that's happened to it since you last processed it.
|
||||
// * You want to process the deletion of some of the objects.
|
||||
// * You might want to periodically reprocess objects.
|
||||
//
|
||||
// DeltaFIFO's Pop(), Get(), and GetByKey() methods return
|
||||
// interface{} to satisfy the Store/Queue interfaces, but they
|
||||
// will always return an object of type Deltas. List() returns
|
||||
// the newest object from each accumulator in the FIFO.
|
||||
//
|
||||
// A DeltaFIFO's knownObjects KeyListerGetter provides the abilities
|
||||
// to list Store keys and to get objects by Store key. The objects in
|
||||
// question are called "known objects" and this set of objects
|
||||
// modifies the behavior of the Delete, Replace, and Resync methods
|
||||
// (each in a different way).
|
||||
//
|
||||
// A note on threading: If you call Pop() in parallel from multiple
|
||||
// threads, you could end up with multiple threads processing slightly
|
||||
// different versions of the same object.
|
||||
type DeltaFIFO struct {
|
||||
// lock/cond protects access to 'items' and 'queue'.
|
||||
lock sync.RWMutex
|
||||
cond sync.Cond
|
||||
|
||||
// `items` maps a key to a Deltas.
|
||||
// Each such Deltas has at least one Delta.
|
||||
items map[string]Deltas
|
||||
|
||||
// `queue` maintains FIFO order of keys for consumption in Pop().
|
||||
// There are no duplicates in `queue`.
|
||||
// A key is in `queue` if and only if it is in `items`.
|
||||
queue []string
|
||||
|
||||
// populated is true if the first batch of items inserted by Replace() has been populated
|
||||
// or Delete/Add/Update/AddIfNotPresent was called first.
|
||||
populated bool
|
||||
// initialPopulationCount is the number of items inserted by the first call of Replace()
|
||||
initialPopulationCount int
|
||||
|
||||
// keyFunc is used to make the key used for queued item
|
||||
// insertion and retrieval, and should be deterministic.
|
||||
keyFunc KeyFunc
|
||||
|
||||
// knownObjects list keys that are "known" --- affecting Delete(),
|
||||
// Replace(), and Resync()
|
||||
knownObjects KeyListerGetter
|
||||
|
||||
// Used to indicate a queue is closed so a control loop can exit when a queue is empty.
|
||||
// Currently, not used to gate any of CRUD operations.
|
||||
closed bool
|
||||
|
||||
// emitDeltaTypeReplaced is whether to emit the Replaced or Sync
|
||||
// DeltaType when Replace() is called (to preserve backwards compat).
|
||||
emitDeltaTypeReplaced bool
|
||||
}
|
||||
|
||||
// DeltaType is the type of a change (addition, deletion, etc)
|
||||
type DeltaType string
|
||||
|
||||
// Change type definition
|
||||
const (
|
||||
Added DeltaType = "Added"
|
||||
Updated DeltaType = "Updated"
|
||||
Deleted DeltaType = "Deleted"
|
||||
// Replaced is emitted when we encountered watch errors and had to do a
|
||||
// relist. We don't know if the replaced object has changed.
|
||||
//
|
||||
// NOTE: Previous versions of DeltaFIFO would use Sync for Replace events
|
||||
// as well. Hence, Replaced is only emitted when the option
|
||||
// EmitDeltaTypeReplaced is true.
|
||||
Replaced DeltaType = "Replaced"
|
||||
// Sync is for synthetic events during a periodic resync.
|
||||
Sync DeltaType = "Sync"
|
||||
)
|
||||
|
||||
// Delta is a member of Deltas (a list of Delta objects) which
|
||||
// in its turn is the type stored by a DeltaFIFO. It tells you what
|
||||
// change happened, and the object's state after* that change.
|
||||
//
|
||||
// [*] Unless the change is a deletion, and then you'll get the final
|
||||
// state of the object before it was deleted.
|
||||
type Delta struct {
|
||||
Type DeltaType
|
||||
Object interface{}
|
||||
}
|
||||
|
||||
// Deltas is a list of one or more 'Delta's to an individual object.
|
||||
// The oldest delta is at index 0, the newest delta is the last one.
|
||||
type Deltas []Delta
|
||||
|
||||
// NewDeltaFIFO returns a Queue which can be used to process changes to items.
|
||||
//
|
||||
// keyFunc is used to figure out what key an object should have. (It is
|
||||
// exposed in the returned DeltaFIFO's KeyOf() method, with additional handling
|
||||
// around deleted objects and queue state).
|
||||
//
|
||||
// 'knownObjects' may be supplied to modify the behavior of Delete,
|
||||
// Replace, and Resync. It may be nil if you do not need those
|
||||
// modifications.
|
||||
//
|
||||
// TODO: consider merging keyLister with this object, tracking a list of
|
||||
// "known" keys when Pop() is called. Have to think about how that
|
||||
// affects error retrying.
|
||||
//
|
||||
// NOTE: It is possible to misuse this and cause a race when using an
|
||||
// external known object source.
|
||||
// Whether there is a potential race depends on how the consumer
|
||||
// modifies knownObjects. In Pop(), process function is called under
|
||||
// lock, so it is safe to update data structures in it that need to be
|
||||
// in sync with the queue (e.g. knownObjects).
|
||||
//
|
||||
// Example:
|
||||
// In case of sharedIndexInformer being a consumer
|
||||
// (https://github.com/kubernetes/kubernetes/blob/0cdd940f/staging/src/k8s.io/client-go/tools/cache/shared_informer.go#L192),
|
||||
// there is no race as knownObjects (s.indexer) is modified safely
|
||||
// under DeltaFIFO's lock. The only exceptions are GetStore() and
|
||||
// GetIndexer() methods, which expose ways to modify the underlying
|
||||
// storage. Currently these two methods are used for creating Lister
|
||||
// and internal tests.
|
||||
//
|
||||
// Also see the comment on DeltaFIFO.
|
||||
//
|
||||
// Warning: This constructs a DeltaFIFO that does not differentiate between
|
||||
// events caused by a call to Replace (e.g., from a relist, which may
|
||||
// contain object updates), and synthetic events caused by a periodic resync
|
||||
// (which just emit the existing object). See https://issue.k8s.io/86015 for details.
|
||||
//
|
||||
// Use `NewDeltaFIFOWithOptions(DeltaFIFOOptions{..., EmitDeltaTypeReplaced: true})`
|
||||
// instead to receive a `Replaced` event depending on the type.
|
||||
//
|
||||
// Deprecated: Equivalent to NewDeltaFIFOWithOptions(DeltaFIFOOptions{KeyFunction: keyFunc, KnownObjects: knownObjects})
|
||||
func NewDeltaFIFO(keyFunc KeyFunc, knownObjects KeyListerGetter) *DeltaFIFO {
|
||||
return NewDeltaFIFOWithOptions(DeltaFIFOOptions{
|
||||
KeyFunction: keyFunc,
|
||||
KnownObjects: knownObjects,
|
||||
})
|
||||
}
|
||||
|
||||
// NewDeltaFIFOWithOptions returns a Queue which can be used to process changes to
|
||||
// items. See also the comment on DeltaFIFO.
|
||||
func NewDeltaFIFOWithOptions(opts DeltaFIFOOptions) *DeltaFIFO {
|
||||
if opts.KeyFunction == nil {
|
||||
opts.KeyFunction = MetaNamespaceKeyFunc
|
||||
}
|
||||
|
||||
f := &DeltaFIFO{
|
||||
items: map[string]Deltas{},
|
||||
queue: []string{},
|
||||
keyFunc: opts.KeyFunction,
|
||||
knownObjects: opts.KnownObjects,
|
||||
|
||||
emitDeltaTypeReplaced: opts.EmitDeltaTypeReplaced,
|
||||
}
|
||||
f.cond.L = &f.lock
|
||||
return f
|
||||
}
|
||||
|
||||
var (
|
||||
_ = Queue(&DeltaFIFO{}) // DeltaFIFO is a Queue
|
||||
)
|
||||
|
||||
var (
|
||||
// ErrZeroLengthDeltasObject is returned in a KeyError if a Deltas
|
||||
// object with zero length is encountered (should be impossible,
|
||||
// but included for completeness).
|
||||
ErrZeroLengthDeltasObject = errors.New("0 length Deltas object; can't get key")
|
||||
)
|
||||
|
||||
// Close the queue.
|
||||
func (f *DeltaFIFO) Close() {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.closed = true
|
||||
f.cond.Broadcast()
|
||||
}
|
||||
|
||||
// KeyOf exposes f's keyFunc, but also detects the key of a Deltas object or
|
||||
// DeletedFinalStateUnknown objects.
|
||||
func (f *DeltaFIFO) KeyOf(obj interface{}) (string, error) {
|
||||
if d, ok := obj.(Deltas); ok {
|
||||
if len(d) == 0 {
|
||||
return "", KeyError{obj, ErrZeroLengthDeltasObject}
|
||||
}
|
||||
obj = d.Newest().Object
|
||||
}
|
||||
if d, ok := obj.(DeletedFinalStateUnknown); ok {
|
||||
return d.Key, nil
|
||||
}
|
||||
return f.keyFunc(obj)
|
||||
}
|
||||
|
||||
// HasSynced returns true if an Add/Update/Delete/AddIfNotPresent are called first,
|
||||
// or the first batch of items inserted by Replace() has been popped.
|
||||
func (f *DeltaFIFO) HasSynced() bool {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
return f.populated && f.initialPopulationCount == 0
|
||||
}
|
||||
|
||||
// Add inserts an item, and puts it in the queue. The item is only enqueued
|
||||
// if it doesn't already exist in the set.
|
||||
func (f *DeltaFIFO) Add(obj interface{}) error {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.populated = true
|
||||
return f.queueActionLocked(Added, obj)
|
||||
}
|
||||
|
||||
// Update is just like Add, but makes an Updated Delta.
|
||||
func (f *DeltaFIFO) Update(obj interface{}) error {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.populated = true
|
||||
return f.queueActionLocked(Updated, obj)
|
||||
}
|
||||
|
||||
// Delete is just like Add, but makes a Deleted Delta. If the given
|
||||
// object does not already exist, it will be ignored. (It may have
|
||||
// already been deleted by a Replace (re-list), for example.) In this
|
||||
// method `f.knownObjects`, if not nil, provides (via GetByKey)
|
||||
// _additional_ objects that are considered to already exist.
|
||||
func (f *DeltaFIFO) Delete(obj interface{}) error {
|
||||
id, err := f.KeyOf(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.populated = true
|
||||
if f.knownObjects == nil {
|
||||
if _, exists := f.items[id]; !exists {
|
||||
// Presumably, this was deleted when a relist happened.
|
||||
// Don't provide a second report of the same deletion.
|
||||
return nil
|
||||
}
|
||||
} else {
|
||||
// We only want to skip the "deletion" action if the object doesn't
|
||||
// exist in knownObjects and it doesn't have corresponding item in items.
|
||||
// Note that even if there is a "deletion" action in items, we can ignore it,
|
||||
// because it will be deduped automatically in "queueActionLocked"
|
||||
_, exists, err := f.knownObjects.GetByKey(id)
|
||||
_, itemsExist := f.items[id]
|
||||
if err == nil && !exists && !itemsExist {
|
||||
// Presumably, this was deleted when a relist happened.
|
||||
// Don't provide a second report of the same deletion.
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// exist in items and/or KnownObjects
|
||||
return f.queueActionLocked(Deleted, obj)
|
||||
}
|
||||
|
||||
// AddIfNotPresent inserts an item, and puts it in the queue. If the item is already
|
||||
// present in the set, it is neither enqueued nor added to the set.
|
||||
//
|
||||
// This is useful in a single producer/consumer scenario so that the consumer can
|
||||
// safely retry items without contending with the producer and potentially enqueueing
|
||||
// stale items.
|
||||
//
|
||||
// Important: obj must be a Deltas (the output of the Pop() function). Yes, this is
|
||||
// different from the Add/Update/Delete functions.
|
||||
func (f *DeltaFIFO) AddIfNotPresent(obj interface{}) error {
|
||||
deltas, ok := obj.(Deltas)
|
||||
if !ok {
|
||||
return fmt.Errorf("object must be of type deltas, but got: %#v", obj)
|
||||
}
|
||||
id, err := f.KeyOf(deltas)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.addIfNotPresent(id, deltas)
|
||||
return nil
|
||||
}
|
||||
|
||||
// addIfNotPresent inserts deltas under id if it does not exist, and assumes the caller
|
||||
// already holds the fifo lock.
|
||||
func (f *DeltaFIFO) addIfNotPresent(id string, deltas Deltas) {
|
||||
f.populated = true
|
||||
if _, exists := f.items[id]; exists {
|
||||
return
|
||||
}
|
||||
|
||||
f.queue = append(f.queue, id)
|
||||
f.items[id] = deltas
|
||||
f.cond.Broadcast()
|
||||
}
|
||||
|
||||
// re-listing and watching can deliver the same update multiple times in any
|
||||
// order. This will combine the most recent two deltas if they are the same.
|
||||
func dedupDeltas(deltas Deltas) Deltas {
|
||||
n := len(deltas)
|
||||
if n < 2 {
|
||||
return deltas
|
||||
}
|
||||
a := &deltas[n-1]
|
||||
b := &deltas[n-2]
|
||||
if out := isDup(a, b); out != nil {
|
||||
deltas[n-2] = *out
|
||||
return deltas[:n-1]
|
||||
}
|
||||
return deltas
|
||||
}
|
||||
|
||||
// If a & b represent the same event, returns the delta that ought to be kept.
|
||||
// Otherwise, returns nil.
|
||||
// TODO: is there anything other than deletions that need deduping?
|
||||
func isDup(a, b *Delta) *Delta {
|
||||
if out := isDeletionDup(a, b); out != nil {
|
||||
return out
|
||||
}
|
||||
// TODO: Detect other duplicate situations? Are there any?
|
||||
return nil
|
||||
}
|
||||
|
||||
// keep the one with the most information if both are deletions.
|
||||
func isDeletionDup(a, b *Delta) *Delta {
|
||||
if b.Type != Deleted || a.Type != Deleted {
|
||||
return nil
|
||||
}
|
||||
// Do more sophisticated checks, or is this sufficient?
|
||||
if _, ok := b.Object.(DeletedFinalStateUnknown); ok {
|
||||
return a
|
||||
}
|
||||
return b
|
||||
}
|
||||
|
||||
// queueActionLocked appends to the delta list for the object.
|
||||
// Caller must lock first.
|
||||
func (f *DeltaFIFO) queueActionLocked(actionType DeltaType, obj interface{}) error {
|
||||
id, err := f.KeyOf(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
oldDeltas := f.items[id]
|
||||
newDeltas := append(oldDeltas, Delta{actionType, obj})
|
||||
newDeltas = dedupDeltas(newDeltas)
|
||||
|
||||
if len(newDeltas) > 0 {
|
||||
if _, exists := f.items[id]; !exists {
|
||||
f.queue = append(f.queue, id)
|
||||
}
|
||||
f.items[id] = newDeltas
|
||||
f.cond.Broadcast()
|
||||
} else {
|
||||
// This never happens, because dedupDeltas never returns an empty list
|
||||
// when given a non-empty list (as it is here).
|
||||
// If somehow it happens anyway, deal with it but complain.
|
||||
if oldDeltas == nil {
|
||||
klog.Errorf("Impossible dedupDeltas for id=%q: oldDeltas=%#+v, obj=%#+v; ignoring", id, oldDeltas, obj)
|
||||
return nil
|
||||
}
|
||||
klog.Errorf("Impossible dedupDeltas for id=%q: oldDeltas=%#+v, obj=%#+v; breaking invariant by storing empty Deltas", id, oldDeltas, obj)
|
||||
f.items[id] = newDeltas
|
||||
return fmt.Errorf("Impossible dedupDeltas for id=%q: oldDeltas=%#+v, obj=%#+v; broke DeltaFIFO invariant by storing empty Deltas", id, oldDeltas, obj)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List returns a list of all the items; it returns the object
|
||||
// from the most recent Delta.
|
||||
// You should treat the items returned inside the deltas as immutable.
|
||||
func (f *DeltaFIFO) List() []interface{} {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
return f.listLocked()
|
||||
}
|
||||
|
||||
func (f *DeltaFIFO) listLocked() []interface{} {
|
||||
list := make([]interface{}, 0, len(f.items))
|
||||
for _, item := range f.items {
|
||||
list = append(list, item.Newest().Object)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// ListKeys returns a list of all the keys of the objects currently
|
||||
// in the FIFO.
|
||||
func (f *DeltaFIFO) ListKeys() []string {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
list := make([]string, 0, len(f.queue))
|
||||
for _, key := range f.queue {
|
||||
list = append(list, key)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// Get returns the complete list of deltas for the requested item,
|
||||
// or sets exists=false.
|
||||
// You should treat the items returned inside the deltas as immutable.
|
||||
func (f *DeltaFIFO) Get(obj interface{}) (item interface{}, exists bool, err error) {
|
||||
key, err := f.KeyOf(obj)
|
||||
if err != nil {
|
||||
return nil, false, KeyError{obj, err}
|
||||
}
|
||||
return f.GetByKey(key)
|
||||
}
|
||||
|
||||
// GetByKey returns the complete list of deltas for the requested item,
|
||||
// setting exists=false if that list is empty.
|
||||
// You should treat the items returned inside the deltas as immutable.
|
||||
func (f *DeltaFIFO) GetByKey(key string) (item interface{}, exists bool, err error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
d, exists := f.items[key]
|
||||
if exists {
|
||||
// Copy item's slice so operations on this slice
|
||||
// won't interfere with the object we return.
|
||||
d = copyDeltas(d)
|
||||
}
|
||||
return d, exists, nil
|
||||
}
|
||||
|
||||
// IsClosed checks if the queue is closed
|
||||
func (f *DeltaFIFO) IsClosed() bool {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
return f.closed
|
||||
}
|
||||
|
||||
// Pop blocks until the queue has some items, and then returns one. If
|
||||
// multiple items are ready, they are returned in the order in which they were
|
||||
// added/updated. The item is removed from the queue (and the store) before it
|
||||
// is returned, so if you don't successfully process it, you need to add it back
|
||||
// with AddIfNotPresent().
|
||||
// process function is called under lock, so it is safe to update data structures
|
||||
// in it that need to be in sync with the queue (e.g. knownKeys). The PopProcessFunc
|
||||
// may return an instance of ErrRequeue with a nested error to indicate the current
|
||||
// item should be requeued (equivalent to calling AddIfNotPresent under the lock).
|
||||
// process should avoid expensive I/O operation so that other queue operations, i.e.
|
||||
// Add() and Get(), won't be blocked for too long.
|
||||
//
|
||||
// Pop returns a 'Deltas', which has a complete list of all the things
|
||||
// that happened to the object (deltas) while it was sitting in the queue.
|
||||
func (f *DeltaFIFO) Pop(process PopProcessFunc) (interface{}, error) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
for {
|
||||
for len(f.queue) == 0 {
|
||||
// When the queue is empty, invocation of Pop() is blocked until new item is enqueued.
|
||||
// When Close() is called, the f.closed is set and the condition is broadcasted.
|
||||
// Which causes this loop to continue and return from the Pop().
|
||||
if f.closed {
|
||||
return nil, ErrFIFOClosed
|
||||
}
|
||||
|
||||
f.cond.Wait()
|
||||
}
|
||||
id := f.queue[0]
|
||||
f.queue = f.queue[1:]
|
||||
depth := len(f.queue)
|
||||
if f.initialPopulationCount > 0 {
|
||||
f.initialPopulationCount--
|
||||
}
|
||||
item, ok := f.items[id]
|
||||
if !ok {
|
||||
// This should never happen
|
||||
klog.Errorf("Inconceivable! %q was in f.queue but not f.items; ignoring.", id)
|
||||
continue
|
||||
}
|
||||
delete(f.items, id)
|
||||
// Only log traces if the queue depth is greater than 10 and it takes more than
|
||||
// 100 milliseconds to process one item from the queue.
|
||||
// Queue depth never goes high because processing an item is locking the queue,
|
||||
// and new items can't be added until processing finish.
|
||||
// https://github.com/kubernetes/kubernetes/issues/103789
|
||||
if depth > 10 {
|
||||
trace := utiltrace.New("DeltaFIFO Pop Process",
|
||||
utiltrace.Field{Key: "ID", Value: id},
|
||||
utiltrace.Field{Key: "Depth", Value: depth},
|
||||
utiltrace.Field{Key: "Reason", Value: "slow event handlers blocking the queue"})
|
||||
defer trace.LogIfLong(100 * time.Millisecond)
|
||||
}
|
||||
err := process(item)
|
||||
if e, ok := err.(ErrRequeue); ok {
|
||||
f.addIfNotPresent(id, item)
|
||||
err = e.Err
|
||||
}
|
||||
// Don't need to copyDeltas here, because we're transferring
|
||||
// ownership to the caller.
|
||||
return item, err
|
||||
}
|
||||
}
|
||||
|
||||
// Replace atomically does two things: (1) it adds the given objects
|
||||
// using the Sync or Replace DeltaType and then (2) it does some deletions.
|
||||
// In particular: for every pre-existing key K that is not the key of
|
||||
// an object in `list` there is the effect of
|
||||
// `Delete(DeletedFinalStateUnknown{K, O})` where O is current object
|
||||
// of K. If `f.knownObjects == nil` then the pre-existing keys are
|
||||
// those in `f.items` and the current object of K is the `.Newest()`
|
||||
// of the Deltas associated with K. Otherwise the pre-existing keys
|
||||
// are those listed by `f.knownObjects` and the current object of K is
|
||||
// what `f.knownObjects.GetByKey(K)` returns.
|
||||
func (f *DeltaFIFO) Replace(list []interface{}, _ string) error {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
keys := make(sets.String, len(list))
|
||||
|
||||
// keep backwards compat for old clients
|
||||
action := Sync
|
||||
if f.emitDeltaTypeReplaced {
|
||||
action = Replaced
|
||||
}
|
||||
|
||||
// Add Sync/Replaced action for each new item.
|
||||
for _, item := range list {
|
||||
key, err := f.KeyOf(item)
|
||||
if err != nil {
|
||||
return KeyError{item, err}
|
||||
}
|
||||
keys.Insert(key)
|
||||
if err := f.queueActionLocked(action, item); err != nil {
|
||||
return fmt.Errorf("couldn't enqueue object: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
if f.knownObjects == nil {
|
||||
// Do deletion detection against our own list.
|
||||
queuedDeletions := 0
|
||||
for k, oldItem := range f.items {
|
||||
if keys.Has(k) {
|
||||
continue
|
||||
}
|
||||
// Delete pre-existing items not in the new list.
|
||||
// This could happen if watch deletion event was missed while
|
||||
// disconnected from apiserver.
|
||||
var deletedObj interface{}
|
||||
if n := oldItem.Newest(); n != nil {
|
||||
deletedObj = n.Object
|
||||
}
|
||||
queuedDeletions++
|
||||
if err := f.queueActionLocked(Deleted, DeletedFinalStateUnknown{k, deletedObj}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !f.populated {
|
||||
f.populated = true
|
||||
// While there shouldn't be any queued deletions in the initial
|
||||
// population of the queue, it's better to be on the safe side.
|
||||
f.initialPopulationCount = keys.Len() + queuedDeletions
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Detect deletions not already in the queue.
|
||||
knownKeys := f.knownObjects.ListKeys()
|
||||
queuedDeletions := 0
|
||||
for _, k := range knownKeys {
|
||||
if keys.Has(k) {
|
||||
continue
|
||||
}
|
||||
|
||||
deletedObj, exists, err := f.knownObjects.GetByKey(k)
|
||||
if err != nil {
|
||||
deletedObj = nil
|
||||
klog.Errorf("Unexpected error %v during lookup of key %v, placing DeleteFinalStateUnknown marker without object", err, k)
|
||||
} else if !exists {
|
||||
deletedObj = nil
|
||||
klog.Infof("Key %v does not exist in known objects store, placing DeleteFinalStateUnknown marker without object", k)
|
||||
}
|
||||
queuedDeletions++
|
||||
if err := f.queueActionLocked(Deleted, DeletedFinalStateUnknown{k, deletedObj}); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
|
||||
if !f.populated {
|
||||
f.populated = true
|
||||
f.initialPopulationCount = keys.Len() + queuedDeletions
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resync adds, with a Sync type of Delta, every object listed by
|
||||
// `f.knownObjects` whose key is not already queued for processing.
|
||||
// If `f.knownObjects` is `nil` then Resync does nothing.
|
||||
func (f *DeltaFIFO) Resync() error {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
if f.knownObjects == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
keys := f.knownObjects.ListKeys()
|
||||
for _, k := range keys {
|
||||
if err := f.syncKeyLocked(k); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (f *DeltaFIFO) syncKeyLocked(key string) error {
|
||||
obj, exists, err := f.knownObjects.GetByKey(key)
|
||||
if err != nil {
|
||||
klog.Errorf("Unexpected error %v during lookup of key %v, unable to queue object for sync", err, key)
|
||||
return nil
|
||||
} else if !exists {
|
||||
klog.Infof("Key %v does not exist in known objects store, unable to queue object for sync", key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// If we are doing Resync() and there is already an event queued for that object,
|
||||
// we ignore the Resync for it. This is to avoid the race, in which the resync
|
||||
// comes with the previous value of object (since queueing an event for the object
|
||||
// doesn't trigger changing the underlying store <knownObjects>.
|
||||
id, err := f.KeyOf(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
if len(f.items[id]) > 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
if err := f.queueActionLocked(Sync, obj); err != nil {
|
||||
return fmt.Errorf("couldn't queue object: %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// A KeyListerGetter is anything that knows how to list its keys and look up by key.
|
||||
type KeyListerGetter interface {
|
||||
KeyLister
|
||||
KeyGetter
|
||||
}
|
||||
|
||||
// A KeyLister is anything that knows how to list its keys.
|
||||
type KeyLister interface {
|
||||
ListKeys() []string
|
||||
}
|
||||
|
||||
// A KeyGetter is anything that knows how to get the value stored under a given key.
|
||||
type KeyGetter interface {
|
||||
// GetByKey returns the value associated with the key, or sets exists=false.
|
||||
GetByKey(key string) (value interface{}, exists bool, err error)
|
||||
}
|
||||
|
||||
// Oldest is a convenience function that returns the oldest delta, or
|
||||
// nil if there are no deltas.
|
||||
func (d Deltas) Oldest() *Delta {
|
||||
if len(d) > 0 {
|
||||
return &d[0]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Newest is a convenience function that returns the newest delta, or
|
||||
// nil if there are no deltas.
|
||||
func (d Deltas) Newest() *Delta {
|
||||
if n := len(d); n > 0 {
|
||||
return &d[n-1]
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// copyDeltas returns a shallow copy of d; that is, it copies the slice but not
|
||||
// the objects in the slice. This allows Get/List to return an object that we
|
||||
// know won't be clobbered by a subsequent modifications.
|
||||
func copyDeltas(d Deltas) Deltas {
|
||||
d2 := make(Deltas, len(d))
|
||||
copy(d2, d)
|
||||
return d2
|
||||
}
|
||||
|
||||
// DeletedFinalStateUnknown is placed into a DeltaFIFO in the case where an object
|
||||
// was deleted but the watch deletion event was missed while disconnected from
|
||||
// apiserver. In this case we don't know the final "resting" state of the object, so
|
||||
// there's a chance the included `Obj` is stale.
|
||||
type DeletedFinalStateUnknown struct {
|
||||
Key string
|
||||
Obj interface{}
|
||||
}
|
24
vendor/k8s.io/client-go/tools/cache/doc.go
generated
vendored
Normal file
24
vendor/k8s.io/client-go/tools/cache/doc.go
generated
vendored
Normal file
@ -0,0 +1,24 @@
|
||||
/*
|
||||
Copyright 2014 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 is a client-side caching mechanism. It is useful for
|
||||
// reducing the number of server calls you'd otherwise need to make.
|
||||
// Reflector watches a server and updates a Store. Two stores are provided;
|
||||
// one that simply caches objects (for example, to allow a scheduler to
|
||||
// list currently available nodes), and one that additionally acts as
|
||||
// a FIFO queue (for example, to allow a scheduler to process incoming
|
||||
// pods).
|
||||
package cache // import "k8s.io/client-go/tools/cache"
|
215
vendor/k8s.io/client-go/tools/cache/expiration_cache.go
generated
vendored
Normal file
215
vendor/k8s.io/client-go/tools/cache/expiration_cache.go
generated
vendored
Normal file
@ -0,0 +1,215 @@
|
||||
/*
|
||||
Copyright 2014 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 (
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
// ExpirationCache implements the store interface
|
||||
// 1. All entries are automatically time stamped on insert
|
||||
// a. The key is computed based off the original item/keyFunc
|
||||
// b. The value inserted under that key is the timestamped item
|
||||
// 2. Expiration happens lazily on read based on the expiration policy
|
||||
// a. No item can be inserted into the store while we're expiring
|
||||
// *any* item in the cache.
|
||||
// 3. Time-stamps are stripped off unexpired entries before return
|
||||
// Note that the ExpirationCache is inherently slower than a normal
|
||||
// threadSafeStore because it takes a write lock every time it checks if
|
||||
// an item has expired.
|
||||
type ExpirationCache struct {
|
||||
cacheStorage ThreadSafeStore
|
||||
keyFunc KeyFunc
|
||||
clock clock.Clock
|
||||
expirationPolicy ExpirationPolicy
|
||||
// expirationLock is a write lock used to guarantee that we don't clobber
|
||||
// newly inserted objects because of a stale expiration timestamp comparison
|
||||
expirationLock sync.Mutex
|
||||
}
|
||||
|
||||
// ExpirationPolicy dictates when an object expires. Currently only abstracted out
|
||||
// so unittests don't rely on the system clock.
|
||||
type ExpirationPolicy interface {
|
||||
IsExpired(obj *TimestampedEntry) bool
|
||||
}
|
||||
|
||||
// TTLPolicy implements a ttl based ExpirationPolicy.
|
||||
type TTLPolicy struct {
|
||||
// >0: Expire entries with an age > ttl
|
||||
// <=0: Don't expire any entry
|
||||
TTL time.Duration
|
||||
|
||||
// Clock used to calculate ttl expiration
|
||||
Clock clock.Clock
|
||||
}
|
||||
|
||||
// IsExpired returns true if the given object is older than the ttl, or it can't
|
||||
// determine its age.
|
||||
func (p *TTLPolicy) IsExpired(obj *TimestampedEntry) bool {
|
||||
return p.TTL > 0 && p.Clock.Since(obj.Timestamp) > p.TTL
|
||||
}
|
||||
|
||||
// TimestampedEntry is the only type allowed in a ExpirationCache.
|
||||
// Keep in mind that it is not safe to share timestamps between computers.
|
||||
// Behavior may be inconsistent if you get a timestamp from the API Server and
|
||||
// use it on the client machine as part of your ExpirationCache.
|
||||
type TimestampedEntry struct {
|
||||
Obj interface{}
|
||||
Timestamp time.Time
|
||||
key string
|
||||
}
|
||||
|
||||
// getTimestampedEntry returns the TimestampedEntry stored under the given key.
|
||||
func (c *ExpirationCache) getTimestampedEntry(key string) (*TimestampedEntry, bool) {
|
||||
item, _ := c.cacheStorage.Get(key)
|
||||
if tsEntry, ok := item.(*TimestampedEntry); ok {
|
||||
return tsEntry, true
|
||||
}
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// getOrExpire retrieves the object from the TimestampedEntry if and only if it hasn't
|
||||
// already expired. It holds a write lock across deletion.
|
||||
func (c *ExpirationCache) getOrExpire(key string) (interface{}, bool) {
|
||||
// Prevent all inserts from the time we deem an item as "expired" to when we
|
||||
// delete it, so an un-expired item doesn't sneak in under the same key, just
|
||||
// before the Delete.
|
||||
c.expirationLock.Lock()
|
||||
defer c.expirationLock.Unlock()
|
||||
timestampedItem, exists := c.getTimestampedEntry(key)
|
||||
if !exists {
|
||||
return nil, false
|
||||
}
|
||||
if c.expirationPolicy.IsExpired(timestampedItem) {
|
||||
klog.V(4).Infof("Entry %v: %+v has expired", key, timestampedItem.Obj)
|
||||
c.cacheStorage.Delete(key)
|
||||
return nil, false
|
||||
}
|
||||
return timestampedItem.Obj, true
|
||||
}
|
||||
|
||||
// GetByKey returns the item stored under the key, or sets exists=false.
|
||||
func (c *ExpirationCache) GetByKey(key string) (interface{}, bool, error) {
|
||||
obj, exists := c.getOrExpire(key)
|
||||
return obj, exists, nil
|
||||
}
|
||||
|
||||
// Get returns unexpired items. It purges the cache of expired items in the
|
||||
// process.
|
||||
func (c *ExpirationCache) Get(obj interface{}) (interface{}, bool, error) {
|
||||
key, err := c.keyFunc(obj)
|
||||
if err != nil {
|
||||
return nil, false, KeyError{obj, err}
|
||||
}
|
||||
obj, exists := c.getOrExpire(key)
|
||||
return obj, exists, nil
|
||||
}
|
||||
|
||||
// List retrieves a list of unexpired items. It purges the cache of expired
|
||||
// items in the process.
|
||||
func (c *ExpirationCache) List() []interface{} {
|
||||
items := c.cacheStorage.List()
|
||||
|
||||
list := make([]interface{}, 0, len(items))
|
||||
for _, item := range items {
|
||||
key := item.(*TimestampedEntry).key
|
||||
if obj, exists := c.getOrExpire(key); exists {
|
||||
list = append(list, obj)
|
||||
}
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// ListKeys returns a list of all keys in the expiration cache.
|
||||
func (c *ExpirationCache) ListKeys() []string {
|
||||
return c.cacheStorage.ListKeys()
|
||||
}
|
||||
|
||||
// Add timestamps an item and inserts it into the cache, overwriting entries
|
||||
// that might exist under the same key.
|
||||
func (c *ExpirationCache) Add(obj interface{}) error {
|
||||
key, err := c.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
c.expirationLock.Lock()
|
||||
defer c.expirationLock.Unlock()
|
||||
|
||||
c.cacheStorage.Add(key, &TimestampedEntry{obj, c.clock.Now(), key})
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update has not been implemented yet for lack of a use case, so this method
|
||||
// simply calls `Add`. This effectively refreshes the timestamp.
|
||||
func (c *ExpirationCache) Update(obj interface{}) error {
|
||||
return c.Add(obj)
|
||||
}
|
||||
|
||||
// Delete removes an item from the cache.
|
||||
func (c *ExpirationCache) Delete(obj interface{}) error {
|
||||
key, err := c.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
c.expirationLock.Lock()
|
||||
defer c.expirationLock.Unlock()
|
||||
c.cacheStorage.Delete(key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Replace will convert all items in the given list to TimestampedEntries
|
||||
// before attempting the replace operation. The replace operation will
|
||||
// delete the contents of the ExpirationCache `c`.
|
||||
func (c *ExpirationCache) Replace(list []interface{}, resourceVersion string) error {
|
||||
items := make(map[string]interface{}, len(list))
|
||||
ts := c.clock.Now()
|
||||
for _, item := range list {
|
||||
key, err := c.keyFunc(item)
|
||||
if err != nil {
|
||||
return KeyError{item, err}
|
||||
}
|
||||
items[key] = &TimestampedEntry{item, ts, key}
|
||||
}
|
||||
c.expirationLock.Lock()
|
||||
defer c.expirationLock.Unlock()
|
||||
c.cacheStorage.Replace(items, resourceVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resync is a no-op for one of these
|
||||
func (c *ExpirationCache) Resync() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewTTLStore creates and returns a ExpirationCache with a TTLPolicy
|
||||
func NewTTLStore(keyFunc KeyFunc, ttl time.Duration) Store {
|
||||
return NewExpirationStore(keyFunc, &TTLPolicy{ttl, clock.RealClock{}})
|
||||
}
|
||||
|
||||
// NewExpirationStore creates and returns a ExpirationCache for a given policy
|
||||
func NewExpirationStore(keyFunc KeyFunc, expirationPolicy ExpirationPolicy) Store {
|
||||
return &ExpirationCache{
|
||||
cacheStorage: NewThreadSafeStore(Indexers{}, Indices{}),
|
||||
keyFunc: keyFunc,
|
||||
clock: clock.RealClock{},
|
||||
expirationPolicy: expirationPolicy,
|
||||
}
|
||||
}
|
57
vendor/k8s.io/client-go/tools/cache/expiration_cache_fakes.go
generated
vendored
Normal file
57
vendor/k8s.io/client-go/tools/cache/expiration_cache_fakes.go
generated
vendored
Normal file
@ -0,0 +1,57 @@
|
||||
/*
|
||||
Copyright 2014 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 (
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/utils/clock"
|
||||
)
|
||||
|
||||
type fakeThreadSafeMap struct {
|
||||
ThreadSafeStore
|
||||
deletedKeys chan<- string
|
||||
}
|
||||
|
||||
func (c *fakeThreadSafeMap) Delete(key string) {
|
||||
if c.deletedKeys != nil {
|
||||
c.ThreadSafeStore.Delete(key)
|
||||
c.deletedKeys <- key
|
||||
}
|
||||
}
|
||||
|
||||
// FakeExpirationPolicy keeps the list for keys which never expires.
|
||||
type FakeExpirationPolicy struct {
|
||||
NeverExpire sets.String
|
||||
RetrieveKeyFunc KeyFunc
|
||||
}
|
||||
|
||||
// IsExpired used to check if object is expired.
|
||||
func (p *FakeExpirationPolicy) IsExpired(obj *TimestampedEntry) bool {
|
||||
key, _ := p.RetrieveKeyFunc(obj)
|
||||
return !p.NeverExpire.Has(key)
|
||||
}
|
||||
|
||||
// NewFakeExpirationStore creates a new instance for the ExpirationCache.
|
||||
func NewFakeExpirationStore(keyFunc KeyFunc, deletedKeys chan<- string, expirationPolicy ExpirationPolicy, cacheClock clock.Clock) Store {
|
||||
cacheStorage := NewThreadSafeStore(Indexers{}, Indices{})
|
||||
return &ExpirationCache{
|
||||
cacheStorage: &fakeThreadSafeMap{cacheStorage, deletedKeys},
|
||||
keyFunc: keyFunc,
|
||||
clock: cacheClock,
|
||||
expirationPolicy: expirationPolicy,
|
||||
}
|
||||
}
|
102
vendor/k8s.io/client-go/tools/cache/fake_custom_store.go
generated
vendored
Normal file
102
vendor/k8s.io/client-go/tools/cache/fake_custom_store.go
generated
vendored
Normal file
@ -0,0 +1,102 @@
|
||||
/*
|
||||
Copyright 2016 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
|
||||
|
||||
// FakeCustomStore lets you define custom functions for store operations.
|
||||
type FakeCustomStore struct {
|
||||
AddFunc func(obj interface{}) error
|
||||
UpdateFunc func(obj interface{}) error
|
||||
DeleteFunc func(obj interface{}) error
|
||||
ListFunc func() []interface{}
|
||||
ListKeysFunc func() []string
|
||||
GetFunc func(obj interface{}) (item interface{}, exists bool, err error)
|
||||
GetByKeyFunc func(key string) (item interface{}, exists bool, err error)
|
||||
ReplaceFunc func(list []interface{}, resourceVersion string) error
|
||||
ResyncFunc func() error
|
||||
}
|
||||
|
||||
// Add calls the custom Add function if defined
|
||||
func (f *FakeCustomStore) Add(obj interface{}) error {
|
||||
if f.AddFunc != nil {
|
||||
return f.AddFunc(obj)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update calls the custom Update function if defined
|
||||
func (f *FakeCustomStore) Update(obj interface{}) error {
|
||||
if f.UpdateFunc != nil {
|
||||
return f.UpdateFunc(obj)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete calls the custom Delete function if defined
|
||||
func (f *FakeCustomStore) Delete(obj interface{}) error {
|
||||
if f.DeleteFunc != nil {
|
||||
return f.DeleteFunc(obj)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// List calls the custom List function if defined
|
||||
func (f *FakeCustomStore) List() []interface{} {
|
||||
if f.ListFunc != nil {
|
||||
return f.ListFunc()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListKeys calls the custom ListKeys function if defined
|
||||
func (f *FakeCustomStore) ListKeys() []string {
|
||||
if f.ListKeysFunc != nil {
|
||||
return f.ListKeysFunc()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Get calls the custom Get function if defined
|
||||
func (f *FakeCustomStore) Get(obj interface{}) (item interface{}, exists bool, err error) {
|
||||
if f.GetFunc != nil {
|
||||
return f.GetFunc(obj)
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// GetByKey calls the custom GetByKey function if defined
|
||||
func (f *FakeCustomStore) GetByKey(key string) (item interface{}, exists bool, err error) {
|
||||
if f.GetByKeyFunc != nil {
|
||||
return f.GetByKeyFunc(key)
|
||||
}
|
||||
return nil, false, nil
|
||||
}
|
||||
|
||||
// Replace calls the custom Replace function if defined
|
||||
func (f *FakeCustomStore) Replace(list []interface{}, resourceVersion string) error {
|
||||
if f.ReplaceFunc != nil {
|
||||
return f.ReplaceFunc(list, resourceVersion)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resync calls the custom Resync function if defined
|
||||
func (f *FakeCustomStore) Resync() error {
|
||||
if f.ResyncFunc != nil {
|
||||
return f.ResyncFunc()
|
||||
}
|
||||
return nil
|
||||
}
|
373
vendor/k8s.io/client-go/tools/cache/fifo.go
generated
vendored
Normal file
373
vendor/k8s.io/client-go/tools/cache/fifo.go
generated
vendored
Normal file
@ -0,0 +1,373 @@
|
||||
/*
|
||||
Copyright 2014 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 (
|
||||
"errors"
|
||||
"sync"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// PopProcessFunc is passed to Pop() method of Queue interface.
|
||||
// It is supposed to process the accumulator popped from the queue.
|
||||
type PopProcessFunc func(interface{}) error
|
||||
|
||||
// ErrRequeue may be returned by a PopProcessFunc to safely requeue
|
||||
// the current item. The value of Err will be returned from Pop.
|
||||
type ErrRequeue struct {
|
||||
// Err is returned by the Pop function
|
||||
Err error
|
||||
}
|
||||
|
||||
// ErrFIFOClosed used when FIFO is closed
|
||||
var ErrFIFOClosed = errors.New("DeltaFIFO: manipulating with closed queue")
|
||||
|
||||
func (e ErrRequeue) Error() string {
|
||||
if e.Err == nil {
|
||||
return "the popped item should be requeued without returning an error"
|
||||
}
|
||||
return e.Err.Error()
|
||||
}
|
||||
|
||||
// Queue extends Store with a collection of Store keys to "process".
|
||||
// Every Add, Update, or Delete may put the object's key in that collection.
|
||||
// A Queue has a way to derive the corresponding key given an accumulator.
|
||||
// A Queue can be accessed concurrently from multiple goroutines.
|
||||
// A Queue can be "closed", after which Pop operations return an error.
|
||||
type Queue interface {
|
||||
Store
|
||||
|
||||
// Pop blocks until there is at least one key to process or the
|
||||
// Queue is closed. In the latter case Pop returns with an error.
|
||||
// In the former case Pop atomically picks one key to process,
|
||||
// removes that (key, accumulator) association from the Store, and
|
||||
// processes the accumulator. Pop returns the accumulator that
|
||||
// was processed and the result of processing. The PopProcessFunc
|
||||
// may return an ErrRequeue{inner} and in this case Pop will (a)
|
||||
// return that (key, accumulator) association to the Queue as part
|
||||
// of the atomic processing and (b) return the inner error from
|
||||
// Pop.
|
||||
Pop(PopProcessFunc) (interface{}, error)
|
||||
|
||||
// AddIfNotPresent puts the given accumulator into the Queue (in
|
||||
// association with the accumulator's key) if and only if that key
|
||||
// is not already associated with a non-empty accumulator.
|
||||
AddIfNotPresent(interface{}) error
|
||||
|
||||
// HasSynced returns true if the first batch of keys have all been
|
||||
// popped. The first batch of keys are those of the first Replace
|
||||
// operation if that happened before any Add, AddIfNotPresent,
|
||||
// Update, or Delete; otherwise the first batch is empty.
|
||||
HasSynced() bool
|
||||
|
||||
// Close the queue
|
||||
Close()
|
||||
}
|
||||
|
||||
// Pop is helper function for popping from Queue.
|
||||
// WARNING: Do NOT use this function in non-test code to avoid races
|
||||
// unless you really really really really know what you are doing.
|
||||
func Pop(queue Queue) interface{} {
|
||||
var result interface{}
|
||||
queue.Pop(func(obj interface{}) error {
|
||||
result = obj
|
||||
return nil
|
||||
})
|
||||
return result
|
||||
}
|
||||
|
||||
// FIFO is a Queue in which (a) each accumulator is simply the most
|
||||
// recently provided object and (b) the collection of keys to process
|
||||
// is a FIFO. The accumulators all start out empty, and deleting an
|
||||
// object from its accumulator empties the accumulator. The Resync
|
||||
// operation is a no-op.
|
||||
//
|
||||
// Thus: if multiple adds/updates of a single object happen while that
|
||||
// object's key is in the queue before it has been processed then it
|
||||
// will only be processed once, and when it is processed the most
|
||||
// recent version will be processed. This can't be done with a channel
|
||||
//
|
||||
// FIFO solves this use case:
|
||||
// * You want to process every object (exactly) once.
|
||||
// * You want to process the most recent version of the object when you process it.
|
||||
// * You do not want to process deleted objects, they should be removed from the queue.
|
||||
// * You do not want to periodically reprocess objects.
|
||||
// Compare with DeltaFIFO for other use cases.
|
||||
type FIFO struct {
|
||||
lock sync.RWMutex
|
||||
cond sync.Cond
|
||||
// We depend on the property that every key in `items` is also in `queue`
|
||||
items map[string]interface{}
|
||||
queue []string
|
||||
|
||||
// populated is true if the first batch of items inserted by Replace() has been populated
|
||||
// or Delete/Add/Update was called first.
|
||||
populated bool
|
||||
// initialPopulationCount is the number of items inserted by the first call of Replace()
|
||||
initialPopulationCount int
|
||||
|
||||
// keyFunc is used to make the key used for queued item insertion and retrieval, and
|
||||
// should be deterministic.
|
||||
keyFunc KeyFunc
|
||||
|
||||
// Indication the queue is closed.
|
||||
// Used to indicate a queue is closed so a control loop can exit when a queue is empty.
|
||||
// Currently, not used to gate any of CRUD operations.
|
||||
closed bool
|
||||
}
|
||||
|
||||
var (
|
||||
_ = Queue(&FIFO{}) // FIFO is a Queue
|
||||
)
|
||||
|
||||
// Close the queue.
|
||||
func (f *FIFO) Close() {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.closed = true
|
||||
f.cond.Broadcast()
|
||||
}
|
||||
|
||||
// HasSynced returns true if an Add/Update/Delete/AddIfNotPresent are called first,
|
||||
// or the first batch of items inserted by Replace() has been popped.
|
||||
func (f *FIFO) HasSynced() bool {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
return f.populated && f.initialPopulationCount == 0
|
||||
}
|
||||
|
||||
// Add inserts an item, and puts it in the queue. The item is only enqueued
|
||||
// if it doesn't already exist in the set.
|
||||
func (f *FIFO) Add(obj interface{}) error {
|
||||
id, err := f.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.populated = true
|
||||
if _, exists := f.items[id]; !exists {
|
||||
f.queue = append(f.queue, id)
|
||||
}
|
||||
f.items[id] = obj
|
||||
f.cond.Broadcast()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddIfNotPresent inserts an item, and puts it in the queue. If the item is already
|
||||
// present in the set, it is neither enqueued nor added to the set.
|
||||
//
|
||||
// This is useful in a single producer/consumer scenario so that the consumer can
|
||||
// safely retry items without contending with the producer and potentially enqueueing
|
||||
// stale items.
|
||||
func (f *FIFO) AddIfNotPresent(obj interface{}) error {
|
||||
id, err := f.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.addIfNotPresent(id, obj)
|
||||
return nil
|
||||
}
|
||||
|
||||
// addIfNotPresent assumes the fifo lock is already held and adds the provided
|
||||
// item to the queue under id if it does not already exist.
|
||||
func (f *FIFO) addIfNotPresent(id string, obj interface{}) {
|
||||
f.populated = true
|
||||
if _, exists := f.items[id]; exists {
|
||||
return
|
||||
}
|
||||
|
||||
f.queue = append(f.queue, id)
|
||||
f.items[id] = obj
|
||||
f.cond.Broadcast()
|
||||
}
|
||||
|
||||
// Update is the same as Add in this implementation.
|
||||
func (f *FIFO) Update(obj interface{}) error {
|
||||
return f.Add(obj)
|
||||
}
|
||||
|
||||
// Delete removes an item. It doesn't add it to the queue, because
|
||||
// this implementation assumes the consumer only cares about the objects,
|
||||
// not the order in which they were created/added.
|
||||
func (f *FIFO) Delete(obj interface{}) error {
|
||||
id, err := f.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
f.populated = true
|
||||
delete(f.items, id)
|
||||
return err
|
||||
}
|
||||
|
||||
// List returns a list of all the items.
|
||||
func (f *FIFO) List() []interface{} {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
list := make([]interface{}, 0, len(f.items))
|
||||
for _, item := range f.items {
|
||||
list = append(list, item)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// ListKeys returns a list of all the keys of the objects currently
|
||||
// in the FIFO.
|
||||
func (f *FIFO) ListKeys() []string {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
list := make([]string, 0, len(f.items))
|
||||
for key := range f.items {
|
||||
list = append(list, key)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// Get returns the requested item, or sets exists=false.
|
||||
func (f *FIFO) Get(obj interface{}) (item interface{}, exists bool, err error) {
|
||||
key, err := f.keyFunc(obj)
|
||||
if err != nil {
|
||||
return nil, false, KeyError{obj, err}
|
||||
}
|
||||
return f.GetByKey(key)
|
||||
}
|
||||
|
||||
// GetByKey returns the requested item, or sets exists=false.
|
||||
func (f *FIFO) GetByKey(key string) (item interface{}, exists bool, err error) {
|
||||
f.lock.RLock()
|
||||
defer f.lock.RUnlock()
|
||||
item, exists = f.items[key]
|
||||
return item, exists, nil
|
||||
}
|
||||
|
||||
// IsClosed checks if the queue is closed
|
||||
func (f *FIFO) IsClosed() bool {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
return f.closed
|
||||
}
|
||||
|
||||
// Pop waits until an item is ready and processes it. If multiple items are
|
||||
// ready, they are returned in the order in which they were added/updated.
|
||||
// The item is removed from the queue (and the store) before it is processed,
|
||||
// so if you don't successfully process it, it should be added back with
|
||||
// AddIfNotPresent(). process function is called under lock, so it is safe
|
||||
// update data structures in it that need to be in sync with the queue.
|
||||
func (f *FIFO) Pop(process PopProcessFunc) (interface{}, error) {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
for {
|
||||
for len(f.queue) == 0 {
|
||||
// When the queue is empty, invocation of Pop() is blocked until new item is enqueued.
|
||||
// When Close() is called, the f.closed is set and the condition is broadcasted.
|
||||
// Which causes this loop to continue and return from the Pop().
|
||||
if f.closed {
|
||||
return nil, ErrFIFOClosed
|
||||
}
|
||||
|
||||
f.cond.Wait()
|
||||
}
|
||||
id := f.queue[0]
|
||||
f.queue = f.queue[1:]
|
||||
if f.initialPopulationCount > 0 {
|
||||
f.initialPopulationCount--
|
||||
}
|
||||
item, ok := f.items[id]
|
||||
if !ok {
|
||||
// Item may have been deleted subsequently.
|
||||
continue
|
||||
}
|
||||
delete(f.items, id)
|
||||
err := process(item)
|
||||
if e, ok := err.(ErrRequeue); ok {
|
||||
f.addIfNotPresent(id, item)
|
||||
err = e.Err
|
||||
}
|
||||
return item, err
|
||||
}
|
||||
}
|
||||
|
||||
// Replace will delete the contents of 'f', using instead the given map.
|
||||
// 'f' takes ownership of the map, you should not reference the map again
|
||||
// after calling this function. f's queue is reset, too; upon return, it
|
||||
// will contain the items in the map, in no particular order.
|
||||
func (f *FIFO) Replace(list []interface{}, resourceVersion string) error {
|
||||
items := make(map[string]interface{}, len(list))
|
||||
for _, item := range list {
|
||||
key, err := f.keyFunc(item)
|
||||
if err != nil {
|
||||
return KeyError{item, err}
|
||||
}
|
||||
items[key] = item
|
||||
}
|
||||
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
if !f.populated {
|
||||
f.populated = true
|
||||
f.initialPopulationCount = len(items)
|
||||
}
|
||||
|
||||
f.items = items
|
||||
f.queue = f.queue[:0]
|
||||
for id := range items {
|
||||
f.queue = append(f.queue, id)
|
||||
}
|
||||
if len(f.queue) > 0 {
|
||||
f.cond.Broadcast()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resync will ensure that every object in the Store has its key in the queue.
|
||||
// This should be a no-op, because that property is maintained by all operations.
|
||||
func (f *FIFO) Resync() error {
|
||||
f.lock.Lock()
|
||||
defer f.lock.Unlock()
|
||||
|
||||
inQueue := sets.NewString()
|
||||
for _, id := range f.queue {
|
||||
inQueue.Insert(id)
|
||||
}
|
||||
for id := range f.items {
|
||||
if !inQueue.Has(id) {
|
||||
f.queue = append(f.queue, id)
|
||||
}
|
||||
}
|
||||
if len(f.queue) > 0 {
|
||||
f.cond.Broadcast()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewFIFO returns a Store which can be used to queue up items to
|
||||
// process.
|
||||
func NewFIFO(keyFunc KeyFunc) *FIFO {
|
||||
f := &FIFO{
|
||||
items: map[string]interface{}{},
|
||||
queue: []string{},
|
||||
keyFunc: keyFunc,
|
||||
}
|
||||
f.cond.L = &f.lock
|
||||
return f
|
||||
}
|
322
vendor/k8s.io/client-go/tools/cache/heap.go
generated
vendored
Normal file
322
vendor/k8s.io/client-go/tools/cache/heap.go
generated
vendored
Normal file
@ -0,0 +1,322 @@
|
||||
/*
|
||||
Copyright 2017 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.
|
||||
*/
|
||||
|
||||
// This file implements a heap data structure.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
const (
|
||||
closedMsg = "heap is closed"
|
||||
)
|
||||
|
||||
// LessFunc is used to compare two objects in the heap.
|
||||
type LessFunc func(interface{}, interface{}) bool
|
||||
|
||||
type heapItem struct {
|
||||
obj interface{} // The object which is stored in the heap.
|
||||
index int // The index of the object's key in the Heap.queue.
|
||||
}
|
||||
|
||||
type itemKeyValue struct {
|
||||
key string
|
||||
obj interface{}
|
||||
}
|
||||
|
||||
// heapData is an internal struct that implements the standard heap interface
|
||||
// and keeps the data stored in the heap.
|
||||
type heapData struct {
|
||||
// items is a map from key of the objects to the objects and their index.
|
||||
// We depend on the property that items in the map are in the queue and vice versa.
|
||||
items map[string]*heapItem
|
||||
// queue implements a heap data structure and keeps the order of elements
|
||||
// according to the heap invariant. The queue keeps the keys of objects stored
|
||||
// in "items".
|
||||
queue []string
|
||||
|
||||
// keyFunc is used to make the key used for queued item insertion and retrieval, and
|
||||
// should be deterministic.
|
||||
keyFunc KeyFunc
|
||||
// lessFunc is used to compare two objects in the heap.
|
||||
lessFunc LessFunc
|
||||
}
|
||||
|
||||
var (
|
||||
_ = heap.Interface(&heapData{}) // heapData is a standard heap
|
||||
)
|
||||
|
||||
// Less compares two objects and returns true if the first one should go
|
||||
// in front of the second one in the heap.
|
||||
func (h *heapData) Less(i, j int) bool {
|
||||
if i > len(h.queue) || j > len(h.queue) {
|
||||
return false
|
||||
}
|
||||
itemi, ok := h.items[h.queue[i]]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
itemj, ok := h.items[h.queue[j]]
|
||||
if !ok {
|
||||
return false
|
||||
}
|
||||
return h.lessFunc(itemi.obj, itemj.obj)
|
||||
}
|
||||
|
||||
// Len returns the number of items in the Heap.
|
||||
func (h *heapData) Len() int { return len(h.queue) }
|
||||
|
||||
// Swap implements swapping of two elements in the heap. This is a part of standard
|
||||
// heap interface and should never be called directly.
|
||||
func (h *heapData) Swap(i, j int) {
|
||||
h.queue[i], h.queue[j] = h.queue[j], h.queue[i]
|
||||
item := h.items[h.queue[i]]
|
||||
item.index = i
|
||||
item = h.items[h.queue[j]]
|
||||
item.index = j
|
||||
}
|
||||
|
||||
// Push is supposed to be called by heap.Push only.
|
||||
func (h *heapData) Push(kv interface{}) {
|
||||
keyValue := kv.(*itemKeyValue)
|
||||
n := len(h.queue)
|
||||
h.items[keyValue.key] = &heapItem{keyValue.obj, n}
|
||||
h.queue = append(h.queue, keyValue.key)
|
||||
}
|
||||
|
||||
// Pop is supposed to be called by heap.Pop only.
|
||||
func (h *heapData) Pop() interface{} {
|
||||
key := h.queue[len(h.queue)-1]
|
||||
h.queue = h.queue[0 : len(h.queue)-1]
|
||||
item, ok := h.items[key]
|
||||
if !ok {
|
||||
// This is an error
|
||||
return nil
|
||||
}
|
||||
delete(h.items, key)
|
||||
return item.obj
|
||||
}
|
||||
|
||||
// Heap is a thread-safe producer/consumer queue that implements a heap data structure.
|
||||
// It can be used to implement priority queues and similar data structures.
|
||||
type Heap struct {
|
||||
lock sync.RWMutex
|
||||
cond sync.Cond
|
||||
|
||||
// data stores objects and has a queue that keeps their ordering according
|
||||
// to the heap invariant.
|
||||
data *heapData
|
||||
|
||||
// closed indicates that the queue is closed.
|
||||
// It is mainly used to let Pop() exit its control loop while waiting for an item.
|
||||
closed bool
|
||||
}
|
||||
|
||||
// Close the Heap and signals condition variables that may be waiting to pop
|
||||
// items from the heap.
|
||||
func (h *Heap) Close() {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
h.closed = true
|
||||
h.cond.Broadcast()
|
||||
}
|
||||
|
||||
// Add inserts an item, and puts it in the queue. The item is updated if it
|
||||
// already exists.
|
||||
func (h *Heap) Add(obj interface{}) error {
|
||||
key, err := h.data.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
if h.closed {
|
||||
return fmt.Errorf(closedMsg)
|
||||
}
|
||||
if _, exists := h.data.items[key]; exists {
|
||||
h.data.items[key].obj = obj
|
||||
heap.Fix(h.data, h.data.items[key].index)
|
||||
} else {
|
||||
h.addIfNotPresentLocked(key, obj)
|
||||
}
|
||||
h.cond.Broadcast()
|
||||
return nil
|
||||
}
|
||||
|
||||
// BulkAdd adds all the items in the list to the queue and then signals the condition
|
||||
// variable. It is useful when the caller would like to add all of the items
|
||||
// to the queue before consumer starts processing them.
|
||||
func (h *Heap) BulkAdd(list []interface{}) error {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
if h.closed {
|
||||
return fmt.Errorf(closedMsg)
|
||||
}
|
||||
for _, obj := range list {
|
||||
key, err := h.data.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
if _, exists := h.data.items[key]; exists {
|
||||
h.data.items[key].obj = obj
|
||||
heap.Fix(h.data, h.data.items[key].index)
|
||||
} else {
|
||||
h.addIfNotPresentLocked(key, obj)
|
||||
}
|
||||
}
|
||||
h.cond.Broadcast()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddIfNotPresent inserts an item, and puts it in the queue. If an item with
|
||||
// the key is present in the map, no changes is made to the item.
|
||||
//
|
||||
// This is useful in a single producer/consumer scenario so that the consumer can
|
||||
// safely retry items without contending with the producer and potentially enqueueing
|
||||
// stale items.
|
||||
func (h *Heap) AddIfNotPresent(obj interface{}) error {
|
||||
id, err := h.data.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
if h.closed {
|
||||
return fmt.Errorf(closedMsg)
|
||||
}
|
||||
h.addIfNotPresentLocked(id, obj)
|
||||
h.cond.Broadcast()
|
||||
return nil
|
||||
}
|
||||
|
||||
// addIfNotPresentLocked assumes the lock is already held and adds the provided
|
||||
// item to the queue if it does not already exist.
|
||||
func (h *Heap) addIfNotPresentLocked(key string, obj interface{}) {
|
||||
if _, exists := h.data.items[key]; exists {
|
||||
return
|
||||
}
|
||||
heap.Push(h.data, &itemKeyValue{key, obj})
|
||||
}
|
||||
|
||||
// Update is the same as Add in this implementation. When the item does not
|
||||
// exist, it is added.
|
||||
func (h *Heap) Update(obj interface{}) error {
|
||||
return h.Add(obj)
|
||||
}
|
||||
|
||||
// Delete removes an item.
|
||||
func (h *Heap) Delete(obj interface{}) error {
|
||||
key, err := h.data.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
if item, ok := h.data.items[key]; ok {
|
||||
heap.Remove(h.data, item.index)
|
||||
return nil
|
||||
}
|
||||
return fmt.Errorf("object not found")
|
||||
}
|
||||
|
||||
// Pop waits until an item is ready. If multiple items are
|
||||
// ready, they are returned in the order given by Heap.data.lessFunc.
|
||||
func (h *Heap) Pop() (interface{}, error) {
|
||||
h.lock.Lock()
|
||||
defer h.lock.Unlock()
|
||||
for len(h.data.queue) == 0 {
|
||||
// When the queue is empty, invocation of Pop() is blocked until new item is enqueued.
|
||||
// When Close() is called, the h.closed is set and the condition is broadcast,
|
||||
// which causes this loop to continue and return from the Pop().
|
||||
if h.closed {
|
||||
return nil, fmt.Errorf("heap is closed")
|
||||
}
|
||||
h.cond.Wait()
|
||||
}
|
||||
obj := heap.Pop(h.data)
|
||||
if obj == nil {
|
||||
return nil, fmt.Errorf("object was removed from heap data")
|
||||
}
|
||||
|
||||
return obj, nil
|
||||
}
|
||||
|
||||
// List returns a list of all the items.
|
||||
func (h *Heap) List() []interface{} {
|
||||
h.lock.RLock()
|
||||
defer h.lock.RUnlock()
|
||||
list := make([]interface{}, 0, len(h.data.items))
|
||||
for _, item := range h.data.items {
|
||||
list = append(list, item.obj)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// ListKeys returns a list of all the keys of the objects currently in the Heap.
|
||||
func (h *Heap) ListKeys() []string {
|
||||
h.lock.RLock()
|
||||
defer h.lock.RUnlock()
|
||||
list := make([]string, 0, len(h.data.items))
|
||||
for key := range h.data.items {
|
||||
list = append(list, key)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// Get returns the requested item, or sets exists=false.
|
||||
func (h *Heap) Get(obj interface{}) (interface{}, bool, error) {
|
||||
key, err := h.data.keyFunc(obj)
|
||||
if err != nil {
|
||||
return nil, false, KeyError{obj, err}
|
||||
}
|
||||
return h.GetByKey(key)
|
||||
}
|
||||
|
||||
// GetByKey returns the requested item, or sets exists=false.
|
||||
func (h *Heap) GetByKey(key string) (interface{}, bool, error) {
|
||||
h.lock.RLock()
|
||||
defer h.lock.RUnlock()
|
||||
item, exists := h.data.items[key]
|
||||
if !exists {
|
||||
return nil, false, nil
|
||||
}
|
||||
return item.obj, true, nil
|
||||
}
|
||||
|
||||
// IsClosed returns true if the queue is closed.
|
||||
func (h *Heap) IsClosed() bool {
|
||||
h.lock.RLock()
|
||||
defer h.lock.RUnlock()
|
||||
return h.closed
|
||||
}
|
||||
|
||||
// NewHeap returns a Heap which can be used to queue up items to process.
|
||||
func NewHeap(keyFn KeyFunc, lessFn LessFunc) *Heap {
|
||||
h := &Heap{
|
||||
data: &heapData{
|
||||
items: map[string]*heapItem{},
|
||||
queue: []string{},
|
||||
keyFunc: keyFn,
|
||||
lessFunc: lessFn,
|
||||
},
|
||||
}
|
||||
h.cond.L = &h.lock
|
||||
return h
|
||||
}
|
101
vendor/k8s.io/client-go/tools/cache/index.go
generated
vendored
Normal file
101
vendor/k8s.io/client-go/tools/cache/index.go
generated
vendored
Normal file
@ -0,0 +1,101 @@
|
||||
/*
|
||||
Copyright 2014 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 (
|
||||
"fmt"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// Indexer extends Store with multiple indices and restricts each
|
||||
// accumulator to simply hold the current object (and be empty after
|
||||
// Delete).
|
||||
//
|
||||
// There are three kinds of strings here:
|
||||
// 1. a storage key, as defined in the Store interface,
|
||||
// 2. a name of an index, and
|
||||
// 3. an "indexed value", which is produced by an IndexFunc and
|
||||
// can be a field value or any other string computed from the object.
|
||||
type Indexer interface {
|
||||
Store
|
||||
// Index returns the stored objects whose set of indexed values
|
||||
// intersects the set of indexed values of the given object, for
|
||||
// the named index
|
||||
Index(indexName string, obj interface{}) ([]interface{}, error)
|
||||
// IndexKeys returns the storage keys of the stored objects whose
|
||||
// set of indexed values for the named index includes the given
|
||||
// indexed value
|
||||
IndexKeys(indexName, indexedValue string) ([]string, error)
|
||||
// ListIndexFuncValues returns all the indexed values of the given index
|
||||
ListIndexFuncValues(indexName string) []string
|
||||
// ByIndex returns the stored objects whose set of indexed values
|
||||
// for the named index includes the given indexed value
|
||||
ByIndex(indexName, indexedValue string) ([]interface{}, error)
|
||||
// GetIndexer return the indexers
|
||||
GetIndexers() Indexers
|
||||
|
||||
// AddIndexers adds more indexers to this store. If you call this after you already have data
|
||||
// in the store, the results are undefined.
|
||||
AddIndexers(newIndexers Indexers) error
|
||||
}
|
||||
|
||||
// IndexFunc knows how to compute the set of indexed values for an object.
|
||||
type IndexFunc func(obj interface{}) ([]string, error)
|
||||
|
||||
// IndexFuncToKeyFuncAdapter adapts an indexFunc to a keyFunc. This is only useful if your index function returns
|
||||
// unique values for every object. This conversion can create errors when more than one key is found. You
|
||||
// should prefer to make proper key and index functions.
|
||||
func IndexFuncToKeyFuncAdapter(indexFunc IndexFunc) KeyFunc {
|
||||
return func(obj interface{}) (string, error) {
|
||||
indexKeys, err := indexFunc(obj)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
if len(indexKeys) > 1 {
|
||||
return "", fmt.Errorf("too many keys: %v", indexKeys)
|
||||
}
|
||||
if len(indexKeys) == 0 {
|
||||
return "", fmt.Errorf("unexpected empty indexKeys")
|
||||
}
|
||||
return indexKeys[0], nil
|
||||
}
|
||||
}
|
||||
|
||||
const (
|
||||
// NamespaceIndex is the lookup name for the most common index function, which is to index by the namespace field.
|
||||
NamespaceIndex string = "namespace"
|
||||
)
|
||||
|
||||
// MetaNamespaceIndexFunc is a default index function that indexes based on an object's namespace
|
||||
func MetaNamespaceIndexFunc(obj interface{}) ([]string, error) {
|
||||
meta, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return []string{""}, fmt.Errorf("object has no meta: %v", err)
|
||||
}
|
||||
return []string{meta.GetNamespace()}, nil
|
||||
}
|
||||
|
||||
// Index maps the indexed value to a set of keys in the store that match on that value
|
||||
type Index map[string]sets.String
|
||||
|
||||
// Indexers maps a name to an IndexFunc
|
||||
type Indexers map[string]IndexFunc
|
||||
|
||||
// Indices maps a name to an Index
|
||||
type Indices map[string]Index
|
183
vendor/k8s.io/client-go/tools/cache/listers.go
generated
vendored
Normal file
183
vendor/k8s.io/client-go/tools/cache/listers.go
generated
vendored
Normal file
@ -0,0 +1,183 @@
|
||||
/*
|
||||
Copyright 2014 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 (
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/labels"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// AppendFunc is used to add a matching item to whatever list the caller is using
|
||||
type AppendFunc func(interface{})
|
||||
|
||||
// ListAll calls appendFn with each value retrieved from store which matches the selector.
|
||||
func ListAll(store Store, selector labels.Selector, appendFn AppendFunc) error {
|
||||
selectAll := selector.Empty()
|
||||
for _, m := range store.List() {
|
||||
if selectAll {
|
||||
// Avoid computing labels of the objects to speed up common flows
|
||||
// of listing all objects.
|
||||
appendFn(m)
|
||||
continue
|
||||
}
|
||||
metadata, err := meta.Accessor(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if selector.Matches(labels.Set(metadata.GetLabels())) {
|
||||
appendFn(m)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ListAllByNamespace used to list items belongs to namespace from Indexer.
|
||||
func ListAllByNamespace(indexer Indexer, namespace string, selector labels.Selector, appendFn AppendFunc) error {
|
||||
selectAll := selector.Empty()
|
||||
if namespace == metav1.NamespaceAll {
|
||||
for _, m := range indexer.List() {
|
||||
if selectAll {
|
||||
// Avoid computing labels of the objects to speed up common flows
|
||||
// of listing all objects.
|
||||
appendFn(m)
|
||||
continue
|
||||
}
|
||||
metadata, err := meta.Accessor(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if selector.Matches(labels.Set(metadata.GetLabels())) {
|
||||
appendFn(m)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
items, err := indexer.Index(NamespaceIndex, &metav1.ObjectMeta{Namespace: namespace})
|
||||
if err != nil {
|
||||
// Ignore error; do slow search without index.
|
||||
klog.Warningf("can not retrieve list of objects using index : %v", err)
|
||||
for _, m := range indexer.List() {
|
||||
metadata, err := meta.Accessor(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if metadata.GetNamespace() == namespace && selector.Matches(labels.Set(metadata.GetLabels())) {
|
||||
appendFn(m)
|
||||
}
|
||||
|
||||
}
|
||||
return nil
|
||||
}
|
||||
for _, m := range items {
|
||||
if selectAll {
|
||||
// Avoid computing labels of the objects to speed up common flows
|
||||
// of listing all objects.
|
||||
appendFn(m)
|
||||
continue
|
||||
}
|
||||
metadata, err := meta.Accessor(m)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if selector.Matches(labels.Set(metadata.GetLabels())) {
|
||||
appendFn(m)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// GenericLister is a lister skin on a generic Indexer
|
||||
type GenericLister interface {
|
||||
// List will return all objects across namespaces
|
||||
List(selector labels.Selector) (ret []runtime.Object, err error)
|
||||
// Get will attempt to retrieve assuming that name==key
|
||||
Get(name string) (runtime.Object, error)
|
||||
// ByNamespace will give you a GenericNamespaceLister for one namespace
|
||||
ByNamespace(namespace string) GenericNamespaceLister
|
||||
}
|
||||
|
||||
// GenericNamespaceLister is a lister skin on a generic Indexer
|
||||
type GenericNamespaceLister interface {
|
||||
// List will return all objects in this namespace
|
||||
List(selector labels.Selector) (ret []runtime.Object, err error)
|
||||
// Get will attempt to retrieve by namespace and name
|
||||
Get(name string) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// NewGenericLister creates a new instance for the genericLister.
|
||||
func NewGenericLister(indexer Indexer, resource schema.GroupResource) GenericLister {
|
||||
return &genericLister{indexer: indexer, resource: resource}
|
||||
}
|
||||
|
||||
type genericLister struct {
|
||||
indexer Indexer
|
||||
resource schema.GroupResource
|
||||
}
|
||||
|
||||
func (s *genericLister) List(selector labels.Selector) (ret []runtime.Object, err error) {
|
||||
err = ListAll(s.indexer, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(runtime.Object))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (s *genericLister) ByNamespace(namespace string) GenericNamespaceLister {
|
||||
return &genericNamespaceLister{indexer: s.indexer, namespace: namespace, resource: s.resource}
|
||||
}
|
||||
|
||||
func (s *genericLister) Get(name string) (runtime.Object, error) {
|
||||
obj, exists, err := s.indexer.GetByKey(name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, errors.NewNotFound(s.resource, name)
|
||||
}
|
||||
return obj.(runtime.Object), nil
|
||||
}
|
||||
|
||||
type genericNamespaceLister struct {
|
||||
indexer Indexer
|
||||
namespace string
|
||||
resource schema.GroupResource
|
||||
}
|
||||
|
||||
func (s *genericNamespaceLister) List(selector labels.Selector) (ret []runtime.Object, err error) {
|
||||
err = ListAllByNamespace(s.indexer, s.namespace, selector, func(m interface{}) {
|
||||
ret = append(ret, m.(runtime.Object))
|
||||
})
|
||||
return ret, err
|
||||
}
|
||||
|
||||
func (s *genericNamespaceLister) Get(name string) (runtime.Object, error) {
|
||||
obj, exists, err := s.indexer.GetByKey(s.namespace + "/" + name)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
return nil, errors.NewNotFound(s.resource, name)
|
||||
}
|
||||
return obj.(runtime.Object), nil
|
||||
}
|
112
vendor/k8s.io/client-go/tools/cache/listwatch.go
generated
vendored
Normal file
112
vendor/k8s.io/client-go/tools/cache/listwatch.go
generated
vendored
Normal file
@ -0,0 +1,112 @@
|
||||
/*
|
||||
Copyright 2015 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"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/fields"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// Lister is any object that knows how to perform an initial list.
|
||||
type Lister interface {
|
||||
// List should return a list type object; the Items field will be extracted, and the
|
||||
// ResourceVersion field will be used to start the watch in the right place.
|
||||
List(options metav1.ListOptions) (runtime.Object, error)
|
||||
}
|
||||
|
||||
// Watcher is any object that knows how to start a watch on a resource.
|
||||
type Watcher interface {
|
||||
// Watch should begin a watch at the specified version.
|
||||
Watch(options metav1.ListOptions) (watch.Interface, error)
|
||||
}
|
||||
|
||||
// ListerWatcher is any object that knows how to perform an initial list and start a watch on a resource.
|
||||
type ListerWatcher interface {
|
||||
Lister
|
||||
Watcher
|
||||
}
|
||||
|
||||
// ListFunc knows how to list resources
|
||||
type ListFunc func(options metav1.ListOptions) (runtime.Object, error)
|
||||
|
||||
// WatchFunc knows how to watch resources
|
||||
type WatchFunc func(options metav1.ListOptions) (watch.Interface, error)
|
||||
|
||||
// ListWatch knows how to list and watch a set of apiserver resources. It satisfies the ListerWatcher interface.
|
||||
// It is a convenience function for users of NewReflector, etc.
|
||||
// ListFunc and WatchFunc must not be nil
|
||||
type ListWatch struct {
|
||||
ListFunc ListFunc
|
||||
WatchFunc WatchFunc
|
||||
// DisableChunking requests no chunking for this list watcher.
|
||||
DisableChunking bool
|
||||
}
|
||||
|
||||
// Getter interface knows how to access Get method from RESTClient.
|
||||
type Getter interface {
|
||||
Get() *restclient.Request
|
||||
}
|
||||
|
||||
// NewListWatchFromClient creates a new ListWatch from the specified client, resource, namespace and field selector.
|
||||
func NewListWatchFromClient(c Getter, resource string, namespace string, fieldSelector fields.Selector) *ListWatch {
|
||||
optionsModifier := func(options *metav1.ListOptions) {
|
||||
options.FieldSelector = fieldSelector.String()
|
||||
}
|
||||
return NewFilteredListWatchFromClient(c, resource, namespace, optionsModifier)
|
||||
}
|
||||
|
||||
// NewFilteredListWatchFromClient creates a new ListWatch from the specified client, resource, namespace, and option modifier.
|
||||
// Option modifier is a function takes a ListOptions and modifies the consumed ListOptions. Provide customized modifier function
|
||||
// to apply modification to ListOptions with a field selector, a label selector, or any other desired options.
|
||||
func NewFilteredListWatchFromClient(c Getter, resource string, namespace string, optionsModifier func(options *metav1.ListOptions)) *ListWatch {
|
||||
listFunc := func(options metav1.ListOptions) (runtime.Object, error) {
|
||||
optionsModifier(&options)
|
||||
return c.Get().
|
||||
Namespace(namespace).
|
||||
Resource(resource).
|
||||
VersionedParams(&options, metav1.ParameterCodec).
|
||||
Do(context.TODO()).
|
||||
Get()
|
||||
}
|
||||
watchFunc := func(options metav1.ListOptions) (watch.Interface, error) {
|
||||
options.Watch = true
|
||||
optionsModifier(&options)
|
||||
return c.Get().
|
||||
Namespace(namespace).
|
||||
Resource(resource).
|
||||
VersionedParams(&options, metav1.ParameterCodec).
|
||||
Watch(context.TODO())
|
||||
}
|
||||
return &ListWatch{ListFunc: listFunc, WatchFunc: watchFunc}
|
||||
}
|
||||
|
||||
// List a set of apiserver resources
|
||||
func (lw *ListWatch) List(options metav1.ListOptions) (runtime.Object, error) {
|
||||
// ListWatch is used in Reflector, which already supports pagination.
|
||||
// Don't paginate here to avoid duplication.
|
||||
return lw.ListFunc(options)
|
||||
}
|
||||
|
||||
// Watch a set of apiserver resources
|
||||
func (lw *ListWatch) Watch(options metav1.ListOptions) (watch.Interface, error) {
|
||||
return lw.WatchFunc(options)
|
||||
}
|
262
vendor/k8s.io/client-go/tools/cache/mutation_cache.go
generated
vendored
Normal file
262
vendor/k8s.io/client-go/tools/cache/mutation_cache.go
generated
vendored
Normal file
@ -0,0 +1,262 @@
|
||||
/*
|
||||
Copyright 2017 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 (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilcache "k8s.io/apimachinery/pkg/util/cache"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// MutationCache is able to take the result of update operations and stores them in an LRU
|
||||
// that can be used to provide a more current view of a requested object. It requires interpreting
|
||||
// resourceVersions for comparisons.
|
||||
// Implementations must be thread-safe.
|
||||
// TODO find a way to layer this into an informer/lister
|
||||
type MutationCache interface {
|
||||
GetByKey(key string) (interface{}, bool, error)
|
||||
ByIndex(indexName, indexKey string) ([]interface{}, error)
|
||||
Mutation(interface{})
|
||||
}
|
||||
|
||||
// ResourceVersionComparator is able to compare object versions.
|
||||
type ResourceVersionComparator interface {
|
||||
CompareResourceVersion(lhs, rhs runtime.Object) int
|
||||
}
|
||||
|
||||
// NewIntegerResourceVersionMutationCache returns a MutationCache that understands how to
|
||||
// deal with objects that have a resource version that:
|
||||
//
|
||||
// - is an integer
|
||||
// - increases when updated
|
||||
// - is comparable across the same resource in a namespace
|
||||
//
|
||||
// Most backends will have these semantics. Indexer may be nil. ttl controls how long an item
|
||||
// remains in the mutation cache before it is removed.
|
||||
//
|
||||
// If includeAdds is true, objects in the mutation cache will be returned even if they don't exist
|
||||
// in the underlying store. This is only safe if your use of the cache can handle mutation entries
|
||||
// remaining in the cache for up to ttl when mutations and deletes occur very closely in time.
|
||||
func NewIntegerResourceVersionMutationCache(backingCache Store, indexer Indexer, ttl time.Duration, includeAdds bool) MutationCache {
|
||||
return &mutationCache{
|
||||
backingCache: backingCache,
|
||||
indexer: indexer,
|
||||
mutationCache: utilcache.NewLRUExpireCache(100),
|
||||
comparator: etcdObjectVersioner{},
|
||||
ttl: ttl,
|
||||
includeAdds: includeAdds,
|
||||
}
|
||||
}
|
||||
|
||||
// mutationCache doesn't guarantee that it returns values added via Mutation since they can page out and
|
||||
// since you can't distinguish between, "didn't observe create" and "was deleted after create",
|
||||
// if the key is missing from the backing cache, we always return it as missing
|
||||
type mutationCache struct {
|
||||
lock sync.Mutex
|
||||
backingCache Store
|
||||
indexer Indexer
|
||||
mutationCache *utilcache.LRUExpireCache
|
||||
includeAdds bool
|
||||
ttl time.Duration
|
||||
|
||||
comparator ResourceVersionComparator
|
||||
}
|
||||
|
||||
// GetByKey is never guaranteed to return back the value set in Mutation. It could be paged out, it could
|
||||
// be older than another copy, the backingCache may be more recent or, you might have written twice into the same key.
|
||||
// You get a value that was valid at some snapshot of time and will always return the newer of backingCache and mutationCache.
|
||||
func (c *mutationCache) GetByKey(key string) (interface{}, bool, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
obj, exists, err := c.backingCache.GetByKey(key)
|
||||
if err != nil {
|
||||
return nil, false, err
|
||||
}
|
||||
if !exists {
|
||||
if !c.includeAdds {
|
||||
// we can't distinguish between, "didn't observe create" and "was deleted after create", so
|
||||
// if the key is missing, we always return it as missing
|
||||
return nil, false, nil
|
||||
}
|
||||
obj, exists = c.mutationCache.Get(key)
|
||||
if !exists {
|
||||
return nil, false, nil
|
||||
}
|
||||
}
|
||||
objRuntime, ok := obj.(runtime.Object)
|
||||
if !ok {
|
||||
return obj, true, nil
|
||||
}
|
||||
return c.newerObject(key, objRuntime), true, nil
|
||||
}
|
||||
|
||||
// ByIndex returns the newer objects that match the provided index and indexer key.
|
||||
// Will return an error if no indexer was provided.
|
||||
func (c *mutationCache) ByIndex(name string, indexKey string) ([]interface{}, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if c.indexer == nil {
|
||||
return nil, fmt.Errorf("no indexer has been provided to the mutation cache")
|
||||
}
|
||||
keys, err := c.indexer.IndexKeys(name, indexKey)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var items []interface{}
|
||||
keySet := sets.NewString()
|
||||
for _, key := range keys {
|
||||
keySet.Insert(key)
|
||||
obj, exists, err := c.indexer.GetByKey(key)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if !exists {
|
||||
continue
|
||||
}
|
||||
if objRuntime, ok := obj.(runtime.Object); ok {
|
||||
items = append(items, c.newerObject(key, objRuntime))
|
||||
} else {
|
||||
items = append(items, obj)
|
||||
}
|
||||
}
|
||||
|
||||
if c.includeAdds {
|
||||
fn := c.indexer.GetIndexers()[name]
|
||||
// Keys() is returned oldest to newest, so full traversal does not alter the LRU behavior
|
||||
for _, key := range c.mutationCache.Keys() {
|
||||
updated, ok := c.mutationCache.Get(key)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if keySet.Has(key.(string)) {
|
||||
continue
|
||||
}
|
||||
elements, err := fn(updated)
|
||||
if err != nil {
|
||||
klog.V(4).Infof("Unable to calculate an index entry for mutation cache entry %s: %v", key, err)
|
||||
continue
|
||||
}
|
||||
for _, inIndex := range elements {
|
||||
if inIndex != indexKey {
|
||||
continue
|
||||
}
|
||||
items = append(items, updated)
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return items, nil
|
||||
}
|
||||
|
||||
// newerObject checks the mutation cache for a newer object and returns one if found. If the
|
||||
// mutated object is older than the backing object, it is removed from the Must be
|
||||
// called while the lock is held.
|
||||
func (c *mutationCache) newerObject(key string, backing runtime.Object) runtime.Object {
|
||||
mutatedObj, exists := c.mutationCache.Get(key)
|
||||
if !exists {
|
||||
return backing
|
||||
}
|
||||
mutatedObjRuntime, ok := mutatedObj.(runtime.Object)
|
||||
if !ok {
|
||||
return backing
|
||||
}
|
||||
if c.comparator.CompareResourceVersion(backing, mutatedObjRuntime) >= 0 {
|
||||
c.mutationCache.Remove(key)
|
||||
return backing
|
||||
}
|
||||
return mutatedObjRuntime
|
||||
}
|
||||
|
||||
// Mutation adds a change to the cache that can be returned in GetByKey if it is newer than the backingCache
|
||||
// copy. If you call Mutation twice with the same object on different threads, one will win, but its not defined
|
||||
// which one. This doesn't affect correctness, since the GetByKey guaranteed of "later of these two caches" is
|
||||
// preserved, but you may not get the version of the object you want. The object you get is only guaranteed to
|
||||
// "one that was valid at some point in time", not "the one that I want".
|
||||
func (c *mutationCache) Mutation(obj interface{}) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
key, err := DeletionHandlingMetaNamespaceKeyFunc(obj)
|
||||
if err != nil {
|
||||
// this is a "nice to have", so failures shouldn't do anything weird
|
||||
utilruntime.HandleError(err)
|
||||
return
|
||||
}
|
||||
|
||||
if objRuntime, ok := obj.(runtime.Object); ok {
|
||||
if mutatedObj, exists := c.mutationCache.Get(key); exists {
|
||||
if mutatedObjRuntime, ok := mutatedObj.(runtime.Object); ok {
|
||||
if c.comparator.CompareResourceVersion(objRuntime, mutatedObjRuntime) < 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
c.mutationCache.Add(key, obj, c.ttl)
|
||||
}
|
||||
|
||||
// etcdObjectVersioner implements versioning and extracting etcd node information
|
||||
// for objects that have an embedded ObjectMeta or ListMeta field.
|
||||
type etcdObjectVersioner struct{}
|
||||
|
||||
// ObjectResourceVersion implements Versioner
|
||||
func (a etcdObjectVersioner) ObjectResourceVersion(obj runtime.Object) (uint64, error) {
|
||||
accessor, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
version := accessor.GetResourceVersion()
|
||||
if len(version) == 0 {
|
||||
return 0, nil
|
||||
}
|
||||
return strconv.ParseUint(version, 10, 64)
|
||||
}
|
||||
|
||||
// CompareResourceVersion compares etcd resource versions. Outside this API they are all strings,
|
||||
// but etcd resource versions are special, they're actually ints, so we can easily compare them.
|
||||
func (a etcdObjectVersioner) CompareResourceVersion(lhs, rhs runtime.Object) int {
|
||||
lhsVersion, err := a.ObjectResourceVersion(lhs)
|
||||
if err != nil {
|
||||
// coder error
|
||||
panic(err)
|
||||
}
|
||||
rhsVersion, err := a.ObjectResourceVersion(rhs)
|
||||
if err != nil {
|
||||
// coder error
|
||||
panic(err)
|
||||
}
|
||||
|
||||
if lhsVersion == rhsVersion {
|
||||
return 0
|
||||
}
|
||||
if lhsVersion < rhsVersion {
|
||||
return -1
|
||||
}
|
||||
|
||||
return 1
|
||||
}
|
166
vendor/k8s.io/client-go/tools/cache/mutation_detector.go
generated
vendored
Normal file
166
vendor/k8s.io/client-go/tools/cache/mutation_detector.go
generated
vendored
Normal file
@ -0,0 +1,166 @@
|
||||
/*
|
||||
Copyright 2016 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 (
|
||||
"fmt"
|
||||
"os"
|
||||
"reflect"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/diff"
|
||||
)
|
||||
|
||||
var mutationDetectionEnabled = false
|
||||
|
||||
func init() {
|
||||
mutationDetectionEnabled, _ = strconv.ParseBool(os.Getenv("KUBE_CACHE_MUTATION_DETECTOR"))
|
||||
}
|
||||
|
||||
// MutationDetector is able to monitor objects for mutation within a limited window of time
|
||||
type MutationDetector interface {
|
||||
// AddObject adds the given object to the set being monitored for a while from now
|
||||
AddObject(obj interface{})
|
||||
|
||||
// Run starts the monitoring and does not return until the monitoring is stopped.
|
||||
Run(stopCh <-chan struct{})
|
||||
}
|
||||
|
||||
// NewCacheMutationDetector creates a new instance for the defaultCacheMutationDetector.
|
||||
func NewCacheMutationDetector(name string) MutationDetector {
|
||||
if !mutationDetectionEnabled {
|
||||
return dummyMutationDetector{}
|
||||
}
|
||||
klog.Warningln("Mutation detector is enabled, this will result in memory leakage.")
|
||||
return &defaultCacheMutationDetector{name: name, period: 1 * time.Second, retainDuration: 2 * time.Minute}
|
||||
}
|
||||
|
||||
type dummyMutationDetector struct{}
|
||||
|
||||
func (dummyMutationDetector) Run(stopCh <-chan struct{}) {
|
||||
}
|
||||
func (dummyMutationDetector) AddObject(obj interface{}) {
|
||||
}
|
||||
|
||||
// defaultCacheMutationDetector gives a way to detect if a cached object has been mutated
|
||||
// It has a list of cached objects and their copies. I haven't thought of a way
|
||||
// to see WHO is mutating it, just that it's getting mutated.
|
||||
type defaultCacheMutationDetector struct {
|
||||
name string
|
||||
period time.Duration
|
||||
|
||||
// compareLock ensures only a single call to CompareObjects runs at a time
|
||||
compareObjectsLock sync.Mutex
|
||||
|
||||
// addLock guards addedObjs between AddObject and CompareObjects
|
||||
addedObjsLock sync.Mutex
|
||||
addedObjs []cacheObj
|
||||
|
||||
cachedObjs []cacheObj
|
||||
|
||||
retainDuration time.Duration
|
||||
lastRotated time.Time
|
||||
retainedCachedObjs []cacheObj
|
||||
|
||||
// failureFunc is injectable for unit testing. If you don't have it, the process will panic.
|
||||
// This panic is intentional, since turning on this detection indicates you want a strong
|
||||
// failure signal. This failure is effectively a p0 bug and you can't trust process results
|
||||
// after a mutation anyway.
|
||||
failureFunc func(message string)
|
||||
}
|
||||
|
||||
// cacheObj holds the actual object and a copy
|
||||
type cacheObj struct {
|
||||
cached interface{}
|
||||
copied interface{}
|
||||
}
|
||||
|
||||
func (d *defaultCacheMutationDetector) Run(stopCh <-chan struct{}) {
|
||||
// we DON'T want protection from panics. If we're running this code, we want to die
|
||||
for {
|
||||
if d.lastRotated.IsZero() {
|
||||
d.lastRotated = time.Now()
|
||||
} else if time.Since(d.lastRotated) > d.retainDuration {
|
||||
d.retainedCachedObjs = d.cachedObjs
|
||||
d.cachedObjs = nil
|
||||
d.lastRotated = time.Now()
|
||||
}
|
||||
|
||||
d.CompareObjects()
|
||||
|
||||
select {
|
||||
case <-stopCh:
|
||||
return
|
||||
case <-time.After(d.period):
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// AddObject makes a deep copy of the object for later comparison. It only works on runtime.Object
|
||||
// but that covers the vast majority of our cached objects
|
||||
func (d *defaultCacheMutationDetector) AddObject(obj interface{}) {
|
||||
if _, ok := obj.(DeletedFinalStateUnknown); ok {
|
||||
return
|
||||
}
|
||||
if obj, ok := obj.(runtime.Object); ok {
|
||||
copiedObj := obj.DeepCopyObject()
|
||||
|
||||
d.addedObjsLock.Lock()
|
||||
defer d.addedObjsLock.Unlock()
|
||||
d.addedObjs = append(d.addedObjs, cacheObj{cached: obj, copied: copiedObj})
|
||||
}
|
||||
}
|
||||
|
||||
func (d *defaultCacheMutationDetector) CompareObjects() {
|
||||
d.compareObjectsLock.Lock()
|
||||
defer d.compareObjectsLock.Unlock()
|
||||
|
||||
// move addedObjs into cachedObjs under lock
|
||||
// this keeps the critical section small to avoid blocking AddObject while we compare cachedObjs
|
||||
d.addedObjsLock.Lock()
|
||||
d.cachedObjs = append(d.cachedObjs, d.addedObjs...)
|
||||
d.addedObjs = nil
|
||||
d.addedObjsLock.Unlock()
|
||||
|
||||
altered := false
|
||||
for i, obj := range d.cachedObjs {
|
||||
if !reflect.DeepEqual(obj.cached, obj.copied) {
|
||||
fmt.Printf("CACHE %s[%d] ALTERED!\n%v\n", d.name, i, diff.ObjectGoPrintSideBySide(obj.cached, obj.copied))
|
||||
altered = true
|
||||
}
|
||||
}
|
||||
for i, obj := range d.retainedCachedObjs {
|
||||
if !reflect.DeepEqual(obj.cached, obj.copied) {
|
||||
fmt.Printf("CACHE %s[%d] ALTERED!\n%v\n", d.name, i, diff.ObjectGoPrintSideBySide(obj.cached, obj.copied))
|
||||
altered = true
|
||||
}
|
||||
}
|
||||
|
||||
if altered {
|
||||
msg := fmt.Sprintf("cache %s modified", d.name)
|
||||
if d.failureFunc != nil {
|
||||
d.failureFunc(msg)
|
||||
return
|
||||
}
|
||||
panic(msg)
|
||||
}
|
||||
}
|
616
vendor/k8s.io/client-go/tools/cache/reflector.go
generated
vendored
Normal file
616
vendor/k8s.io/client-go/tools/cache/reflector.go
generated
vendored
Normal file
@ -0,0 +1,616 @@
|
||||
/*
|
||||
Copyright 2014 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"
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/rand"
|
||||
"reflect"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
apierrors "k8s.io/apimachinery/pkg/api/errors"
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/naming"
|
||||
utilnet "k8s.io/apimachinery/pkg/util/net"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/apimachinery/pkg/watch"
|
||||
"k8s.io/client-go/tools/pager"
|
||||
"k8s.io/klog/v2"
|
||||
"k8s.io/utils/clock"
|
||||
"k8s.io/utils/trace"
|
||||
)
|
||||
|
||||
const defaultExpectedTypeName = "<unspecified>"
|
||||
|
||||
// Reflector watches a specified resource and causes all changes to be reflected in the given store.
|
||||
type Reflector struct {
|
||||
// name identifies this reflector. By default it will be a file:line if possible.
|
||||
name string
|
||||
|
||||
// The name of the type we expect to place in the store. The name
|
||||
// will be the stringification of expectedGVK if provided, and the
|
||||
// stringification of expectedType otherwise. It is for display
|
||||
// only, and should not be used for parsing or comparison.
|
||||
expectedTypeName string
|
||||
// An example object of the type we expect to place in the store.
|
||||
// Only the type needs to be right, except that when that is
|
||||
// `unstructured.Unstructured` the object's `"apiVersion"` and
|
||||
// `"kind"` must also be right.
|
||||
expectedType reflect.Type
|
||||
// The GVK of the object we expect to place in the store if unstructured.
|
||||
expectedGVK *schema.GroupVersionKind
|
||||
// The destination to sync up with the watch source
|
||||
store Store
|
||||
// listerWatcher is used to perform lists and watches.
|
||||
listerWatcher ListerWatcher
|
||||
|
||||
// backoff manages backoff of ListWatch
|
||||
backoffManager wait.BackoffManager
|
||||
// initConnBackoffManager manages backoff the initial connection with the Watch call of ListAndWatch.
|
||||
initConnBackoffManager wait.BackoffManager
|
||||
|
||||
resyncPeriod time.Duration
|
||||
// ShouldResync is invoked periodically and whenever it returns `true` the Store's Resync operation is invoked
|
||||
ShouldResync func() bool
|
||||
// clock allows tests to manipulate time
|
||||
clock clock.Clock
|
||||
// paginatedResult defines whether pagination should be forced for list calls.
|
||||
// It is set based on the result of the initial list call.
|
||||
paginatedResult bool
|
||||
// lastSyncResourceVersion is the resource version token last
|
||||
// observed when doing a sync with the underlying store
|
||||
// it is thread safe, but not synchronized with the underlying store
|
||||
lastSyncResourceVersion string
|
||||
// isLastSyncResourceVersionUnavailable is true if the previous list or watch request with
|
||||
// lastSyncResourceVersion failed with an "expired" or "too large resource version" error.
|
||||
isLastSyncResourceVersionUnavailable bool
|
||||
// lastSyncResourceVersionMutex guards read/write access to lastSyncResourceVersion
|
||||
lastSyncResourceVersionMutex sync.RWMutex
|
||||
// WatchListPageSize is the requested chunk size of initial and resync watch lists.
|
||||
// If unset, for consistent reads (RV="") or reads that opt-into arbitrarily old data
|
||||
// (RV="0") it will default to pager.PageSize, for the rest (RV != "" && RV != "0")
|
||||
// it will turn off pagination to allow serving them from watch cache.
|
||||
// NOTE: It should be used carefully as paginated lists are always served directly from
|
||||
// etcd, which is significantly less efficient and may lead to serious performance and
|
||||
// scalability problems.
|
||||
WatchListPageSize int64
|
||||
// Called whenever the ListAndWatch drops the connection with an error.
|
||||
watchErrorHandler WatchErrorHandler
|
||||
}
|
||||
|
||||
// ResourceVersionUpdater is an interface that allows store implementation to
|
||||
// track the current resource version of the reflector. This is especially
|
||||
// important if storage bookmarks are enabled.
|
||||
type ResourceVersionUpdater interface {
|
||||
// UpdateResourceVersion is called each time current resource version of the reflector
|
||||
// is updated.
|
||||
UpdateResourceVersion(resourceVersion string)
|
||||
}
|
||||
|
||||
// The WatchErrorHandler is called whenever ListAndWatch drops the
|
||||
// connection with an error. After calling this handler, the informer
|
||||
// will backoff and retry.
|
||||
//
|
||||
// The default implementation looks at the error type and tries to log
|
||||
// the error message at an appropriate level.
|
||||
//
|
||||
// Implementations of this handler may display the error message in other
|
||||
// ways. Implementations should return quickly - any expensive processing
|
||||
// should be offloaded.
|
||||
type WatchErrorHandler func(r *Reflector, err error)
|
||||
|
||||
// DefaultWatchErrorHandler is the default implementation of WatchErrorHandler
|
||||
func DefaultWatchErrorHandler(r *Reflector, err error) {
|
||||
switch {
|
||||
case isExpiredError(err):
|
||||
// Don't set LastSyncResourceVersionUnavailable - LIST call with ResourceVersion=RV already
|
||||
// has a semantic that it returns data at least as fresh as provided RV.
|
||||
// So first try to LIST with setting RV to resource version of last observed object.
|
||||
klog.V(4).Infof("%s: watch of %v closed with: %v", r.name, r.expectedTypeName, err)
|
||||
case err == io.EOF:
|
||||
// watch closed normally
|
||||
case err == io.ErrUnexpectedEOF:
|
||||
klog.V(1).Infof("%s: Watch for %v closed with unexpected EOF: %v", r.name, r.expectedTypeName, err)
|
||||
default:
|
||||
utilruntime.HandleError(fmt.Errorf("%s: Failed to watch %v: %v", r.name, r.expectedTypeName, err))
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
// We try to spread the load on apiserver by setting timeouts for
|
||||
// watch requests - it is random in [minWatchTimeout, 2*minWatchTimeout].
|
||||
minWatchTimeout = 5 * time.Minute
|
||||
)
|
||||
|
||||
// NewNamespaceKeyedIndexerAndReflector creates an Indexer and a Reflector
|
||||
// The indexer is configured to key on namespace
|
||||
func NewNamespaceKeyedIndexerAndReflector(lw ListerWatcher, expectedType interface{}, resyncPeriod time.Duration) (indexer Indexer, reflector *Reflector) {
|
||||
indexer = NewIndexer(MetaNamespaceKeyFunc, Indexers{NamespaceIndex: MetaNamespaceIndexFunc})
|
||||
reflector = NewReflector(lw, expectedType, indexer, resyncPeriod)
|
||||
return indexer, reflector
|
||||
}
|
||||
|
||||
// NewReflector creates a new Reflector object which will keep the
|
||||
// given store up to date with the server's contents for the given
|
||||
// resource. Reflector promises to only put things in the store that
|
||||
// have the type of expectedType, unless expectedType is nil. If
|
||||
// resyncPeriod is non-zero, then the reflector will periodically
|
||||
// consult its ShouldResync function to determine whether to invoke
|
||||
// the Store's Resync operation; `ShouldResync==nil` means always
|
||||
// "yes". This enables you to use reflectors to periodically process
|
||||
// everything as well as incrementally processing the things that
|
||||
// change.
|
||||
func NewReflector(lw ListerWatcher, expectedType interface{}, store Store, resyncPeriod time.Duration) *Reflector {
|
||||
return NewNamedReflector(naming.GetNameFromCallsite(internalPackages...), lw, expectedType, store, resyncPeriod)
|
||||
}
|
||||
|
||||
// NewNamedReflector same as NewReflector, but with a specified name for logging
|
||||
func NewNamedReflector(name string, lw ListerWatcher, expectedType interface{}, store Store, resyncPeriod time.Duration) *Reflector {
|
||||
realClock := &clock.RealClock{}
|
||||
r := &Reflector{
|
||||
name: name,
|
||||
listerWatcher: lw,
|
||||
store: store,
|
||||
// We used to make the call every 1sec (1 QPS), the goal here is to achieve ~98% traffic reduction when
|
||||
// API server is not healthy. With these parameters, backoff will stop at [30,60) sec interval which is
|
||||
// 0.22 QPS. If we don't backoff for 2min, assume API server is healthy and we reset the backoff.
|
||||
backoffManager: wait.NewExponentialBackoffManager(800*time.Millisecond, 30*time.Second, 2*time.Minute, 2.0, 1.0, realClock),
|
||||
initConnBackoffManager: wait.NewExponentialBackoffManager(800*time.Millisecond, 30*time.Second, 2*time.Minute, 2.0, 1.0, realClock),
|
||||
resyncPeriod: resyncPeriod,
|
||||
clock: realClock,
|
||||
watchErrorHandler: WatchErrorHandler(DefaultWatchErrorHandler),
|
||||
}
|
||||
r.setExpectedType(expectedType)
|
||||
return r
|
||||
}
|
||||
|
||||
func (r *Reflector) setExpectedType(expectedType interface{}) {
|
||||
r.expectedType = reflect.TypeOf(expectedType)
|
||||
if r.expectedType == nil {
|
||||
r.expectedTypeName = defaultExpectedTypeName
|
||||
return
|
||||
}
|
||||
|
||||
r.expectedTypeName = r.expectedType.String()
|
||||
|
||||
if obj, ok := expectedType.(*unstructured.Unstructured); ok {
|
||||
// Use gvk to check that watch event objects are of the desired type.
|
||||
gvk := obj.GroupVersionKind()
|
||||
if gvk.Empty() {
|
||||
klog.V(4).Infof("Reflector from %s configured with expectedType of *unstructured.Unstructured with empty GroupVersionKind.", r.name)
|
||||
return
|
||||
}
|
||||
r.expectedGVK = &gvk
|
||||
r.expectedTypeName = gvk.String()
|
||||
}
|
||||
}
|
||||
|
||||
// internalPackages are packages that ignored when creating a default reflector name. These packages are in the common
|
||||
// call chains to NewReflector, so they'd be low entropy names for reflectors
|
||||
var internalPackages = []string{"client-go/tools/cache/"}
|
||||
|
||||
// Run repeatedly uses the reflector's ListAndWatch to fetch all the
|
||||
// objects and subsequent deltas.
|
||||
// Run will exit when stopCh is closed.
|
||||
func (r *Reflector) Run(stopCh <-chan struct{}) {
|
||||
klog.V(3).Infof("Starting reflector %s (%s) from %s", r.expectedTypeName, r.resyncPeriod, r.name)
|
||||
wait.BackoffUntil(func() {
|
||||
if err := r.ListAndWatch(stopCh); err != nil {
|
||||
r.watchErrorHandler(r, err)
|
||||
}
|
||||
}, r.backoffManager, true, stopCh)
|
||||
klog.V(3).Infof("Stopping reflector %s (%s) from %s", r.expectedTypeName, r.resyncPeriod, r.name)
|
||||
}
|
||||
|
||||
var (
|
||||
// nothing will ever be sent down this channel
|
||||
neverExitWatch <-chan time.Time = make(chan time.Time)
|
||||
|
||||
// Used to indicate that watching stopped because of a signal from the stop
|
||||
// channel passed in from a client of the reflector.
|
||||
errorStopRequested = errors.New("stop requested")
|
||||
)
|
||||
|
||||
// resyncChan returns a channel which will receive something when a resync is
|
||||
// required, and a cleanup function.
|
||||
func (r *Reflector) resyncChan() (<-chan time.Time, func() bool) {
|
||||
if r.resyncPeriod == 0 {
|
||||
return neverExitWatch, func() bool { return false }
|
||||
}
|
||||
// The cleanup function is required: imagine the scenario where watches
|
||||
// always fail so we end up listing frequently. Then, if we don't
|
||||
// manually stop the timer, we could end up with many timers active
|
||||
// concurrently.
|
||||
t := r.clock.NewTimer(r.resyncPeriod)
|
||||
return t.C(), t.Stop
|
||||
}
|
||||
|
||||
// ListAndWatch first lists all items and get the resource version at the moment of call,
|
||||
// and then use the resource version to watch.
|
||||
// It returns error if ListAndWatch didn't even try to initialize watch.
|
||||
func (r *Reflector) ListAndWatch(stopCh <-chan struct{}) error {
|
||||
klog.V(3).Infof("Listing and watching %v from %s", r.expectedTypeName, r.name)
|
||||
var resourceVersion string
|
||||
|
||||
options := metav1.ListOptions{ResourceVersion: r.relistResourceVersion()}
|
||||
|
||||
if err := func() error {
|
||||
initTrace := trace.New("Reflector ListAndWatch", trace.Field{Key: "name", Value: r.name})
|
||||
defer initTrace.LogIfLong(10 * time.Second)
|
||||
var list runtime.Object
|
||||
var paginatedResult bool
|
||||
var err error
|
||||
listCh := make(chan struct{}, 1)
|
||||
panicCh := make(chan interface{}, 1)
|
||||
go func() {
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
panicCh <- r
|
||||
}
|
||||
}()
|
||||
// Attempt to gather list in chunks, if supported by listerWatcher, if not, the first
|
||||
// list request will return the full response.
|
||||
pager := pager.New(pager.SimplePageFunc(func(opts metav1.ListOptions) (runtime.Object, error) {
|
||||
return r.listerWatcher.List(opts)
|
||||
}))
|
||||
switch {
|
||||
case r.WatchListPageSize != 0:
|
||||
pager.PageSize = r.WatchListPageSize
|
||||
case r.paginatedResult:
|
||||
// We got a paginated result initially. Assume this resource and server honor
|
||||
// paging requests (i.e. watch cache is probably disabled) and leave the default
|
||||
// pager size set.
|
||||
case options.ResourceVersion != "" && options.ResourceVersion != "0":
|
||||
// User didn't explicitly request pagination.
|
||||
//
|
||||
// With ResourceVersion != "", we have a possibility to list from watch cache,
|
||||
// but we do that (for ResourceVersion != "0") only if Limit is unset.
|
||||
// To avoid thundering herd on etcd (e.g. on master upgrades), we explicitly
|
||||
// switch off pagination to force listing from watch cache (if enabled).
|
||||
// With the existing semantic of RV (result is at least as fresh as provided RV),
|
||||
// this is correct and doesn't lead to going back in time.
|
||||
//
|
||||
// We also don't turn off pagination for ResourceVersion="0", since watch cache
|
||||
// is ignoring Limit in that case anyway, and if watch cache is not enabled
|
||||
// we don't introduce regression.
|
||||
pager.PageSize = 0
|
||||
}
|
||||
|
||||
list, paginatedResult, err = pager.List(context.Background(), options)
|
||||
if isExpiredError(err) || isTooLargeResourceVersionError(err) {
|
||||
r.setIsLastSyncResourceVersionUnavailable(true)
|
||||
// Retry immediately if the resource version used to list is unavailable.
|
||||
// The pager already falls back to full list if paginated list calls fail due to an "Expired" error on
|
||||
// continuation pages, but the pager might not be enabled, the full list might fail because the
|
||||
// resource version it is listing at is expired or the cache may not yet be synced to the provided
|
||||
// resource version. So we need to fallback to resourceVersion="" in all to recover and ensure
|
||||
// the reflector makes forward progress.
|
||||
list, paginatedResult, err = pager.List(context.Background(), metav1.ListOptions{ResourceVersion: r.relistResourceVersion()})
|
||||
}
|
||||
close(listCh)
|
||||
}()
|
||||
select {
|
||||
case <-stopCh:
|
||||
return nil
|
||||
case r := <-panicCh:
|
||||
panic(r)
|
||||
case <-listCh:
|
||||
}
|
||||
initTrace.Step("Objects listed", trace.Field{Key: "error", Value: err})
|
||||
if err != nil {
|
||||
klog.Warningf("%s: failed to list %v: %v", r.name, r.expectedTypeName, err)
|
||||
return fmt.Errorf("failed to list %v: %v", r.expectedTypeName, err)
|
||||
}
|
||||
|
||||
// We check if the list was paginated and if so set the paginatedResult based on that.
|
||||
// However, we want to do that only for the initial list (which is the only case
|
||||
// when we set ResourceVersion="0"). The reasoning behind it is that later, in some
|
||||
// situations we may force listing directly from etcd (by setting ResourceVersion="")
|
||||
// which will return paginated result, even if watch cache is enabled. However, in
|
||||
// that case, we still want to prefer sending requests to watch cache if possible.
|
||||
//
|
||||
// Paginated result returned for request with ResourceVersion="0" mean that watch
|
||||
// cache is disabled and there are a lot of objects of a given type. In such case,
|
||||
// there is no need to prefer listing from watch cache.
|
||||
if options.ResourceVersion == "0" && paginatedResult {
|
||||
r.paginatedResult = true
|
||||
}
|
||||
|
||||
r.setIsLastSyncResourceVersionUnavailable(false) // list was successful
|
||||
listMetaInterface, err := meta.ListAccessor(list)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to understand list result %#v: %v", list, err)
|
||||
}
|
||||
resourceVersion = listMetaInterface.GetResourceVersion()
|
||||
initTrace.Step("Resource version extracted")
|
||||
items, err := meta.ExtractList(list)
|
||||
if err != nil {
|
||||
return fmt.Errorf("unable to understand list result %#v (%v)", list, err)
|
||||
}
|
||||
initTrace.Step("Objects extracted")
|
||||
if err := r.syncWith(items, resourceVersion); err != nil {
|
||||
return fmt.Errorf("unable to sync list result: %v", err)
|
||||
}
|
||||
initTrace.Step("SyncWith done")
|
||||
r.setLastSyncResourceVersion(resourceVersion)
|
||||
initTrace.Step("Resource version updated")
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
resyncerrc := make(chan error, 1)
|
||||
cancelCh := make(chan struct{})
|
||||
defer close(cancelCh)
|
||||
go func() {
|
||||
resyncCh, cleanup := r.resyncChan()
|
||||
defer func() {
|
||||
cleanup() // Call the last one written into cleanup
|
||||
}()
|
||||
for {
|
||||
select {
|
||||
case <-resyncCh:
|
||||
case <-stopCh:
|
||||
return
|
||||
case <-cancelCh:
|
||||
return
|
||||
}
|
||||
if r.ShouldResync == nil || r.ShouldResync() {
|
||||
klog.V(4).Infof("%s: forcing resync", r.name)
|
||||
if err := r.store.Resync(); err != nil {
|
||||
resyncerrc <- err
|
||||
return
|
||||
}
|
||||
}
|
||||
cleanup()
|
||||
resyncCh, cleanup = r.resyncChan()
|
||||
}
|
||||
}()
|
||||
|
||||
for {
|
||||
// give the stopCh a chance to stop the loop, even in case of continue statements further down on errors
|
||||
select {
|
||||
case <-stopCh:
|
||||
return nil
|
||||
default:
|
||||
}
|
||||
|
||||
timeoutSeconds := int64(minWatchTimeout.Seconds() * (rand.Float64() + 1.0))
|
||||
options = metav1.ListOptions{
|
||||
ResourceVersion: resourceVersion,
|
||||
// We want to avoid situations of hanging watchers. Stop any watchers that do not
|
||||
// receive any events within the timeout window.
|
||||
TimeoutSeconds: &timeoutSeconds,
|
||||
// To reduce load on kube-apiserver on watch restarts, you may enable watch bookmarks.
|
||||
// Reflector doesn't assume bookmarks are returned at all (if the server do not support
|
||||
// watch bookmarks, it will ignore this field).
|
||||
AllowWatchBookmarks: true,
|
||||
}
|
||||
|
||||
// start the clock before sending the request, since some proxies won't flush headers until after the first watch event is sent
|
||||
start := r.clock.Now()
|
||||
w, err := r.listerWatcher.Watch(options)
|
||||
if err != nil {
|
||||
// If this is "connection refused" error, it means that most likely apiserver is not responsive.
|
||||
// It doesn't make sense to re-list all objects because most likely we will be able to restart
|
||||
// watch where we ended.
|
||||
// If that's the case begin exponentially backing off and resend watch request.
|
||||
// Do the same for "429" errors.
|
||||
if utilnet.IsConnectionRefused(err) || apierrors.IsTooManyRequests(err) {
|
||||
<-r.initConnBackoffManager.Backoff().C()
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
if err := r.watchHandler(start, w, &resourceVersion, resyncerrc, stopCh); err != nil {
|
||||
if err != errorStopRequested {
|
||||
switch {
|
||||
case isExpiredError(err):
|
||||
// Don't set LastSyncResourceVersionUnavailable - LIST call with ResourceVersion=RV already
|
||||
// has a semantic that it returns data at least as fresh as provided RV.
|
||||
// So first try to LIST with setting RV to resource version of last observed object.
|
||||
klog.V(4).Infof("%s: watch of %v closed with: %v", r.name, r.expectedTypeName, err)
|
||||
case apierrors.IsTooManyRequests(err):
|
||||
klog.V(2).Infof("%s: watch of %v returned 429 - backing off", r.name, r.expectedTypeName)
|
||||
<-r.initConnBackoffManager.Backoff().C()
|
||||
continue
|
||||
default:
|
||||
klog.Warningf("%s: watch of %v ended with: %v", r.name, r.expectedTypeName, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// syncWith replaces the store's items with the given list.
|
||||
func (r *Reflector) syncWith(items []runtime.Object, resourceVersion string) error {
|
||||
found := make([]interface{}, 0, len(items))
|
||||
for _, item := range items {
|
||||
found = append(found, item)
|
||||
}
|
||||
return r.store.Replace(found, resourceVersion)
|
||||
}
|
||||
|
||||
// watchHandler watches w and keeps *resourceVersion up to date.
|
||||
func (r *Reflector) watchHandler(start time.Time, w watch.Interface, resourceVersion *string, errc chan error, stopCh <-chan struct{}) error {
|
||||
eventCount := 0
|
||||
|
||||
// Stopping the watcher should be idempotent and if we return from this function there's no way
|
||||
// we're coming back in with the same watch interface.
|
||||
defer w.Stop()
|
||||
|
||||
loop:
|
||||
for {
|
||||
select {
|
||||
case <-stopCh:
|
||||
return errorStopRequested
|
||||
case err := <-errc:
|
||||
return err
|
||||
case event, ok := <-w.ResultChan():
|
||||
if !ok {
|
||||
break loop
|
||||
}
|
||||
if event.Type == watch.Error {
|
||||
return apierrors.FromObject(event.Object)
|
||||
}
|
||||
if r.expectedType != nil {
|
||||
if e, a := r.expectedType, reflect.TypeOf(event.Object); e != a {
|
||||
utilruntime.HandleError(fmt.Errorf("%s: expected type %v, but watch event object had type %v", r.name, e, a))
|
||||
continue
|
||||
}
|
||||
}
|
||||
if r.expectedGVK != nil {
|
||||
if e, a := *r.expectedGVK, event.Object.GetObjectKind().GroupVersionKind(); e != a {
|
||||
utilruntime.HandleError(fmt.Errorf("%s: expected gvk %v, but watch event object had gvk %v", r.name, e, a))
|
||||
continue
|
||||
}
|
||||
}
|
||||
meta, err := meta.Accessor(event.Object)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event))
|
||||
continue
|
||||
}
|
||||
newResourceVersion := meta.GetResourceVersion()
|
||||
switch event.Type {
|
||||
case watch.Added:
|
||||
err := r.store.Add(event.Object)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("%s: unable to add watch event object (%#v) to store: %v", r.name, event.Object, err))
|
||||
}
|
||||
case watch.Modified:
|
||||
err := r.store.Update(event.Object)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("%s: unable to update watch event object (%#v) to store: %v", r.name, event.Object, err))
|
||||
}
|
||||
case watch.Deleted:
|
||||
// TODO: Will any consumers need access to the "last known
|
||||
// state", which is passed in event.Object? If so, may need
|
||||
// to change this.
|
||||
err := r.store.Delete(event.Object)
|
||||
if err != nil {
|
||||
utilruntime.HandleError(fmt.Errorf("%s: unable to delete watch event object (%#v) from store: %v", r.name, event.Object, err))
|
||||
}
|
||||
case watch.Bookmark:
|
||||
// A `Bookmark` means watch has synced here, just update the resourceVersion
|
||||
default:
|
||||
utilruntime.HandleError(fmt.Errorf("%s: unable to understand watch event %#v", r.name, event))
|
||||
}
|
||||
*resourceVersion = newResourceVersion
|
||||
r.setLastSyncResourceVersion(newResourceVersion)
|
||||
if rvu, ok := r.store.(ResourceVersionUpdater); ok {
|
||||
rvu.UpdateResourceVersion(newResourceVersion)
|
||||
}
|
||||
eventCount++
|
||||
}
|
||||
}
|
||||
|
||||
watchDuration := r.clock.Since(start)
|
||||
if watchDuration < 1*time.Second && eventCount == 0 {
|
||||
return fmt.Errorf("very short watch: %s: Unexpected watch close - watch lasted less than a second and no items received", r.name)
|
||||
}
|
||||
klog.V(4).Infof("%s: Watch close - %v total %v items received", r.name, r.expectedTypeName, eventCount)
|
||||
return nil
|
||||
}
|
||||
|
||||
// LastSyncResourceVersion is the resource version observed when last sync with the underlying store
|
||||
// The value returned is not synchronized with access to the underlying store and is not thread-safe
|
||||
func (r *Reflector) LastSyncResourceVersion() string {
|
||||
r.lastSyncResourceVersionMutex.RLock()
|
||||
defer r.lastSyncResourceVersionMutex.RUnlock()
|
||||
return r.lastSyncResourceVersion
|
||||
}
|
||||
|
||||
func (r *Reflector) setLastSyncResourceVersion(v string) {
|
||||
r.lastSyncResourceVersionMutex.Lock()
|
||||
defer r.lastSyncResourceVersionMutex.Unlock()
|
||||
r.lastSyncResourceVersion = v
|
||||
}
|
||||
|
||||
// relistResourceVersion determines the resource version the reflector should list or relist from.
|
||||
// Returns either the lastSyncResourceVersion so that this reflector will relist with a resource
|
||||
// versions no older than has already been observed in relist results or watch events, or, if the last relist resulted
|
||||
// in an HTTP 410 (Gone) status code, returns "" so that the relist will use the latest resource version available in
|
||||
// etcd via a quorum read.
|
||||
func (r *Reflector) relistResourceVersion() string {
|
||||
r.lastSyncResourceVersionMutex.RLock()
|
||||
defer r.lastSyncResourceVersionMutex.RUnlock()
|
||||
|
||||
if r.isLastSyncResourceVersionUnavailable {
|
||||
// Since this reflector makes paginated list requests, and all paginated list requests skip the watch cache
|
||||
// if the lastSyncResourceVersion is unavailable, we set ResourceVersion="" and list again to re-establish reflector
|
||||
// to the latest available ResourceVersion, using a consistent read from etcd.
|
||||
return ""
|
||||
}
|
||||
if r.lastSyncResourceVersion == "" {
|
||||
// For performance reasons, initial list performed by reflector uses "0" as resource version to allow it to
|
||||
// be served from the watch cache if it is enabled.
|
||||
return "0"
|
||||
}
|
||||
return r.lastSyncResourceVersion
|
||||
}
|
||||
|
||||
// setIsLastSyncResourceVersionUnavailable sets if the last list or watch request with lastSyncResourceVersion returned
|
||||
// "expired" or "too large resource version" error.
|
||||
func (r *Reflector) setIsLastSyncResourceVersionUnavailable(isUnavailable bool) {
|
||||
r.lastSyncResourceVersionMutex.Lock()
|
||||
defer r.lastSyncResourceVersionMutex.Unlock()
|
||||
r.isLastSyncResourceVersionUnavailable = isUnavailable
|
||||
}
|
||||
|
||||
func isExpiredError(err error) bool {
|
||||
// In Kubernetes 1.17 and earlier, the api server returns both apierrors.StatusReasonExpired and
|
||||
// apierrors.StatusReasonGone for HTTP 410 (Gone) status code responses. In 1.18 the kube server is more consistent
|
||||
// and always returns apierrors.StatusReasonExpired. For backward compatibility we can only remove the apierrors.IsGone
|
||||
// check when we fully drop support for Kubernetes 1.17 servers from reflectors.
|
||||
return apierrors.IsResourceExpired(err) || apierrors.IsGone(err)
|
||||
}
|
||||
|
||||
func isTooLargeResourceVersionError(err error) bool {
|
||||
if apierrors.HasStatusCause(err, metav1.CauseTypeResourceVersionTooLarge) {
|
||||
return true
|
||||
}
|
||||
// In Kubernetes 1.17.0-1.18.5, the api server doesn't set the error status cause to
|
||||
// metav1.CauseTypeResourceVersionTooLarge to indicate that the requested minimum resource
|
||||
// version is larger than the largest currently available resource version. To ensure backward
|
||||
// compatibility with these server versions we also need to detect the error based on the content
|
||||
// of the error message field.
|
||||
if !apierrors.IsTimeout(err) {
|
||||
return false
|
||||
}
|
||||
apierr, ok := err.(apierrors.APIStatus)
|
||||
if !ok || apierr == nil || apierr.Status().Details == nil {
|
||||
return false
|
||||
}
|
||||
for _, cause := range apierr.Status().Details.Causes {
|
||||
// Matches the message returned by api server 1.17.0-1.18.5 for this error condition
|
||||
if cause.Message == "Too large resource version" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
89
vendor/k8s.io/client-go/tools/cache/reflector_metrics.go
generated
vendored
Normal file
89
vendor/k8s.io/client-go/tools/cache/reflector_metrics.go
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
Copyright 2016 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.
|
||||
*/
|
||||
|
||||
// This file provides abstractions for setting the provider (e.g., prometheus)
|
||||
// of metrics.
|
||||
|
||||
package cache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// GaugeMetric represents a single numerical value that can arbitrarily go up
|
||||
// and down.
|
||||
type GaugeMetric interface {
|
||||
Set(float64)
|
||||
}
|
||||
|
||||
// CounterMetric represents a single numerical value that only ever
|
||||
// goes up.
|
||||
type CounterMetric interface {
|
||||
Inc()
|
||||
}
|
||||
|
||||
// SummaryMetric captures individual observations.
|
||||
type SummaryMetric interface {
|
||||
Observe(float64)
|
||||
}
|
||||
|
||||
type noopMetric struct{}
|
||||
|
||||
func (noopMetric) Inc() {}
|
||||
func (noopMetric) Dec() {}
|
||||
func (noopMetric) Observe(float64) {}
|
||||
func (noopMetric) Set(float64) {}
|
||||
|
||||
// MetricsProvider generates various metrics used by the reflector.
|
||||
type MetricsProvider interface {
|
||||
NewListsMetric(name string) CounterMetric
|
||||
NewListDurationMetric(name string) SummaryMetric
|
||||
NewItemsInListMetric(name string) SummaryMetric
|
||||
|
||||
NewWatchesMetric(name string) CounterMetric
|
||||
NewShortWatchesMetric(name string) CounterMetric
|
||||
NewWatchDurationMetric(name string) SummaryMetric
|
||||
NewItemsInWatchMetric(name string) SummaryMetric
|
||||
|
||||
NewLastResourceVersionMetric(name string) GaugeMetric
|
||||
}
|
||||
|
||||
type noopMetricsProvider struct{}
|
||||
|
||||
func (noopMetricsProvider) NewListsMetric(name string) CounterMetric { return noopMetric{} }
|
||||
func (noopMetricsProvider) NewListDurationMetric(name string) SummaryMetric { return noopMetric{} }
|
||||
func (noopMetricsProvider) NewItemsInListMetric(name string) SummaryMetric { return noopMetric{} }
|
||||
func (noopMetricsProvider) NewWatchesMetric(name string) CounterMetric { return noopMetric{} }
|
||||
func (noopMetricsProvider) NewShortWatchesMetric(name string) CounterMetric { return noopMetric{} }
|
||||
func (noopMetricsProvider) NewWatchDurationMetric(name string) SummaryMetric { return noopMetric{} }
|
||||
func (noopMetricsProvider) NewItemsInWatchMetric(name string) SummaryMetric { return noopMetric{} }
|
||||
func (noopMetricsProvider) NewLastResourceVersionMetric(name string) GaugeMetric {
|
||||
return noopMetric{}
|
||||
}
|
||||
|
||||
var metricsFactory = struct {
|
||||
metricsProvider MetricsProvider
|
||||
setProviders sync.Once
|
||||
}{
|
||||
metricsProvider: noopMetricsProvider{},
|
||||
}
|
||||
|
||||
// SetReflectorMetricsProvider sets the metrics provider
|
||||
func SetReflectorMetricsProvider(metricsProvider MetricsProvider) {
|
||||
metricsFactory.setProviders.Do(func() {
|
||||
metricsFactory.metricsProvider = metricsProvider
|
||||
})
|
||||
}
|
855
vendor/k8s.io/client-go/tools/cache/shared_informer.go
generated
vendored
Normal file
855
vendor/k8s.io/client-go/tools/cache/shared_informer.go
generated
vendored
Normal file
@ -0,0 +1,855 @@
|
||||
/*
|
||||
Copyright 2015 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 (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/util/wait"
|
||||
"k8s.io/utils/buffer"
|
||||
"k8s.io/utils/clock"
|
||||
|
||||
"k8s.io/klog/v2"
|
||||
)
|
||||
|
||||
// SharedInformer provides eventually consistent linkage of its
|
||||
// clients to the authoritative state of a given collection of
|
||||
// objects. An object is identified by its API group, kind/resource,
|
||||
// namespace (if any), and name; the `ObjectMeta.UID` is not part of
|
||||
// an object's ID as far as this contract is concerned. One
|
||||
// SharedInformer provides linkage to objects of a particular API
|
||||
// group and kind/resource. The linked object collection of a
|
||||
// SharedInformer may be further restricted to one namespace (if
|
||||
// applicable) and/or by label selector and/or field selector.
|
||||
//
|
||||
// The authoritative state of an object is what apiservers provide
|
||||
// access to, and an object goes through a strict sequence of states.
|
||||
// An object state is either (1) present with a ResourceVersion and
|
||||
// other appropriate content or (2) "absent".
|
||||
//
|
||||
// A SharedInformer maintains a local cache --- exposed by GetStore(),
|
||||
// by GetIndexer() in the case of an indexed informer, and possibly by
|
||||
// machinery involved in creating and/or accessing the informer --- of
|
||||
// the state of each relevant object. This cache is eventually
|
||||
// consistent with the authoritative state. This means that, unless
|
||||
// prevented by persistent communication problems, if ever a
|
||||
// particular object ID X is authoritatively associated with a state S
|
||||
// then for every SharedInformer I whose collection includes (X, S)
|
||||
// eventually either (1) I's cache associates X with S or a later
|
||||
// state of X, (2) I is stopped, or (3) the authoritative state
|
||||
// service for X terminates. To be formally complete, we say that the
|
||||
// absent state meets any restriction by label selector or field
|
||||
// selector.
|
||||
//
|
||||
// For a given informer and relevant object ID X, the sequence of
|
||||
// states that appears in the informer's cache is a subsequence of the
|
||||
// states authoritatively associated with X. That is, some states
|
||||
// might never appear in the cache but ordering among the appearing
|
||||
// states is correct. Note, however, that there is no promise about
|
||||
// ordering between states seen for different objects.
|
||||
//
|
||||
// The local cache starts out empty, and gets populated and updated
|
||||
// during `Run()`.
|
||||
//
|
||||
// As a simple example, if a collection of objects is henceforth
|
||||
// unchanging, a SharedInformer is created that links to that
|
||||
// collection, and that SharedInformer is `Run()` then that
|
||||
// SharedInformer's cache eventually holds an exact copy of that
|
||||
// collection (unless it is stopped too soon, the authoritative state
|
||||
// service ends, or communication problems between the two
|
||||
// persistently thwart achievement).
|
||||
//
|
||||
// As another simple example, if the local cache ever holds a
|
||||
// non-absent state for some object ID and the object is eventually
|
||||
// removed from the authoritative state then eventually the object is
|
||||
// removed from the local cache (unless the SharedInformer is stopped
|
||||
// too soon, the authoritative state service ends, or communication
|
||||
// problems persistently thwart the desired result).
|
||||
//
|
||||
// The keys in the Store are of the form namespace/name for namespaced
|
||||
// objects, and are simply the name for non-namespaced objects.
|
||||
// Clients can use `MetaNamespaceKeyFunc(obj)` to extract the key for
|
||||
// a given object, and `SplitMetaNamespaceKey(key)` to split a key
|
||||
// into its constituent parts.
|
||||
//
|
||||
// Every query against the local cache is answered entirely from one
|
||||
// snapshot of the cache's state. Thus, the result of a `List` call
|
||||
// will not contain two entries with the same namespace and name.
|
||||
//
|
||||
// A client is identified here by a ResourceEventHandler. For every
|
||||
// update to the SharedInformer's local cache and for every client
|
||||
// added before `Run()`, eventually either the SharedInformer is
|
||||
// stopped or the client is notified of the update. A client added
|
||||
// after `Run()` starts gets a startup batch of notifications of
|
||||
// additions of the objects existing in the cache at the time that
|
||||
// client was added; also, for every update to the SharedInformer's
|
||||
// local cache after that client was added, eventually either the
|
||||
// SharedInformer is stopped or that client is notified of that
|
||||
// update. Client notifications happen after the corresponding cache
|
||||
// update and, in the case of a SharedIndexInformer, after the
|
||||
// corresponding index updates. It is possible that additional cache
|
||||
// and index updates happen before such a prescribed notification.
|
||||
// For a given SharedInformer and client, the notifications are
|
||||
// delivered sequentially. For a given SharedInformer, client, and
|
||||
// object ID, the notifications are delivered in order. Because
|
||||
// `ObjectMeta.UID` has no role in identifying objects, it is possible
|
||||
// that when (1) object O1 with ID (e.g. namespace and name) X and
|
||||
// `ObjectMeta.UID` U1 in the SharedInformer's local cache is deleted
|
||||
// and later (2) another object O2 with ID X and ObjectMeta.UID U2 is
|
||||
// created the informer's clients are not notified of (1) and (2) but
|
||||
// rather are notified only of an update from O1 to O2. Clients that
|
||||
// need to detect such cases might do so by comparing the `ObjectMeta.UID`
|
||||
// field of the old and the new object in the code that handles update
|
||||
// notifications (i.e. `OnUpdate` method of ResourceEventHandler).
|
||||
//
|
||||
// A client must process each notification promptly; a SharedInformer
|
||||
// is not engineered to deal well with a large backlog of
|
||||
// notifications to deliver. Lengthy processing should be passed off
|
||||
// to something else, for example through a
|
||||
// `client-go/util/workqueue`.
|
||||
//
|
||||
// A delete notification exposes the last locally known non-absent
|
||||
// state, except that its ResourceVersion is replaced with a
|
||||
// ResourceVersion in which the object is actually absent.
|
||||
type SharedInformer 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.
|
||||
AddEventHandler(handler ResourceEventHandler)
|
||||
// AddEventHandlerWithResyncPeriod adds an event handler to the
|
||||
// shared informer with the requested resync period; zero means
|
||||
// this handler does not care about resyncs. The resync operation
|
||||
// consists of delivering to the handler an update notification
|
||||
// for every object in the informer's local cache; it does not add
|
||||
// any interactions with the authoritative storage. Some
|
||||
// informers do no resyncs at all, not even for handlers added
|
||||
// with a non-zero resyncPeriod. For an informer that does
|
||||
// resyncs, and for each handler that requests resyncs, that
|
||||
// informer develops a nominal resync period that is no shorter
|
||||
// than the requested period but may be longer. The actual time
|
||||
// between any two resyncs may be longer than the nominal period
|
||||
// because the implementation takes time to do work and there may
|
||||
// be competing load and scheduling noise.
|
||||
AddEventHandlerWithResyncPeriod(handler ResourceEventHandler, resyncPeriod time.Duration)
|
||||
// GetStore returns the informer's local cache as a Store.
|
||||
GetStore() Store
|
||||
// GetController is deprecated, it does nothing useful
|
||||
GetController() Controller
|
||||
// Run starts and runs the shared informer, returning after it stops.
|
||||
// The informer will be stopped when stopCh is closed.
|
||||
Run(stopCh <-chan struct{})
|
||||
// HasSynced returns true if the shared informer's store has been
|
||||
// informed by at least one full LIST of the authoritative state
|
||||
// of the informer's object collection. This is unrelated to "resync".
|
||||
HasSynced() bool
|
||||
// LastSyncResourceVersion is the resource version observed when last synced with the underlying
|
||||
// store. The value returned is not synchronized with access to the underlying store and is not
|
||||
// thread-safe.
|
||||
LastSyncResourceVersion() string
|
||||
|
||||
// The WatchErrorHandler is called whenever ListAndWatch drops the
|
||||
// connection with an error. After calling this handler, the informer
|
||||
// will backoff and retry.
|
||||
//
|
||||
// The default implementation looks at the error type and tries to log
|
||||
// the error message at an appropriate level.
|
||||
//
|
||||
// There's only one handler, so if you call this multiple times, last one
|
||||
// wins; calling after the informer has been started returns an error.
|
||||
//
|
||||
// The handler is intended for visibility, not to e.g. pause the consumers.
|
||||
// The handler should return quickly - any expensive processing should be
|
||||
// offloaded.
|
||||
SetWatchErrorHandler(handler WatchErrorHandler) error
|
||||
|
||||
// The TransformFunc is called for each object which is about to be stored.
|
||||
//
|
||||
// This function is intended for you to take the opportunity to
|
||||
// remove, transform, or normalize fields. One use case is to strip unused
|
||||
// metadata fields out of objects to save on RAM cost.
|
||||
//
|
||||
// Must be set before starting the informer.
|
||||
//
|
||||
// Note: Since the object given to the handler may be already shared with
|
||||
// other goroutines, it is advisable to copy the object being
|
||||
// transform before mutating it at all and returning the copy to prevent
|
||||
// data races.
|
||||
SetTransform(handler TransformFunc) error
|
||||
}
|
||||
|
||||
// SharedIndexInformer provides add and get Indexers ability based on SharedInformer.
|
||||
type SharedIndexInformer interface {
|
||||
SharedInformer
|
||||
// AddIndexers add indexers to the informer before it starts.
|
||||
AddIndexers(indexers Indexers) error
|
||||
GetIndexer() Indexer
|
||||
}
|
||||
|
||||
// NewSharedInformer creates a new instance for the listwatcher.
|
||||
func NewSharedInformer(lw ListerWatcher, exampleObject runtime.Object, defaultEventHandlerResyncPeriod time.Duration) SharedInformer {
|
||||
return NewSharedIndexInformer(lw, exampleObject, defaultEventHandlerResyncPeriod, Indexers{})
|
||||
}
|
||||
|
||||
// NewSharedIndexInformer creates a new instance for the listwatcher.
|
||||
// The created informer will not do resyncs if the given
|
||||
// defaultEventHandlerResyncPeriod is zero. Otherwise: for each
|
||||
// handler that with a non-zero requested resync period, whether added
|
||||
// before or after the informer starts, the nominal resync period is
|
||||
// the requested resync period rounded up to a multiple of the
|
||||
// informer's resync checking period. Such an informer's resync
|
||||
// checking period is established when the informer starts running,
|
||||
// and is the maximum of (a) the minimum of the resync periods
|
||||
// requested before the informer starts and the
|
||||
// defaultEventHandlerResyncPeriod given here and (b) the constant
|
||||
// `minimumResyncPeriod` defined in this file.
|
||||
func NewSharedIndexInformer(lw ListerWatcher, exampleObject runtime.Object, defaultEventHandlerResyncPeriod time.Duration, indexers Indexers) SharedIndexInformer {
|
||||
realClock := &clock.RealClock{}
|
||||
sharedIndexInformer := &sharedIndexInformer{
|
||||
processor: &sharedProcessor{clock: realClock},
|
||||
indexer: NewIndexer(DeletionHandlingMetaNamespaceKeyFunc, indexers),
|
||||
listerWatcher: lw,
|
||||
objectType: exampleObject,
|
||||
resyncCheckPeriod: defaultEventHandlerResyncPeriod,
|
||||
defaultEventHandlerResyncPeriod: defaultEventHandlerResyncPeriod,
|
||||
cacheMutationDetector: NewCacheMutationDetector(fmt.Sprintf("%T", exampleObject)),
|
||||
clock: realClock,
|
||||
}
|
||||
return sharedIndexInformer
|
||||
}
|
||||
|
||||
// InformerSynced is a function that can be used to determine if an informer has synced. This is useful for determining if caches have synced.
|
||||
type InformerSynced func() bool
|
||||
|
||||
const (
|
||||
// syncedPollPeriod controls how often you look at the status of your sync funcs
|
||||
syncedPollPeriod = 100 * time.Millisecond
|
||||
|
||||
// initialBufferSize is the initial number of event notifications that can be buffered.
|
||||
initialBufferSize = 1024
|
||||
)
|
||||
|
||||
// WaitForNamedCacheSync is a wrapper around WaitForCacheSync that generates log messages
|
||||
// indicating that the caller identified by name is waiting for syncs, followed by
|
||||
// either a successful or failed sync.
|
||||
func WaitForNamedCacheSync(controllerName string, stopCh <-chan struct{}, cacheSyncs ...InformerSynced) bool {
|
||||
klog.Infof("Waiting for caches to sync for %s", controllerName)
|
||||
|
||||
if !WaitForCacheSync(stopCh, cacheSyncs...) {
|
||||
utilruntime.HandleError(fmt.Errorf("unable to sync caches for %s", controllerName))
|
||||
return false
|
||||
}
|
||||
|
||||
klog.Infof("Caches are synced for %s", controllerName)
|
||||
return true
|
||||
}
|
||||
|
||||
// WaitForCacheSync waits for caches to populate. It returns true if it was successful, false
|
||||
// if the controller should shutdown
|
||||
// callers should prefer WaitForNamedCacheSync()
|
||||
func WaitForCacheSync(stopCh <-chan struct{}, cacheSyncs ...InformerSynced) bool {
|
||||
err := wait.PollImmediateUntil(syncedPollPeriod,
|
||||
func() (bool, error) {
|
||||
for _, syncFunc := range cacheSyncs {
|
||||
if !syncFunc() {
|
||||
return false, nil
|
||||
}
|
||||
}
|
||||
return true, nil
|
||||
},
|
||||
stopCh)
|
||||
if err != nil {
|
||||
klog.V(2).Infof("stop requested")
|
||||
return false
|
||||
}
|
||||
|
||||
klog.V(4).Infof("caches populated")
|
||||
return true
|
||||
}
|
||||
|
||||
// `*sharedIndexInformer` implements SharedIndexInformer and has three
|
||||
// main components. One is an indexed local cache, `indexer Indexer`.
|
||||
// The second main component is a Controller that pulls
|
||||
// objects/notifications using the ListerWatcher and pushes them into
|
||||
// a DeltaFIFO --- whose knownObjects is the informer's local cache
|
||||
// --- while concurrently Popping Deltas values from that fifo and
|
||||
// processing them with `sharedIndexInformer::HandleDeltas`. Each
|
||||
// invocation of HandleDeltas, which is done with the fifo's lock
|
||||
// held, processes each Delta in turn. For each Delta this both
|
||||
// updates the local cache and stuffs the relevant notification into
|
||||
// the sharedProcessor. The third main component is that
|
||||
// sharedProcessor, which is responsible for relaying those
|
||||
// notifications to each of the informer's clients.
|
||||
type sharedIndexInformer struct {
|
||||
indexer Indexer
|
||||
controller Controller
|
||||
|
||||
processor *sharedProcessor
|
||||
cacheMutationDetector MutationDetector
|
||||
|
||||
listerWatcher ListerWatcher
|
||||
|
||||
// objectType is an example object of the type this informer is
|
||||
// expected to handle. Only the type needs to be right, except
|
||||
// that when that is `unstructured.Unstructured` the object's
|
||||
// `"apiVersion"` and `"kind"` must also be right.
|
||||
objectType runtime.Object
|
||||
|
||||
// resyncCheckPeriod is how often we want the reflector's resync timer to fire so it can call
|
||||
// shouldResync to check if any of our listeners need a resync.
|
||||
resyncCheckPeriod time.Duration
|
||||
// defaultEventHandlerResyncPeriod is the default resync period for any handlers added via
|
||||
// AddEventHandler (i.e. they don't specify one and just want to use the shared informer's default
|
||||
// value).
|
||||
defaultEventHandlerResyncPeriod time.Duration
|
||||
// clock allows for testability
|
||||
clock clock.Clock
|
||||
|
||||
started, stopped bool
|
||||
startedLock sync.Mutex
|
||||
|
||||
// blockDeltas gives a way to stop all event distribution so that a late event handler
|
||||
// can safely join the shared informer.
|
||||
blockDeltas sync.Mutex
|
||||
|
||||
// Called whenever the ListAndWatch drops the connection with an error.
|
||||
watchErrorHandler WatchErrorHandler
|
||||
|
||||
transform TransformFunc
|
||||
}
|
||||
|
||||
// dummyController hides the fact that a SharedInformer is different from a dedicated one
|
||||
// where a caller can `Run`. The run method is disconnected in this case, because higher
|
||||
// level logic will decide when to start the SharedInformer and related controller.
|
||||
// Because returning information back is always asynchronous, the legacy callers shouldn't
|
||||
// notice any change in behavior.
|
||||
type dummyController struct {
|
||||
informer *sharedIndexInformer
|
||||
}
|
||||
|
||||
func (v *dummyController) Run(stopCh <-chan struct{}) {
|
||||
}
|
||||
|
||||
func (v *dummyController) HasSynced() bool {
|
||||
return v.informer.HasSynced()
|
||||
}
|
||||
|
||||
func (v *dummyController) LastSyncResourceVersion() string {
|
||||
return ""
|
||||
}
|
||||
|
||||
type updateNotification struct {
|
||||
oldObj interface{}
|
||||
newObj interface{}
|
||||
}
|
||||
|
||||
type addNotification struct {
|
||||
newObj interface{}
|
||||
}
|
||||
|
||||
type deleteNotification struct {
|
||||
oldObj interface{}
|
||||
}
|
||||
|
||||
func (s *sharedIndexInformer) SetWatchErrorHandler(handler WatchErrorHandler) error {
|
||||
s.startedLock.Lock()
|
||||
defer s.startedLock.Unlock()
|
||||
|
||||
if s.started {
|
||||
return fmt.Errorf("informer has already started")
|
||||
}
|
||||
|
||||
s.watchErrorHandler = handler
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sharedIndexInformer) SetTransform(handler TransformFunc) error {
|
||||
s.startedLock.Lock()
|
||||
defer s.startedLock.Unlock()
|
||||
|
||||
if s.started {
|
||||
return fmt.Errorf("informer has already started")
|
||||
}
|
||||
|
||||
s.transform = handler
|
||||
return nil
|
||||
}
|
||||
|
||||
func (s *sharedIndexInformer) Run(stopCh <-chan struct{}) {
|
||||
defer utilruntime.HandleCrash()
|
||||
|
||||
if s.HasStarted() {
|
||||
klog.Warningf("The sharedIndexInformer has started, run more than once is not allowed")
|
||||
return
|
||||
}
|
||||
fifo := NewDeltaFIFOWithOptions(DeltaFIFOOptions{
|
||||
KnownObjects: s.indexer,
|
||||
EmitDeltaTypeReplaced: true,
|
||||
})
|
||||
|
||||
cfg := &Config{
|
||||
Queue: fifo,
|
||||
ListerWatcher: s.listerWatcher,
|
||||
ObjectType: s.objectType,
|
||||
FullResyncPeriod: s.resyncCheckPeriod,
|
||||
RetryOnError: false,
|
||||
ShouldResync: s.processor.shouldResync,
|
||||
|
||||
Process: s.HandleDeltas,
|
||||
WatchErrorHandler: s.watchErrorHandler,
|
||||
}
|
||||
|
||||
func() {
|
||||
s.startedLock.Lock()
|
||||
defer s.startedLock.Unlock()
|
||||
|
||||
s.controller = New(cfg)
|
||||
s.controller.(*controller).clock = s.clock
|
||||
s.started = true
|
||||
}()
|
||||
|
||||
// Separate stop channel because Processor should be stopped strictly after controller
|
||||
processorStopCh := make(chan struct{})
|
||||
var wg wait.Group
|
||||
defer wg.Wait() // Wait for Processor to stop
|
||||
defer close(processorStopCh) // Tell Processor to stop
|
||||
wg.StartWithChannel(processorStopCh, s.cacheMutationDetector.Run)
|
||||
wg.StartWithChannel(processorStopCh, s.processor.run)
|
||||
|
||||
defer func() {
|
||||
s.startedLock.Lock()
|
||||
defer s.startedLock.Unlock()
|
||||
s.stopped = true // Don't want any new listeners
|
||||
}()
|
||||
s.controller.Run(stopCh)
|
||||
}
|
||||
|
||||
func (s *sharedIndexInformer) HasStarted() bool {
|
||||
s.startedLock.Lock()
|
||||
defer s.startedLock.Unlock()
|
||||
return s.started
|
||||
}
|
||||
|
||||
func (s *sharedIndexInformer) HasSynced() bool {
|
||||
s.startedLock.Lock()
|
||||
defer s.startedLock.Unlock()
|
||||
|
||||
if s.controller == nil {
|
||||
return false
|
||||
}
|
||||
return s.controller.HasSynced()
|
||||
}
|
||||
|
||||
func (s *sharedIndexInformer) LastSyncResourceVersion() string {
|
||||
s.startedLock.Lock()
|
||||
defer s.startedLock.Unlock()
|
||||
|
||||
if s.controller == nil {
|
||||
return ""
|
||||
}
|
||||
return s.controller.LastSyncResourceVersion()
|
||||
}
|
||||
|
||||
func (s *sharedIndexInformer) GetStore() Store {
|
||||
return s.indexer
|
||||
}
|
||||
|
||||
func (s *sharedIndexInformer) GetIndexer() Indexer {
|
||||
return s.indexer
|
||||
}
|
||||
|
||||
func (s *sharedIndexInformer) AddIndexers(indexers Indexers) error {
|
||||
s.startedLock.Lock()
|
||||
defer s.startedLock.Unlock()
|
||||
|
||||
if s.started {
|
||||
return fmt.Errorf("informer has already started")
|
||||
}
|
||||
|
||||
return s.indexer.AddIndexers(indexers)
|
||||
}
|
||||
|
||||
func (s *sharedIndexInformer) GetController() Controller {
|
||||
return &dummyController{informer: s}
|
||||
}
|
||||
|
||||
func (s *sharedIndexInformer) AddEventHandler(handler ResourceEventHandler) {
|
||||
s.AddEventHandlerWithResyncPeriod(handler, s.defaultEventHandlerResyncPeriod)
|
||||
}
|
||||
|
||||
func determineResyncPeriod(desired, check time.Duration) time.Duration {
|
||||
if desired == 0 {
|
||||
return desired
|
||||
}
|
||||
if check == 0 {
|
||||
klog.Warningf("The specified resyncPeriod %v is invalid because this shared informer doesn't support resyncing", desired)
|
||||
return 0
|
||||
}
|
||||
if desired < check {
|
||||
klog.Warningf("The specified resyncPeriod %v is being increased to the minimum resyncCheckPeriod %v", desired, check)
|
||||
return check
|
||||
}
|
||||
return desired
|
||||
}
|
||||
|
||||
const minimumResyncPeriod = 1 * time.Second
|
||||
|
||||
func (s *sharedIndexInformer) AddEventHandlerWithResyncPeriod(handler ResourceEventHandler, resyncPeriod time.Duration) {
|
||||
s.startedLock.Lock()
|
||||
defer s.startedLock.Unlock()
|
||||
|
||||
if s.stopped {
|
||||
klog.V(2).Infof("Handler %v was not added to shared informer because it has stopped already", handler)
|
||||
return
|
||||
}
|
||||
|
||||
if resyncPeriod > 0 {
|
||||
if resyncPeriod < minimumResyncPeriod {
|
||||
klog.Warningf("resyncPeriod %v is too small. Changing it to the minimum allowed value of %v", resyncPeriod, minimumResyncPeriod)
|
||||
resyncPeriod = minimumResyncPeriod
|
||||
}
|
||||
|
||||
if resyncPeriod < s.resyncCheckPeriod {
|
||||
if s.started {
|
||||
klog.Warningf("resyncPeriod %v is smaller than resyncCheckPeriod %v and the informer has already started. Changing it to %v", resyncPeriod, s.resyncCheckPeriod, s.resyncCheckPeriod)
|
||||
resyncPeriod = s.resyncCheckPeriod
|
||||
} else {
|
||||
// if the event handler's resyncPeriod is smaller than the current resyncCheckPeriod, update
|
||||
// resyncCheckPeriod to match resyncPeriod and adjust the resync periods of all the listeners
|
||||
// accordingly
|
||||
s.resyncCheckPeriod = resyncPeriod
|
||||
s.processor.resyncCheckPeriodChanged(resyncPeriod)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
listener := newProcessListener(handler, resyncPeriod, determineResyncPeriod(resyncPeriod, s.resyncCheckPeriod), s.clock.Now(), initialBufferSize)
|
||||
|
||||
if !s.started {
|
||||
s.processor.addListener(listener)
|
||||
return
|
||||
}
|
||||
|
||||
// in order to safely join, we have to
|
||||
// 1. stop sending add/update/delete notifications
|
||||
// 2. do a list against the store
|
||||
// 3. send synthetic "Add" events to the new handler
|
||||
// 4. unblock
|
||||
s.blockDeltas.Lock()
|
||||
defer s.blockDeltas.Unlock()
|
||||
|
||||
s.processor.addListener(listener)
|
||||
for _, item := range s.indexer.List() {
|
||||
listener.add(addNotification{newObj: item})
|
||||
}
|
||||
}
|
||||
|
||||
func (s *sharedIndexInformer) HandleDeltas(obj interface{}) error {
|
||||
s.blockDeltas.Lock()
|
||||
defer s.blockDeltas.Unlock()
|
||||
|
||||
if deltas, ok := obj.(Deltas); ok {
|
||||
return processDeltas(s, s.indexer, s.transform, deltas)
|
||||
}
|
||||
return errors.New("object given as Process argument is not Deltas")
|
||||
}
|
||||
|
||||
// Conforms to ResourceEventHandler
|
||||
func (s *sharedIndexInformer) OnAdd(obj interface{}) {
|
||||
// Invocation of this function is locked under s.blockDeltas, so it is
|
||||
// save to distribute the notification
|
||||
s.cacheMutationDetector.AddObject(obj)
|
||||
s.processor.distribute(addNotification{newObj: obj}, false)
|
||||
}
|
||||
|
||||
// Conforms to ResourceEventHandler
|
||||
func (s *sharedIndexInformer) OnUpdate(old, new interface{}) {
|
||||
isSync := false
|
||||
|
||||
// If is a Sync event, isSync should be true
|
||||
// If is a Replaced event, isSync is true if resource version is unchanged.
|
||||
// If RV is unchanged: this is a Sync/Replaced event, so isSync is true
|
||||
|
||||
if accessor, err := meta.Accessor(new); err == nil {
|
||||
if oldAccessor, err := meta.Accessor(old); err == nil {
|
||||
// Events that didn't change resourceVersion are treated as resync events
|
||||
// and only propagated to listeners that requested resync
|
||||
isSync = accessor.GetResourceVersion() == oldAccessor.GetResourceVersion()
|
||||
}
|
||||
}
|
||||
|
||||
// Invocation of this function is locked under s.blockDeltas, so it is
|
||||
// save to distribute the notification
|
||||
s.cacheMutationDetector.AddObject(new)
|
||||
s.processor.distribute(updateNotification{oldObj: old, newObj: new}, isSync)
|
||||
}
|
||||
|
||||
// Conforms to ResourceEventHandler
|
||||
func (s *sharedIndexInformer) OnDelete(old interface{}) {
|
||||
// Invocation of this function is locked under s.blockDeltas, so it is
|
||||
// save to distribute the notification
|
||||
s.processor.distribute(deleteNotification{oldObj: old}, false)
|
||||
}
|
||||
|
||||
// sharedProcessor has a collection of processorListener and can
|
||||
// distribute a notification object to its listeners. There are two
|
||||
// kinds of distribute operations. The sync distributions go to a
|
||||
// subset of the listeners that (a) is recomputed in the occasional
|
||||
// calls to shouldResync and (b) every listener is initially put in.
|
||||
// The non-sync distributions go to every listener.
|
||||
type sharedProcessor struct {
|
||||
listenersStarted bool
|
||||
listenersLock sync.RWMutex
|
||||
listeners []*processorListener
|
||||
syncingListeners []*processorListener
|
||||
clock clock.Clock
|
||||
wg wait.Group
|
||||
}
|
||||
|
||||
func (p *sharedProcessor) addListener(listener *processorListener) {
|
||||
p.listenersLock.Lock()
|
||||
defer p.listenersLock.Unlock()
|
||||
|
||||
p.addListenerLocked(listener)
|
||||
if p.listenersStarted {
|
||||
p.wg.Start(listener.run)
|
||||
p.wg.Start(listener.pop)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *sharedProcessor) addListenerLocked(listener *processorListener) {
|
||||
p.listeners = append(p.listeners, listener)
|
||||
p.syncingListeners = append(p.syncingListeners, listener)
|
||||
}
|
||||
|
||||
func (p *sharedProcessor) distribute(obj interface{}, sync bool) {
|
||||
p.listenersLock.RLock()
|
||||
defer p.listenersLock.RUnlock()
|
||||
|
||||
if sync {
|
||||
for _, listener := range p.syncingListeners {
|
||||
listener.add(obj)
|
||||
}
|
||||
} else {
|
||||
for _, listener := range p.listeners {
|
||||
listener.add(obj)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *sharedProcessor) run(stopCh <-chan struct{}) {
|
||||
func() {
|
||||
p.listenersLock.RLock()
|
||||
defer p.listenersLock.RUnlock()
|
||||
for _, listener := range p.listeners {
|
||||
p.wg.Start(listener.run)
|
||||
p.wg.Start(listener.pop)
|
||||
}
|
||||
p.listenersStarted = true
|
||||
}()
|
||||
<-stopCh
|
||||
p.listenersLock.RLock()
|
||||
defer p.listenersLock.RUnlock()
|
||||
for _, listener := range p.listeners {
|
||||
close(listener.addCh) // Tell .pop() to stop. .pop() will tell .run() to stop
|
||||
}
|
||||
p.wg.Wait() // Wait for all .pop() and .run() to stop
|
||||
}
|
||||
|
||||
// shouldResync queries every listener to determine if any of them need a resync, based on each
|
||||
// listener's resyncPeriod.
|
||||
func (p *sharedProcessor) shouldResync() bool {
|
||||
p.listenersLock.Lock()
|
||||
defer p.listenersLock.Unlock()
|
||||
|
||||
p.syncingListeners = []*processorListener{}
|
||||
|
||||
resyncNeeded := false
|
||||
now := p.clock.Now()
|
||||
for _, listener := range p.listeners {
|
||||
// need to loop through all the listeners to see if they need to resync so we can prepare any
|
||||
// listeners that are going to be resyncing.
|
||||
if listener.shouldResync(now) {
|
||||
resyncNeeded = true
|
||||
p.syncingListeners = append(p.syncingListeners, listener)
|
||||
listener.determineNextResync(now)
|
||||
}
|
||||
}
|
||||
return resyncNeeded
|
||||
}
|
||||
|
||||
func (p *sharedProcessor) resyncCheckPeriodChanged(resyncCheckPeriod time.Duration) {
|
||||
p.listenersLock.RLock()
|
||||
defer p.listenersLock.RUnlock()
|
||||
|
||||
for _, listener := range p.listeners {
|
||||
resyncPeriod := determineResyncPeriod(listener.requestedResyncPeriod, resyncCheckPeriod)
|
||||
listener.setResyncPeriod(resyncPeriod)
|
||||
}
|
||||
}
|
||||
|
||||
// processorListener relays notifications from a sharedProcessor to
|
||||
// one ResourceEventHandler --- using two goroutines, two unbuffered
|
||||
// channels, and an unbounded ring buffer. The `add(notification)`
|
||||
// function sends the given notification to `addCh`. One goroutine
|
||||
// runs `pop()`, which pumps notifications from `addCh` to `nextCh`
|
||||
// using storage in the ring buffer while `nextCh` is not keeping up.
|
||||
// Another goroutine runs `run()`, which receives notifications from
|
||||
// `nextCh` and synchronously invokes the appropriate handler method.
|
||||
//
|
||||
// processorListener also keeps track of the adjusted requested resync
|
||||
// period of the listener.
|
||||
type processorListener struct {
|
||||
nextCh chan interface{}
|
||||
addCh chan interface{}
|
||||
|
||||
handler ResourceEventHandler
|
||||
|
||||
// pendingNotifications is an unbounded ring buffer that holds all notifications not yet distributed.
|
||||
// There is one per listener, but a failing/stalled listener will have infinite pendingNotifications
|
||||
// added until we OOM.
|
||||
// TODO: This is no worse than before, since reflectors were backed by unbounded DeltaFIFOs, but
|
||||
// we should try to do something better.
|
||||
pendingNotifications buffer.RingGrowing
|
||||
|
||||
// requestedResyncPeriod is how frequently the listener wants a
|
||||
// full resync from the shared informer, but modified by two
|
||||
// adjustments. One is imposing a lower bound,
|
||||
// `minimumResyncPeriod`. The other is another lower bound, the
|
||||
// sharedIndexInformer's `resyncCheckPeriod`, that is imposed (a) only
|
||||
// in AddEventHandlerWithResyncPeriod invocations made after the
|
||||
// sharedIndexInformer starts and (b) only if the informer does
|
||||
// resyncs at all.
|
||||
requestedResyncPeriod time.Duration
|
||||
// resyncPeriod is the threshold that will be used in the logic
|
||||
// for this listener. This value differs from
|
||||
// requestedResyncPeriod only when the sharedIndexInformer does
|
||||
// not do resyncs, in which case the value here is zero. The
|
||||
// actual time between resyncs depends on when the
|
||||
// sharedProcessor's `shouldResync` function is invoked and when
|
||||
// the sharedIndexInformer processes `Sync` type Delta objects.
|
||||
resyncPeriod time.Duration
|
||||
// nextResync is the earliest time the listener should get a full resync
|
||||
nextResync time.Time
|
||||
// resyncLock guards access to resyncPeriod and nextResync
|
||||
resyncLock sync.Mutex
|
||||
}
|
||||
|
||||
func newProcessListener(handler ResourceEventHandler, requestedResyncPeriod, resyncPeriod time.Duration, now time.Time, bufferSize int) *processorListener {
|
||||
ret := &processorListener{
|
||||
nextCh: make(chan interface{}),
|
||||
addCh: make(chan interface{}),
|
||||
handler: handler,
|
||||
pendingNotifications: *buffer.NewRingGrowing(bufferSize),
|
||||
requestedResyncPeriod: requestedResyncPeriod,
|
||||
resyncPeriod: resyncPeriod,
|
||||
}
|
||||
|
||||
ret.determineNextResync(now)
|
||||
|
||||
return ret
|
||||
}
|
||||
|
||||
func (p *processorListener) add(notification interface{}) {
|
||||
p.addCh <- notification
|
||||
}
|
||||
|
||||
func (p *processorListener) pop() {
|
||||
defer utilruntime.HandleCrash()
|
||||
defer close(p.nextCh) // Tell .run() to stop
|
||||
|
||||
var nextCh chan<- interface{}
|
||||
var notification interface{}
|
||||
for {
|
||||
select {
|
||||
case nextCh <- notification:
|
||||
// Notification dispatched
|
||||
var ok bool
|
||||
notification, ok = p.pendingNotifications.ReadOne()
|
||||
if !ok { // Nothing to pop
|
||||
nextCh = nil // Disable this select case
|
||||
}
|
||||
case notificationToAdd, ok := <-p.addCh:
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
if notification == nil { // No notification to pop (and pendingNotifications is empty)
|
||||
// Optimize the case - skip adding to pendingNotifications
|
||||
notification = notificationToAdd
|
||||
nextCh = p.nextCh
|
||||
} else { // There is already a notification waiting to be dispatched
|
||||
p.pendingNotifications.WriteOne(notificationToAdd)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (p *processorListener) run() {
|
||||
// this call blocks until the channel is closed. When a panic happens during the notification
|
||||
// we will catch it, **the offending item will be skipped!**, and after a short delay (one second)
|
||||
// the next notification will be attempted. This is usually better than the alternative of never
|
||||
// delivering again.
|
||||
stopCh := make(chan struct{})
|
||||
wait.Until(func() {
|
||||
for next := range p.nextCh {
|
||||
switch notification := next.(type) {
|
||||
case updateNotification:
|
||||
p.handler.OnUpdate(notification.oldObj, notification.newObj)
|
||||
case addNotification:
|
||||
p.handler.OnAdd(notification.newObj)
|
||||
case deleteNotification:
|
||||
p.handler.OnDelete(notification.oldObj)
|
||||
default:
|
||||
utilruntime.HandleError(fmt.Errorf("unrecognized notification: %T", next))
|
||||
}
|
||||
}
|
||||
// the only way to get here is if the p.nextCh is empty and closed
|
||||
close(stopCh)
|
||||
}, 1*time.Second, stopCh)
|
||||
}
|
||||
|
||||
// shouldResync deterimines if the listener needs a resync. If the listener's resyncPeriod is 0,
|
||||
// this always returns false.
|
||||
func (p *processorListener) shouldResync(now time.Time) bool {
|
||||
p.resyncLock.Lock()
|
||||
defer p.resyncLock.Unlock()
|
||||
|
||||
if p.resyncPeriod == 0 {
|
||||
return false
|
||||
}
|
||||
|
||||
return now.After(p.nextResync) || now.Equal(p.nextResync)
|
||||
}
|
||||
|
||||
func (p *processorListener) determineNextResync(now time.Time) {
|
||||
p.resyncLock.Lock()
|
||||
defer p.resyncLock.Unlock()
|
||||
|
||||
p.nextResync = now.Add(p.resyncPeriod)
|
||||
}
|
||||
|
||||
func (p *processorListener) setResyncPeriod(resyncPeriod time.Duration) {
|
||||
p.resyncLock.Lock()
|
||||
defer p.resyncLock.Unlock()
|
||||
|
||||
p.resyncPeriod = resyncPeriod
|
||||
}
|
271
vendor/k8s.io/client-go/tools/cache/store.go
generated
vendored
Normal file
271
vendor/k8s.io/client-go/tools/cache/store.go
generated
vendored
Normal file
@ -0,0 +1,271 @@
|
||||
/*
|
||||
Copyright 2014 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 (
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
)
|
||||
|
||||
// Store is a generic object storage and processing interface. A
|
||||
// Store holds a map from string keys to accumulators, and has
|
||||
// operations to add, update, and delete a given object to/from the
|
||||
// accumulator currently associated with a given key. A Store also
|
||||
// knows how to extract the key from a given object, so many operations
|
||||
// are given only the object.
|
||||
//
|
||||
// In the simplest Store implementations each accumulator is simply
|
||||
// the last given object, or empty after Delete, and thus the Store's
|
||||
// behavior is simple storage.
|
||||
//
|
||||
// Reflector knows how to watch a server and update a Store. This
|
||||
// package provides a variety of implementations of Store.
|
||||
type Store interface {
|
||||
|
||||
// Add adds the given object to the accumulator associated with the given object's key
|
||||
Add(obj interface{}) error
|
||||
|
||||
// Update updates the given object in the accumulator associated with the given object's key
|
||||
Update(obj interface{}) error
|
||||
|
||||
// Delete deletes the given object from the accumulator associated with the given object's key
|
||||
Delete(obj interface{}) error
|
||||
|
||||
// List returns a list of all the currently non-empty accumulators
|
||||
List() []interface{}
|
||||
|
||||
// ListKeys returns a list of all the keys currently associated with non-empty accumulators
|
||||
ListKeys() []string
|
||||
|
||||
// Get returns the accumulator associated with the given object's key
|
||||
Get(obj interface{}) (item interface{}, exists bool, err error)
|
||||
|
||||
// GetByKey returns the accumulator associated with the given key
|
||||
GetByKey(key string) (item interface{}, exists bool, err error)
|
||||
|
||||
// Replace will delete the contents of the store, using instead the
|
||||
// given list. Store takes ownership of the list, you should not reference
|
||||
// it after calling this function.
|
||||
Replace([]interface{}, string) error
|
||||
|
||||
// Resync is meaningless in the terms appearing here but has
|
||||
// meaning in some implementations that have non-trivial
|
||||
// additional behavior (e.g., DeltaFIFO).
|
||||
Resync() error
|
||||
}
|
||||
|
||||
// KeyFunc knows how to make a key from an object. Implementations should be deterministic.
|
||||
type KeyFunc func(obj interface{}) (string, error)
|
||||
|
||||
// KeyError will be returned any time a KeyFunc gives an error; it includes the object
|
||||
// at fault.
|
||||
type KeyError struct {
|
||||
Obj interface{}
|
||||
Err error
|
||||
}
|
||||
|
||||
// Error gives a human-readable description of the error.
|
||||
func (k KeyError) Error() string {
|
||||
return fmt.Sprintf("couldn't create key for object %+v: %v", k.Obj, k.Err)
|
||||
}
|
||||
|
||||
// Unwrap implements errors.Unwrap
|
||||
func (k KeyError) Unwrap() error {
|
||||
return k.Err
|
||||
}
|
||||
|
||||
// ExplicitKey can be passed to MetaNamespaceKeyFunc if you have the key for
|
||||
// the object but not the object itself.
|
||||
type ExplicitKey string
|
||||
|
||||
// MetaNamespaceKeyFunc is a convenient default KeyFunc which knows how to make
|
||||
// keys for API objects which implement meta.Interface.
|
||||
// The key uses the format <namespace>/<name> unless <namespace> is empty, then
|
||||
// it's just <name>.
|
||||
//
|
||||
// TODO: replace key-as-string with a key-as-struct so that this
|
||||
// packing/unpacking won't be necessary.
|
||||
func MetaNamespaceKeyFunc(obj interface{}) (string, error) {
|
||||
if key, ok := obj.(ExplicitKey); ok {
|
||||
return string(key), nil
|
||||
}
|
||||
meta, err := meta.Accessor(obj)
|
||||
if err != nil {
|
||||
return "", fmt.Errorf("object has no meta: %v", err)
|
||||
}
|
||||
if len(meta.GetNamespace()) > 0 {
|
||||
return meta.GetNamespace() + "/" + meta.GetName(), nil
|
||||
}
|
||||
return meta.GetName(), nil
|
||||
}
|
||||
|
||||
// SplitMetaNamespaceKey returns the namespace and name that
|
||||
// MetaNamespaceKeyFunc encoded into key.
|
||||
//
|
||||
// TODO: replace key-as-string with a key-as-struct so that this
|
||||
// packing/unpacking won't be necessary.
|
||||
func SplitMetaNamespaceKey(key string) (namespace, name string, err error) {
|
||||
parts := strings.Split(key, "/")
|
||||
switch len(parts) {
|
||||
case 1:
|
||||
// name only, no namespace
|
||||
return "", parts[0], nil
|
||||
case 2:
|
||||
// namespace and name
|
||||
return parts[0], parts[1], nil
|
||||
}
|
||||
|
||||
return "", "", fmt.Errorf("unexpected key format: %q", key)
|
||||
}
|
||||
|
||||
// `*cache` implements Indexer in terms of a ThreadSafeStore and an
|
||||
// associated KeyFunc.
|
||||
type cache struct {
|
||||
// cacheStorage bears the burden of thread safety for the cache
|
||||
cacheStorage ThreadSafeStore
|
||||
// keyFunc is used to make the key for objects stored in and retrieved from items, and
|
||||
// should be deterministic.
|
||||
keyFunc KeyFunc
|
||||
}
|
||||
|
||||
var _ Store = &cache{}
|
||||
|
||||
// Add inserts an item into the cache.
|
||||
func (c *cache) Add(obj interface{}) error {
|
||||
key, err := c.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
c.cacheStorage.Add(key, obj)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update sets an item in the cache to its updated state.
|
||||
func (c *cache) Update(obj interface{}) error {
|
||||
key, err := c.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
c.cacheStorage.Update(key, obj)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes an item from the cache.
|
||||
func (c *cache) Delete(obj interface{}) error {
|
||||
key, err := c.keyFunc(obj)
|
||||
if err != nil {
|
||||
return KeyError{obj, err}
|
||||
}
|
||||
c.cacheStorage.Delete(key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// List returns a list of all the items.
|
||||
// List is completely threadsafe as long as you treat all items as immutable.
|
||||
func (c *cache) List() []interface{} {
|
||||
return c.cacheStorage.List()
|
||||
}
|
||||
|
||||
// ListKeys returns a list of all the keys of the objects currently
|
||||
// in the cache.
|
||||
func (c *cache) ListKeys() []string {
|
||||
return c.cacheStorage.ListKeys()
|
||||
}
|
||||
|
||||
// GetIndexers returns the indexers of cache
|
||||
func (c *cache) GetIndexers() Indexers {
|
||||
return c.cacheStorage.GetIndexers()
|
||||
}
|
||||
|
||||
// Index returns a list of items that match on the index function
|
||||
// Index is thread-safe so long as you treat all items as immutable
|
||||
func (c *cache) Index(indexName string, obj interface{}) ([]interface{}, error) {
|
||||
return c.cacheStorage.Index(indexName, obj)
|
||||
}
|
||||
|
||||
func (c *cache) IndexKeys(indexName, indexKey string) ([]string, error) {
|
||||
return c.cacheStorage.IndexKeys(indexName, indexKey)
|
||||
}
|
||||
|
||||
// ListIndexFuncValues returns the list of generated values of an Index func
|
||||
func (c *cache) ListIndexFuncValues(indexName string) []string {
|
||||
return c.cacheStorage.ListIndexFuncValues(indexName)
|
||||
}
|
||||
|
||||
func (c *cache) ByIndex(indexName, indexKey string) ([]interface{}, error) {
|
||||
return c.cacheStorage.ByIndex(indexName, indexKey)
|
||||
}
|
||||
|
||||
func (c *cache) AddIndexers(newIndexers Indexers) error {
|
||||
return c.cacheStorage.AddIndexers(newIndexers)
|
||||
}
|
||||
|
||||
// Get returns the requested item, or sets exists=false.
|
||||
// Get is completely threadsafe as long as you treat all items as immutable.
|
||||
func (c *cache) Get(obj interface{}) (item interface{}, exists bool, err error) {
|
||||
key, err := c.keyFunc(obj)
|
||||
if err != nil {
|
||||
return nil, false, KeyError{obj, err}
|
||||
}
|
||||
return c.GetByKey(key)
|
||||
}
|
||||
|
||||
// GetByKey returns the request item, or exists=false.
|
||||
// GetByKey is completely threadsafe as long as you treat all items as immutable.
|
||||
func (c *cache) GetByKey(key string) (item interface{}, exists bool, err error) {
|
||||
item, exists = c.cacheStorage.Get(key)
|
||||
return item, exists, nil
|
||||
}
|
||||
|
||||
// Replace will delete the contents of 'c', using instead the given list.
|
||||
// 'c' takes ownership of the list, you should not reference the list again
|
||||
// after calling this function.
|
||||
func (c *cache) Replace(list []interface{}, resourceVersion string) error {
|
||||
items := make(map[string]interface{}, len(list))
|
||||
for _, item := range list {
|
||||
key, err := c.keyFunc(item)
|
||||
if err != nil {
|
||||
return KeyError{item, err}
|
||||
}
|
||||
items[key] = item
|
||||
}
|
||||
c.cacheStorage.Replace(items, resourceVersion)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Resync is meaningless for one of these
|
||||
func (c *cache) Resync() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewStore returns a Store implemented simply with a map and a lock.
|
||||
func NewStore(keyFunc KeyFunc) Store {
|
||||
return &cache{
|
||||
cacheStorage: NewThreadSafeStore(Indexers{}, Indices{}),
|
||||
keyFunc: keyFunc,
|
||||
}
|
||||
}
|
||||
|
||||
// NewIndexer returns an Indexer implemented simply with a map and a lock.
|
||||
func NewIndexer(keyFunc KeyFunc, indexers Indexers) Indexer {
|
||||
return &cache{
|
||||
cacheStorage: NewThreadSafeStore(indexers, Indices{}),
|
||||
keyFunc: keyFunc,
|
||||
}
|
||||
}
|
332
vendor/k8s.io/client-go/tools/cache/thread_safe_store.go
generated
vendored
Normal file
332
vendor/k8s.io/client-go/tools/cache/thread_safe_store.go
generated
vendored
Normal file
@ -0,0 +1,332 @@
|
||||
/*
|
||||
Copyright 2014 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 (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
)
|
||||
|
||||
// ThreadSafeStore is an interface that allows concurrent indexed
|
||||
// access to a storage backend. It is like Indexer but does not
|
||||
// (necessarily) know how to extract the Store key from a given
|
||||
// object.
|
||||
//
|
||||
// TL;DR caveats: you must not modify anything returned by Get or List as it will break
|
||||
// the indexing feature in addition to not being thread safe.
|
||||
//
|
||||
// The guarantees of thread safety provided by List/Get are only valid if the caller
|
||||
// treats returned items as read-only. For example, a pointer inserted in the store
|
||||
// through `Add` will be returned as is by `Get`. Multiple clients might invoke `Get`
|
||||
// on the same key and modify the pointer in a non-thread-safe way. Also note that
|
||||
// modifying objects stored by the indexers (if any) will *not* automatically lead
|
||||
// to a re-index. So it's not a good idea to directly modify the objects returned by
|
||||
// Get/List, in general.
|
||||
type ThreadSafeStore interface {
|
||||
Add(key string, obj interface{})
|
||||
Update(key string, obj interface{})
|
||||
Delete(key string)
|
||||
Get(key string) (item interface{}, exists bool)
|
||||
List() []interface{}
|
||||
ListKeys() []string
|
||||
Replace(map[string]interface{}, string)
|
||||
Index(indexName string, obj interface{}) ([]interface{}, error)
|
||||
IndexKeys(indexName, indexKey string) ([]string, error)
|
||||
ListIndexFuncValues(name string) []string
|
||||
ByIndex(indexName, indexKey string) ([]interface{}, error)
|
||||
GetIndexers() Indexers
|
||||
|
||||
// AddIndexers adds more indexers to this store. If you call this after you already have data
|
||||
// in the store, the results are undefined.
|
||||
AddIndexers(newIndexers Indexers) error
|
||||
// Resync is a no-op and is deprecated
|
||||
Resync() error
|
||||
}
|
||||
|
||||
// threadSafeMap implements ThreadSafeStore
|
||||
type threadSafeMap struct {
|
||||
lock sync.RWMutex
|
||||
items map[string]interface{}
|
||||
|
||||
// indexers maps a name to an IndexFunc
|
||||
indexers Indexers
|
||||
// indices maps a name to an Index
|
||||
indices Indices
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) Add(key string, obj interface{}) {
|
||||
c.Update(key, obj)
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) Update(key string, obj interface{}) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
oldObject := c.items[key]
|
||||
c.items[key] = obj
|
||||
c.updateIndices(oldObject, obj, key)
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) Delete(key string) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if obj, exists := c.items[key]; exists {
|
||||
c.updateIndices(obj, nil, key)
|
||||
delete(c.items, key)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) Get(key string) (item interface{}, exists bool) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
item, exists = c.items[key]
|
||||
return item, exists
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) List() []interface{} {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
list := make([]interface{}, 0, len(c.items))
|
||||
for _, item := range c.items {
|
||||
list = append(list, item)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
// ListKeys returns a list of all the keys of the objects currently
|
||||
// in the threadSafeMap.
|
||||
func (c *threadSafeMap) ListKeys() []string {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
list := make([]string, 0, len(c.items))
|
||||
for key := range c.items {
|
||||
list = append(list, key)
|
||||
}
|
||||
return list
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) Replace(items map[string]interface{}, resourceVersion string) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
c.items = items
|
||||
|
||||
// rebuild any index
|
||||
c.indices = Indices{}
|
||||
for key, item := range c.items {
|
||||
c.updateIndices(nil, item, key)
|
||||
}
|
||||
}
|
||||
|
||||
// Index returns a list of items that match the given object on the index function.
|
||||
// Index is thread-safe so long as you treat all items as immutable.
|
||||
func (c *threadSafeMap) Index(indexName string, obj interface{}) ([]interface{}, error) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
indexFunc := c.indexers[indexName]
|
||||
if indexFunc == nil {
|
||||
return nil, fmt.Errorf("Index with name %s does not exist", indexName)
|
||||
}
|
||||
|
||||
indexedValues, err := indexFunc(obj)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
index := c.indices[indexName]
|
||||
|
||||
var storeKeySet sets.String
|
||||
if len(indexedValues) == 1 {
|
||||
// In majority of cases, there is exactly one value matching.
|
||||
// Optimize the most common path - deduping is not needed here.
|
||||
storeKeySet = index[indexedValues[0]]
|
||||
} else {
|
||||
// Need to de-dupe the return list.
|
||||
// Since multiple keys are allowed, this can happen.
|
||||
storeKeySet = sets.String{}
|
||||
for _, indexedValue := range indexedValues {
|
||||
for key := range index[indexedValue] {
|
||||
storeKeySet.Insert(key)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
list := make([]interface{}, 0, storeKeySet.Len())
|
||||
for storeKey := range storeKeySet {
|
||||
list = append(list, c.items[storeKey])
|
||||
}
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// ByIndex returns a list of the items whose indexed values in the given index include the given indexed value
|
||||
func (c *threadSafeMap) ByIndex(indexName, indexedValue string) ([]interface{}, error) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
indexFunc := c.indexers[indexName]
|
||||
if indexFunc == nil {
|
||||
return nil, fmt.Errorf("Index with name %s does not exist", indexName)
|
||||
}
|
||||
|
||||
index := c.indices[indexName]
|
||||
|
||||
set := index[indexedValue]
|
||||
list := make([]interface{}, 0, set.Len())
|
||||
for key := range set {
|
||||
list = append(list, c.items[key])
|
||||
}
|
||||
|
||||
return list, nil
|
||||
}
|
||||
|
||||
// IndexKeys returns a list of the Store keys of the objects whose indexed values in the given index include the given indexed value.
|
||||
// IndexKeys is thread-safe so long as you treat all items as immutable.
|
||||
func (c *threadSafeMap) IndexKeys(indexName, indexedValue string) ([]string, error) {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
indexFunc := c.indexers[indexName]
|
||||
if indexFunc == nil {
|
||||
return nil, fmt.Errorf("Index with name %s does not exist", indexName)
|
||||
}
|
||||
|
||||
index := c.indices[indexName]
|
||||
|
||||
set := index[indexedValue]
|
||||
return set.List(), nil
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) ListIndexFuncValues(indexName string) []string {
|
||||
c.lock.RLock()
|
||||
defer c.lock.RUnlock()
|
||||
|
||||
index := c.indices[indexName]
|
||||
names := make([]string, 0, len(index))
|
||||
for key := range index {
|
||||
names = append(names, key)
|
||||
}
|
||||
return names
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) GetIndexers() Indexers {
|
||||
return c.indexers
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) AddIndexers(newIndexers Indexers) error {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
|
||||
if len(c.items) > 0 {
|
||||
return fmt.Errorf("cannot add indexers to running index")
|
||||
}
|
||||
|
||||
oldKeys := sets.StringKeySet(c.indexers)
|
||||
newKeys := sets.StringKeySet(newIndexers)
|
||||
|
||||
if oldKeys.HasAny(newKeys.List()...) {
|
||||
return fmt.Errorf("indexer conflict: %v", oldKeys.Intersection(newKeys))
|
||||
}
|
||||
|
||||
for k, v := range newIndexers {
|
||||
c.indexers[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// updateIndices modifies the objects location in the managed indexes:
|
||||
// - for create you must provide only the newObj
|
||||
// - for update you must provide both the oldObj and the newObj
|
||||
// - for delete you must provide only the oldObj
|
||||
// updateIndices must be called from a function that already has a lock on the cache
|
||||
func (c *threadSafeMap) updateIndices(oldObj interface{}, newObj interface{}, key string) {
|
||||
var oldIndexValues, indexValues []string
|
||||
var err error
|
||||
for name, indexFunc := range c.indexers {
|
||||
if oldObj != nil {
|
||||
oldIndexValues, err = indexFunc(oldObj)
|
||||
} else {
|
||||
oldIndexValues = oldIndexValues[:0]
|
||||
}
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unable to calculate an index entry for key %q on index %q: %v", key, name, err))
|
||||
}
|
||||
|
||||
if newObj != nil {
|
||||
indexValues, err = indexFunc(newObj)
|
||||
} else {
|
||||
indexValues = indexValues[:0]
|
||||
}
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("unable to calculate an index entry for key %q on index %q: %v", key, name, err))
|
||||
}
|
||||
|
||||
index := c.indices[name]
|
||||
if index == nil {
|
||||
index = Index{}
|
||||
c.indices[name] = index
|
||||
}
|
||||
|
||||
if len(indexValues) == 1 && len(oldIndexValues) == 1 && indexValues[0] == oldIndexValues[0] {
|
||||
// We optimize for the most common case where indexFunc returns a single value which has not been changed
|
||||
continue
|
||||
}
|
||||
|
||||
for _, value := range oldIndexValues {
|
||||
c.deleteKeyFromIndex(key, value, index)
|
||||
}
|
||||
for _, value := range indexValues {
|
||||
c.addKeyToIndex(key, value, index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) addKeyToIndex(key, indexValue string, index Index) {
|
||||
set := index[indexValue]
|
||||
if set == nil {
|
||||
set = sets.String{}
|
||||
index[indexValue] = set
|
||||
}
|
||||
set.Insert(key)
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) deleteKeyFromIndex(key, indexValue string, index Index) {
|
||||
set := index[indexValue]
|
||||
if set == nil {
|
||||
return
|
||||
}
|
||||
set.Delete(key)
|
||||
// If we don't delete the set when zero, indices with high cardinality
|
||||
// short lived resources can cause memory to increase over time from
|
||||
// unused empty sets. See `kubernetes/kubernetes/issues/84959`.
|
||||
if len(set) == 0 {
|
||||
delete(index, indexValue)
|
||||
}
|
||||
}
|
||||
|
||||
func (c *threadSafeMap) Resync() error {
|
||||
// Nothing to do
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewThreadSafeStore creates a new instance of ThreadSafeStore.
|
||||
func NewThreadSafeStore(indexers Indexers, indices Indices) ThreadSafeStore {
|
||||
return &threadSafeMap{
|
||||
items: map[string]interface{}{},
|
||||
indexers: indexers,
|
||||
indices: indices,
|
||||
}
|
||||
}
|
89
vendor/k8s.io/client-go/tools/cache/undelta_store.go
generated
vendored
Normal file
89
vendor/k8s.io/client-go/tools/cache/undelta_store.go
generated
vendored
Normal file
@ -0,0 +1,89 @@
|
||||
/*
|
||||
Copyright 2015 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
|
||||
|
||||
// UndeltaStore listens to incremental updates and sends complete state on every change.
|
||||
// It implements the Store interface so that it can receive a stream of mirrored objects
|
||||
// from Reflector. Whenever it receives any complete (Store.Replace) or incremental change
|
||||
// (Store.Add, Store.Update, Store.Delete), it sends the complete state by calling PushFunc.
|
||||
// It is thread-safe. It guarantees that every change (Add, Update, Replace, Delete) results
|
||||
// in one call to PushFunc, but sometimes PushFunc may be called twice with the same values.
|
||||
// PushFunc should be thread safe.
|
||||
type UndeltaStore struct {
|
||||
Store
|
||||
PushFunc func([]interface{})
|
||||
}
|
||||
|
||||
// Assert that it implements the Store interface.
|
||||
var _ Store = &UndeltaStore{}
|
||||
|
||||
// Add inserts an object into the store and sends complete state by calling PushFunc.
|
||||
// Note about thread safety. The Store implementation (cache.cache) uses a lock for all methods.
|
||||
// In the functions below, the lock gets released and reacquired betweend the {Add,Delete,etc}
|
||||
// and the List. So, the following can happen, resulting in two identical calls to PushFunc.
|
||||
// time thread 1 thread 2
|
||||
// 0 UndeltaStore.Add(a)
|
||||
// 1 UndeltaStore.Add(b)
|
||||
// 2 Store.Add(a)
|
||||
// 3 Store.Add(b)
|
||||
// 4 Store.List() -> [a,b]
|
||||
// 5 Store.List() -> [a,b]
|
||||
func (u *UndeltaStore) Add(obj interface{}) error {
|
||||
if err := u.Store.Add(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
u.PushFunc(u.Store.List())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Update sets an item in the cache to its updated state and sends complete state by calling PushFunc.
|
||||
func (u *UndeltaStore) Update(obj interface{}) error {
|
||||
if err := u.Store.Update(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
u.PushFunc(u.Store.List())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes an item from the cache and sends complete state by calling PushFunc.
|
||||
func (u *UndeltaStore) Delete(obj interface{}) error {
|
||||
if err := u.Store.Delete(obj); err != nil {
|
||||
return err
|
||||
}
|
||||
u.PushFunc(u.Store.List())
|
||||
return nil
|
||||
}
|
||||
|
||||
// Replace will delete the contents of current store, using instead the given list.
|
||||
// 'u' takes ownership of the list, you should not reference the list again
|
||||
// after calling this function.
|
||||
// The new contents complete state will be sent by calling PushFunc after replacement.
|
||||
func (u *UndeltaStore) Replace(list []interface{}, resourceVersion string) error {
|
||||
if err := u.Store.Replace(list, resourceVersion); err != nil {
|
||||
return err
|
||||
}
|
||||
u.PushFunc(u.Store.List())
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewUndeltaStore returns an UndeltaStore implemented with a Store.
|
||||
func NewUndeltaStore(pushFunc func([]interface{}), keyFunc KeyFunc) *UndeltaStore {
|
||||
return &UndeltaStore{
|
||||
Store: NewStore(keyFunc),
|
||||
PushFunc: pushFunc,
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user