github.com/cilium/cilium@v1.16.2/pkg/bgpv1/manager/reconcilerv2/pod_ip_pool.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  
    16  	"github.com/cilium/cilium/pkg/bgpv1/manager/instance"
    17  	"github.com/cilium/cilium/pkg/bgpv1/manager/store"
    18  	"github.com/cilium/cilium/pkg/bgpv1/types"
    19  	v2api "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    20  	v2alpha1 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1"
    21  	"github.com/cilium/cilium/pkg/k8s/resource"
    22  	"github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/labels"
    23  	slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
    24  )
    25  
    26  const (
    27  	podIPPoolNameLabel      = "io.cilium.podippool.name"
    28  	podIPPoolNamespaceLabel = "io.cilium.podippool.namespace"
    29  )
    30  
    31  type PodIPPoolReconcilerOut struct {
    32  	cell.Out
    33  
    34  	Reconciler ConfigReconciler `group:"bgp-config-reconciler-v2"`
    35  }
    36  
    37  type PodIPPoolReconcilerIn struct {
    38  	cell.In
    39  
    40  	Logger     logrus.FieldLogger
    41  	PeerAdvert *CiliumPeerAdvertisement
    42  	PoolStore  store.BGPCPResourceStore[*v2alpha1.CiliumPodIPPool]
    43  }
    44  
    45  type PodIPPoolReconciler struct {
    46  	logger     logrus.FieldLogger
    47  	peerAdvert *CiliumPeerAdvertisement
    48  	poolStore  store.BGPCPResourceStore[*v2alpha1.CiliumPodIPPool]
    49  }
    50  
    51  // PodIPPoolReconcilerMetadata holds any announced pod ip pool CIDRs keyed by pool name of the backing CiliumPodIPPool.
    52  type PodIPPoolReconcilerMetadata struct {
    53  	PoolAFPaths       ResourceAFPathsMap
    54  	PoolRoutePolicies ResourceRoutePolicyMap
    55  }
    56  
    57  func NewPodIPPoolReconciler(in PodIPPoolReconcilerIn) PodIPPoolReconcilerOut {
    58  	if in.PoolStore == nil {
    59  		return PodIPPoolReconcilerOut{}
    60  	}
    61  
    62  	return PodIPPoolReconcilerOut{
    63  		Reconciler: &PodIPPoolReconciler{
    64  			logger:     in.Logger.WithField(types.ReconcilerLogField, "PodIPPool"),
    65  			peerAdvert: in.PeerAdvert,
    66  			poolStore:  in.PoolStore,
    67  		},
    68  	}
    69  }
    70  
    71  func (r *PodIPPoolReconciler) Name() string {
    72  	return "PodIPPool"
    73  }
    74  
    75  func (r *PodIPPoolReconciler) Priority() int {
    76  	return 50
    77  }
    78  
    79  func (r *PodIPPoolReconciler) Init(_ *instance.BGPInstance) error {
    80  	return nil
    81  }
    82  
    83  func (r *PodIPPoolReconciler) Cleanup(_ *instance.BGPInstance) {}
    84  
    85  func (r *PodIPPoolReconciler) Reconcile(ctx context.Context, p ReconcileParams) error {
    86  	if p.DesiredConfig == nil {
    87  		return fmt.Errorf("BUG: PodIPPoolReconciler reconciler called with nil CiliumBGPNodeConfig")
    88  	}
    89  
    90  	if p.CiliumNode == nil {
    91  		return fmt.Errorf("BUG: PodIPPoolReconciler reconciler called with nil CiliumNode")
    92  	}
    93  
    94  	lp := r.populateLocalPools(p.CiliumNode)
    95  
    96  	desiredPeerAdverts, err := r.peerAdvert.GetConfiguredAdvertisements(p.DesiredConfig, v2alpha1.BGPCiliumPodIPPoolAdvert)
    97  	if err != nil {
    98  		return err
    99  	}
   100  
   101  	err = r.reconcileRoutePolicies(ctx, p, desiredPeerAdverts, lp)
   102  	if err != nil {
   103  		return err
   104  	}
   105  
   106  	return r.reconcilePaths(ctx, p, desiredPeerAdverts, lp)
   107  }
   108  
   109  func (r *PodIPPoolReconciler) reconcilePaths(ctx context.Context, p ReconcileParams, desiredPeerAdverts PeerAdvertisements, lp map[string][]netip.Prefix) error {
   110  	poolsAFPaths, err := r.getDesiredPoolAFPaths(p, desiredPeerAdverts, lp)
   111  	if err != nil {
   112  		return err
   113  	}
   114  
   115  	metadata := r.getMetadata(p.BGPInstance)
   116  	for poolKey, desiredPoolAFPaths := range poolsAFPaths {
   117  		currentPoolAFPaths, exists := metadata.PoolAFPaths[poolKey]
   118  		if !exists && len(desiredPoolAFPaths) == 0 {
   119  			// No paths to reconcile for this pool.
   120  			continue
   121  		}
   122  
   123  		updatedPoolAFPaths, rErr := ReconcileAFPaths(&ReconcileAFPathsParams{
   124  			Logger: r.logger.WithFields(
   125  				logrus.Fields{
   126  					types.InstanceLogField:  p.DesiredConfig.Name,
   127  					types.PodIPPoolLogField: poolKey,
   128  				}),
   129  			Ctx:          ctx,
   130  			Router:       p.BGPInstance.Router,
   131  			DesiredPaths: desiredPoolAFPaths,
   132  			CurrentPaths: currentPoolAFPaths,
   133  		})
   134  
   135  		if rErr == nil && len(desiredPoolAFPaths) == 0 {
   136  			// No paths left for this pool.
   137  			delete(metadata.PoolAFPaths, poolKey)
   138  		} else {
   139  			metadata.PoolAFPaths[poolKey] = updatedPoolAFPaths
   140  		}
   141  		err = errors.Join(err, rErr)
   142  	}
   143  	r.setMetadata(p.BGPInstance, metadata)
   144  	return err
   145  }
   146  
   147  func (r *PodIPPoolReconciler) getDesiredPoolAFPaths(p ReconcileParams, desiredFamilyAdverts PeerAdvertisements, lp map[string][]netip.Prefix) (ResourceAFPathsMap, error) {
   148  	desiredPoolAFPaths := make(ResourceAFPathsMap)
   149  
   150  	metadata := r.getMetadata(p.BGPInstance)
   151  
   152  	// check if any pool is deleted
   153  	for poolKey := range metadata.PoolAFPaths {
   154  		_, exists, err := r.poolStore.GetByKey(poolKey)
   155  		if err != nil {
   156  			return nil, err
   157  		}
   158  
   159  		if !exists {
   160  			// pool is deleted, mark it for removal
   161  			desiredPoolAFPaths[poolKey] = nil
   162  		}
   163  	}
   164  
   165  	pools, err := r.poolStore.List()
   166  	if err != nil {
   167  		return nil, err
   168  	}
   169  
   170  	for _, pool := range pools {
   171  		desiredPaths, err := r.getDesiredAFPaths(pool, desiredFamilyAdverts, lp)
   172  		if err != nil {
   173  			return nil, err
   174  		}
   175  
   176  		poolKey := resource.Key{
   177  			Name:      pool.Name,
   178  			Namespace: pool.Namespace,
   179  		}
   180  
   181  		desiredPoolAFPaths[poolKey] = desiredPaths
   182  	}
   183  	return desiredPoolAFPaths, nil
   184  }
   185  
   186  func (r *PodIPPoolReconciler) reconcileRoutePolicies(ctx context.Context, p ReconcileParams, desiredPeerAdverts PeerAdvertisements, lp map[string][]netip.Prefix) error {
   187  	desiredPoolsRPs, err := r.getDesiredPodIPPoolRoutePolicies(p, desiredPeerAdverts, lp)
   188  	if err != nil {
   189  		return err
   190  	}
   191  
   192  	metadata := r.getMetadata(p.BGPInstance)
   193  	for poolKey, desiredRPs := range desiredPoolsRPs {
   194  		currentRPs, exists := metadata.PoolRoutePolicies[poolKey]
   195  		if !exists && len(desiredRPs) == 0 {
   196  			continue
   197  		}
   198  
   199  		updatedRPs, rErr := ReconcileRoutePolicies(&ReconcileRoutePoliciesParams{
   200  			Logger: r.logger.WithFields(
   201  				logrus.Fields{
   202  					types.InstanceLogField:  p.DesiredConfig.Name,
   203  					types.PodIPPoolLogField: poolKey,
   204  				}),
   205  			Ctx:             ctx,
   206  			Router:          p.BGPInstance.Router,
   207  			DesiredPolicies: desiredRPs,
   208  			CurrentPolicies: currentRPs,
   209  		})
   210  
   211  		if rErr == nil && len(desiredRPs) == 0 {
   212  			delete(metadata.PoolRoutePolicies, poolKey)
   213  		} else {
   214  			metadata.PoolRoutePolicies[poolKey] = updatedRPs
   215  		}
   216  
   217  		err = errors.Join(err, rErr)
   218  	}
   219  	r.setMetadata(p.BGPInstance, metadata)
   220  
   221  	return err
   222  }
   223  
   224  func (r *PodIPPoolReconciler) getDesiredPodIPPoolRoutePolicies(p ReconcileParams, desiredPeerAdverts PeerAdvertisements, lp map[string][]netip.Prefix) (ResourceRoutePolicyMap, error) {
   225  	metadata := r.getMetadata(p.BGPInstance)
   226  
   227  	desiredPodIPPoolRoutePolicies := make(ResourceRoutePolicyMap)
   228  
   229  	// mark for deleting pool policies
   230  	for poolKey := range metadata.PoolRoutePolicies {
   231  		_, exists, err := r.poolStore.GetByKey(poolKey)
   232  		if err != nil {
   233  			return nil, err
   234  		}
   235  
   236  		if !exists {
   237  			// pool is deleted, mark it for removal
   238  			desiredPodIPPoolRoutePolicies[poolKey] = nil
   239  		}
   240  	}
   241  
   242  	// get all pools and their route policies
   243  	pools, err := r.poolStore.List()
   244  	if err != nil {
   245  		return nil, err
   246  	}
   247  
   248  	for _, pool := range pools {
   249  		desiredPoolRoutePolicies, err := r.getPodIPPoolPolicies(p, pool, desiredPeerAdverts, lp)
   250  		if err != nil {
   251  			return nil, err
   252  		}
   253  
   254  		key := resource.Key{
   255  			Name:      pool.Name,
   256  			Namespace: pool.Namespace,
   257  		}
   258  		desiredPodIPPoolRoutePolicies[key] = desiredPoolRoutePolicies
   259  	}
   260  
   261  	return desiredPodIPPoolRoutePolicies, nil
   262  }
   263  
   264  func (r *PodIPPoolReconciler) getPodIPPoolPolicies(p ReconcileParams, pool *v2alpha1.CiliumPodIPPool, desiredPeerAdverts PeerAdvertisements, lp map[string][]netip.Prefix) (RoutePolicyMap, error) {
   265  	desiredRoutePolicies := make(RoutePolicyMap)
   266  
   267  	for peer, afAdverts := range desiredPeerAdverts {
   268  		for family, adverts := range afAdverts {
   269  			fam := types.ToAgentFamily(family)
   270  			for _, advert := range adverts {
   271  				policy, err := r.getPodIPPoolPolicy(p, peer, fam, pool, advert, lp)
   272  				if err != nil {
   273  					return nil, err
   274  				}
   275  				if policy != nil {
   276  					desiredRoutePolicies[policy.Name] = policy
   277  				}
   278  			}
   279  		}
   280  	}
   281  
   282  	return desiredRoutePolicies, nil
   283  }
   284  
   285  // populateLocalPools returns a map of allocated multi-pool IPAM CIDRs of the local CiliumNode,
   286  // keyed by the pool name.
   287  func (r *PodIPPoolReconciler) populateLocalPools(localNode *v2api.CiliumNode) map[string][]netip.Prefix {
   288  	if localNode == nil {
   289  		return nil
   290  	}
   291  
   292  	lp := make(map[string][]netip.Prefix)
   293  	for _, pool := range localNode.Spec.IPAM.Pools.Allocated {
   294  		var prefixes []netip.Prefix
   295  		for _, cidr := range pool.CIDRs {
   296  			if p, err := cidr.ToPrefix(); err == nil {
   297  				prefixes = append(prefixes, *p)
   298  			} else {
   299  				r.logger.WithField(types.PrefixLogField, cidr).WithError(err).Error("invalid IPAM pool CIDR")
   300  			}
   301  		}
   302  		lp[pool.Pool] = prefixes
   303  	}
   304  
   305  	return lp
   306  }
   307  
   308  func (r *PodIPPoolReconciler) getDesiredAFPaths(pool *v2alpha1.CiliumPodIPPool, desiredPeerAdverts PeerAdvertisements, lp map[string][]netip.Prefix) (AFPathsMap, error) {
   309  	// Calculate desired paths per address family, collapsing per-peer advertisements into per-family advertisements.
   310  	desiredFamilyAdverts := make(AFPathsMap)
   311  
   312  	for _, peerFamilyAdverts := range desiredPeerAdverts {
   313  		for family, familyAdverts := range peerFamilyAdverts {
   314  			agentFamily := types.ToAgentFamily(family)
   315  
   316  			for _, advert := range familyAdverts {
   317  				// sanity check advertisement type
   318  				if advert.AdvertisementType != v2alpha1.BGPCiliumPodIPPoolAdvert {
   319  					r.logger.WithField(types.AdvertTypeLogField, advert.AdvertisementType).Error("BUG: unexpected advertisement type")
   320  					continue
   321  				}
   322  
   323  				// check if the pool selector matches the advertisement
   324  				poolSelector, err := slim_metav1.LabelSelectorAsSelector(advert.Selector)
   325  				if err != nil {
   326  					return nil, fmt.Errorf("failed to convert label selector: %w", err)
   327  				}
   328  
   329  				// Ignore non matching pool.
   330  				if !poolSelector.Matches(podIPPoolLabelSet(pool)) {
   331  					continue
   332  				}
   333  
   334  				if prefixes, exists := lp[pool.Name]; exists {
   335  					// on the local node we have this pool configured.
   336  					// add the prefixes to the desiredPaths.
   337  					for _, prefix := range prefixes {
   338  						path := types.NewPathForPrefix(prefix)
   339  						path.Family = agentFamily
   340  
   341  						// we only add path corresponding to the family of the prefix.
   342  						if agentFamily.Afi == types.AfiIPv4 && prefix.Addr().Is4() {
   343  							addPathToAFPathsMap(desiredFamilyAdverts, agentFamily, path)
   344  						}
   345  						if agentFamily.Afi == types.AfiIPv6 && prefix.Addr().Is6() {
   346  							addPathToAFPathsMap(desiredFamilyAdverts, agentFamily, path)
   347  						}
   348  					}
   349  				}
   350  			}
   351  		}
   352  	}
   353  
   354  	return desiredFamilyAdverts, nil
   355  }
   356  
   357  func (r *PodIPPoolReconciler) getPodIPPoolPolicy(p ReconcileParams, peer string, family types.Family, pool *v2alpha1.CiliumPodIPPool, advert v2alpha1.BGPAdvertisement, lp map[string][]netip.Prefix) (*types.RoutePolicy, error) {
   358  	// get the peer address
   359  	peerAddr, err := GetPeerAddressFromConfig(p.DesiredConfig, peer)
   360  	if err != nil {
   361  		return nil, fmt.Errorf("failed to get peer address: %w", err)
   362  	}
   363  
   364  	// check if the pool selector matches the advertisement
   365  	poolSelector, err := slim_metav1.LabelSelectorAsSelector(advert.Selector)
   366  	if err != nil {
   367  		return nil, fmt.Errorf("failed to convert label selector: %w", err)
   368  	}
   369  
   370  	// Ignore non matching pool.
   371  	if !poolSelector.Matches(podIPPoolLabelSet(pool)) {
   372  		return nil, nil
   373  	}
   374  
   375  	// only include pool cidrs that have been allocated to the local node.
   376  	prefixes, exists := lp[pool.Name]
   377  	if !exists {
   378  		return nil, nil
   379  	}
   380  
   381  	var v4Prefixes, v6Prefixes types.PolicyPrefixMatchList
   382  
   383  	for _, prefix := range prefixes {
   384  		if family.Afi == types.AfiIPv4 && prefix.Addr().Is4() {
   385  			prefixLen := int(pool.Spec.IPv4.MaskSize)
   386  			v4Prefixes = append(v4Prefixes, &types.RoutePolicyPrefixMatch{
   387  				CIDR:         prefix,
   388  				PrefixLenMin: prefixLen,
   389  				PrefixLenMax: prefixLen,
   390  			})
   391  		}
   392  
   393  		if family.Afi == types.AfiIPv6 && prefix.Addr().Is6() {
   394  			prefixLen := int(pool.Spec.IPv6.MaskSize)
   395  			v6Prefixes = append(v6Prefixes, &types.RoutePolicyPrefixMatch{
   396  				CIDR:         prefix,
   397  				PrefixLenMin: prefixLen,
   398  				PrefixLenMax: prefixLen,
   399  			})
   400  		}
   401  	}
   402  
   403  	// if no prefixes are found for the pool, return nil
   404  	if len(v4Prefixes) == 0 && len(v6Prefixes) == 0 {
   405  		return nil, nil
   406  	}
   407  
   408  	policyName := PolicyName(peer, family.Afi.String(), advert.AdvertisementType, pool.Name)
   409  	return CreatePolicy(policyName, peerAddr, v4Prefixes, v6Prefixes, advert)
   410  }
   411  
   412  func podIPPoolLabelSet(pool *v2alpha1.CiliumPodIPPool) labels.Labels {
   413  	poolLabels := maps.Clone(pool.Labels)
   414  	if poolLabels == nil {
   415  		poolLabels = make(map[string]string)
   416  	}
   417  	poolLabels[podIPPoolNameLabel] = pool.Name
   418  	poolLabels[podIPPoolNamespaceLabel] = pool.Namespace
   419  	return labels.Set(poolLabels)
   420  }
   421  
   422  func (r *PodIPPoolReconciler) getMetadata(i *instance.BGPInstance) PodIPPoolReconcilerMetadata {
   423  	if _, found := i.Metadata[r.Name()]; !found {
   424  		i.Metadata[r.Name()] = PodIPPoolReconcilerMetadata{
   425  			PoolAFPaths:       make(ResourceAFPathsMap),
   426  			PoolRoutePolicies: make(ResourceRoutePolicyMap),
   427  		}
   428  	}
   429  	return i.Metadata[r.Name()].(PodIPPoolReconcilerMetadata)
   430  }
   431  
   432  func (r *PodIPPoolReconciler) setMetadata(i *instance.BGPInstance, metadata PodIPPoolReconcilerMetadata) {
   433  	i.Metadata[r.Name()] = metadata
   434  }