github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/container-collection/podinformer.go (about)

     1  // Copyright 2017 The Kubernetes Authors.
     2  // Copyright 2019-2022 The Inspektor Gadget authors
     3  //
     4  // Licensed under the Apache License, Version 2.0 (the "License");
     5  // you may not use this file except in compliance with the License.
     6  // You may obtain a copy of the License at
     7  //
     8  //     http://www.apache.org/licenses/LICENSE-2.0
     9  //
    10  // Unless required by applicable law or agreed to in writing, software
    11  // distributed under the License is distributed on an "AS IS" BASIS,
    12  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  // See the License for the specific language governing permissions and
    14  // limitations under the License.
    15  
    16  /* Code based on the official client-go example:
    17   * https://github.com/kubernetes/client-go/blob/master/examples/workqueue/main.go
    18   */
    19  
    20  package containercollection
    21  
    22  import (
    23  	"errors"
    24  	"sync"
    25  	"time"
    26  
    27  	log "github.com/sirupsen/logrus"
    28  
    29  	v1 "k8s.io/api/core/v1"
    30  	"k8s.io/apimachinery/pkg/fields"
    31  	"k8s.io/apimachinery/pkg/util/runtime"
    32  	"k8s.io/apimachinery/pkg/util/wait"
    33  	"k8s.io/client-go/kubernetes"
    34  	"k8s.io/client-go/rest"
    35  	"k8s.io/client-go/tools/cache"
    36  	"k8s.io/client-go/util/workqueue"
    37  
    38  	_ "k8s.io/client-go/plugin/pkg/client/auth"
    39  )
    40  
    41  type PodInformer struct {
    42  	indexer  cache.Indexer
    43  	queue    workqueue.RateLimitingInterface
    44  	informer cache.Controller
    45  
    46  	stop           chan struct{}
    47  	updatedPodChan chan *v1.Pod
    48  	deletedPodChan chan string
    49  	wg             sync.WaitGroup
    50  }
    51  
    52  func NewPodInformer(node string) (*PodInformer, error) {
    53  	config, err := rest.InClusterConfig()
    54  	if err != nil {
    55  		return nil, err
    56  	}
    57  	clientset, err := kubernetes.NewForConfig(config)
    58  	if err != nil {
    59  		return nil, err
    60  	}
    61  
    62  	podListWatcher := cache.NewListWatchFromClient(clientset.CoreV1().RESTClient(), "pods", "", fields.OneTermEqualSelector("spec.nodeName", node))
    63  
    64  	// creates the queue
    65  	queue := workqueue.NewRateLimitingQueue(workqueue.DefaultControllerRateLimiter())
    66  
    67  	indexer, informer := cache.NewIndexerInformer(podListWatcher, &v1.Pod{}, 0, cache.ResourceEventHandlerFuncs{
    68  		AddFunc: func(obj interface{}) {
    69  			key, err := cache.MetaNamespaceKeyFunc(obj)
    70  			if err == nil {
    71  				queue.Add(key)
    72  			}
    73  		},
    74  		UpdateFunc: func(old interface{}, new interface{}) {
    75  			key, err := cache.MetaNamespaceKeyFunc(new)
    76  			if err == nil {
    77  				queue.Add(key)
    78  			}
    79  		},
    80  		DeleteFunc: func(obj interface{}) {
    81  			// IndexerInformer uses a delta queue, therefore for deletes we have to use this
    82  			// key function.
    83  			key, err := cache.DeletionHandlingMetaNamespaceKeyFunc(obj)
    84  			if err == nil {
    85  				queue.Add(key)
    86  			}
    87  		},
    88  	}, cache.Indexers{})
    89  
    90  	p := &PodInformer{
    91  		indexer:        indexer,
    92  		queue:          queue,
    93  		informer:       informer,
    94  		stop:           make(chan struct{}),
    95  		updatedPodChan: make(chan *v1.Pod),
    96  		deletedPodChan: make(chan string),
    97  	}
    98  
    99  	// Now let's start the controller
   100  	go p.Run(1, p.stop)
   101  
   102  	return p, nil
   103  }
   104  
   105  func (p *PodInformer) Stop() {
   106  	// tell all workers to end
   107  	close(p.stop)
   108  
   109  	// wait for workers to end before closing channels to avoid
   110  	// writing to closed channels
   111  	p.wg.Wait()
   112  
   113  	close(p.updatedPodChan)
   114  	close(p.deletedPodChan)
   115  }
   116  
   117  func (p *PodInformer) UpdatedChan() <-chan *v1.Pod {
   118  	return p.updatedPodChan
   119  }
   120  
   121  func (p *PodInformer) DeletedChan() <-chan string {
   122  	return p.deletedPodChan
   123  }
   124  
   125  func (p *PodInformer) processNextItem() bool {
   126  	// Wait until there is a new item in the working queue
   127  	key, quit := p.queue.Get()
   128  	if quit {
   129  		return false
   130  	}
   131  
   132  	defer p.queue.Done(key)
   133  
   134  	p.notifyChans(key.(string))
   135  	return true
   136  }
   137  
   138  // notifyChans passes the event to the channels configured by the user
   139  func (p *PodInformer) notifyChans(key string) error {
   140  	obj, exists, err := p.indexer.GetByKey(key)
   141  	if err != nil {
   142  		log.Errorf("Fetching object with key %s from store failed with %v", key, err)
   143  		return err
   144  	}
   145  	defer p.queue.Forget(key)
   146  
   147  	if !exists {
   148  		p.deletedPodChan <- key
   149  		return nil
   150  	}
   151  
   152  	p.updatedPodChan <- obj.(*v1.Pod)
   153  	return nil
   154  }
   155  
   156  func (p *PodInformer) Run(threadiness int, stopCh chan struct{}) {
   157  	defer runtime.HandleCrash()
   158  
   159  	// Let the workers stop when we are done
   160  	defer p.queue.ShutDown()
   161  	log.Info("Starting Pod controller")
   162  
   163  	go p.informer.Run(stopCh)
   164  
   165  	// Wait for all involved caches to be synced, before processing items from the queue is started
   166  	if !cache.WaitForCacheSync(stopCh, p.informer.HasSynced) {
   167  		runtime.HandleError(errors.New("timed out waiting for caches to sync"))
   168  		return
   169  	}
   170  
   171  	for i := 0; i < threadiness; i++ {
   172  		p.wg.Add(1)
   173  		go wait.Until(p.runWorker, time.Second, stopCh)
   174  	}
   175  
   176  	<-stopCh
   177  	log.Info("Stopping Pod controller")
   178  }
   179  
   180  func (p *PodInformer) runWorker() {
   181  	defer p.wg.Done()
   182  
   183  	for p.processNextItem() {
   184  	}
   185  }