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

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package reconcilerv2
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  
    10  	"github.com/cilium/hive/cell"
    11  	"github.com/sirupsen/logrus"
    12  
    13  	"github.com/cilium/cilium/pkg/bgpv1/agent"
    14  	"github.com/cilium/cilium/pkg/bgpv1/manager/instance"
    15  	"github.com/cilium/cilium/pkg/bgpv1/types"
    16  )
    17  
    18  // PreflightReconciler reconciles BPG Global configuration. This reconciler is similar to v1 preflight reconciler.
    19  // It must be run before any other reconcilers for given BGP instance.
    20  type PreflightReconciler struct {
    21  	Logger logrus.FieldLogger
    22  }
    23  
    24  type PreflightReconcilerIn struct {
    25  	cell.In
    26  
    27  	Logger logrus.FieldLogger
    28  }
    29  
    30  type PreflightReconcilerOut struct {
    31  	cell.Out
    32  
    33  	Reconciler ConfigReconciler `group:"bgp-config-reconciler-v2"`
    34  }
    35  
    36  func NewPreflightReconciler(params PreflightReconcilerIn) PreflightReconcilerOut {
    37  	logger := params.Logger.WithField(types.ReconcilerLogField, "Preflight")
    38  	return PreflightReconcilerOut{
    39  		Reconciler: &PreflightReconciler{
    40  			Logger: logger,
    41  		},
    42  	}
    43  }
    44  
    45  func (r *PreflightReconciler) Name() string {
    46  	return "Preflight"
    47  }
    48  
    49  func (r *PreflightReconciler) Priority() int {
    50  	return 10
    51  }
    52  
    53  func (r *PreflightReconciler) Init(_ *instance.BGPInstance) error {
    54  	return nil
    55  }
    56  
    57  func (r *PreflightReconciler) Cleanup(_ *instance.BGPInstance) {}
    58  
    59  func (r *PreflightReconciler) Reconcile(ctx context.Context, p ReconcileParams) error {
    60  	l := r.Logger.WithFields(logrus.Fields{
    61  		types.InstanceLogField: p.DesiredConfig.Name,
    62  	})
    63  
    64  	// If we have no config attached, we don't need to perform a preflight for
    65  	// reconciliation.
    66  	//
    67  	// This is the first time this instance is being registered and BGPRouterManager
    68  	// set any fields needing reconciliation in this function already.
    69  	if p.BGPInstance.Config == nil {
    70  		l.Debug("Preflight for instance not necessary, first instantiation of this BgpServer.")
    71  		return nil
    72  	}
    73  
    74  	l.Debug("Begin preflight reconciliation")
    75  	bgpInfo, err := p.BGPInstance.Router.GetBGP(ctx)
    76  	if err != nil {
    77  		return fmt.Errorf("failed to retrieve BgpServer info for instance %v: %w", p.DesiredConfig.Name, err)
    78  	}
    79  
    80  	localASN, err := r.getLocalASN(p)
    81  	if err != nil {
    82  		return fmt.Errorf("failed to get local ASN for instance %v: %w", p.DesiredConfig.Name, err)
    83  	}
    84  
    85  	localPort, err := r.getLocalPort(p, localASN)
    86  	if err != nil {
    87  		return fmt.Errorf("failed to get local port for instance %v: %w", p.DesiredConfig.Name, err)
    88  	}
    89  
    90  	routerID, err := r.getRouterID(p, localASN)
    91  	if err != nil {
    92  		return fmt.Errorf("failed to get router ID for instance %v: %w", p.DesiredConfig.Name, err)
    93  	}
    94  
    95  	var shouldRecreate bool
    96  	if localASN != int64(bgpInfo.Global.ASN) {
    97  		shouldRecreate = true
    98  		l.WithFields(logrus.Fields{
    99  			types.LocalASNLogField: bgpInfo.Global.ASN,
   100  			"new_asn":              localASN,
   101  		}).Info("BGP instance ASN modified")
   102  	}
   103  	if localPort != bgpInfo.Global.ListenPort {
   104  		shouldRecreate = true
   105  		l.WithFields(logrus.Fields{
   106  			types.ListenPortLogField: bgpInfo.Global.ListenPort,
   107  			"new_port":               localPort,
   108  		}).Info("BGP instance local port modified")
   109  	}
   110  	if routerID != bgpInfo.Global.RouterID {
   111  		shouldRecreate = true
   112  		l.WithFields(logrus.Fields{
   113  			types.RouterIDLogField: bgpInfo.Global.RouterID,
   114  			"new_router_id":        routerID,
   115  		}).Info("BGP instance router ID modified")
   116  	}
   117  
   118  	if !shouldRecreate {
   119  		l.Debug("No preflight reconciliation necessary")
   120  		return nil
   121  	}
   122  
   123  	l.Info("Recreating BGP instance for changes to take effect")
   124  	globalConfig := types.ServerParameters{
   125  		Global: types.BGPGlobal{
   126  			ASN:        uint32(localASN),
   127  			RouterID:   routerID,
   128  			ListenPort: localPort,
   129  			RouteSelectionOptions: &types.RouteSelectionOptions{
   130  				AdvertiseInactiveRoutes: true,
   131  			},
   132  		},
   133  	}
   134  
   135  	// stop the old BGP instance
   136  	p.BGPInstance.Router.Stop()
   137  
   138  	// create a new one via BGPInstance constructor
   139  	s, err := instance.NewBGPInstance(ctx, l, globalConfig)
   140  	if err != nil {
   141  		return fmt.Errorf("failed to start BGP instance for %s: %w", p.DesiredConfig.Name, err)
   142  	}
   143  
   144  	// replace the old underlying instance with our recreated one
   145  	p.BGPInstance.Router = s.Router
   146  
   147  	// dump the existing config so all subsequent reconcilers perform their
   148  	// actions as if this is a new instance.
   149  	p.BGPInstance.Config = nil
   150  
   151  	// Clear the shadow state since any peer, advertisements will be gone now that the instance has been recreated.
   152  	p.BGPInstance.Metadata = make(map[string]any)
   153  
   154  	return nil
   155  }
   156  
   157  // getLocalASN returns the local ASN for the given BGP instance. If the local ASN is defined in the desired config, it
   158  // will be returned. Currently, we do not support auto-ASN assignment, so if the local ASN is not defined in the
   159  // desired config, an error will be returned.
   160  func (r *PreflightReconciler) getLocalASN(p ReconcileParams) (int64, error) {
   161  	if p.DesiredConfig.LocalASN != nil {
   162  		return *p.DesiredConfig.LocalASN, nil
   163  	}
   164  
   165  	return -1, fmt.Errorf("missing ASN in desired config")
   166  }
   167  
   168  // getRouterID returns the router ID for the given ASN. If the router ID is defined in the desired config, it will
   169  // be returned. Otherwise, the router ID will be resolved from the ciliumnode annotations. If the router ID is not
   170  // defined in the annotations, the node IP from cilium node will be returned.
   171  func (r *PreflightReconciler) getRouterID(p ReconcileParams, asn int64) (string, error) {
   172  	if p.DesiredConfig.RouterID != nil {
   173  		return *p.DesiredConfig.RouterID, nil
   174  	}
   175  
   176  	// parse Node annotations into helper Annotation map
   177  	annoMap, err := agent.NewAnnotationMap(p.CiliumNode.Annotations)
   178  	if err != nil {
   179  		return "", fmt.Errorf("failed to parse Node annotations for instance %v: %w", p.DesiredConfig.Name, err)
   180  	}
   181  
   182  	routerID, err := annoMap.ResolveRouterID(asn)
   183  	if err != nil {
   184  		if nodeIP := p.CiliumNode.GetIP(false); nodeIP == nil {
   185  			return "", fmt.Errorf("failed to get ciliumnode IP %v: %w", nodeIP, err)
   186  		} else {
   187  			routerID = nodeIP.String()
   188  		}
   189  	}
   190  
   191  	return routerID, nil
   192  }
   193  
   194  // getLocalPort returns the local port for the given ASN. If the local port is defined in the desired config, it will
   195  // be returned. Otherwise, the local port will be resolved from the ciliumnode annotations. If the local port is not
   196  // defined in the annotations, -1 will be returned.
   197  //
   198  // In gobgp, with -1 as the local port, bgp instance will start in non-listening mode.
   199  func (r *PreflightReconciler) getLocalPort(p ReconcileParams, asn int64) (int32, error) {
   200  	if p.DesiredConfig.LocalPort != nil {
   201  		return *p.DesiredConfig.LocalPort, nil
   202  	}
   203  
   204  	// parse Node annotations into helper Annotation map
   205  	annoMap, err := agent.NewAnnotationMap(p.CiliumNode.Annotations)
   206  	if err != nil {
   207  		return -1, fmt.Errorf("failed to parse Node annotations for instance %v: %w", p.DesiredConfig.Name, err)
   208  	}
   209  
   210  	localPort := int32(-1)
   211  	if attrs, ok := annoMap[asn]; ok {
   212  		if attrs.LocalPort != 0 {
   213  			localPort = attrs.LocalPort
   214  		}
   215  	}
   216  
   217  	return localPort, nil
   218  }