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

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package reconcilerv2
     5  
     6  import (
     7  	"context"
     8  	"maps"
     9  
    10  	"github.com/sirupsen/logrus"
    11  
    12  	"github.com/cilium/cilium/pkg/bgpv1/types"
    13  	"github.com/cilium/cilium/pkg/k8s/resource"
    14  )
    15  
    16  // PathMap is a map of paths indexed by the NLRI string
    17  type PathMap map[string]*types.Path
    18  
    19  // AFPathsMap is a map of paths per address family, indexed by the family
    20  type AFPathsMap map[types.Family]PathMap
    21  
    22  // ResourceAFPathsMap holds the AF paths keyed by the resource name.
    23  type ResourceAFPathsMap map[resource.Key]AFPathsMap
    24  
    25  type ReconcileAFPathsParams struct {
    26  	Logger       logrus.FieldLogger
    27  	Ctx          context.Context
    28  	Router       types.Router
    29  	DesiredPaths AFPathsMap
    30  	CurrentPaths AFPathsMap
    31  }
    32  
    33  type reconcilePathsParams struct {
    34  	Logger                logrus.FieldLogger
    35  	Ctx                   context.Context
    36  	Router                types.Router
    37  	CurrentAdvertisements PathMap
    38  	ToAdvertise           PathMap
    39  }
    40  
    41  // ReconcileAFPaths reconciles BGP advertisements per address family. It will consume desired and current paths (AFPathsMap)
    42  // and will return the outcome of the reconciliation.
    43  func ReconcileAFPaths(rp *ReconcileAFPathsParams) (AFPathsMap, error) {
    44  	runningAFPaths := make(AFPathsMap)
    45  	maps.Copy(runningAFPaths, rp.CurrentPaths)
    46  
    47  	// to delete family advertisements that are not in desiredPaths
    48  	for family, runningPaths := range runningAFPaths {
    49  		if _, ok := rp.DesiredPaths[family]; !ok {
    50  			runningAdverts, err := reconcilePaths(&reconcilePathsParams{
    51  				Logger:                rp.Logger,
    52  				Ctx:                   rp.Ctx,
    53  				Router:                rp.Router,
    54  				CurrentAdvertisements: runningPaths,
    55  				ToAdvertise:           nil,
    56  			})
    57  			if err != nil {
    58  				runningAFPaths[family] = runningAdverts
    59  				return runningAFPaths, err
    60  			}
    61  			delete(runningAFPaths, family)
    62  		}
    63  	}
    64  
    65  	// to update family advertisements that are in both runningState and desiredPaths
    66  	for family := range rp.DesiredPaths {
    67  		runningAdverts, err := reconcilePaths(&reconcilePathsParams{
    68  			Logger:                rp.Logger,
    69  			Ctx:                   rp.Ctx,
    70  			Router:                rp.Router,
    71  			CurrentAdvertisements: runningAFPaths[family],
    72  			ToAdvertise:           rp.DesiredPaths[family],
    73  		})
    74  
    75  		// update runningState with the new advertisements
    76  		// even on error, we want to update the runningState with current advertisements.
    77  		runningAFPaths[family] = runningAdverts
    78  		if err != nil {
    79  			return runningAFPaths, err
    80  		}
    81  	}
    82  
    83  	return runningAFPaths, nil
    84  }
    85  
    86  // reconcilePaths reconciles the state of the BGP advertisements
    87  // with the provided toAdvertise path map and returns a path map of the advertisements
    88  // currently being announced.
    89  // If there is an error from the BGP Router, the function will return the current advertisements in BGP router
    90  // and the error.
    91  func reconcilePaths(params *reconcilePathsParams) (PathMap, error) {
    92  	var (
    93  		// logger for the reconciler
    94  		l = params.Logger
    95  		// holds advertisements which must be advertised
    96  		toAdvertise = make(PathMap)
    97  		// holds advertisements which must be removed
    98  		toWithdraw = make(PathMap)
    99  	)
   100  
   101  	// running advertisements
   102  	runningAdverts := make(PathMap)
   103  	maps.Copy(runningAdverts, params.CurrentAdvertisements)
   104  
   105  	// if there are no advertisements to be made, we will withdraw all current advertisements
   106  	if len(params.ToAdvertise) == 0 {
   107  		for advrtKey, advrt := range runningAdverts {
   108  			if advrt == nil {
   109  				l.WithField(types.PathLogField, advrtKey).Error("BUG: nil path in running advertisements map")
   110  				continue
   111  			}
   112  
   113  			l.WithFields(logrus.Fields{
   114  				types.PathLogField:   advrt.NLRI.String(),
   115  				types.FamilyLogField: advrt.Family.String(),
   116  			}).Debug("Withdrawing path")
   117  
   118  			if err := params.Router.WithdrawPath(params.Ctx, types.PathRequest{Path: advrt}); err != nil {
   119  				return runningAdverts, err
   120  			}
   121  			delete(runningAdverts, advrtKey)
   122  		}
   123  		return nil, nil
   124  	}
   125  
   126  	for advrtKey, advrt := range params.ToAdvertise {
   127  		if advrt == nil {
   128  			l.WithField(types.PathLogField, advrtKey).Error("BUG: nil path in advertise advertisements map")
   129  			continue
   130  		}
   131  
   132  		if _, exists := runningAdverts[advrtKey]; !exists {
   133  			toAdvertise[advrtKey] = advrt
   134  		}
   135  	}
   136  
   137  	for advrtKey, advrt := range runningAdverts {
   138  		if advrt == nil {
   139  			l.WithField(types.PathLogField, advrtKey).Error("BUG: nil path in running advertisements map")
   140  			continue
   141  		}
   142  
   143  		if _, exists := params.ToAdvertise[advrtKey]; !exists {
   144  			toWithdraw[advrtKey] = advrt
   145  		}
   146  	}
   147  
   148  	if len(toAdvertise) == 0 && len(toWithdraw) == 0 {
   149  		l.Debug("no reconciliation necessary")
   150  		return params.CurrentAdvertisements, nil
   151  	}
   152  
   153  	// withdraw unneeded adverts
   154  	for advrtKey, advrt := range toWithdraw {
   155  		l.WithFields(logrus.Fields{
   156  			types.PathLogField:   advrt.NLRI.String(),
   157  			types.FamilyLogField: advrt.Family.String(),
   158  		}).Debug("Withdrawing path")
   159  
   160  		if err := params.Router.WithdrawPath(params.Ctx, types.PathRequest{Path: advrt}); err != nil {
   161  			return runningAdverts, err
   162  		}
   163  		delete(runningAdverts, advrtKey)
   164  	}
   165  
   166  	// create new adverts
   167  	for advrtKey, advrt := range toAdvertise {
   168  		l.WithFields(logrus.Fields{
   169  			types.PathLogField:   advrt.NLRI.String(),
   170  			types.FamilyLogField: advrt.Family.String(),
   171  		}).Debug("Advertising path")
   172  
   173  		advrtResp, err := params.Router.AdvertisePath(params.Ctx, types.PathRequest{Path: advrt})
   174  		if err != nil {
   175  			return runningAdverts, err
   176  		}
   177  		runningAdverts[advrtKey] = advrtResp.Path
   178  	}
   179  
   180  	return runningAdverts, nil
   181  }
   182  
   183  func addPathToAFPathsMap(m AFPathsMap, fam types.Family, path *types.Path) {
   184  	pathsPerFamily, exists := m[fam]
   185  	if !exists {
   186  		pathsPerFamily = make(PathMap)
   187  		m[fam] = pathsPerFamily
   188  	}
   189  	pathsPerFamily[path.NLRI.String()] = path
   190  }