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 }