github.com/inspektor-gadget/inspektor-gadget@v0.28.1/pkg/operators/common/kubeinventorycache.go (about)

     1  // Copyright 2023-2024 The Inspektor Gadget authors
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package common
    16  
    17  import (
    18  	"fmt"
    19  	"sync"
    20  	"time"
    21  
    22  	log "github.com/sirupsen/logrus"
    23  	v1 "k8s.io/api/core/v1"
    24  	"k8s.io/client-go/informers"
    25  	"k8s.io/client-go/kubernetes"
    26  	k8sCache "k8s.io/client-go/tools/cache"
    27  
    28  	"github.com/inspektor-gadget/inspektor-gadget/pkg/cachedmap"
    29  	"github.com/inspektor-gadget/inspektor-gadget/pkg/k8sutil"
    30  )
    31  
    32  // K8sInventoryCache is a cache of Kubernetes resources such as pods and services
    33  // that can be used by operators to enrich events.
    34  type K8sInventoryCache interface {
    35  	Start()
    36  	Stop()
    37  
    38  	GetPods() []*v1.Pod
    39  	GetPodByName(namespace string, name string) *v1.Pod
    40  	GetPodByIp(ip string) *v1.Pod
    41  
    42  	GetSvcs() []*v1.Service
    43  	GetSvcByName(namespace string, name string) *v1.Service
    44  	GetSvcByIp(ip string) *v1.Service
    45  }
    46  
    47  type inventoryCache struct {
    48  	clientset *kubernetes.Clientset
    49  
    50  	factory informers.SharedInformerFactory
    51  
    52  	pods cachedmap.CachedMap[string, *v1.Pod]
    53  	svcs cachedmap.CachedMap[string, *v1.Service]
    54  
    55  	exit chan struct{}
    56  
    57  	useCount      int
    58  	useCountMutex sync.Mutex
    59  }
    60  
    61  const (
    62  	informerResync = 10 * time.Minute
    63  )
    64  
    65  var (
    66  	k8sInventorySingleton *inventoryCache
    67  	k8sInventoryErr       error
    68  	k8sInventoryOnce      sync.Once
    69  )
    70  
    71  func GetK8sInventoryCache() (K8sInventoryCache, error) {
    72  	k8sInventoryOnce.Do(func() {
    73  		k8sInventorySingleton, k8sInventoryErr = newCache()
    74  	})
    75  	return k8sInventorySingleton, k8sInventoryErr
    76  }
    77  
    78  func newCache() (*inventoryCache, error) {
    79  	clientset, err := k8sutil.NewClientset("")
    80  	if err != nil {
    81  		return nil, fmt.Errorf("creating new k8s clientset: %w", err)
    82  	}
    83  
    84  	return &inventoryCache{
    85  		clientset: clientset,
    86  		pods:      cachedmap.NewCachedMap[string, *v1.Pod](2 * time.Second),
    87  		svcs:      cachedmap.NewCachedMap[string, *v1.Service](2 * time.Second),
    88  	}, nil
    89  }
    90  
    91  func (cache *inventoryCache) Close() {
    92  	if cache.exit != nil {
    93  		close(cache.exit)
    94  		cache.exit = nil
    95  	}
    96  	if cache.factory != nil {
    97  		cache.factory.Shutdown()
    98  		cache.factory = nil
    99  	}
   100  }
   101  
   102  func (cache *inventoryCache) Start() {
   103  	cache.useCountMutex.Lock()
   104  	defer cache.useCountMutex.Unlock()
   105  
   106  	// No uses before us, we are the first one
   107  	if cache.useCount == 0 {
   108  		cache.factory = informers.NewSharedInformerFactory(cache.clientset, informerResync)
   109  		cache.factory.Core().V1().Pods().Informer().AddEventHandler(cache)
   110  		cache.factory.Core().V1().Services().Informer().AddEventHandler(cache)
   111  
   112  		cache.exit = make(chan struct{})
   113  		cache.factory.Start(cache.exit)
   114  		cache.factory.WaitForCacheSync(cache.exit)
   115  	}
   116  	cache.useCount++
   117  }
   118  
   119  func (cache *inventoryCache) Stop() {
   120  	cache.useCountMutex.Lock()
   121  	defer cache.useCountMutex.Unlock()
   122  
   123  	// We are the last user, stop everything
   124  	if cache.useCount == 1 {
   125  		cache.Close()
   126  	}
   127  	cache.useCount--
   128  }
   129  
   130  func (cache *inventoryCache) GetPods() []*v1.Pod {
   131  	return cache.pods.Values()
   132  }
   133  
   134  func (cache *inventoryCache) GetPodByName(namespace string, name string) *v1.Pod {
   135  	pod, found := cache.pods.Get(namespace + "/" + name)
   136  	if !found {
   137  		return nil
   138  	}
   139  	return pod
   140  }
   141  
   142  func (cache *inventoryCache) GetPodByIp(ip string) *v1.Pod {
   143  	pod, found := cache.pods.GetCmp(func(pod *v1.Pod) bool {
   144  		return pod.Status.PodIP == ip
   145  	})
   146  	if !found {
   147  		return nil
   148  	}
   149  	return pod
   150  }
   151  
   152  func (cache *inventoryCache) GetSvcs() []*v1.Service {
   153  	return cache.svcs.Values()
   154  }
   155  
   156  func (cache *inventoryCache) GetSvcByName(namespace string, name string) *v1.Service {
   157  	svc, found := cache.svcs.Get(namespace + "/" + name)
   158  	if !found {
   159  		return nil
   160  	}
   161  	return svc
   162  }
   163  
   164  func (cache *inventoryCache) GetSvcByIp(ip string) *v1.Service {
   165  	svc, found := cache.svcs.GetCmp(func(svc *v1.Service) bool {
   166  		return svc.Spec.ClusterIP == ip
   167  	})
   168  	if !found {
   169  		return nil
   170  	}
   171  	return svc
   172  }
   173  
   174  func (cache *inventoryCache) OnAdd(obj any, _ bool) {
   175  	switch o := obj.(type) {
   176  	case *v1.Pod:
   177  		key, err := k8sCache.MetaNamespaceKeyFunc(o)
   178  		if err != nil {
   179  			log.Warnf("OnAdd: error getting key for pod: %v", err)
   180  			return
   181  		}
   182  		cache.pods.Add(key, o)
   183  	case *v1.Service:
   184  		key, err := k8sCache.MetaNamespaceKeyFunc(o)
   185  		if err != nil {
   186  			log.Warnf("OnAdd: error getting key for service: %v", err)
   187  			return
   188  		}
   189  		cache.svcs.Add(key, o)
   190  	default:
   191  		log.Warnf("OnAdd: unknown object type: %T", o)
   192  	}
   193  }
   194  
   195  func (cache *inventoryCache) OnUpdate(_, newObj any) {
   196  	switch o := newObj.(type) {
   197  	case *v1.Pod:
   198  		key, err := k8sCache.MetaNamespaceKeyFunc(o)
   199  		if err != nil {
   200  			log.Warnf("OnUpdate: error getting key for pod: %v", err)
   201  			return
   202  		}
   203  		cache.pods.Add(key, o)
   204  	case *v1.Service:
   205  		key, err := k8sCache.MetaNamespaceKeyFunc(o)
   206  		if err != nil {
   207  			log.Warnf("OnUpdate: error getting key for service: %v", err)
   208  			return
   209  		}
   210  		cache.svcs.Add(key, o)
   211  	default:
   212  		log.Warnf("OnUpdate: unknown object type: %T", o)
   213  	}
   214  }
   215  
   216  func (cache *inventoryCache) OnDelete(obj any) {
   217  	switch o := obj.(type) {
   218  	case *v1.Pod:
   219  		key, err := k8sCache.MetaNamespaceKeyFunc(o)
   220  		if err != nil {
   221  			log.Warnf("OnDelete: error getting key for pod: %v", err)
   222  			return
   223  		}
   224  		cache.pods.Remove(key)
   225  	case *v1.Service:
   226  		key, err := k8sCache.MetaNamespaceKeyFunc(o)
   227  		if err != nil {
   228  			log.Warnf("OnDelete: error getting key for service: %v", err)
   229  			return
   230  		}
   231  		cache.svcs.Remove(key)
   232  	case k8sCache.DeletedFinalStateUnknown:
   233  		cache.OnDelete(o.Obj)
   234  	default:
   235  		log.Warnf("OnDelete: unknown object type: %T", o)
   236  	}
   237  }