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 }