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 }