github.com/cilium/cilium@v1.16.2/pkg/bgpv1/manager/reconcilerv2/neighbor_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 "testing" 9 10 "github.com/sirupsen/logrus" 11 "github.com/stretchr/testify/require" 12 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" 13 "k8s.io/utils/ptr" 14 15 "github.com/cilium/cilium/pkg/bgpv1/manager/instance" 16 "github.com/cilium/cilium/pkg/bgpv1/manager/store" 17 "github.com/cilium/cilium/pkg/bgpv1/types" 18 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1" 19 "github.com/cilium/cilium/pkg/k8s/resource" 20 slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1" 21 slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1" 22 "github.com/cilium/cilium/pkg/option" 23 ) 24 25 type checks struct { 26 holdTimer bool 27 connectRetryTimer bool 28 keepaliveTimer bool 29 grRestartTime bool 30 } 31 32 var ( 33 peer1 = PeerData{ 34 Peer: &v2alpha1.CiliumBGPNodePeer{ 35 Name: "peer-1", 36 PeerAddress: ptr.To[string]("192.168.0.1"), 37 PeerASN: ptr.To[int64](64124), 38 PeerConfigRef: &v2alpha1.PeerConfigReference{ 39 Group: "cilium.io", 40 Kind: v2alpha1.BGPPCKindDefinition, 41 Name: "peer-config", 42 }, 43 }, 44 Config: &v2alpha1.CiliumBGPPeerConfigSpec{ 45 Transport: &v2alpha1.CiliumBGPTransport{ 46 LocalPort: ptr.To[int32](v2alpha1.DefaultBGPPeerLocalPort), 47 PeerPort: ptr.To[int32](v2alpha1.DefaultBGPPeerPort), 48 }, 49 }, 50 } 51 52 peer2 = PeerData{ 53 Peer: &v2alpha1.CiliumBGPNodePeer{ 54 Name: "peer-2", 55 PeerAddress: ptr.To[string]("192.168.0.2"), 56 PeerASN: ptr.To[int64](64124), 57 PeerConfigRef: &v2alpha1.PeerConfigReference{ 58 Group: "cilium.io", 59 Kind: v2alpha1.BGPPCKindDefinition, 60 Name: "peer-config", 61 }, 62 }, 63 Config: &v2alpha1.CiliumBGPPeerConfigSpec{ 64 Transport: &v2alpha1.CiliumBGPTransport{ 65 LocalPort: ptr.To[int32](v2alpha1.DefaultBGPPeerLocalPort), 66 PeerPort: ptr.To[int32](v2alpha1.DefaultBGPPeerPort), 67 }, 68 }, 69 } 70 71 peer2UpdatedASN = func() PeerData { 72 peer2Copy := PeerData{ 73 Peer: peer2.Peer.DeepCopy(), 74 Config: peer2.Config.DeepCopy(), 75 Password: peer2.Password, 76 } 77 78 peer2Copy.Peer.PeerASN = ptr.To[int64](64125) 79 return peer2Copy 80 }() 81 82 peer2UpdatedTimers = func() PeerData { 83 peer2Copy := PeerData{ 84 Peer: peer2.Peer.DeepCopy(), 85 Config: peer2.Config.DeepCopy(), 86 Password: peer2.Password, 87 } 88 89 peer2Copy.Config.Timers = &v2alpha1.CiliumBGPTimers{ 90 ConnectRetryTimeSeconds: ptr.To[int32](3), 91 HoldTimeSeconds: ptr.To[int32](9), 92 KeepAliveTimeSeconds: ptr.To[int32](3), 93 } 94 95 return peer2Copy 96 }() 97 98 peer2UpdatedPorts = func() PeerData { 99 peer2Copy := PeerData{ 100 Peer: peer2.Peer.DeepCopy(), 101 Config: peer2.Config.DeepCopy(), 102 Password: peer2.Password, 103 } 104 105 peer2Copy.Config.Transport = &v2alpha1.CiliumBGPTransport{ 106 LocalPort: ptr.To[int32](1790), 107 PeerPort: ptr.To[int32](1790), 108 } 109 110 return peer2Copy 111 }() 112 113 peer2UpdatedGR = func() PeerData { 114 peer2Copy := PeerData{ 115 Peer: peer2.Peer.DeepCopy(), 116 Config: peer2.Config.DeepCopy(), 117 Password: peer2.Password, 118 } 119 120 peer2Copy.Config.GracefulRestart = &v2alpha1.CiliumBGPNeighborGracefulRestart{ 121 Enabled: true, 122 RestartTimeSeconds: ptr.To[int32](3), 123 } 124 125 return peer2Copy 126 }() 127 128 peer2Pass = func() PeerData { 129 peer2Copy := PeerData{ 130 Peer: peer2.Peer.DeepCopy(), 131 Config: peer2.Config.DeepCopy(), 132 Password: peer2.Password, 133 } 134 135 peer2Copy.Config.AuthSecretRef = ptr.To[string]("a-secret") 136 peer2Copy.Password = "a-password" 137 138 return peer2Copy 139 }() 140 141 peer2UpdatePass = func() PeerData { 142 peer2Copy := PeerData{ 143 Peer: peer2.Peer.DeepCopy(), 144 Config: peer2.Config.DeepCopy(), 145 Password: peer2.Password, 146 } 147 148 peer2Copy.Config.AuthSecretRef = ptr.To[string]("a-secret") 149 peer2Copy.Password = "b-password" 150 151 return peer2Copy 152 }() 153 ) 154 155 // TestNeighborReconciler confirms the `neighborReconciler` function configures 156 // the desired BGP neighbors given a CiliumBGPVirtualRouter configuration. 157 func TestNeighborReconciler(t *testing.T) { 158 req := require.New(t) 159 160 table := []struct { 161 name string 162 neighbors []PeerData 163 newNeighbors []PeerData 164 secretStore resource.Store[*slim_corev1.Secret] 165 checks checks 166 err error 167 }{ 168 { 169 name: "no change", 170 neighbors: []PeerData{peer1, peer2}, 171 newNeighbors: []PeerData{peer1, peer2}, 172 err: nil, 173 }, 174 { 175 name: "add peers", 176 neighbors: []PeerData{peer1}, 177 newNeighbors: []PeerData{peer1, peer2}, 178 err: nil, 179 }, 180 { 181 name: "remove peers", 182 neighbors: []PeerData{peer1, peer2}, 183 newNeighbors: []PeerData{peer1}, 184 err: nil, 185 }, 186 { 187 name: "update config : ASN", 188 neighbors: []PeerData{peer1, peer2}, 189 newNeighbors: []PeerData{peer1, peer2UpdatedASN}, 190 err: nil, 191 }, 192 { 193 name: "update config : timers", 194 neighbors: []PeerData{peer1, peer2}, 195 newNeighbors: []PeerData{peer1, peer2UpdatedTimers}, 196 err: nil, 197 }, 198 { 199 name: "update config : ports", 200 neighbors: []PeerData{peer1, peer2}, 201 newNeighbors: []PeerData{peer1, peer2UpdatedPorts}, 202 err: nil, 203 }, 204 { 205 name: "update config : graceful restart", 206 neighbors: []PeerData{peer1, peer2}, 207 newNeighbors: []PeerData{peer1, peer2UpdatedGR}, 208 err: nil, 209 }, 210 { 211 name: "update config : password", 212 neighbors: []PeerData{peer2}, 213 newNeighbors: []PeerData{peer2Pass}, 214 err: nil, 215 }, 216 { 217 name: "update config : password updated", 218 neighbors: []PeerData{peer2Pass}, 219 newNeighbors: []PeerData{peer2UpdatePass}, 220 err: nil, 221 }, 222 { 223 name: "update config : password removed", 224 neighbors: []PeerData{peer2Pass}, 225 newNeighbors: []PeerData{peer2}, 226 err: nil, 227 }, 228 } 229 for _, tt := range table { 230 t.Run(tt.name, func(t *testing.T) { 231 // our test BgpServer with our original router ID and local port 232 srvParams := types.ServerParameters{ 233 Global: types.BGPGlobal{ 234 ASN: 64125, 235 RouterID: "127.0.0.1", 236 ListenPort: -1, 237 }, 238 } 239 240 testInstance, err := instance.NewBGPInstance(context.Background(), logrus.WithField("unit_test", tt.name), srvParams) 241 req.NoError(err) 242 243 t.Cleanup(func() { 244 testInstance.Router.Stop() 245 }) 246 247 params, nodeConfig := setupNeighbors(tt.neighbors) 248 249 // setup initial neighbors 250 neighborReconciler := NewNeighborReconciler(params).Reconciler 251 reconcileParams := ReconcileParams{ 252 BGPInstance: testInstance, 253 DesiredConfig: nodeConfig, 254 } 255 err = neighborReconciler.Reconcile(context.Background(), reconcileParams) 256 req.NoError(err) 257 258 // validate neighbors 259 validatePeers(req, tt.neighbors, getRunningPeers(req, testInstance), tt.checks) 260 261 // update neighbors 262 params, nodeConfig = setupNeighbors(tt.newNeighbors) 263 neighborReconciler = NewNeighborReconciler(params).Reconciler 264 reconcileParams = ReconcileParams{ 265 BGPInstance: testInstance, 266 DesiredConfig: nodeConfig, 267 } 268 err = neighborReconciler.Reconcile(context.Background(), reconcileParams) 269 req.NoError(err) 270 271 // validate neighbors 272 validatePeers(req, tt.newNeighbors, getRunningPeers(req, testInstance), tt.checks) 273 }) 274 } 275 } 276 277 func setupNeighbors(peers []PeerData) (NeighborReconcilerIn, *v2alpha1.CiliumBGPNodeInstance) { 278 // Desired BGP Node config 279 nodeConfig := &v2alpha1.CiliumBGPNodeInstance{ 280 Name: "bgp-node", 281 } 282 283 // setup fake store for peer config 284 var objects []*v2alpha1.CiliumBGPPeerConfig 285 for _, p := range peers { 286 obj := &v2alpha1.CiliumBGPPeerConfig{ 287 ObjectMeta: metav1.ObjectMeta{ 288 Name: p.Peer.PeerConfigRef.Name, 289 }, 290 Spec: *p.Config.DeepCopy(), 291 } 292 objects = append(objects, obj) 293 nodeConfig.Peers = append(nodeConfig.Peers, *p.Peer) 294 } 295 peerConfigStore := store.InitMockStore[*v2alpha1.CiliumBGPPeerConfig](objects) 296 297 // setup secret store 298 secrets := make(map[string][]byte) 299 for _, p := range peers { 300 if p.Config.AuthSecretRef != nil { 301 secrets[*p.Config.AuthSecretRef] = []byte(p.Password) 302 } 303 } 304 var secretObjs []*slim_corev1.Secret 305 for _, s := range secrets { 306 secretObjs = append(secretObjs, &slim_corev1.Secret{ 307 ObjectMeta: slim_metav1.ObjectMeta{ 308 Namespace: "bgp-secrets", 309 Name: "a-secret", 310 }, 311 Data: map[string]slim_corev1.Bytes{"password": slim_corev1.Bytes(s)}, 312 }) 313 } 314 secretStore := store.InitMockStore[*slim_corev1.Secret](secretObjs) 315 316 return NeighborReconcilerIn{ 317 Logger: logrus.WithField("unit_test", "neighbors"), 318 SecretStore: secretStore, 319 PeerConfig: peerConfigStore, 320 DaemonConfig: &option.DaemonConfig{BGPSecretsNamespace: "bgp-secrets"}, 321 }, nodeConfig 322 } 323 324 func validatePeers(req *require.Assertions, expected, running []PeerData, checks checks) { 325 req.Equal(len(expected), len(running)) 326 327 for _, expPeer := range expected { 328 found := false 329 for _, runPeer := range running { 330 req.NotNil(runPeer.Peer.PeerAddress) 331 req.NotNil(runPeer.Peer.PeerASN) 332 333 if *expPeer.Peer.PeerAddress == *runPeer.Peer.PeerAddress && *expPeer.Peer.PeerASN == *runPeer.Peer.PeerASN { 334 found = true 335 336 if checks.holdTimer { 337 req.Equal(*expPeer.Config.Timers.HoldTimeSeconds, *runPeer.Config.Timers.HoldTimeSeconds) 338 } 339 340 if checks.connectRetryTimer { 341 req.Equal(*expPeer.Config.Timers.ConnectRetryTimeSeconds, *runPeer.Config.Timers.ConnectRetryTimeSeconds) 342 } 343 344 if checks.keepaliveTimer { 345 req.Equal(*expPeer.Config.Timers.KeepAliveTimeSeconds, *runPeer.Config.Timers.KeepAliveTimeSeconds) 346 } 347 348 if checks.grRestartTime { 349 req.Equal(expPeer.Config.GracefulRestart.Enabled, runPeer.Config.GracefulRestart.Enabled) 350 req.Equal(*expPeer.Config.GracefulRestart.RestartTimeSeconds, *runPeer.Config.GracefulRestart.RestartTimeSeconds) 351 } 352 353 if expPeer.Password != "" { 354 req.NotEmpty(runPeer.Password) 355 } 356 357 break 358 } 359 } 360 req.True(found) 361 } 362 } 363 364 func getRunningPeers(req *require.Assertions, instance *instance.BGPInstance) []PeerData { 365 getPeerResp, err := instance.Router.GetPeerState(context.Background()) 366 req.NoError(err) 367 368 var runningPeers []PeerData 369 for _, peer := range getPeerResp.Peers { 370 peerObj := &v2alpha1.CiliumBGPNodePeer{ 371 PeerAddress: ptr.To[string](peer.PeerAddress), 372 PeerASN: ptr.To[int64](peer.PeerAsn), 373 } 374 375 peerConfObj := &v2alpha1.CiliumBGPPeerConfigSpec{ 376 Transport: &v2alpha1.CiliumBGPTransport{ 377 PeerPort: ptr.To[int32](int32(peer.PeerPort)), 378 }, 379 Timers: &v2alpha1.CiliumBGPTimers{ 380 ConnectRetryTimeSeconds: ptr.To[int32](int32(peer.ConnectRetryTimeSeconds)), 381 HoldTimeSeconds: ptr.To[int32](int32(peer.ConfiguredHoldTimeSeconds)), 382 KeepAliveTimeSeconds: ptr.To[int32](int32(peer.ConfiguredKeepAliveTimeSeconds)), 383 }, 384 GracefulRestart: &v2alpha1.CiliumBGPNeighborGracefulRestart{ 385 Enabled: peer.GracefulRestart.Enabled, 386 RestartTimeSeconds: ptr.To[int32](int32(peer.GracefulRestart.RestartTimeSeconds)), 387 }, 388 EBGPMultihop: ptr.To[int32](int32(peer.EbgpMultihopTTL)), 389 } 390 391 password := "" 392 if peer.TCPPasswordEnabled { 393 password = "something-is-set-dont-care-what" 394 } 395 396 runningPeers = append(runningPeers, PeerData{ 397 Peer: peerObj, 398 Config: peerConfObj, 399 Password: password, 400 }) 401 } 402 return runningPeers 403 }