github.com/cilium/cilium@v1.16.2/pkg/clustermesh/common/services.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package common
     5  
     6  import (
     7  	"maps"
     8  
     9  	"github.com/sirupsen/logrus"
    10  	"k8s.io/apimachinery/pkg/types"
    11  
    12  	"github.com/cilium/cilium/pkg/kvstore/store"
    13  	"github.com/cilium/cilium/pkg/lock"
    14  	"github.com/cilium/cilium/pkg/logging/logfields"
    15  	"github.com/cilium/cilium/pkg/metrics/metric"
    16  	serviceStore "github.com/cilium/cilium/pkg/service/store"
    17  )
    18  
    19  type GlobalService struct {
    20  	ClusterServices map[string]*serviceStore.ClusterService
    21  }
    22  
    23  func newGlobalService() *GlobalService {
    24  	return &GlobalService{
    25  		ClusterServices: map[string]*serviceStore.ClusterService{},
    26  	}
    27  }
    28  
    29  type GlobalServiceCache struct {
    30  	mutex  lock.RWMutex
    31  	byName map[types.NamespacedName]*GlobalService
    32  
    33  	// metricTotalGlobalServices is the gauge metric for total of global services
    34  	metricTotalGlobalServices metric.Gauge
    35  }
    36  
    37  func NewGlobalServiceCache(metricTotalGlobalServices metric.Gauge) *GlobalServiceCache {
    38  	return &GlobalServiceCache{
    39  		byName:                    map[types.NamespacedName]*GlobalService{},
    40  		metricTotalGlobalServices: metricTotalGlobalServices,
    41  	}
    42  }
    43  
    44  // Has returns whether a given service is present in the cache.
    45  func (c *GlobalServiceCache) Has(svc *serviceStore.ClusterService) bool {
    46  	c.mutex.RLock()
    47  	defer c.mutex.RUnlock()
    48  
    49  	if globalSvc, ok := c.byName[svc.NamespaceServiceName()]; ok {
    50  		_, ok = globalSvc.ClusterServices[svc.Cluster]
    51  		return ok
    52  	}
    53  
    54  	return false
    55  }
    56  
    57  // GetService returns the service for a specific cluster. This function does not
    58  // make a copy of the cluster service object and should not be mutated.
    59  func (c *GlobalServiceCache) GetService(serviceNN types.NamespacedName, clusterName string) *serviceStore.ClusterService {
    60  	c.mutex.RLock()
    61  	defer c.mutex.RUnlock()
    62  
    63  	if globalSvc, ok := c.byName[serviceNN]; ok {
    64  		if svc, ok := globalSvc.ClusterServices[clusterName]; ok {
    65  			return svc
    66  		}
    67  	}
    68  
    69  	return nil
    70  }
    71  
    72  // GetGlobalService returns a global service object. This function returns
    73  // a shallow copy of the GlobalService object, thus the ClusterService objects
    74  // should not be mutated.
    75  func (c *GlobalServiceCache) GetGlobalService(serviceNN types.NamespacedName) *GlobalService {
    76  	c.mutex.RLock()
    77  	defer c.mutex.RUnlock()
    78  
    79  	if globalSvc, ok := c.byName[serviceNN]; ok {
    80  		// We copy the global service to make this thread safe
    81  		newGlobalSvc := newGlobalService()
    82  		newGlobalSvc.ClusterServices = maps.Clone(globalSvc.ClusterServices)
    83  		return newGlobalSvc
    84  	}
    85  
    86  	return nil
    87  }
    88  
    89  func (c *GlobalServiceCache) OnUpdate(svc *serviceStore.ClusterService) {
    90  	scopedLog := log.WithFields(logrus.Fields{
    91  		logfields.ServiceName: svc.String(),
    92  		logfields.ClusterName: svc.Cluster,
    93  	})
    94  
    95  	c.mutex.Lock()
    96  
    97  	// Validate that the global service is known
    98  	globalSvc, ok := c.byName[svc.NamespaceServiceName()]
    99  	if !ok {
   100  		globalSvc = newGlobalService()
   101  		c.byName[svc.NamespaceServiceName()] = globalSvc
   102  		scopedLog.Debugf("Created global service %s", svc.NamespaceServiceName())
   103  		c.metricTotalGlobalServices.Set(float64(len(c.byName)))
   104  	}
   105  
   106  	scopedLog.Debugf("Updated service definition of remote cluster %#v", svc)
   107  
   108  	globalSvc.ClusterServices[svc.Cluster] = svc
   109  	c.mutex.Unlock()
   110  }
   111  
   112  // must be called with c.mutex held
   113  func (c *GlobalServiceCache) delete(globalService *GlobalService, clusterName string, serviceNN types.NamespacedName) bool {
   114  	scopedLog := log.WithFields(logrus.Fields{
   115  		logfields.ServiceName: serviceNN.String(),
   116  		logfields.ClusterName: clusterName,
   117  	})
   118  
   119  	if _, ok := globalService.ClusterServices[clusterName]; !ok {
   120  		scopedLog.Debug("Ignoring delete request for unknown cluster")
   121  		return false
   122  	}
   123  
   124  	scopedLog.Debugf("Deleted service definition of remote cluster")
   125  	delete(globalService.ClusterServices, clusterName)
   126  
   127  	// After the last cluster service is removed, remove the global service
   128  	if len(globalService.ClusterServices) == 0 {
   129  		scopedLog.Debugf("Deleted global service %s", serviceNN.String())
   130  		delete(c.byName, serviceNN)
   131  		c.metricTotalGlobalServices.Set(float64(len(c.byName)))
   132  	}
   133  
   134  	return true
   135  }
   136  
   137  func (c *GlobalServiceCache) OnDelete(svc *serviceStore.ClusterService) bool {
   138  	scopedLog := log.WithFields(logrus.Fields{logfields.ServiceName: svc.String()})
   139  	scopedLog.Debug("Delete event for service")
   140  
   141  	c.mutex.Lock()
   142  	defer c.mutex.Unlock()
   143  
   144  	if globalService, ok := c.byName[svc.NamespaceServiceName()]; ok {
   145  		return c.delete(globalService, svc.Cluster, svc.NamespaceServiceName())
   146  	} else {
   147  		scopedLog.Debugf("Ignoring delete request for unknown global service")
   148  		return false
   149  	}
   150  }
   151  
   152  // Size returns the number of global services in the cache
   153  func (c *GlobalServiceCache) Size() (num int) {
   154  	c.mutex.RLock()
   155  	num = len(c.byName)
   156  	c.mutex.RUnlock()
   157  	return
   158  }
   159  
   160  type remoteServiceObserver struct {
   161  	log logrus.FieldLogger
   162  
   163  	cache *GlobalServiceCache
   164  
   165  	onUpdate func(*serviceStore.ClusterService)
   166  	onDelete func(*serviceStore.ClusterService)
   167  }
   168  
   169  // NewSharedServicesObserver returns an observer implementing the logic to convert
   170  // and filter shared services notifications, update the global service cache and
   171  // call the upstream handlers when appropriate.
   172  func NewSharedServicesObserver(
   173  	log logrus.FieldLogger, cache *GlobalServiceCache,
   174  	onUpdate, onDelete func(*serviceStore.ClusterService),
   175  ) store.Observer {
   176  	return &remoteServiceObserver{
   177  		log:   log,
   178  		cache: cache,
   179  
   180  		onUpdate: onUpdate,
   181  		onDelete: onDelete,
   182  	}
   183  }
   184  
   185  // OnUpdate is called when a service in a remote cluster is updated
   186  func (r *remoteServiceObserver) OnUpdate(key store.Key) {
   187  	svc := &(key.(*serviceStore.ValidatingClusterService).ClusterService)
   188  	scopedLog := r.log.WithFields(logrus.Fields{logfields.ServiceName: svc.String()})
   189  	scopedLog.Debug("Received remote service update event")
   190  
   191  	// Short-circuit the handling of non-shared services
   192  	if !svc.Shared {
   193  		if r.cache.Has(svc) {
   194  			scopedLog.Debug("Previously shared service is no longer shared: triggering deletion event")
   195  			r.OnDelete(key)
   196  		} else {
   197  			scopedLog.Debug("Ignoring remote service update: service is not shared")
   198  		}
   199  		return
   200  	}
   201  
   202  	r.cache.OnUpdate(svc)
   203  	r.onUpdate(svc)
   204  }
   205  
   206  // OnDelete is called when a service in a remote cluster is deleted
   207  func (r *remoteServiceObserver) OnDelete(key store.NamedKey) {
   208  	svc := &(key.(*serviceStore.ValidatingClusterService).ClusterService)
   209  	scopedLog := r.log.WithFields(logrus.Fields{logfields.ServiceName: svc.String()})
   210  	scopedLog.Debug("Received remote service delete event")
   211  
   212  	// Short-circuit the deletion logic if the service was not present (i.e., not shared)
   213  	if !r.cache.OnDelete(svc) {
   214  		scopedLog.Debug("Ignoring remote service delete. Service was not shared")
   215  		return
   216  	}
   217  
   218  	r.onDelete(svc)
   219  }