github.com/cilium/cilium@v1.16.2/pkg/identity/cache/local.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package cache 5 6 import ( 7 "fmt" 8 9 "github.com/cilium/cilium/pkg/allocator" 10 "github.com/cilium/cilium/pkg/identity" 11 "github.com/cilium/cilium/pkg/identity/key" 12 "github.com/cilium/cilium/pkg/idpool" 13 "github.com/cilium/cilium/pkg/labels" 14 "github.com/cilium/cilium/pkg/lock" 15 "github.com/cilium/cilium/pkg/logging/logfields" 16 ) 17 18 type localIdentityCache struct { 19 mutex lock.RWMutex 20 identitiesByID map[identity.NumericIdentity]*identity.Identity 21 identitiesByLabels map[string]*identity.Identity 22 nextNumericIdentity identity.NumericIdentity 23 scope identity.NumericIdentity 24 minID identity.NumericIdentity 25 maxID identity.NumericIdentity 26 events allocator.AllocatorEventSendChan 27 28 // withheldIdentities is a set of identities that should be considered unavailable for allocation, 29 // but not yet allocated. 30 // They are used during agent restart, where local identities are restored to prevent unnecessary 31 // ID flapping on restart. 32 // 33 // If an old nID is passed to lookupOrCreate(), then it is allowed to use a withhend entry here. Otherwise 34 // it must allocate a new ID not in this set. 35 withheldIdentities map[identity.NumericIdentity]struct{} 36 } 37 38 func newLocalIdentityCache(scope, minID, maxID identity.NumericIdentity, events allocator.AllocatorEventSendChan) *localIdentityCache { 39 return &localIdentityCache{ 40 identitiesByID: map[identity.NumericIdentity]*identity.Identity{}, 41 identitiesByLabels: map[string]*identity.Identity{}, 42 nextNumericIdentity: minID, 43 scope: scope, 44 minID: minID, 45 maxID: maxID, 46 events: events, 47 withheldIdentities: map[identity.NumericIdentity]struct{}{}, 48 } 49 } 50 51 func (l *localIdentityCache) bumpNextNumericIdentity() { 52 if l.nextNumericIdentity == l.maxID { 53 l.nextNumericIdentity = l.minID 54 } else { 55 l.nextNumericIdentity++ 56 } 57 } 58 59 // getNextFreeNumericIdentity returns the next available numeric identity or an error 60 // If idCandidate has the local scope and is available, it will be returned instead of 61 // searching for a new numeric identity. 62 // The l.mutex must be held 63 func (l *localIdentityCache) getNextFreeNumericIdentity(idCandidate identity.NumericIdentity) (identity.NumericIdentity, error) { 64 // Try first with the given candidate 65 if idCandidate.Scope() == l.scope { 66 if _, taken := l.identitiesByID[idCandidate]; !taken { 67 // let nextNumericIdentity be, allocated identities will be skipped anyway 68 log.Debugf("Reallocated restored local identity: %d", idCandidate) 69 return idCandidate, nil 70 } else { 71 log.WithField(logfields.Identity, idCandidate).Debug("Requested local identity not available to allocate") 72 } 73 } 74 firstID := l.nextNumericIdentity 75 for { 76 idCandidate = l.nextNumericIdentity | l.scope 77 _, taken := l.identitiesByID[idCandidate] 78 _, withheld := l.withheldIdentities[idCandidate] 79 if !taken && !withheld { 80 l.bumpNextNumericIdentity() 81 return idCandidate, nil 82 } 83 84 l.bumpNextNumericIdentity() 85 if l.nextNumericIdentity == firstID { 86 // Desperation: no local identities left (unlikely). If there are withheld 87 // but not-taken identities, claim one of them. 88 for withheldID := range l.withheldIdentities { 89 if _, taken := l.identitiesByID[withheldID]; !taken { 90 delete(l.withheldIdentities, withheldID) 91 log.WithField(logfields.Identity, withheldID).Warn("Local identity allocator full; claiming first withheld identity. This may cause momentary policy drops") 92 return withheldID, nil 93 } 94 } 95 96 return 0, fmt.Errorf("out of local identity space") 97 } 98 } 99 } 100 101 // lookupOrCreate searches for the existence of a local identity with the given 102 // labels. If it exists, the reference count is incremented and the identity is 103 // returned. If it does not exist, a new identity is created with a unique 104 // numeric identity. All identities returned by lookupOrCreate() must be 105 // released again via localIdentityCache.release(). 106 // A possible previously used numeric identity for these labels can be passed 107 // in as the 'oldNID' parameter; identity.InvalidIdentity must be passed if no 108 // previous numeric identity exists. 'oldNID' will be reallocated if available. 109 func (l *localIdentityCache) lookupOrCreate(lbls labels.Labels, oldNID identity.NumericIdentity, notifyOwner bool) (*identity.Identity, bool, error) { 110 // Not converting to string saves an allocation, as byte key lookups into 111 // string maps are optimized by the compiler, see 112 // https://github.com/golang/go/issues/3512. 113 repr := lbls.SortedList() 114 115 l.mutex.Lock() 116 defer l.mutex.Unlock() 117 118 if id, ok := l.identitiesByLabels[string(repr)]; ok { 119 id.ReferenceCount++ 120 return id, false, nil 121 } 122 123 numericIdentity, err := l.getNextFreeNumericIdentity(oldNID) 124 if err != nil { 125 return nil, false, err 126 } 127 128 id := &identity.Identity{ 129 ID: numericIdentity, 130 Labels: lbls, 131 LabelArray: lbls.LabelArray(), 132 ReferenceCount: 1, 133 } 134 135 l.identitiesByLabels[string(repr)] = id 136 l.identitiesByID[numericIdentity] = id 137 138 if l.events != nil && notifyOwner { 139 l.events <- allocator.AllocatorEvent{ 140 Typ: allocator.AllocatorChangeUpsert, 141 ID: idpool.ID(id.ID), 142 Key: &key.GlobalIdentity{LabelArray: id.LabelArray}, 143 } 144 } 145 146 return id, true, nil 147 } 148 149 // release releases a local identity from the cache. true is returned when the 150 // last use of the identity has been released and the identity has been 151 // forgotten. 152 func (l *localIdentityCache) release(id *identity.Identity, notifyOwner bool) bool { 153 l.mutex.Lock() 154 defer l.mutex.Unlock() 155 156 if id, ok := l.identitiesByID[id.ID]; ok { 157 switch { 158 case id.ReferenceCount > 1: 159 id.ReferenceCount-- 160 return false 161 162 case id.ReferenceCount == 1: 163 // Release is only attempted once, when the reference count is 164 // hitting the last use 165 delete(l.identitiesByLabels, string(id.Labels.SortedList())) 166 delete(l.identitiesByID, id.ID) 167 168 if l.events != nil && notifyOwner { 169 l.events <- allocator.AllocatorEvent{ 170 Typ: allocator.AllocatorChangeDelete, 171 ID: idpool.ID(id.ID), 172 } 173 } 174 175 return true 176 } 177 } 178 179 return false 180 } 181 182 // withhold marks the nids as unavailable. Any out-of-scope identities are returned. 183 func (l *localIdentityCache) withhold(nids []identity.NumericIdentity) []identity.NumericIdentity { 184 if len(nids) == 0 { 185 return nil 186 } 187 188 unused := make([]identity.NumericIdentity, 0, len(nids)) 189 l.mutex.Lock() 190 defer l.mutex.Unlock() 191 for _, nid := range nids { 192 if nid.Scope() != l.scope { 193 unused = append(unused, nid) 194 continue 195 } 196 l.withheldIdentities[nid] = struct{}{} 197 } 198 199 return unused 200 } 201 202 func (l *localIdentityCache) unwithhold(nids []identity.NumericIdentity) { 203 if len(nids) == 0 { 204 return 205 } 206 l.mutex.Lock() 207 defer l.mutex.Unlock() 208 for _, nid := range nids { 209 if nid.Scope() != l.scope { 210 continue 211 } 212 delete(l.withheldIdentities, nid) 213 } 214 } 215 216 // lookup searches for a local identity matching the given labels and returns 217 // it. If found, the reference count is NOT incremented and thus release must 218 // NOT be called. 219 func (l *localIdentityCache) lookup(lbls labels.Labels) *identity.Identity { 220 l.mutex.RLock() 221 defer l.mutex.RUnlock() 222 223 if id, ok := l.identitiesByLabels[string(lbls.SortedList())]; ok { 224 return id 225 } 226 227 return nil 228 } 229 230 // lookupByID searches for a local identity matching the given ID and returns 231 // it. If found, the reference count is NOT incremented and thus release must 232 // NOT be called. 233 func (l *localIdentityCache) lookupByID(id identity.NumericIdentity) *identity.Identity { 234 l.mutex.RLock() 235 defer l.mutex.RUnlock() 236 237 if id, ok := l.identitiesByID[id]; ok { 238 return id 239 } 240 241 return nil 242 } 243 244 // GetIdentities returns all local identities 245 func (l *localIdentityCache) GetIdentities() map[identity.NumericIdentity]*identity.Identity { 246 cache := map[identity.NumericIdentity]*identity.Identity{} 247 248 l.mutex.RLock() 249 defer l.mutex.RUnlock() 250 251 for key, id := range l.identitiesByID { 252 cache[key] = id 253 } 254 255 return cache 256 } 257 258 func (l *localIdentityCache) checkpoint(dst []*identity.Identity) []*identity.Identity { 259 l.mutex.RLock() 260 defer l.mutex.RUnlock() 261 for _, id := range l.identitiesByID { 262 dst = append(dst, id) 263 } 264 return dst 265 } 266 267 func (l *localIdentityCache) size() int { 268 l.mutex.RLock() 269 defer l.mutex.RUnlock() 270 return len(l.identitiesByID) 271 } 272 273 // close removes the events channel. 274 func (l *localIdentityCache) close() { 275 l.mutex.Lock() 276 defer l.mutex.Unlock() 277 278 l.events = nil 279 }