github.com/cilium/cilium@v1.16.2/operator/pkg/ciliumendpointslice/reconciler.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package ciliumendpointslice
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  
    10  	"github.com/sirupsen/logrus"
    11  	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    12  
    13  	"github.com/cilium/cilium/pkg/k8s"
    14  	cilium_v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    15  	cilium_v2a1 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1"
    16  	clientset "github.com/cilium/cilium/pkg/k8s/client/clientset/versioned/typed/cilium.io/v2alpha1"
    17  	"github.com/cilium/cilium/pkg/k8s/resource"
    18  	"github.com/cilium/cilium/pkg/logging/logfields"
    19  )
    20  
    21  // reconciler is used to sync the current (i.e. desired) state of the CESs in datastore into current state CESs in the k8s-apiserver.
    22  // The source of truth is in local datastore.
    23  type reconciler struct {
    24  	logger     logrus.FieldLogger
    25  	client     clientset.CiliumV2alpha1Interface
    26  	context    context.Context
    27  	cesManager operations
    28  	cepStore   resource.Store[*cilium_v2.CiliumEndpoint]
    29  	cesStore   resource.Store[*cilium_v2a1.CiliumEndpointSlice]
    30  	metrics    *Metrics
    31  }
    32  
    33  // newReconciler creates and initializes a new reconciler.
    34  func newReconciler(
    35  	ctx context.Context,
    36  	client clientset.CiliumV2alpha1Interface,
    37  	cesMgr operations,
    38  	logger logrus.FieldLogger,
    39  	ciliumEndpoint resource.Resource[*cilium_v2.CiliumEndpoint],
    40  	ciliumEndpointSlice resource.Resource[*cilium_v2a1.CiliumEndpointSlice],
    41  	metrics *Metrics,
    42  ) *reconciler {
    43  	cepStore, _ := ciliumEndpoint.Store(ctx)
    44  	cesStore, _ := ciliumEndpointSlice.Store(ctx)
    45  	return &reconciler{
    46  		context:    ctx,
    47  		logger:     logger,
    48  		client:     client,
    49  		cesManager: cesMgr,
    50  		cepStore:   cepStore,
    51  		cesStore:   cesStore,
    52  		metrics:    metrics,
    53  	}
    54  }
    55  
    56  func (r *reconciler) reconcileCES(cesName CESName) (err error) {
    57  	desiredCEPsNumber := r.cesManager.getCEPCountInCES(cesName)
    58  	r.metrics.CiliumEndpointSliceDensity.Observe(float64(desiredCEPsNumber))
    59  	// Check the CES exists is in cesStore i.e. in api-server copy of CESs, if exist update or delete the CES.
    60  	cesObj, exists, err := r.cesStore.GetByKey(cesName.key())
    61  	if err != nil {
    62  		return
    63  	}
    64  	if !exists && desiredCEPsNumber > 0 {
    65  		return r.reconcileCESCreate(cesName)
    66  	} else if exists && desiredCEPsNumber > 0 {
    67  		return r.reconcileCESUpdate(cesName, cesObj)
    68  	} else if exists && desiredCEPsNumber == 0 {
    69  		return r.reconcileCESDelete(cesObj)
    70  	} else { //!exist && desiredCEPsNumber == 0 => no op
    71  		return nil
    72  	}
    73  }
    74  
    75  // Create a new CES in api-server.
    76  func (r *reconciler) reconcileCESCreate(cesName CESName) (err error) {
    77  	r.logger.WithFields(logrus.Fields{
    78  		logfields.CESName: cesName.string(),
    79  	}).Debug("Reconciling CES Create.")
    80  	ceps := r.cesManager.getCEPinCES(cesName)
    81  	r.metrics.CiliumEndpointsChangeCount.WithLabelValues(LabelValueCEPInsert).Observe(float64(len(ceps)))
    82  	newCES := &cilium_v2a1.CiliumEndpointSlice{
    83  		TypeMeta: meta_v1.TypeMeta{
    84  			Kind:       "CiliumEndpointSlice",
    85  			APIVersion: cilium_v2.SchemeGroupVersion.String(),
    86  		},
    87  		ObjectMeta: meta_v1.ObjectMeta{
    88  			Name: cesName.Name,
    89  		},
    90  		Endpoints: make([]cilium_v2a1.CoreCiliumEndpoint, 0, len(ceps)),
    91  	}
    92  
    93  	cesData := r.cesManager.getCESData(cesName)
    94  	newCES.Namespace = cesData.ns
    95  
    96  	for _, cepName := range ceps {
    97  		ccep := r.getCoreEndpointFromStore(cepName)
    98  		r.logger.WithFields(logrus.Fields{
    99  			logfields.CESName: cesName.string(),
   100  			logfields.CEPName: cepName.string(),
   101  		}).Debugf("Adding CEP to new CES (exist %v)", ccep != nil)
   102  		if ccep != nil {
   103  			newCES.Endpoints = append(newCES.Endpoints, *ccep)
   104  		}
   105  	}
   106  
   107  	// Call the client API, to Create CES
   108  	if _, err = r.client.CiliumEndpointSlices().Create(
   109  		r.context, newCES, meta_v1.CreateOptions{}); err != nil && !errors.Is(err, context.Canceled) {
   110  		r.logger.WithError(err).WithFields(logrus.Fields{
   111  			logfields.CESName: newCES.Name,
   112  		}).Info("Unable to create CiliumEndpointSlice in k8s-apiserver")
   113  	}
   114  	return
   115  }
   116  
   117  // Update an existing CES
   118  func (r *reconciler) reconcileCESUpdate(cesName CESName, cesObj *cilium_v2a1.CiliumEndpointSlice) (err error) {
   119  	r.logger.WithFields(logrus.Fields{
   120  		logfields.CESName: cesName.string(),
   121  	}).Debug("Reconciling CES Update.")
   122  	updatedCES := cesObj.DeepCopy()
   123  	cepInserted := 0
   124  	cepRemoved := 0
   125  	cepUpdated := 0
   126  
   127  	// Names of all CEPs that should be in the CES
   128  	cepsAssignedToCES := r.cesManager.getCEPinCES(cesName)
   129  	// Final endpoints list. CES endpoints will be set to this list.
   130  	updatedEndpoints := make([]cilium_v2a1.CoreCiliumEndpoint, 0, len(cepsAssignedToCES))
   131  	cepNameToCEP := make(map[CEPName]*cilium_v2a1.CoreCiliumEndpoint)
   132  	// Get the CEPs objects from the CEP Store and map the names to them
   133  	for _, cepName := range cepsAssignedToCES {
   134  		ccep := r.getCoreEndpointFromStore(cepName)
   135  		r.logger.WithFields(logrus.Fields{
   136  			logfields.CESName: cesName.string(),
   137  			logfields.CEPName: cepName.string(),
   138  		}).Debugf("Adding CEP to existing CES (exist %v)", ccep != nil)
   139  		if ccep != nil {
   140  			updatedEndpoints = append(updatedEndpoints, *ccep)
   141  			cepNameToCEP[cepName] = ccep
   142  		}
   143  		cepInserted = cepInserted + 1
   144  	}
   145  	// Grab metrics about number of inserted, updated and deleted CEPs and
   146  	// determine whether CES needs to be updated at all.
   147  	for _, ep := range updatedCES.Endpoints {
   148  		epName := GetCEPNameFromCCEP(&ep, updatedCES.Namespace)
   149  		if r.cesManager.isCEPinCES(epName, cesName) {
   150  			cepInserted = cepInserted - 1
   151  			if !ep.DeepEqual(cepNameToCEP[epName]) {
   152  				cepUpdated = cepUpdated + 1
   153  			}
   154  		} else {
   155  			cepRemoved = cepRemoved + 1
   156  		}
   157  	}
   158  	updatedCES.Endpoints = updatedEndpoints
   159  	r.logger.WithFields(logrus.Fields{
   160  		logfields.CESName: cesName.string(),
   161  	}).Debugf("Inserted %d endpoints, updated %d endpoints, removed %d endpoints", cepInserted, cepUpdated, cepRemoved)
   162  
   163  	cesEqual := cepInserted == 0 && cepUpdated == 0 && cepRemoved == 0
   164  	data := r.cesManager.getCESData(cesName)
   165  	if updatedCES.Namespace != data.ns {
   166  		updatedCES.Namespace = data.ns
   167  		cesEqual = false
   168  	}
   169  
   170  	r.metrics.CiliumEndpointsChangeCount.WithLabelValues(LabelValueCEPInsert).Observe(float64(cepInserted + cepUpdated))
   171  	r.metrics.CiliumEndpointsChangeCount.WithLabelValues(LabelValueCEPRemove).Observe(float64(cepRemoved))
   172  
   173  	if !cesEqual {
   174  		r.logger.WithFields(logrus.Fields{
   175  			logfields.CESName: cesName.string(),
   176  		}).Debug("CES changed, updating")
   177  		// Call the client API, to Create CESs
   178  		if _, err = r.client.CiliumEndpointSlices().Update(
   179  			r.context, updatedCES, meta_v1.UpdateOptions{}); err != nil && !errors.Is(err, context.Canceled) {
   180  			r.logger.WithError(err).WithFields(logrus.Fields{
   181  				logfields.CESName: updatedCES.Name,
   182  			}).Info("Unable to update CiliumEndpointSlice in k8s-apiserver")
   183  		}
   184  	} else {
   185  		r.logger.WithFields(logrus.Fields{
   186  			logfields.CESName: cesName.string(),
   187  		}).Debug("CES up to date, skipping update")
   188  	}
   189  	return
   190  }
   191  
   192  // Delete the CES.
   193  func (r *reconciler) reconcileCESDelete(ces *cilium_v2a1.CiliumEndpointSlice) (err error) {
   194  	r.logger.WithFields(logrus.Fields{
   195  		logfields.CESName: ces.Name,
   196  	}).Debug("Reconciling CES Delete.")
   197  	r.metrics.CiliumEndpointsChangeCount.WithLabelValues(LabelValueCEPRemove).Observe(float64(len(ces.Endpoints)))
   198  	if err = r.client.CiliumEndpointSlices().Delete(
   199  		r.context, ces.Name, meta_v1.DeleteOptions{}); err != nil && !errors.Is(err, context.Canceled) {
   200  		r.logger.WithError(err).WithFields(logrus.Fields{
   201  			logfields.CESName: ces.Name,
   202  		}).Info("Unable to delete CiliumEndpointSlice in k8s-apiserver")
   203  		return
   204  	}
   205  	return
   206  }
   207  
   208  func (r *reconciler) getCoreEndpointFromStore(cepName CEPName) *cilium_v2a1.CoreCiliumEndpoint {
   209  	cepObj, exists, err := r.cepStore.GetByKey(cepName.key())
   210  	if err == nil && exists {
   211  		return k8s.ConvertCEPToCoreCEP(cepObj)
   212  	}
   213  	r.logger.WithFields(logrus.Fields{
   214  		logfields.CEPName: cepName.string(),
   215  	}).Debugf("Couldn't get CEP from Store (err=%v, exists=%v)", err, exists)
   216  	return nil
   217  }