github.com/cilium/cilium@v1.16.2/operator/pkg/ciliumendpointslice/manager.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package ciliumendpointslice 5 6 import ( 7 "github.com/sirupsen/logrus" 8 9 cilium_v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1" 10 "github.com/cilium/cilium/pkg/k8s/resource" 11 "github.com/cilium/cilium/pkg/logging/logfields" 12 ) 13 14 var ( 15 // sequentialLetters contains lower case alphabets without vowels and few numbers. 16 // skipped vowels and numbers [0, 1] to avoid generating controversial names. 17 sequentialLetters = []rune("bcdfghjklmnpqrstvwxyz2456789") 18 ) 19 20 // operations is an interface to all operations that a CES manager can perform. 21 type operations interface { 22 // External APIs to Insert/Remove CEP in local dataStore 23 UpdateCEPMapping(cep *cilium_v2.CoreCiliumEndpoint, ns string) []CESName 24 RemoveCEPMapping(cep *cilium_v2.CoreCiliumEndpoint, ns string) CESName 25 26 initializeMappingForCES(ces *cilium_v2.CiliumEndpointSlice) CESName 27 initializeMappingCEPtoCES(cep *cilium_v2.CoreCiliumEndpoint, ns string, ces CESName) 28 29 getCEPCountInCES(ces CESName) int 30 getCEPinCES(ces CESName) []CEPName 31 getCESData(ces CESName) CESData 32 isCEPinCES(cep CEPName, ces CESName) bool 33 } 34 35 // cesMgr is used to batch CEP into a CES, based on FirstComeFirstServe. If a new CEP 36 // is inserted, then the CEP is queued in any one of the available CES. CEPs are 37 // inserted into CESs without any preference or any priority. 38 type cesMgr struct { 39 logger logrus.FieldLogger 40 // mapping is used to map CESName to CESTracker[i.e. list of CEPs], 41 // as well as CEPName to CESName. 42 mapping *CESToCEPMapping 43 44 // maxCEPsInCES is the maximum number of CiliumCoreEndpoint(s) packed in 45 // a CiliumEndpointSlice Resource. 46 maxCEPsInCES int 47 } 48 49 // cesManagerFcfs use cesMgr by design, it inherits all the methods from the base cesMgr and there is no 50 // special handling required for cesManagerFcfs. 51 // cesManagerFcfs indicates ciliumEndpoints are batched based on FirstComeFirtServe algorithm. 52 // refer cesMgr comments for more information. 53 type cesManagerFcfs struct { 54 cesMgr 55 } 56 57 // cesManagerIdentity is used to batch CEPs in CES based on CEP identity. 58 type cesManagerIdentity struct { 59 cesMgr 60 // CEP identity to cesTracker map 61 identityToCES map[int64][]CESName 62 // reverse map of identityToCES i.e. cesName to CEP identity 63 cesToIdentity map[CESName]int64 64 } 65 66 // newCESManagerFcfs creates and initializes a new FirstComeFirstServe based CES 67 // manager, in this mode CEPs are batched based on FirstComeFirtServe algorithm. 68 func newCESManagerFcfs(maxCEPsInCES int, logger logrus.FieldLogger) operations { 69 return &cesManagerFcfs{ 70 cesMgr{ 71 logger: logger, 72 mapping: newCESToCEPMapping(), 73 maxCEPsInCES: maxCEPsInCES, 74 }, 75 } 76 } 77 78 // newCESManagerIdentity creates and initializes a new Identity based manager. 79 func newCESManagerIdentity(maxCEPsInCES int, logger logrus.FieldLogger) operations { 80 return &cesManagerIdentity{ 81 cesMgr: cesMgr{ 82 logger: logger, 83 mapping: newCESToCEPMapping(), 84 maxCEPsInCES: maxCEPsInCES, 85 }, 86 identityToCES: make(map[int64][]CESName), 87 cesToIdentity: make(map[CESName]int64), 88 } 89 } 90 91 // This function create a new ces and capacity to hold maximum ceps in a CES. 92 // This is called in 2 different scenarios: 93 // 1. During runtime, when ces manager decides to create a new ces, it calls 94 // with an empty name, it generates a random unique name and assign it to the CES. 95 // 2. During operator warm boot [after crash or software upgrade], slicing manager 96 // creates a CES, by passing unique name. 97 func (c *cesMgr) createCES(name, ns string) CESName { 98 if name == "" { 99 name = uniqueCESliceName(c.mapping) 100 } 101 cesName := NewCESName(name) 102 c.mapping.insertCES(cesName, ns) 103 c.logger.WithFields(logrus.Fields{ 104 logfields.CESName: cesName.string(), 105 }).Debug("Generated CES") 106 return cesName 107 } 108 109 // UpdateCEPMapping is used to insert CEP in local cache, this may result in creating a new 110 // CES object or updating an existing CES object. 111 func (c *cesManagerFcfs) UpdateCEPMapping(cep *cilium_v2.CoreCiliumEndpoint, ns string) []CESName { 112 cepName := GetCEPNameFromCCEP(cep, ns) 113 c.logger.WithFields(logrus.Fields{ 114 logfields.CEPName: cepName.string(), 115 }).Debug("Insert CEP in local cache") 116 // check the given cep is already exists in any of the CES. 117 // if yes, Update a ces with the given cep object. 118 cesName, exists := c.mapping.getCESName(cepName) 119 if exists { 120 c.logger.WithFields(logrus.Fields{ 121 logfields.CEPName: cepName.string(), 122 logfields.CESName: cesName.string(), 123 }).Debug("CEP already mapped to CES") 124 return []CESName{cesName} 125 } 126 127 // Get the largest available CES. 128 // This ensures the minimum number of CES updates, as the CESs will be 129 // consistently filled up in order. 130 cesName = c.getLargestAvailableCESForNamespace(ns) 131 if cesName.Name == "" { 132 cesName = c.createCES("", ns) 133 } 134 c.mapping.insertCEP(cepName, cesName) 135 c.logger.WithFields(logrus.Fields{ 136 logfields.CEPName: cepName.string(), 137 logfields.CESName: cesName.string(), 138 }).Debug("CEP mapped to CES") 139 return []CESName{cesName} 140 } 141 142 func (c *cesManagerFcfs) RemoveCEPMapping(cep *cilium_v2.CoreCiliumEndpoint, ns string) CESName { 143 cepName := GetCEPNameFromCCEP(cep, ns) 144 c.logger.WithFields(logrus.Fields{ 145 logfields.CEPName: cepName.string(), 146 }).Debug("Removing CEP from local cache") 147 cesName, exists := c.mapping.getCESName(cepName) 148 if exists { 149 c.logger.WithFields(logrus.Fields{ 150 logfields.CEPName: cepName.string(), 151 logfields.CESName: cesName.string(), 152 }).Debug("Removing CEP from CES") 153 c.mapping.deleteCEP(cepName) 154 if c.mapping.countCEPsInCES(cesName) == 0 { 155 c.mapping.deleteCES(cesName) 156 } 157 return cesName 158 } 159 return CESName(resource.Key{}) 160 } 161 162 // getLargestAvailableCESForNamespace returns the largest CES from cache for the 163 // specified namespace that has at least 1 CEP and 1 available spot (less than 164 // maximum CEPs). If it is not found, a nil is returned. 165 func (c *cesManagerFcfs) getLargestAvailableCESForNamespace(ns string) CESName { 166 largestCEPCount := 0 167 selectedCES := CESName(resource.Key{}) 168 for _, ces := range c.mapping.getAllCESs() { 169 cepCount := c.mapping.countCEPsInCES(ces) 170 if cepCount < c.maxCEPsInCES && cepCount > largestCEPCount && c.mapping.getCESData(ces).ns == ns { 171 selectedCES = ces 172 largestCEPCount = cepCount 173 if largestCEPCount == c.maxCEPsInCES-1 { 174 break 175 } 176 } 177 } 178 return selectedCES 179 } 180 181 // UpdateCEPMapping is used to insert CEP in local cache, this may result in creating a new 182 // CES object or updating an existing CES object. CEPs are grouped based on CEP identity. 183 func (c *cesManagerIdentity) UpdateCEPMapping(cep *cilium_v2.CoreCiliumEndpoint, ns string) []CESName { 184 // check the given cep is already exists in any of the CES. 185 // if yes, compare the given CEP Identity with the CEPs stored in CES. 186 // If they are same UPDATE the CEP in the CES. This will trigger CES UPDATE to k8s-apiserver. 187 // If the Identities differ, remove the CEP from the existing CES 188 // and find a new CES to batch the given CEP in a CES. This will trigger following actions, 189 // 1) CES UPDATE to k8s-apiserver, removing CEP in old CES 190 // 2) CES CREATE to k8s-apiserver, inserting the given CEP in a new CES or 191 // 3) CES UPDATE to k8s-apiserver, inserting the given CEP in existing CES 192 cepName := GetCEPNameFromCCEP(cep, ns) 193 c.logger.WithFields(logrus.Fields{ 194 logfields.CEPName: cepName.string(), 195 }).Debug("Insert CEP in local cache") 196 var cesName CESName 197 var exists bool 198 removedFromCES := CESName(resource.Key{}) 199 if cesName, exists = c.mapping.getCESName(cepName); exists { 200 if c.cesToIdentity[cesName] != cep.IdentityID { 201 c.logger.WithFields(logrus.Fields{ 202 logfields.CEPName: cepName.string(), 203 logfields.CESName: cesName.string(), 204 logfields.OldIdentity: c.cesToIdentity[cesName], 205 logfields.Identity: cep.IdentityID, 206 }).Debug("CEP already mapped to CES but identity has changed") 207 removedFromCES = cesName 208 c.mapping.deleteCEP(cepName) 209 } else { 210 c.logger.WithFields(logrus.Fields{ 211 logfields.CEPName: cepName.string(), 212 logfields.CESName: cesName.string(), 213 }).Debug("CEP already mapped to CES") 214 return []CESName{cesName} 215 } 216 } 217 218 // If given cep object isn't packed in any of the CES. find a new ces 219 // to pack this cep. 220 cesName = c.getLargestAvailableCESForIdentity(cep.IdentityID, ns) 221 if cesName.Name == "" { 222 cesName = c.createCES("", ns) 223 // Update the identityToCES and cesToIdentity maps respectively. 224 c.identityToCES[cep.IdentityID] = append(c.identityToCES[cep.IdentityID], cesName) 225 c.cesToIdentity[cesName] = cep.IdentityID 226 } 227 c.mapping.insertCEP(cepName, cesName) 228 c.logger.WithFields(logrus.Fields{ 229 logfields.CEPName: cepName.string(), 230 logfields.CESName: cesName.string(), 231 }).Debug("CEP mapped to CES") 232 return []CESName{removedFromCES, cesName} 233 } 234 235 func (c *cesManagerIdentity) getLargestAvailableCESForIdentity(id int64, ns string) CESName { 236 largestCEPCount := 0 237 selectedCES := CESName(resource.Key{}) 238 if cess, exist := c.identityToCES[id]; exist { 239 for _, ces := range cess { 240 cepCount := c.mapping.countCEPsInCES(ces) 241 if cepCount < c.maxCEPsInCES && cepCount > largestCEPCount && c.mapping.getCESData(ces).ns == ns { 242 selectedCES = ces 243 largestCEPCount = cepCount 244 if largestCEPCount == c.maxCEPsInCES-1 { 245 break 246 } 247 } 248 } 249 } 250 return selectedCES 251 } 252 253 func (c *cesManagerIdentity) RemoveCEPMapping(cep *cilium_v2.CoreCiliumEndpoint, ns string) CESName { 254 cepName := GetCEPNameFromCCEP(cep, ns) 255 c.logger.WithFields(logrus.Fields{ 256 logfields.CEPName: cepName.string(), 257 }).Debug("Removing CEP from local cache") 258 cesName, exists := c.mapping.getCESName(cepName) 259 if exists { 260 c.logger.WithFields(logrus.Fields{ 261 logfields.CEPName: cepName.string(), 262 logfields.CESName: cesName.string(), 263 }).Debug("Removing CEP from CES") 264 c.mapping.deleteCEP(cepName) 265 if c.mapping.countCEPsInCES(cesName) == 0 { 266 c.removeCESToIdentity(cep.IdentityID, cesName) 267 c.mapping.deleteCES(cesName) 268 } 269 return cesName 270 } 271 return CESName(resource.Key{}) 272 } 273 274 func (c *cesManagerIdentity) removeCESToIdentity(id int64, cesName CESName) { 275 cesSlice := c.identityToCES[id] 276 removed := 0 277 for i, ces := range cesSlice { 278 if ces == cesName { 279 cesSlice[i] = cesSlice[len(cesSlice)-1] 280 removed = removed + 1 281 } 282 } 283 if removed < len(cesSlice) { 284 c.identityToCES[id] = cesSlice[:len(cesSlice)-removed] 285 } else { 286 delete(c.identityToCES, id) 287 } 288 delete(c.cesToIdentity, cesName) 289 } 290 291 // initializeMappingCEPtoCES overrides the same method on cesMgr and is used to 292 // populate the local cache for the given CEP, including identity-related maps 293 // specific to the cesManagerIdentity. 294 func (c *cesManagerIdentity) initializeMappingCEPtoCES(cep *cilium_v2.CoreCiliumEndpoint, ns string, ces CESName) { 295 cepName := GetCEPNameFromCCEP(cep, ns) 296 c.mapping.insertCEP(cepName, ces) 297 c.identityToCES[cep.IdentityID] = append(c.identityToCES[cep.IdentityID], ces) 298 c.cesToIdentity[ces] = cep.IdentityID 299 } 300 301 func (c *cesMgr) initializeMappingForCES(ces *cilium_v2.CiliumEndpointSlice) CESName { 302 return c.createCES(ces.Name, ces.Namespace) 303 } 304 305 func (c *cesMgr) initializeMappingCEPtoCES(cep *cilium_v2.CoreCiliumEndpoint, ns string, ces CESName) { 306 cepName := GetCEPNameFromCCEP(cep, ns) 307 c.mapping.insertCEP(cepName, ces) 308 } 309 310 func (c *cesMgr) getCEPCountInCES(ces CESName) int { 311 return c.mapping.countCEPsInCES(ces) 312 } 313 314 func (c *cesMgr) getCESData(ces CESName) CESData { 315 return c.mapping.getCESData(ces) 316 } 317 318 func (c *cesMgr) getCEPinCES(ces CESName) []CEPName { 319 return c.mapping.getCEPsInCES(ces) 320 } 321 322 func (c *cesMgr) isCEPinCES(cep CEPName, ces CESName) bool { 323 mappedCES, exists := c.mapping.getCESName(cep) 324 return exists && mappedCES.Name == ces.Name 325 }