github.com/operator-framework/operator-lifecycle-manager@v0.30.0/pkg/lib/queueinformer/queueinformer.go (about)

     1  package queueinformer
     2  
     3  import (
     4  	"context"
     5  
     6  	"github.com/pkg/errors"
     7  	"github.com/sirupsen/logrus"
     8  	"k8s.io/client-go/tools/cache"
     9  	"k8s.io/client-go/util/workqueue"
    10  
    11  	"github.com/operator-framework/operator-lifecycle-manager/pkg/lib/kubestate"
    12  	"github.com/operator-framework/operator-lifecycle-manager/pkg/metrics"
    13  )
    14  
    15  // KeyFunc returns a key for the given object and a bool which is true if the key was
    16  // successfully generated and false otherwise.
    17  type KeyFunc func(obj interface{}) (string, bool)
    18  
    19  // QueueInformer ties an informer to a queue in order to process events from the informer
    20  // the informer watches objects of interest and adds objects to the queue for processing
    21  // the syncHandler is called for all objects on the queue
    22  type QueueInformer struct {
    23  	metrics.MetricsProvider
    24  
    25  	logger   *logrus.Logger
    26  	queue    workqueue.RateLimitingInterface
    27  	informer cache.SharedIndexInformer
    28  	indexer  cache.Indexer
    29  	keyFunc  KeyFunc
    30  	syncer   kubestate.Syncer
    31  }
    32  
    33  // Sync invokes all registered sync handlers in the QueueInformer's chain
    34  func (q *QueueInformer) Sync(ctx context.Context, event kubestate.ResourceEvent) error {
    35  	return q.syncer.Sync(ctx, event)
    36  }
    37  
    38  // Enqueue adds a key to the queue. If obj is a key already it gets added directly.
    39  // Otherwise, the key is extracted via keyFunc.
    40  func (q *QueueInformer) Enqueue(event kubestate.ResourceEvent) {
    41  	if event == nil {
    42  		// Don't enqueue nil events
    43  		return
    44  	}
    45  
    46  	resource := event.Resource()
    47  	if event.Type() == kubestate.ResourceDeleted {
    48  		// Get object from tombstone if possible
    49  		if tombstone, ok := resource.(cache.DeletedFinalStateUnknown); ok {
    50  			resource = tombstone
    51  		}
    52  	} else {
    53  		// Extract key for add and update events
    54  		if key, ok := q.key(resource); ok {
    55  			resource = key
    56  		}
    57  	}
    58  
    59  	// Create new resource event and add to queue
    60  	e := kubestate.NewResourceEvent(event.Type(), resource)
    61  	q.logger.WithField("event", e).Trace("enqueuing resource event")
    62  	q.queue.Add(e)
    63  }
    64  
    65  // key turns an object into a key for the indexer.
    66  func (q *QueueInformer) key(obj interface{}) (string, bool) {
    67  	return q.keyFunc(obj)
    68  }
    69  
    70  // resourceHandlers provides the default implementation for responding to events
    71  // these simply Log the event and add the object's key to the queue for later processing.
    72  func (q *QueueInformer) resourceHandlers(ctx context.Context) *cache.ResourceEventHandlerFuncs {
    73  	return &cache.ResourceEventHandlerFuncs{
    74  		AddFunc: func(obj interface{}) {
    75  			q.Enqueue(kubestate.NewResourceEvent(kubestate.ResourceUpdated, obj))
    76  		},
    77  		UpdateFunc: func(oldObj, newObj interface{}) {
    78  			q.Enqueue(kubestate.NewResourceEvent(kubestate.ResourceUpdated, newObj))
    79  		},
    80  		DeleteFunc: func(obj interface{}) {
    81  			q.Enqueue(kubestate.NewResourceEvent(kubestate.ResourceDeleted, obj))
    82  		},
    83  	}
    84  }
    85  
    86  // metricHandlers provides the default implementation for handling metrics in response to events.
    87  func (q *QueueInformer) metricHandlers() *cache.ResourceEventHandlerFuncs {
    88  	return &cache.ResourceEventHandlerFuncs{
    89  		AddFunc: func(obj interface{}) {
    90  			if err := q.HandleMetrics(); err != nil {
    91  				q.logger.WithError(err).WithField("key", obj).Warn("error handling metrics on add event")
    92  			}
    93  		},
    94  		DeleteFunc: func(obj interface{}) {
    95  			if err := q.HandleMetrics(); err != nil {
    96  				q.logger.WithError(err).WithField("key", obj).Warn("error handling metrics on delete event")
    97  			}
    98  		},
    99  		UpdateFunc: func(oldObj, newObj interface{}) {
   100  			if err := q.HandleMetrics(); err != nil {
   101  				q.logger.WithError(err).WithField("key", newObj).Warn("error handling metrics on update event")
   102  			}
   103  		},
   104  	}
   105  }
   106  
   107  func NewQueue(ctx context.Context, options ...Option) (*QueueInformer, error) {
   108  	config := defaultConfig()
   109  	config.apply(options)
   110  
   111  	if err := config.validateQueue(); err != nil {
   112  		return nil, err
   113  	}
   114  
   115  	queue := &QueueInformer{
   116  		MetricsProvider: config.provider,
   117  		logger:          config.logger,
   118  		queue:           config.queue,
   119  		keyFunc:         config.keyFunc,
   120  		syncer:          config.syncer,
   121  	}
   122  
   123  	return queue, nil
   124  }
   125  
   126  // NewQueueInformer returns a new QueueInformer configured with options.
   127  func NewQueueInformer(ctx context.Context, options ...Option) (*QueueInformer, error) {
   128  	// Get default config and apply given options
   129  	config := defaultConfig()
   130  	config.apply(options)
   131  	config.complete()
   132  
   133  	return newQueueInformerFromConfig(ctx, config)
   134  }
   135  
   136  func newQueueInformerFromConfig(ctx context.Context, config *queueInformerConfig) (*QueueInformer, error) {
   137  	if err := config.validateQueueInformer(); err != nil {
   138  		return nil, err
   139  	}
   140  
   141  	// Extract config
   142  	queueInformer := &QueueInformer{
   143  		MetricsProvider: config.provider,
   144  		logger:          config.logger,
   145  		queue:           config.queue,
   146  		indexer:         config.indexer,
   147  		informer:        config.informer,
   148  		keyFunc:         config.keyFunc,
   149  		syncer:          config.syncer,
   150  	}
   151  
   152  	// Register event handlers for resource and metrics
   153  	if queueInformer.informer != nil {
   154  		queueInformer.informer.AddEventHandler(queueInformer.resourceHandlers(ctx))
   155  		queueInformer.informer.AddEventHandler(queueInformer.metricHandlers())
   156  	}
   157  
   158  	return queueInformer, nil
   159  }
   160  
   161  // LegacySyncHandler is a deprecated signature for syncing resources.
   162  type LegacySyncHandler func(obj interface{}) error
   163  
   164  // ToSyncer returns the Syncer equivalent of the sync handler.
   165  func (l LegacySyncHandler) ToSyncer() kubestate.Syncer {
   166  	return l.ToSyncerWithDelete(nil)
   167  }
   168  
   169  // ToSyncerWithDelete returns the Syncer equivalent of the given sync handler and delete function.
   170  func (l LegacySyncHandler) ToSyncerWithDelete(onDelete func(obj interface{})) kubestate.Syncer {
   171  	var syncer kubestate.SyncFunc = func(ctx context.Context, event kubestate.ResourceEvent) error {
   172  		switch event.Type() {
   173  		case kubestate.ResourceDeleted:
   174  			if onDelete != nil {
   175  				onDelete(event.Resource())
   176  			}
   177  		case kubestate.ResourceAdded:
   178  			// Added and updated are treated the same
   179  			fallthrough
   180  		case kubestate.ResourceUpdated:
   181  			return l(event.Resource())
   182  		default:
   183  			return errors.Errorf("unexpected resource event type: %s", event.Type())
   184  		}
   185  
   186  		return nil
   187  	}
   188  
   189  	return syncer
   190  }