github.com/cilium/cilium@v1.16.2/pkg/k8s/watchers/cilium_endpoint_slice_subscriber.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package watchers
     5  
     6  import (
     7  	"github.com/sirupsen/logrus"
     8  
     9  	"github.com/cilium/cilium/pkg/endpoint"
    10  	"github.com/cilium/cilium/pkg/k8s"
    11  	cilium_v2a1 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1"
    12  	"github.com/cilium/cilium/pkg/k8s/types"
    13  	k8sUtils "github.com/cilium/cilium/pkg/k8s/utils"
    14  	"github.com/cilium/cilium/pkg/lock"
    15  	"github.com/cilium/cilium/pkg/metrics"
    16  	"github.com/cilium/cilium/pkg/time"
    17  )
    18  
    19  type endpointWatcher interface {
    20  	endpointUpdated(oldC, newC *types.CiliumEndpoint)
    21  	endpointDeleted(c *types.CiliumEndpoint)
    22  }
    23  
    24  type localEndpointCache interface {
    25  	LookupCEPName(namespacedName string) *endpoint.Endpoint
    26  }
    27  
    28  type cesSubscriber struct {
    29  	epWatcher endpointWatcher
    30  	epCache   localEndpointCache
    31  	cepMap    *cepToCESmap
    32  }
    33  
    34  func newCESSubscriber(k *K8sCiliumEndpointsWatcher) *cesSubscriber {
    35  	return &cesSubscriber{
    36  		epWatcher: k,
    37  		epCache:   k.endpointManager,
    38  		cepMap:    newCEPToCESMap(),
    39  	}
    40  }
    41  
    42  // OnAdd invoked for newly created CESs, iterates over coreCEPs
    43  // packed in the CES, converts coreCEP into types.CEP and calls endpointUpdated only for remoteNode CEPs.
    44  func (cs *cesSubscriber) OnAdd(ces *cilium_v2a1.CiliumEndpointSlice) {
    45  	for i, ep := range ces.Endpoints {
    46  		CEPName := ces.Namespace + "/" + ep.Name
    47  		log.WithFields(logrus.Fields{
    48  			"CESName": ces.GetName(),
    49  			"CEPName": CEPName,
    50  		}).Debug("CES added, calling CoreEndpointUpdate")
    51  		cep := k8s.ConvertCoreCiliumEndpointToTypesCiliumEndpoint(&ces.Endpoints[i], ces.Namespace)
    52  		if p := cs.epCache.LookupCEPName(k8sUtils.GetObjNamespaceName(cep)); p != nil {
    53  			timeSinceCepCreated := time.Since(p.GetCreatedAt())
    54  			metrics.EndpointPropagationDelay.WithLabelValues().Observe(timeSinceCepCreated.Seconds())
    55  		}
    56  		// Map cep name to CES name
    57  		cs.addCEPwithCES(CEPName, ces.GetName(), cep)
    58  	}
    59  }
    60  
    61  // OnUpdate invoked for modified CESs, it compares old CES and new CES objects
    62  // determines below things
    63  // 1) any coreCEPs are removed from CES
    64  // 2) any new coreCEPs are packed in CES
    65  // 3) any existing coreCEPs are modified in CES
    66  // call endpointUpdated/endpointDeleted only for remote node CEPs.
    67  func (cs *cesSubscriber) OnUpdate(oldCES, newCES *cilium_v2a1.CiliumEndpointSlice) {
    68  	oldCEPs := make(map[string]*cilium_v2a1.CoreCiliumEndpoint, len(oldCES.Endpoints))
    69  	for i, ep := range oldCES.Endpoints {
    70  		oldCEPs[oldCES.Namespace+"/"+ep.Name] = &oldCES.Endpoints[i]
    71  	}
    72  
    73  	newCEPs := make(map[string]*cilium_v2a1.CoreCiliumEndpoint, len(newCES.Endpoints))
    74  	for i, ep := range newCES.Endpoints {
    75  		newCEPs[newCES.Namespace+"/"+ep.Name] = &newCES.Endpoints[i]
    76  	}
    77  
    78  	// Handle, removed CEPs from the CES.
    79  	// old CES would have one or more stale cep entries, remove stale CEPs from oldCES.
    80  	for CEPName, oldCEP := range oldCEPs {
    81  		if _, exists := newCEPs[CEPName]; !exists {
    82  			log.WithFields(logrus.Fields{
    83  				"CESName": newCES.GetName(),
    84  				"CEPName": CEPName,
    85  			}).Debug("CEP deleted, calling endpointDeleted")
    86  			cep := k8s.ConvertCoreCiliumEndpointToTypesCiliumEndpoint(oldCEP, oldCES.Namespace)
    87  			// LocalNode already has the latest CEP.
    88  			// Hence, skip processing endpointupdate for localNode CEPs.
    89  			if p := cs.epCache.LookupCEPName(k8sUtils.GetObjNamespaceName(cep)); p != nil {
    90  				continue
    91  			}
    92  			cs.deleteCEPfromCES(CEPName, newCES.GetName(), cep)
    93  		}
    94  	}
    95  
    96  	// Handle any new CEPs inserted in the CES.
    97  	for CEPName, newCEP := range newCEPs {
    98  		if _, exists := oldCEPs[CEPName]; !exists {
    99  			log.WithFields(logrus.Fields{
   100  				"CESName": newCES.GetName(),
   101  				"CEPName": CEPName,
   102  			}).Debug("CEP inserted, calling endpointUpdated")
   103  			cep := k8s.ConvertCoreCiliumEndpointToTypesCiliumEndpoint(newCEP, newCES.Namespace)
   104  			if p := cs.epCache.LookupCEPName(k8sUtils.GetObjNamespaceName(cep)); p != nil {
   105  				timeSinceCepCreated := time.Since(p.GetCreatedAt())
   106  				metrics.EndpointPropagationDelay.WithLabelValues().Observe(timeSinceCepCreated.Seconds())
   107  			}
   108  			cs.addCEPwithCES(CEPName, newCES.GetName(), cep)
   109  		}
   110  	}
   111  
   112  	// process if any CEP value changed from old to new
   113  	for CEPName, newCEP := range newCEPs {
   114  		if oldCEP, exists := oldCEPs[CEPName]; exists {
   115  			if oldCEP.DeepEqual(newCEP) {
   116  				continue
   117  			}
   118  			log.WithFields(logrus.Fields{
   119  				"CESName": newCES.GetName(),
   120  				"CEPName": CEPName,
   121  			}).Debug("CES updated, calling endpointUpdated")
   122  			newC := k8s.ConvertCoreCiliumEndpointToTypesCiliumEndpoint(newCEP, newCES.Namespace)
   123  			cs.addCEPwithCES(CEPName, newCES.GetName(), newC)
   124  		}
   125  	}
   126  }
   127  
   128  // OnDelete invoked for deleted CESs, iterates over coreCEPs
   129  // and calls endpointDeleted only for remoteNode CEPs.
   130  func (cs *cesSubscriber) OnDelete(ces *cilium_v2a1.CiliumEndpointSlice) {
   131  	for i, ep := range ces.Endpoints {
   132  		CEPName := ces.Namespace + "/" + ep.Name
   133  		log.WithFields(logrus.Fields{
   134  			"CESName": ces.GetName(),
   135  			"CEPName": CEPName,
   136  		}).Debug("CES deleted, calling endpointDeleted")
   137  		cep := k8s.ConvertCoreCiliumEndpointToTypesCiliumEndpoint(&ces.Endpoints[i], ces.Namespace)
   138  		// LocalNode already deleted the CEP.
   139  		// Hence, skip processing endpointDeleted for localNode CEPs.
   140  		if p := cs.epCache.LookupCEPName(k8sUtils.GetObjNamespaceName(cep)); p != nil {
   141  			continue
   142  		}
   143  		// Delete CEP if and only if that CEP is owned by a CES, that was used during CES updated.
   144  		// Delete CEP only if there is match in CEPToCES map and also delete CEPName in CEPToCES map.
   145  		cs.deleteCEPfromCES(CEPName, ces.GetName(), cep)
   146  	}
   147  }
   148  
   149  // deleteCEP deletes the CEP and CES from the map.
   150  // If this was last CES for the CEP it triggers endpointDeleted.
   151  // If this was used CES for the CEP it picks other CES and triggers endpointUpdated.
   152  func (cs *cesSubscriber) deleteCEPfromCES(CEPName, CESName string, c *types.CiliumEndpoint) {
   153  	cs.cepMap.cesMutex.Lock()
   154  	defer cs.cepMap.cesMutex.Unlock()
   155  	needUpdate := cs.cepMap.currentCES[CEPName] == CESName
   156  	cs.cepMap.deleteCEPLocked(CEPName, CESName)
   157  	if !needUpdate {
   158  		return
   159  	}
   160  	cep, exists := cs.cepMap.getCEPLocked(CEPName)
   161  	if !exists {
   162  		log.WithFields(logrus.Fields{
   163  			"CESName": CESName,
   164  			"CEPName": CEPName,
   165  		}).Info("CEP deleted, calling endpointDeleted")
   166  		cs.epWatcher.endpointDeleted(c)
   167  	} else {
   168  		log.WithFields(logrus.Fields{
   169  			"CESName": CESName,
   170  			"CEPName": CEPName,
   171  		}).Info("CEP deleted, other CEP exists, calling endpointUpdated")
   172  		cs.epWatcher.endpointUpdated(c, cep)
   173  	}
   174  }
   175  
   176  // addCEPwithCES insert CEP with CES to the map and triggers endpointUpdated.
   177  func (cs *cesSubscriber) addCEPwithCES(CEPName, CESName string, newCep *types.CiliumEndpoint) {
   178  	cs.cepMap.cesMutex.Lock()
   179  	defer cs.cepMap.cesMutex.Unlock()
   180  	// Not checking if exists because it's fine and WAI if oldCep is nil.
   181  	// When there is no previous endpoint the endpointUpdated should be called with nil.
   182  	oldCep, _ := cs.cepMap.getCEPLocked(CEPName)
   183  	cs.cepMap.insertCEPLocked(CEPName, CESName, newCep)
   184  	cs.epWatcher.endpointUpdated(oldCep, newCep)
   185  }
   186  
   187  type cesToCEPRef map[string]*types.CiliumEndpoint
   188  
   189  // cepToCESmap is used to map CiliumEndpoint name to CiliumEndpointSlice names.
   190  // In steady state, there should be exactly one CiliumEndpointSlice associated
   191  // with a CiliumEndpoint. But when a CEP is being transferred between two CESes,
   192  // there will be a brief period of time in which the CEP exists in both the CESes.
   193  type cepToCESmap struct {
   194  	// cesMutex is used to lock all the operations changing cepMap and ipcache.
   195  	cesMutex lock.Mutex
   196  	// Maps CEP by name to a map of CES and pointer to CiliumEndpoint.
   197  	// In rare case when CEP exists in multiple CESs it would contain all the
   198  	// occurrences. This is needed to retrieve currently used Cilium Endpoint
   199  	// (cepMap[cepName][currentCES[cepName]]) when update comes and to pick other
   200  	// representation when the current one is deleted and other exist.
   201  	// The Cilium Endpoint pointers will point to different objects from different
   202  	// CES. They may or may not be equal to each other.
   203  	cepMap map[string]cesToCEPRef
   204  	// map of CEP name and currently used CES name.
   205  	// Current CEP is cepMap[CEP][currentCES[CEP]]
   206  	currentCES map[string]string
   207  }
   208  
   209  func newCEPToCESMap() *cepToCESmap {
   210  	return &cepToCESmap{
   211  		cepMap:     make(map[string]cesToCEPRef),
   212  		currentCES: make(map[string]string),
   213  	}
   214  }
   215  
   216  func (c *cepToCESmap) insertCEPLocked(cepName, cesName string, cep *types.CiliumEndpoint) {
   217  	if _, exists := c.cepMap[cepName]; !exists {
   218  		c.cepMap[cepName] = make(map[string]*types.CiliumEndpoint)
   219  	}
   220  	c.cepMap[cepName][cesName] = cep
   221  	c.currentCES[cepName] = cesName
   222  }
   223  
   224  func (c *cepToCESmap) deleteCEPLocked(cepName, cesName string) {
   225  	cesToCEPMap, exists := c.cepMap[cepName]
   226  	if !exists {
   227  		return
   228  	}
   229  	if _, exists = cesToCEPMap[cesName]; !exists {
   230  		return
   231  	}
   232  	if len(cesToCEPMap) == 1 {
   233  		delete(c.cepMap, cepName)
   234  		delete(c.currentCES, cepName)
   235  	} else {
   236  		delete(cesToCEPMap, cesName)
   237  		if c.currentCES[cepName] == cesName {
   238  			for k := range cesToCEPMap {
   239  				c.currentCES[cepName] = k
   240  				break
   241  			}
   242  		}
   243  	}
   244  }
   245  
   246  // getCEPLocked returns a currently used CEP associated with one of the CESes for the given CEP name.
   247  func (c *cepToCESmap) getCEPLocked(cepName string) (*types.CiliumEndpoint, bool) {
   248  	cep, exists := c.cepMap[cepName][c.currentCES[cepName]]
   249  	return cep, exists
   250  }