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  }