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 }