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  }