github.com/cilium/cilium@v1.16.2/pkg/bgpv1/manager/reconcilerv2/preflight_test.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 "testing" 10 11 "github.com/sirupsen/logrus" 12 "github.com/stretchr/testify/require" 13 meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 14 "k8s.io/utils/ptr" 15 16 "github.com/cilium/cilium/pkg/bgpv1/manager/instance" 17 "github.com/cilium/cilium/pkg/bgpv1/types" 18 v2api "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" 19 v2alpha1api "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1" 20 "github.com/cilium/cilium/pkg/node/addressing" 21 ) 22 23 // We use similar local listen ports as the tests in the pkg/bgpv1/test package. 24 // It is important to NOT use ports from the /proc/sys/net/ipv4/ip_local_port_range 25 // (defaulted to 32768-60999 on most Linux distributions) to avoid collisions with 26 // the ephemeral (source) ports. As this range is configurable, ideally, we should 27 // use the IANA-assigned ports below 1024 (e.g. 179) or mock GoBGP in these tests. 28 // See https://github.com/cilium/cilium/issues/26209 for more info. 29 // Note these ports should be different from the ports used in the pkg/bgpv1/manager/reconciler 30 const ( 31 localListenPort = 1780 32 localListenPort2 = 1781 33 localListenPort3 = 1782 34 ) 35 36 // TestPreflightReconciler ensures if a BgpServer must be recreated, due to 37 // permanent configuration of the said server changing, its done so correctly. 38 func TestPreflightReconciler(t *testing.T) { 39 req := require.New(t) 40 41 var table = []struct { 42 // modified BGPNodeInstance config 43 configModified bool 44 // name of test 45 name string 46 // ASN of original server 47 asn int64 48 // routerID of original server 49 routerID string 50 // routerID to reconcile 51 newAnnoRouterID string 52 // local node IP, from which router id will be generated as last resort 53 nodeIP string 54 // local annotation listen port of original server 55 localPort int32 56 // local annotation listen port to reconcile 57 newLocalPort int32 58 // virtual router configuration to reconcile 59 config *v2alpha1api.CiliumBGPNodeInstance 60 // should a recreation of the BgpServer 61 shouldRecreate bool 62 // export a nil error or not 63 err error 64 }{ 65 { 66 name: "no change", 67 asn: 64125, 68 routerID: "192.168.0.1", 69 newAnnoRouterID: "192.168.0.1", 70 localPort: localListenPort, 71 newLocalPort: localListenPort, 72 config: &v2alpha1api.CiliumBGPNodeInstance{ 73 Name: "test-instance", 74 LocalASN: ptr.To[int64](64125), 75 }, 76 shouldRecreate: false, 77 err: nil, 78 }, 79 { 80 name: "router-id annotation change", 81 asn: 64125, 82 routerID: "192.168.0.1", 83 newAnnoRouterID: "192.168.0.2", 84 localPort: localListenPort, 85 newLocalPort: localListenPort, 86 config: &v2alpha1api.CiliumBGPNodeInstance{ 87 Name: "test-instance", 88 LocalASN: ptr.To[int64](64125), 89 }, 90 shouldRecreate: true, 91 err: nil, 92 }, 93 { 94 name: "router-id from node IP", 95 asn: 64125, 96 routerID: "192.168.0.1", 97 nodeIP: "192.168.0.3", 98 localPort: localListenPort, 99 newLocalPort: localListenPort, 100 config: &v2alpha1api.CiliumBGPNodeInstance{ 101 Name: "test-instance", 102 LocalASN: ptr.To[int64](64125), 103 }, 104 shouldRecreate: true, 105 err: nil, 106 }, 107 { 108 configModified: true, 109 name: "router-id config change", 110 asn: 64125, 111 routerID: "192.168.0.1", 112 newAnnoRouterID: "192.168.0.1", 113 localPort: localListenPort, 114 newLocalPort: localListenPort, 115 config: &v2alpha1api.CiliumBGPNodeInstance{ 116 Name: "test-instance", 117 LocalASN: ptr.To[int64](64125), 118 RouterID: ptr.To[string]("192.168.0.3"), 119 }, 120 shouldRecreate: true, 121 err: nil, 122 }, 123 { 124 configModified: true, 125 name: "router-id annotation and config change", // config change takes precedence 126 asn: 64125, 127 routerID: "192.168.0.1", 128 newAnnoRouterID: "192.168.0.2", 129 localPort: localListenPort, 130 newLocalPort: localListenPort, 131 config: &v2alpha1api.CiliumBGPNodeInstance{ 132 Name: "test-instance", 133 LocalASN: ptr.To[int64](64125), 134 RouterID: ptr.To[string]("192.168.0.3"), 135 }, 136 shouldRecreate: true, 137 err: nil, 138 }, 139 { 140 name: "local-port annotation change", 141 asn: 64125, 142 routerID: "192.168.0.1", 143 newAnnoRouterID: "192.168.0.1", 144 localPort: localListenPort, 145 newLocalPort: localListenPort2, 146 config: &v2alpha1api.CiliumBGPNodeInstance{ 147 Name: "test-instance", 148 LocalASN: ptr.To[int64](64125), 149 }, 150 shouldRecreate: true, 151 err: nil, 152 }, 153 { 154 configModified: true, 155 name: "local-port config change", 156 asn: 64125, 157 routerID: "192.168.0.1", 158 newAnnoRouterID: "192.168.0.1", 159 localPort: localListenPort, 160 newLocalPort: localListenPort, 161 config: &v2alpha1api.CiliumBGPNodeInstance{ 162 Name: "test-instance", 163 LocalASN: ptr.To[int64](64125), 164 LocalPort: ptr.To[int32](localListenPort2), 165 }, 166 shouldRecreate: true, 167 err: nil, 168 }, 169 { 170 configModified: true, 171 name: "local-port annotation and config change", // config change takes precedence 172 asn: 64125, 173 routerID: "192.168.0.1", 174 newAnnoRouterID: "192.168.0.1", 175 localPort: localListenPort, 176 newLocalPort: localListenPort2, 177 config: &v2alpha1api.CiliumBGPNodeInstance{ 178 Name: "test-instance", 179 LocalASN: ptr.To[int64](64125), 180 LocalPort: ptr.To[int32](localListenPort3), 181 }, 182 shouldRecreate: true, 183 err: nil, 184 }, 185 { 186 name: "local-port, router-id annotation change", 187 asn: 64125, 188 routerID: "192.168.0.1", 189 newAnnoRouterID: "192.168.0.2", 190 localPort: localListenPort, 191 newLocalPort: localListenPort2, 192 config: &v2alpha1api.CiliumBGPNodeInstance{ 193 Name: "test-instance", 194 LocalASN: ptr.To[int64](64125), 195 }, 196 shouldRecreate: true, 197 err: nil, 198 }, 199 { 200 configModified: true, 201 name: "local-port, router-id config change", 202 asn: 64125, 203 routerID: "192.168.0.1", 204 newAnnoRouterID: "192.168.0.1", 205 localPort: localListenPort, 206 newLocalPort: localListenPort, 207 config: &v2alpha1api.CiliumBGPNodeInstance{ 208 Name: "test-instance", 209 LocalASN: ptr.To[int64](64125), 210 RouterID: ptr.To[string]("192.168.0.3"), 211 LocalPort: ptr.To[int32](localListenPort2), 212 }, 213 shouldRecreate: true, 214 err: nil, 215 }, 216 { 217 configModified: true, 218 name: "ASN in config change", 219 asn: 64125, 220 routerID: "192.168.0.1", 221 newAnnoRouterID: "192.168.0.1", 222 localPort: localListenPort, 223 newLocalPort: localListenPort, 224 config: &v2alpha1api.CiliumBGPNodeInstance{ 225 Name: "test-instance", 226 LocalASN: ptr.To[int64](64126), 227 RouterID: ptr.To[string]("192.168.0.1"), 228 LocalPort: ptr.To[int32](localListenPort), 229 }, 230 shouldRecreate: true, 231 err: nil, 232 }, 233 } 234 for _, tt := range table { 235 t.Run(tt.name, func(t *testing.T) { 236 // our test BgpServer with our original router ID and local port 237 srvParams := types.ServerParameters{ 238 Global: types.BGPGlobal{ 239 ASN: uint32(tt.asn), 240 RouterID: tt.routerID, 241 ListenPort: tt.localPort, 242 }, 243 } 244 testInstance, err := instance.NewBGPInstance(context.Background(), logrus.WithField("unit_test", "preflight"), srvParams) 245 if err != nil { 246 req.NoError(err) 247 } 248 249 // keep a pointer to the original server to avoid gc and to check 250 // later 251 originalRouter := testInstance.Router 252 t.Cleanup(func() { 253 originalRouter.Stop() // stop our test server 254 testInstance.Router.Stop() // stop any recreated server 255 }) 256 257 preflightReconciler := NewPreflightReconciler(PreflightReconcilerIn{ 258 Logger: logrus.WithField("unit_test", "preflight"), 259 }).Reconciler 260 261 annotationMap := "" 262 if tt.newAnnoRouterID != "" && tt.newLocalPort != 0 { 263 annotationMap = fmt.Sprintf("router-id=%s,local-port=%d", tt.newAnnoRouterID, tt.newLocalPort) 264 } else if tt.newAnnoRouterID != "" { 265 annotationMap = fmt.Sprintf("router-id=%s", tt.newAnnoRouterID) 266 } else if tt.newLocalPort != 0 { 267 annotationMap = fmt.Sprintf("local-port=%d", tt.newLocalPort) 268 } 269 270 ciliumNode := &v2api.CiliumNode{ 271 ObjectMeta: meta_v1.ObjectMeta{ 272 Name: "Test Node", 273 Annotations: map[string]string{ 274 fmt.Sprintf("cilium.io/bgp-virtual-router.%d", tt.asn): annotationMap, 275 }, 276 }, 277 Spec: v2api.NodeSpec{ 278 Addresses: []v2api.NodeAddress{ 279 { 280 Type: addressing.NodeInternalIP, 281 IP: tt.nodeIP, 282 }, 283 }, 284 }, 285 } 286 287 testInstance.Config = tt.config 288 289 params := ReconcileParams{ 290 BGPInstance: testInstance, 291 DesiredConfig: tt.config, 292 CiliumNode: ciliumNode, 293 } 294 295 err = preflightReconciler.Reconcile(context.Background(), params) 296 req.Equal(tt.err == nil, err == nil) 297 298 if tt.shouldRecreate && testInstance.Router == originalRouter { 299 req.Fail("preflightReconciler did not recreate router") 300 } 301 302 getBgpResp, err := testInstance.Router.GetBGP(context.Background()) 303 req.NoError(err) 304 305 bgpInfo := getBgpResp.Global 306 307 if tt.configModified { 308 if tt.config.LocalASN != nil { 309 req.Equal(*tt.config.LocalASN, int64(bgpInfo.ASN)) 310 } 311 if tt.config.RouterID != nil { 312 req.Equal(*tt.config.RouterID, bgpInfo.RouterID) 313 } 314 if tt.config.LocalPort != nil { 315 req.Equal(*tt.config.LocalPort, bgpInfo.ListenPort) 316 } 317 } else { 318 // check router ID is as expected (either from annotation or node IP) 319 if tt.newAnnoRouterID == "" && tt.nodeIP != "" { 320 req.Equal(tt.nodeIP, bgpInfo.RouterID) 321 } else { 322 req.Equal(tt.newAnnoRouterID, bgpInfo.RouterID) 323 } 324 req.Equal(tt.newLocalPort, bgpInfo.ListenPort) 325 } 326 }) 327 } 328 }