github.com/cilium/cilium@v1.16.2/pkg/bgpv1/manager/reconcilerv2/service.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package reconcilerv2
     5  
     6  import (
     7  	"context"
     8  	"errors"
     9  	"fmt"
    10  	"maps"
    11  	"net/netip"
    12  
    13  	"github.com/cilium/hive/cell"
    14  	"github.com/sirupsen/logrus"
    15  	corev1 "k8s.io/api/core/v1"
    16  	"k8s.io/apimachinery/pkg/util/sets"
    17  
    18  	"github.com/cilium/cilium/pkg/bgpv1/manager/instance"
    19  	"github.com/cilium/cilium/pkg/bgpv1/manager/store"
    20  	"github.com/cilium/cilium/pkg/bgpv1/types"
    21  	"github.com/cilium/cilium/pkg/k8s"
    22  	"github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1"
    23  	"github.com/cilium/cilium/pkg/k8s/resource"
    24  	slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1"
    25  	"github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/labels"
    26  	slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
    27  	ciliumslices "github.com/cilium/cilium/pkg/slices"
    28  )
    29  
    30  type ServiceReconcilerOut struct {
    31  	cell.Out
    32  
    33  	Reconciler ConfigReconciler `group:"bgp-config-reconciler-v2"`
    34  }
    35  
    36  type ServiceReconcilerIn struct {
    37  	cell.In
    38  
    39  	Logger       logrus.FieldLogger
    40  	PeerAdvert   *CiliumPeerAdvertisement
    41  	SvcDiffStore store.DiffStore[*slim_corev1.Service]
    42  	EPDiffStore  store.DiffStore[*k8s.Endpoints]
    43  }
    44  
    45  type ServiceReconciler struct {
    46  	logger       logrus.FieldLogger
    47  	peerAdvert   *CiliumPeerAdvertisement
    48  	svcDiffStore store.DiffStore[*slim_corev1.Service]
    49  	epDiffStore  store.DiffStore[*k8s.Endpoints]
    50  }
    51  
    52  func NewServiceReconciler(in ServiceReconcilerIn) ServiceReconcilerOut {
    53  	if in.SvcDiffStore == nil || in.EPDiffStore == nil {
    54  		return ServiceReconcilerOut{}
    55  	}
    56  
    57  	return ServiceReconcilerOut{
    58  		Reconciler: &ServiceReconciler{
    59  			logger:       in.Logger,
    60  			peerAdvert:   in.PeerAdvert,
    61  			svcDiffStore: in.SvcDiffStore,
    62  			epDiffStore:  in.EPDiffStore,
    63  		},
    64  	}
    65  }
    66  
    67  // ServiceReconcilerMetadata holds any announced service CIDRs per address family.
    68  type ServiceReconcilerMetadata struct {
    69  	ServicePaths          ResourceAFPathsMap
    70  	ServiceAdvertisements PeerAdvertisements
    71  	ServiceRoutePolicies  ResourceRoutePolicyMap
    72  }
    73  
    74  func (r *ServiceReconciler) getMetadata(i *instance.BGPInstance) ServiceReconcilerMetadata {
    75  	if _, found := i.Metadata[r.Name()]; !found {
    76  		i.Metadata[r.Name()] = ServiceReconcilerMetadata{
    77  			ServicePaths:          make(ResourceAFPathsMap),
    78  			ServiceAdvertisements: make(PeerAdvertisements),
    79  			ServiceRoutePolicies:  make(ResourceRoutePolicyMap),
    80  		}
    81  	}
    82  	return i.Metadata[r.Name()].(ServiceReconcilerMetadata)
    83  }
    84  
    85  func (r *ServiceReconciler) setMetadata(i *instance.BGPInstance, metadata ServiceReconcilerMetadata) {
    86  	i.Metadata[r.Name()] = metadata
    87  }
    88  
    89  func (r *ServiceReconciler) Name() string {
    90  	return "Service"
    91  }
    92  
    93  func (r *ServiceReconciler) Priority() int {
    94  	return 40
    95  }
    96  
    97  func (r *ServiceReconciler) Init(i *instance.BGPInstance) error {
    98  	if i == nil {
    99  		return fmt.Errorf("BUG: service reconciler initialization with nil BGPInstance")
   100  	}
   101  	r.svcDiffStore.InitDiff(r.diffID(i.ASN))
   102  	r.epDiffStore.InitDiff(r.diffID(i.ASN))
   103  	return nil
   104  }
   105  
   106  func (r *ServiceReconciler) Cleanup(i *instance.BGPInstance) {
   107  	if i != nil {
   108  		r.svcDiffStore.CleanupDiff(r.diffID(i.ASN))
   109  		r.epDiffStore.CleanupDiff(r.diffID(i.ASN))
   110  	}
   111  }
   112  
   113  func (r *ServiceReconciler) Reconcile(ctx context.Context, p ReconcileParams) error {
   114  	if p.DesiredConfig == nil {
   115  		return fmt.Errorf("BUG: attempted service reconciliation with nil CiliumBGPNodeConfig")
   116  	}
   117  
   118  	if p.CiliumNode == nil {
   119  		return fmt.Errorf("BUG: attempted service reconciliation with nil local CiliumNode")
   120  	}
   121  
   122  	desiredPeerAdverts, err := r.peerAdvert.GetConfiguredAdvertisements(p.DesiredConfig, v2alpha1.BGPServiceAdvert)
   123  	if err != nil {
   124  		return err
   125  	}
   126  
   127  	ls, err := r.populateLocalServices(p.CiliumNode.Name)
   128  	if err != nil {
   129  		return fmt.Errorf("failed to populate local services: %w", err)
   130  	}
   131  
   132  	// must be done before reconciling paths and policies since it sets metadata with latest desiredPeerAdverts
   133  	reqFullReconcile := r.modifiedServiceAdvertisements(p, desiredPeerAdverts)
   134  
   135  	return r.reconcileServices(ctx, p, ls, reqFullReconcile)
   136  }
   137  
   138  func (r *ServiceReconciler) reconcileServices(ctx context.Context, p ReconcileParams, ls sets.Set[resource.Key], fullReconcile bool) error {
   139  	var desiredSvcRoutePolicies ResourceRoutePolicyMap
   140  	var desiredSvcPaths ResourceAFPathsMap
   141  	var err error
   142  
   143  	if fullReconcile {
   144  		r.logger.Debug("performing all services reconciliation")
   145  
   146  		// Init diff in diffstores, so that it contains only changes since the last full reconciliation.
   147  		// Despite doing it in Init(), we still need this InitDiff to clean up the old diff when the instance is re-created
   148  		// by the preflight reconciler. Once Init() is called upon re-create by preflight, we can remove this.
   149  		r.svcDiffStore.InitDiff(r.diffID(p.BGPInstance.ASN))
   150  		r.epDiffStore.InitDiff(r.diffID(p.BGPInstance.ASN))
   151  
   152  		desiredSvcRoutePolicies, err = r.getAllRoutePolicies(p, ls)
   153  		if err != nil {
   154  			return err
   155  		}
   156  
   157  		// BGP configuration for service advertisement changed, we should reconcile all services.
   158  		desiredSvcPaths, err = r.getAllPaths(p, ls)
   159  		if err != nil {
   160  			return err
   161  		}
   162  	} else {
   163  		r.logger.Debug("performing modified services reconciliation")
   164  
   165  		// get services to reconcile and to withdraw.
   166  		// Note : we should only call svc diff only once in a reconcile loop.
   167  		toReconcile, toWithdraw, err := r.diffReconciliationServiceList(p)
   168  		if err != nil {
   169  			return err
   170  		}
   171  
   172  		desiredSvcRoutePolicies, err = r.getDiffRoutePolicies(p, toReconcile, toWithdraw, ls)
   173  		if err != nil {
   174  			return err
   175  		}
   176  
   177  		// BGP configuration is unchanged, only reconcile modified services.
   178  		desiredSvcPaths, err = r.getDiffPaths(p, toReconcile, toWithdraw, ls)
   179  		if err != nil {
   180  			return err
   181  		}
   182  	}
   183  
   184  	// reconcile service route policies
   185  	err = r.reconcileSvcRoutePolicies(ctx, p, desiredSvcRoutePolicies)
   186  	if err != nil {
   187  		return fmt.Errorf("failed to reconcile service route policies: %w", err)
   188  	}
   189  
   190  	// reconcile service paths
   191  	err = r.reconcilePaths(ctx, p, desiredSvcPaths)
   192  	if err != nil {
   193  		return fmt.Errorf("failed to reconcile service paths: %w", err)
   194  	}
   195  
   196  	return nil
   197  }
   198  
   199  func (r *ServiceReconciler) reconcileSvcRoutePolicies(ctx context.Context, p ReconcileParams, desiredSvcRoutePolicies ResourceRoutePolicyMap) error {
   200  	var err error
   201  	metadata := r.getMetadata(p.BGPInstance)
   202  	for svcKey, desiredSvcRoutePolicies := range desiredSvcRoutePolicies {
   203  		currentSvcRoutePolicies, exists := metadata.ServiceRoutePolicies[svcKey]
   204  		if !exists && len(desiredSvcRoutePolicies) == 0 {
   205  			continue
   206  		}
   207  
   208  		updatedSvcRoutePolicies, rErr := ReconcileRoutePolicies(&ReconcileRoutePoliciesParams{
   209  			Logger:          r.logger.WithField(types.InstanceLogField, p.DesiredConfig.Name),
   210  			Ctx:             ctx,
   211  			Router:          p.BGPInstance.Router,
   212  			DesiredPolicies: desiredSvcRoutePolicies,
   213  			CurrentPolicies: currentSvcRoutePolicies,
   214  		})
   215  
   216  		if rErr == nil && len(desiredSvcRoutePolicies) == 0 {
   217  			delete(metadata.ServiceRoutePolicies, svcKey)
   218  		} else {
   219  			metadata.ServiceRoutePolicies[svcKey] = updatedSvcRoutePolicies
   220  		}
   221  		err = errors.Join(err, rErr)
   222  	}
   223  	r.setMetadata(p.BGPInstance, metadata)
   224  
   225  	return err
   226  }
   227  
   228  func (r *ServiceReconciler) getAllRoutePolicies(p ReconcileParams, ls sets.Set[resource.Key]) (ResourceRoutePolicyMap, error) {
   229  	desiredSvcRoutePolicies := make(ResourceRoutePolicyMap)
   230  
   231  	// check for services which are no longer present
   232  	svcRoutePolicies := r.getMetadata(p.BGPInstance).ServiceRoutePolicies
   233  	for svcKey := range svcRoutePolicies {
   234  		_, exists, err := r.svcDiffStore.GetByKey(svcKey)
   235  		if err != nil {
   236  			return nil, fmt.Errorf("svcDiffStore.GetByKey(): %w", err)
   237  		}
   238  
   239  		// if the service no longer exists, withdraw it
   240  		if !exists {
   241  			desiredSvcRoutePolicies[svcKey] = nil
   242  		}
   243  	}
   244  
   245  	// check all services for route policies
   246  	svcList, err := r.svcDiffStore.List()
   247  	if err != nil {
   248  		return nil, fmt.Errorf("failed to list services from svcDiffstore: %w", err)
   249  	}
   250  
   251  	for _, svc := range svcList {
   252  		svcKey := resource.Key{
   253  			Name:      svc.GetName(),
   254  			Namespace: svc.GetNamespace(),
   255  		}
   256  
   257  		// get desired route policies for the service
   258  		svcRoutePolicies, err := r.getDesiredSvcRoutePolicies(p, svc, ls)
   259  		if err != nil {
   260  			return nil, err
   261  		}
   262  
   263  		desiredSvcRoutePolicies[svcKey] = svcRoutePolicies
   264  	}
   265  
   266  	return desiredSvcRoutePolicies, nil
   267  }
   268  
   269  func (r *ServiceReconciler) getDiffRoutePolicies(p ReconcileParams, toUpdate []*slim_corev1.Service, toRemove []resource.Key, ls sets.Set[resource.Key]) (ResourceRoutePolicyMap, error) {
   270  	desiredSvcRoutePolicies := make(ResourceRoutePolicyMap)
   271  
   272  	for _, svc := range toUpdate {
   273  		svcKey := resource.Key{
   274  			Name:      svc.GetName(),
   275  			Namespace: svc.GetNamespace(),
   276  		}
   277  
   278  		// get desired route policies for the service
   279  		svcRoutePolicies, err := r.getDesiredSvcRoutePolicies(p, svc, ls)
   280  		if err != nil {
   281  			return nil, err
   282  		}
   283  
   284  		desiredSvcRoutePolicies[svcKey] = svcRoutePolicies
   285  	}
   286  
   287  	for _, svcKey := range toRemove {
   288  		// for withdrawn services, we need to set route policies to nil.
   289  		desiredSvcRoutePolicies[svcKey] = nil
   290  	}
   291  
   292  	return desiredSvcRoutePolicies, nil
   293  }
   294  
   295  func (r *ServiceReconciler) getDesiredSvcRoutePolicies(p ReconcileParams, svc *slim_corev1.Service, ls sets.Set[resource.Key]) (RoutePolicyMap, error) {
   296  	desiredSvcRoutePolicies := make(RoutePolicyMap)
   297  
   298  	for peer, afAdverts := range r.getMetadata(p.BGPInstance).ServiceAdvertisements {
   299  		for fam, adverts := range afAdverts {
   300  			agentFamily := types.ToAgentFamily(fam)
   301  
   302  			for _, advert := range adverts {
   303  				labelSelector, err := slim_metav1.LabelSelectorAsSelector(advert.Selector)
   304  				if err != nil {
   305  					return nil, fmt.Errorf("failed constructing LabelSelector: %w", err)
   306  				}
   307  				if !labelSelector.Matches(serviceLabelSet(svc)) {
   308  					continue
   309  				}
   310  				// LoadBalancerIP
   311  				lbPolicy, err := r.getLoadBalancerIPRoutePolicy(p, peer, agentFamily, svc, advert, ls)
   312  				if err != nil {
   313  					return nil, fmt.Errorf("failed to get desired LoadBalancerIP route policy: %w", err)
   314  				}
   315  				if lbPolicy != nil {
   316  					desiredSvcRoutePolicies[lbPolicy.Name] = lbPolicy
   317  				}
   318  				// ExternalIP
   319  				extPolicy, err := r.getExternalIPRoutePolicy(p, peer, agentFamily, svc, advert, ls)
   320  				if err != nil {
   321  					return nil, fmt.Errorf("failed to get desired ExternalIP route policy: %w", err)
   322  				}
   323  				if extPolicy != nil {
   324  					desiredSvcRoutePolicies[extPolicy.Name] = extPolicy
   325  				}
   326  				// ClusterIP
   327  				clusterPolicy, err := r.getClusterIPRoutePolicy(p, peer, agentFamily, svc, advert, ls)
   328  				if err != nil {
   329  					return nil, fmt.Errorf("failed to get desired ClusterIP route policy: %w", err)
   330  				}
   331  				if clusterPolicy != nil {
   332  					desiredSvcRoutePolicies[clusterPolicy.Name] = clusterPolicy
   333  				}
   334  			}
   335  		}
   336  	}
   337  
   338  	return desiredSvcRoutePolicies, nil
   339  }
   340  
   341  func (r *ServiceReconciler) reconcilePaths(ctx context.Context, p ReconcileParams, desiredSvcPaths ResourceAFPathsMap) error {
   342  	var err error
   343  	metadata := r.getMetadata(p.BGPInstance)
   344  	for svc, desiredAFPaths := range desiredSvcPaths {
   345  		// check if service exists
   346  		currentAFPaths, exists := metadata.ServicePaths[svc]
   347  		if !exists && len(desiredAFPaths) == 0 {
   348  			// service does not exist in our local state, and there is nothing to advertise
   349  			continue
   350  		}
   351  
   352  		// reconcile service paths
   353  		updatedAFPaths, rErr := ReconcileAFPaths(&ReconcileAFPathsParams{
   354  			Logger:       r.logger.WithField(types.InstanceLogField, p.DesiredConfig.Name),
   355  			Ctx:          ctx,
   356  			Router:       p.BGPInstance.Router,
   357  			DesiredPaths: desiredAFPaths,
   358  			CurrentPaths: currentAFPaths,
   359  		})
   360  
   361  		if rErr == nil && len(desiredAFPaths) == 0 {
   362  			// no error is reported and desiredAFPaths is empty, we should delete the service
   363  			delete(metadata.ServicePaths, svc)
   364  		} else {
   365  			// update service paths with returned updatedAFPaths even if there was an error.
   366  			metadata.ServicePaths[svc] = updatedAFPaths
   367  		}
   368  		err = errors.Join(err, rErr)
   369  	}
   370  	r.setMetadata(p.BGPInstance, metadata)
   371  
   372  	return err
   373  }
   374  
   375  // modifiedServiceAdvertisements compares local advertisement state with desiredPeerAdverts, if they differ, it updates the local state and returns true
   376  // for full reconciliation.
   377  func (r *ServiceReconciler) modifiedServiceAdvertisements(p ReconcileParams, desiredPeerAdverts PeerAdvertisements) bool {
   378  	// current metadata
   379  	serviceMetadata := r.getMetadata(p.BGPInstance)
   380  
   381  	// check if BGP advertisement configuration modified
   382  	modified := !PeerAdvertisementsEqual(serviceMetadata.ServiceAdvertisements, desiredPeerAdverts)
   383  
   384  	// update local state, if modified
   385  	if modified {
   386  		r.setMetadata(p.BGPInstance, ServiceReconcilerMetadata{
   387  			ServicePaths:          serviceMetadata.ServicePaths,
   388  			ServiceRoutePolicies:  serviceMetadata.ServiceRoutePolicies,
   389  			ServiceAdvertisements: desiredPeerAdverts,
   390  		})
   391  	}
   392  
   393  	return modified
   394  }
   395  
   396  // Populate locally available services used for externalTrafficPolicy=local handling
   397  func (r *ServiceReconciler) populateLocalServices(localNodeName string) (sets.Set[resource.Key], error) {
   398  	ls := sets.New[resource.Key]()
   399  
   400  	epList, err := r.epDiffStore.List()
   401  	if err != nil {
   402  		return nil, fmt.Errorf("failed to list EPs from diffstore: %w", err)
   403  	}
   404  
   405  endpointsLoop:
   406  	for _, eps := range epList {
   407  		_, exists, err := r.resolveSvcFromEndpoints(eps)
   408  		if err != nil {
   409  			// Cannot resolve service from EPs. We have nothing to do here.
   410  			continue
   411  		}
   412  
   413  		if !exists {
   414  			// No service associated with this endpoint. We're not interested in this.
   415  			continue
   416  		}
   417  
   418  		svcKey := resource.Key{
   419  			Name:      eps.ServiceID.Name,
   420  			Namespace: eps.ServiceID.Namespace,
   421  		}
   422  
   423  		for _, be := range eps.Backends {
   424  			if !be.Terminating && be.NodeName == localNodeName {
   425  				// At least one endpoint is available on this node. We
   426  				// can add service to the local services set.
   427  				ls.Insert(svcKey)
   428  				continue endpointsLoop
   429  			}
   430  		}
   431  	}
   432  
   433  	return ls, nil
   434  }
   435  
   436  func hasLocalEndpoints(svc *slim_corev1.Service, ls sets.Set[resource.Key]) bool {
   437  	return ls.Has(resource.Key{Name: svc.GetName(), Namespace: svc.GetNamespace()})
   438  }
   439  
   440  func (r *ServiceReconciler) resolveSvcFromEndpoints(eps *k8s.Endpoints) (*slim_corev1.Service, bool, error) {
   441  	k := resource.Key{
   442  		Name:      eps.ServiceID.Name,
   443  		Namespace: eps.ServiceID.Namespace,
   444  	}
   445  	return r.svcDiffStore.GetByKey(k)
   446  }
   447  
   448  func (r *ServiceReconciler) getAllPaths(p ReconcileParams, ls sets.Set[resource.Key]) (ResourceAFPathsMap, error) {
   449  	desiredServiceAFPaths := make(ResourceAFPathsMap)
   450  
   451  	// check for services which are no longer present
   452  	serviceAFPaths := r.getMetadata(p.BGPInstance).ServicePaths
   453  	for svcKey := range serviceAFPaths {
   454  		_, exists, err := r.svcDiffStore.GetByKey(svcKey)
   455  		if err != nil {
   456  			return nil, fmt.Errorf("svcDiffStore.GetByKey(): %w", err)
   457  		}
   458  
   459  		// if the service no longer exists, withdraw it
   460  		if !exists {
   461  			desiredServiceAFPaths[svcKey] = nil
   462  		}
   463  	}
   464  
   465  	// check all services for advertisement
   466  	svcList, err := r.svcDiffStore.List()
   467  	if err != nil {
   468  		return nil, fmt.Errorf("failed to list services from svcDiffstore: %w", err)
   469  	}
   470  
   471  	for _, svc := range svcList {
   472  		svcKey := resource.Key{
   473  			Name:      svc.GetName(),
   474  			Namespace: svc.GetNamespace(),
   475  		}
   476  
   477  		afPaths, err := r.getServiceAFPaths(p, svc, ls)
   478  		if err != nil {
   479  			return nil, err
   480  		}
   481  
   482  		desiredServiceAFPaths[svcKey] = afPaths
   483  	}
   484  
   485  	return desiredServiceAFPaths, nil
   486  }
   487  
   488  func (r *ServiceReconciler) getDiffPaths(p ReconcileParams, toReconcile []*slim_corev1.Service, toWithdraw []resource.Key, ls sets.Set[resource.Key]) (ResourceAFPathsMap, error) {
   489  	desiredServiceAFPaths := make(ResourceAFPathsMap)
   490  	for _, svc := range toReconcile {
   491  		svcKey := resource.Key{
   492  			Name:      svc.GetName(),
   493  			Namespace: svc.GetNamespace(),
   494  		}
   495  
   496  		afPaths, err := r.getServiceAFPaths(p, svc, ls)
   497  		if err != nil {
   498  			return nil, err
   499  		}
   500  
   501  		desiredServiceAFPaths[svcKey] = afPaths
   502  	}
   503  
   504  	for _, svcKey := range toWithdraw {
   505  		// for withdrawn services, we need to set paths to nil.
   506  		desiredServiceAFPaths[svcKey] = nil
   507  	}
   508  
   509  	return desiredServiceAFPaths, nil
   510  }
   511  
   512  // diffReconciliationServiceList returns a list of services to reconcile and to withdraw when
   513  // performing partial (diff) service reconciliation.
   514  func (r *ServiceReconciler) diffReconciliationServiceList(p ReconcileParams) (toReconcile []*slim_corev1.Service, toWithdraw []resource.Key, err error) {
   515  	upserted, deleted, err := r.svcDiffStore.Diff(r.diffID(p.BGPInstance.ASN))
   516  	if err != nil {
   517  		return nil, nil, fmt.Errorf("svc store diff: %w", err)
   518  	}
   519  
   520  	// For externalTrafficPolicy=local, we need to take care of
   521  	// the endpoint changes in addition to the service changes.
   522  	// Take a diff of the EPs and get affected services.
   523  	// We don't handle service deletion here since we only see
   524  	// the key, we cannot resolve associated service, so we have
   525  	// nothing to do.
   526  	epsUpserted, _, err := r.epDiffStore.Diff(r.diffID(p.BGPInstance.ASN))
   527  	if err != nil {
   528  		return nil, nil, fmt.Errorf("EPs store diff: %w", err)
   529  	}
   530  
   531  	for _, eps := range epsUpserted {
   532  		svc, exists, err := r.resolveSvcFromEndpoints(eps)
   533  		if err != nil {
   534  			// Cannot resolve service from EPs. We have nothing to do here.
   535  			continue
   536  		}
   537  
   538  		if !exists {
   539  			// No service associated with this endpoint. We're not interested in this.
   540  			continue
   541  		}
   542  
   543  		// We only need Endpoints tracking for externalTrafficPolicy=Local or internalTrafficPolicy=Local services.
   544  		if svc.Spec.ExternalTrafficPolicy == slim_corev1.ServiceExternalTrafficPolicyLocal ||
   545  			(svc.Spec.InternalTrafficPolicy != nil && *svc.Spec.InternalTrafficPolicy == slim_corev1.ServiceInternalTrafficPolicyLocal) {
   546  			upserted = append(upserted, svc)
   547  		}
   548  	}
   549  
   550  	// We may have duplicated services that changes happened for both of
   551  	// service and associated EPs.
   552  	deduped := ciliumslices.UniqueFunc(
   553  		upserted,
   554  		func(i int) resource.Key {
   555  			return resource.Key{
   556  				Name:      upserted[i].GetName(),
   557  				Namespace: upserted[i].GetNamespace(),
   558  			}
   559  		},
   560  	)
   561  
   562  	return deduped, deleted, nil
   563  }
   564  
   565  func (r *ServiceReconciler) getServiceAFPaths(p ReconcileParams, svc *slim_corev1.Service, ls sets.Set[resource.Key]) (AFPathsMap, error) {
   566  	desiredFamilyAdverts := make(AFPathsMap)
   567  	metadata := r.getMetadata(p.BGPInstance)
   568  
   569  	for _, peerFamilyAdverts := range metadata.ServiceAdvertisements {
   570  		for family, familyAdverts := range peerFamilyAdverts {
   571  			agentFamily := types.ToAgentFamily(family)
   572  
   573  			for _, advert := range familyAdverts {
   574  				// get prefixes for the service
   575  				desiredPrefixes, err := r.getServicePrefixes(svc, advert, ls)
   576  				if err != nil {
   577  					return nil, err
   578  				}
   579  
   580  				for _, prefix := range desiredPrefixes {
   581  					path := types.NewPathForPrefix(prefix)
   582  					path.Family = agentFamily
   583  
   584  					// we only add path corresponding to the family of the prefix.
   585  					if agentFamily.Afi == types.AfiIPv4 && prefix.Addr().Is4() {
   586  						addPathToAFPathsMap(desiredFamilyAdverts, agentFamily, path)
   587  					}
   588  					if agentFamily.Afi == types.AfiIPv6 && prefix.Addr().Is6() {
   589  						addPathToAFPathsMap(desiredFamilyAdverts, agentFamily, path)
   590  					}
   591  				}
   592  			}
   593  		}
   594  	}
   595  	return desiredFamilyAdverts, nil
   596  }
   597  
   598  func (r *ServiceReconciler) getServicePrefixes(svc *slim_corev1.Service, advert v2alpha1.BGPAdvertisement, ls sets.Set[resource.Key]) ([]netip.Prefix, error) {
   599  	if advert.AdvertisementType != v2alpha1.BGPServiceAdvert {
   600  		return nil, fmt.Errorf("unexpected advertisement type: %s", advert.AdvertisementType)
   601  	}
   602  
   603  	if advert.Selector == nil || advert.Service == nil {
   604  		// advertisement has no selector or no service options, default behavior is not to match any service.
   605  		return nil, nil
   606  	}
   607  
   608  	// The vRouter has a service selector, so determine the desired routes.
   609  	svcSelector, err := slim_metav1.LabelSelectorAsSelector(advert.Selector)
   610  	if err != nil {
   611  		return nil, fmt.Errorf("labelSelectorAsSelector: %w", err)
   612  	}
   613  
   614  	// Ignore non matching services.
   615  	if !svcSelector.Matches(serviceLabelSet(svc)) {
   616  		return nil, nil
   617  	}
   618  
   619  	var desiredRoutes []netip.Prefix
   620  	// Loop over the service upsertAdverts and determine the desired routes.
   621  	for _, svcAdv := range advert.Service.Addresses {
   622  		switch svcAdv {
   623  		case v2alpha1.BGPLoadBalancerIPAddr:
   624  			desiredRoutes = append(desiredRoutes, r.getLBSvcPaths(svc, ls)...)
   625  		case v2alpha1.BGPClusterIPAddr:
   626  			desiredRoutes = append(desiredRoutes, r.getClusterIPPaths(svc, ls)...)
   627  		case v2alpha1.BGPExternalIPAddr:
   628  			desiredRoutes = append(desiredRoutes, r.getExternalIPPaths(svc, ls)...)
   629  		}
   630  	}
   631  
   632  	return desiredRoutes, nil
   633  }
   634  
   635  func (r *ServiceReconciler) getExternalIPPaths(svc *slim_corev1.Service, ls sets.Set[resource.Key]) []netip.Prefix {
   636  	var desiredRoutes []netip.Prefix
   637  	// Ignore externalTrafficPolicy == Local && no local EPs.
   638  	if svc.Spec.ExternalTrafficPolicy == slim_corev1.ServiceExternalTrafficPolicyLocal &&
   639  		!hasLocalEndpoints(svc, ls) {
   640  		return desiredRoutes
   641  	}
   642  	for _, extIP := range svc.Spec.ExternalIPs {
   643  		if extIP == "" {
   644  			continue
   645  		}
   646  		addr, err := netip.ParseAddr(extIP)
   647  		if err != nil {
   648  			continue
   649  		}
   650  		desiredRoutes = append(desiredRoutes, netip.PrefixFrom(addr, addr.BitLen()))
   651  	}
   652  	return desiredRoutes
   653  }
   654  
   655  func (r *ServiceReconciler) getClusterIPPaths(svc *slim_corev1.Service, ls sets.Set[resource.Key]) []netip.Prefix {
   656  	var desiredRoutes []netip.Prefix
   657  	// Ignore internalTrafficPolicy == Local && no local EPs.
   658  	if svc.Spec.InternalTrafficPolicy != nil && *svc.Spec.InternalTrafficPolicy == slim_corev1.ServiceInternalTrafficPolicyLocal &&
   659  		!hasLocalEndpoints(svc, ls) {
   660  		return desiredRoutes
   661  	}
   662  	if svc.Spec.ClusterIP == "" || len(svc.Spec.ClusterIPs) == 0 || svc.Spec.ClusterIP == corev1.ClusterIPNone {
   663  		return desiredRoutes
   664  	}
   665  	ips := sets.New[string]()
   666  	if svc.Spec.ClusterIP != "" {
   667  		ips.Insert(svc.Spec.ClusterIP)
   668  	}
   669  	for _, clusterIP := range svc.Spec.ClusterIPs {
   670  		if clusterIP == "" || clusterIP == corev1.ClusterIPNone {
   671  			continue
   672  		}
   673  		ips.Insert(clusterIP)
   674  	}
   675  	for _, ip := range sets.List(ips) {
   676  		addr, err := netip.ParseAddr(ip)
   677  		if err != nil {
   678  			continue
   679  		}
   680  		desiredRoutes = append(desiredRoutes, netip.PrefixFrom(addr, addr.BitLen()))
   681  	}
   682  	return desiredRoutes
   683  }
   684  
   685  func (r *ServiceReconciler) getLBSvcPaths(svc *slim_corev1.Service, ls sets.Set[resource.Key]) []netip.Prefix {
   686  	var desiredRoutes []netip.Prefix
   687  	if svc.Spec.Type != slim_corev1.ServiceTypeLoadBalancer {
   688  		return desiredRoutes
   689  	}
   690  	// Ignore externalTrafficPolicy == Local && no local EPs.
   691  	if svc.Spec.ExternalTrafficPolicy == slim_corev1.ServiceExternalTrafficPolicyLocal &&
   692  		!hasLocalEndpoints(svc, ls) {
   693  		return desiredRoutes
   694  	}
   695  	// Ignore service managed by an unsupported LB class.
   696  	if svc.Spec.LoadBalancerClass != nil && *svc.Spec.LoadBalancerClass != v2alpha1.BGPLoadBalancerClass {
   697  		// The service is managed by a different LB class.
   698  		return desiredRoutes
   699  	}
   700  	for _, ingress := range svc.Status.LoadBalancer.Ingress {
   701  		if ingress.IP == "" {
   702  			continue
   703  		}
   704  		addr, err := netip.ParseAddr(ingress.IP)
   705  		if err != nil {
   706  			continue
   707  		}
   708  		desiredRoutes = append(desiredRoutes, netip.PrefixFrom(addr, addr.BitLen()))
   709  	}
   710  	return desiredRoutes
   711  }
   712  
   713  func (r *ServiceReconciler) getLoadBalancerIPRoutePolicy(p ReconcileParams, peer string, family types.Family, svc *slim_corev1.Service, advert v2alpha1.BGPAdvertisement, ls sets.Set[resource.Key]) (*types.RoutePolicy, error) {
   714  	if svc.Spec.Type != slim_corev1.ServiceTypeLoadBalancer {
   715  		return nil, nil
   716  	}
   717  	// Ignore externalTrafficPolicy == Local && no local EPs.
   718  	if svc.Spec.ExternalTrafficPolicy == slim_corev1.ServiceExternalTrafficPolicyLocal &&
   719  		!hasLocalEndpoints(svc, ls) {
   720  		return nil, nil
   721  	}
   722  	// Ignore service managed by an unsupported LB class.
   723  	if svc.Spec.LoadBalancerClass != nil && *svc.Spec.LoadBalancerClass != v2alpha1.BGPLoadBalancerClass {
   724  		// The service is managed by a different LB class.
   725  		return nil, nil
   726  	}
   727  
   728  	// get the peer address
   729  	peerAddr, err := GetPeerAddressFromConfig(p.DesiredConfig, peer)
   730  	if err != nil {
   731  		return nil, fmt.Errorf("failed to get peer address: %w", err)
   732  	}
   733  
   734  	valid, err := checkServiceAdvertisement(advert, v2alpha1.BGPLoadBalancerIPAddr)
   735  	if err != nil {
   736  		return nil, fmt.Errorf("failed to check service advertisement: %w", err)
   737  	}
   738  	if !valid {
   739  		return nil, nil
   740  	}
   741  
   742  	var v4Prefixes, v6Prefixes types.PolicyPrefixMatchList
   743  	for _, ingress := range svc.Status.LoadBalancer.Ingress {
   744  		addr, err := netip.ParseAddr(ingress.IP)
   745  		if err != nil {
   746  			continue
   747  		}
   748  
   749  		if family.Afi == types.AfiIPv4 && addr.Is4() {
   750  			v4Prefixes = append(v4Prefixes, &types.RoutePolicyPrefixMatch{CIDR: netip.PrefixFrom(addr, addr.BitLen()), PrefixLenMin: addr.BitLen(), PrefixLenMax: addr.BitLen()})
   751  		}
   752  
   753  		if family.Afi == types.AfiIPv6 && addr.Is6() {
   754  			v6Prefixes = append(v6Prefixes, &types.RoutePolicyPrefixMatch{CIDR: netip.PrefixFrom(addr, addr.BitLen()), PrefixLenMin: addr.BitLen(), PrefixLenMax: addr.BitLen()})
   755  		}
   756  	}
   757  
   758  	if len(v4Prefixes) == 0 && len(v6Prefixes) == 0 {
   759  		return nil, nil
   760  	}
   761  
   762  	policyName := PolicyName(peer, family.Afi.String(), advert.AdvertisementType, fmt.Sprintf("%s-%s-%s", svc.Name, svc.Namespace, v2alpha1.BGPLoadBalancerIPAddr))
   763  	policy, err := CreatePolicy(policyName, peerAddr, v4Prefixes, v6Prefixes, advert)
   764  	if err != nil {
   765  		return nil, fmt.Errorf("failed to create LoadBalancer IP route policy: %w", err)
   766  	}
   767  
   768  	return policy, nil
   769  }
   770  
   771  func (r *ServiceReconciler) getExternalIPRoutePolicy(p ReconcileParams, peer string, family types.Family, svc *slim_corev1.Service, advert v2alpha1.BGPAdvertisement, ls sets.Set[resource.Key]) (*types.RoutePolicy, error) {
   772  	// get the peer address
   773  	peerAddr, err := GetPeerAddressFromConfig(p.DesiredConfig, peer)
   774  	if err != nil {
   775  		return nil, fmt.Errorf("failed to get peer address: %w", err)
   776  	}
   777  
   778  	valid, err := checkServiceAdvertisement(advert, v2alpha1.BGPExternalIPAddr)
   779  	if err != nil {
   780  		return nil, fmt.Errorf("failed to check service advertisement: %w", err)
   781  	}
   782  
   783  	if !valid {
   784  		return nil, nil
   785  	}
   786  
   787  	// Ignore externalTrafficPolicy == Local && no local EPs.
   788  	if svc.Spec.ExternalTrafficPolicy == slim_corev1.ServiceExternalTrafficPolicyLocal &&
   789  		!hasLocalEndpoints(svc, ls) {
   790  		return nil, nil
   791  	}
   792  
   793  	var v4Prefixes, v6Prefixes types.PolicyPrefixMatchList
   794  	for _, extIP := range svc.Spec.ExternalIPs {
   795  		if extIP == "" {
   796  			continue
   797  		}
   798  		addr, err := netip.ParseAddr(extIP)
   799  		if err != nil {
   800  			continue
   801  		}
   802  
   803  		if family.Afi == types.AfiIPv4 && addr.Is4() {
   804  			v4Prefixes = append(v4Prefixes, &types.RoutePolicyPrefixMatch{CIDR: netip.PrefixFrom(addr, addr.BitLen()), PrefixLenMin: addr.BitLen(), PrefixLenMax: addr.BitLen()})
   805  		}
   806  
   807  		if family.Afi == types.AfiIPv6 && addr.Is6() {
   808  			v6Prefixes = append(v6Prefixes, &types.RoutePolicyPrefixMatch{CIDR: netip.PrefixFrom(addr, addr.BitLen()), PrefixLenMin: addr.BitLen(), PrefixLenMax: addr.BitLen()})
   809  		}
   810  	}
   811  
   812  	if len(v4Prefixes) == 0 && len(v6Prefixes) == 0 {
   813  		return nil, nil
   814  	}
   815  
   816  	policyName := PolicyName(peer, family.Afi.String(), advert.AdvertisementType, fmt.Sprintf("%s-%s-%s", svc.Name, svc.Namespace, v2alpha1.BGPExternalIPAddr))
   817  	policy, err := CreatePolicy(policyName, peerAddr, v4Prefixes, v6Prefixes, advert)
   818  	if err != nil {
   819  		return nil, fmt.Errorf("failed to create external IP route policy: %w", err)
   820  	}
   821  
   822  	return policy, nil
   823  }
   824  
   825  func (r *ServiceReconciler) getClusterIPRoutePolicy(p ReconcileParams, peer string, family types.Family, svc *slim_corev1.Service, advert v2alpha1.BGPAdvertisement, ls sets.Set[resource.Key]) (*types.RoutePolicy, error) {
   826  	// get the peer address
   827  	peerAddr, err := GetPeerAddressFromConfig(p.DesiredConfig, peer)
   828  	if err != nil {
   829  		return nil, fmt.Errorf("failed to get peer address: %w", err)
   830  	}
   831  
   832  	valid, err := checkServiceAdvertisement(advert, v2alpha1.BGPClusterIPAddr)
   833  	if err != nil {
   834  		return nil, fmt.Errorf("failed to check service advertisement: %w", err)
   835  	}
   836  
   837  	if !valid {
   838  		return nil, nil
   839  	}
   840  
   841  	// Ignore internalTrafficPolicy == Local && no local EPs.
   842  	if svc.Spec.InternalTrafficPolicy != nil && *svc.Spec.InternalTrafficPolicy == slim_corev1.ServiceInternalTrafficPolicyLocal &&
   843  		!hasLocalEndpoints(svc, ls) {
   844  		return nil, nil
   845  	}
   846  
   847  	var v4Prefixes, v6Prefixes types.PolicyPrefixMatchList
   848  
   849  	ips := sets.New[string]()
   850  	if svc.Spec.ClusterIP != "" {
   851  		ips.Insert(svc.Spec.ClusterIP)
   852  	}
   853  	for _, clusterIP := range svc.Spec.ClusterIPs {
   854  		if clusterIP == "" || clusterIP == corev1.ClusterIPNone {
   855  			continue
   856  		}
   857  		ips.Insert(clusterIP)
   858  	}
   859  	for _, ip := range sets.List(ips) {
   860  		addr, err := netip.ParseAddr(ip)
   861  		if err != nil {
   862  			continue
   863  		}
   864  
   865  		if family.Afi == types.AfiIPv4 && addr.Is4() {
   866  			v4Prefixes = append(v4Prefixes, &types.RoutePolicyPrefixMatch{CIDR: netip.PrefixFrom(addr, addr.BitLen()), PrefixLenMin: addr.BitLen(), PrefixLenMax: addr.BitLen()})
   867  		}
   868  
   869  		if family.Afi == types.AfiIPv6 && addr.Is6() {
   870  			v6Prefixes = append(v6Prefixes, &types.RoutePolicyPrefixMatch{CIDR: netip.PrefixFrom(addr, addr.BitLen()), PrefixLenMin: addr.BitLen(), PrefixLenMax: addr.BitLen()})
   871  		}
   872  	}
   873  
   874  	if len(v4Prefixes) == 0 && len(v6Prefixes) == 0 {
   875  		return nil, nil
   876  	}
   877  
   878  	policyName := PolicyName(peer, family.Afi.String(), advert.AdvertisementType, fmt.Sprintf("%s-%s-%s", svc.Name, svc.Namespace, v2alpha1.BGPClusterIPAddr))
   879  	policy, err := CreatePolicy(policyName, peerAddr, v4Prefixes, v6Prefixes, advert)
   880  	if err != nil {
   881  		return nil, fmt.Errorf("failed to create cluster IP route policy: %w", err)
   882  	}
   883  
   884  	return policy, nil
   885  }
   886  
   887  func (r *ServiceReconciler) diffID(asn uint32) string {
   888  	return fmt.Sprintf("%s-%d", r.Name(), asn)
   889  }
   890  
   891  // checkServiceAdvertisement checks if the service advertisement is enabled in the advertisement.
   892  func checkServiceAdvertisement(advert v2alpha1.BGPAdvertisement, advertServiceType v2alpha1.BGPServiceAddressType) (bool, error) {
   893  	if advert.Service == nil {
   894  		return false, fmt.Errorf("advertisement has no service options")
   895  	}
   896  
   897  	// If selector is nil, we do not use this advertisement.
   898  	if advert.Selector == nil {
   899  		return false, nil
   900  	}
   901  
   902  	// check service type is enabled in advertisement
   903  	svcTypeEnabled := false
   904  	for _, serviceType := range advert.Service.Addresses {
   905  		if serviceType == advertServiceType {
   906  			svcTypeEnabled = true
   907  			break
   908  		}
   909  	}
   910  	if !svcTypeEnabled {
   911  		return false, nil
   912  	}
   913  
   914  	return true, nil
   915  }
   916  
   917  func serviceLabelSet(svc *slim_corev1.Service) labels.Labels {
   918  	svcLabels := maps.Clone(svc.Labels)
   919  	if svcLabels == nil {
   920  		svcLabels = make(map[string]string)
   921  	}
   922  	svcLabels["io.kubernetes.service.name"] = svc.Name
   923  	svcLabels["io.kubernetes.service.namespace"] = svc.Namespace
   924  	return labels.Set(svcLabels)
   925  }