github.com/cilium/cilium@v1.16.2/operator/pkg/bgpv2/cluster_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package bgpv2 5 6 import ( 7 "context" 8 "testing" 9 10 "github.com/cilium/hive/hivetest" 11 "github.com/stretchr/testify/assert" 12 "github.com/stretchr/testify/require" 13 k8sErrors "k8s.io/apimachinery/pkg/api/errors" 14 meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1" 15 "k8s.io/utils/ptr" 16 17 cilium_api_v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" 18 cilium_api_v2alpha1 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1" 19 slim_meta_v1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1" 20 "github.com/cilium/cilium/pkg/time" 21 ) 22 23 var ( 24 cluster1 = cilium_api_v2alpha1.CiliumBGPInstance{ 25 Name: "cluster-1-instance-65001", 26 LocalASN: ptr.To[int64](65001), 27 Peers: []cilium_api_v2alpha1.CiliumBGPPeer{ 28 { 29 Name: "cluster-1-instance-65002-peer-10.0.0.2", 30 PeerAddress: ptr.To[string]("10.0.0.2"), 31 PeerASN: ptr.To[int64](65002), 32 PeerConfigRef: &cilium_api_v2alpha1.PeerConfigReference{ 33 Name: "peer-1", 34 }, 35 }, 36 }, 37 } 38 39 expectedNode1 = cilium_api_v2alpha1.CiliumBGPNodeInstance{ 40 Name: "cluster-1-instance-65001", 41 LocalASN: ptr.To[int64](65001), 42 Peers: []cilium_api_v2alpha1.CiliumBGPNodePeer{ 43 { 44 Name: "cluster-1-instance-65002-peer-10.0.0.2", 45 PeerAddress: ptr.To[string]("10.0.0.2"), 46 PeerASN: ptr.To[int64](65002), 47 PeerConfigRef: &cilium_api_v2alpha1.PeerConfigReference{ 48 Name: "peer-1", 49 }, 50 }, 51 }, 52 } 53 54 nodeOverride1 = cilium_api_v2alpha1.CiliumBGPNodeConfigOverrideSpec{ 55 BGPInstances: []cilium_api_v2alpha1.CiliumBGPNodeConfigInstanceOverride{ 56 { 57 Name: "cluster-1-instance-65001", 58 RouterID: ptr.To[string]("10.10.10.10"), 59 LocalPort: ptr.To[int32](5400), 60 Peers: []cilium_api_v2alpha1.CiliumBGPNodeConfigPeerOverride{ 61 { 62 Name: "cluster-1-instance-65002-peer-10.0.0.2", 63 LocalAddress: ptr.To[string]("10.10.10.1"), 64 }, 65 }, 66 }, 67 }, 68 } 69 70 expectedNodeWithOverride1 = cilium_api_v2alpha1.CiliumBGPNodeInstance{ 71 Name: "cluster-1-instance-65001", 72 LocalASN: ptr.To[int64](65001), 73 RouterID: ptr.To[string]("10.10.10.10"), 74 LocalPort: ptr.To[int32](5400), 75 Peers: []cilium_api_v2alpha1.CiliumBGPNodePeer{ 76 { 77 Name: "cluster-1-instance-65002-peer-10.0.0.2", 78 PeerAddress: ptr.To[string]("10.0.0.2"), 79 PeerASN: ptr.To[int64](65002), 80 LocalAddress: ptr.To[string]("10.10.10.1"), 81 PeerConfigRef: &cilium_api_v2alpha1.PeerConfigReference{ 82 Name: "peer-1", 83 }, 84 }, 85 }, 86 } 87 ) 88 89 func Test_NodeLabels(t *testing.T) { 90 tests := []struct { 91 description string 92 node *cilium_api_v2.CiliumNode 93 clusterConfig *cilium_api_v2alpha1.CiliumBGPClusterConfig 94 expectedNodeConfig *cilium_api_v2alpha1.CiliumBGPNodeConfig 95 }{ 96 { 97 description: "node without any labels", 98 node: &cilium_api_v2.CiliumNode{ 99 ObjectMeta: meta_v1.ObjectMeta{ 100 Name: "node-1", 101 }, 102 }, 103 clusterConfig: &cilium_api_v2alpha1.CiliumBGPClusterConfig{ 104 ObjectMeta: meta_v1.ObjectMeta{ 105 Name: "bgp-cluster-config", 106 }, 107 Spec: cilium_api_v2alpha1.CiliumBGPClusterConfigSpec{ 108 NodeSelector: &slim_meta_v1.LabelSelector{ 109 MatchLabels: map[string]string{ 110 "bgp": "rack1", 111 }, 112 }, 113 BGPInstances: []cilium_api_v2alpha1.CiliumBGPInstance{ 114 cluster1, 115 }, 116 }, 117 }, 118 expectedNodeConfig: nil, 119 }, 120 { 121 description: "node with label and cluster config with MatchLabels", 122 node: &cilium_api_v2.CiliumNode{ 123 ObjectMeta: meta_v1.ObjectMeta{ 124 Name: "node-1", 125 Labels: map[string]string{ 126 "bgp": "rack1", 127 }, 128 }, 129 }, 130 clusterConfig: &cilium_api_v2alpha1.CiliumBGPClusterConfig{ 131 ObjectMeta: meta_v1.ObjectMeta{ 132 Name: "bgp-cluster-config", 133 }, 134 Spec: cilium_api_v2alpha1.CiliumBGPClusterConfigSpec{ 135 NodeSelector: &slim_meta_v1.LabelSelector{ 136 MatchLabels: map[string]string{ 137 "bgp": "rack1", 138 }, 139 }, 140 BGPInstances: []cilium_api_v2alpha1.CiliumBGPInstance{ 141 cluster1, 142 }, 143 }, 144 }, 145 expectedNodeConfig: &cilium_api_v2alpha1.CiliumBGPNodeConfig{ 146 ObjectMeta: meta_v1.ObjectMeta{ 147 Name: "node-1", 148 OwnerReferences: []meta_v1.OwnerReference{ 149 { 150 Kind: cilium_api_v2alpha1.BGPCCKindDefinition, 151 Name: "bgp-cluster-config", 152 }, 153 }, 154 }, 155 Spec: cilium_api_v2alpha1.CiliumBGPNodeSpec{ 156 BGPInstances: []cilium_api_v2alpha1.CiliumBGPNodeInstance{ 157 expectedNode1, 158 }, 159 }, 160 }, 161 }, 162 { 163 description: "node with label and cluster config with MatchExpression", 164 node: &cilium_api_v2.CiliumNode{ 165 ObjectMeta: meta_v1.ObjectMeta{ 166 Name: "node-1", 167 Labels: map[string]string{ 168 "bgp": "rack1", 169 }, 170 }, 171 }, 172 clusterConfig: &cilium_api_v2alpha1.CiliumBGPClusterConfig{ 173 ObjectMeta: meta_v1.ObjectMeta{ 174 Name: "bgp-cluster-config", 175 }, 176 Spec: cilium_api_v2alpha1.CiliumBGPClusterConfigSpec{ 177 NodeSelector: &slim_meta_v1.LabelSelector{ 178 MatchExpressions: []slim_meta_v1.LabelSelectorRequirement{ 179 { 180 Key: "bgp", 181 Operator: slim_meta_v1.LabelSelectorOpIn, 182 Values: []string{"rack1"}, 183 }, 184 }, 185 }, 186 BGPInstances: []cilium_api_v2alpha1.CiliumBGPInstance{ 187 cluster1, 188 }, 189 }, 190 }, 191 expectedNodeConfig: &cilium_api_v2alpha1.CiliumBGPNodeConfig{ 192 ObjectMeta: meta_v1.ObjectMeta{ 193 Name: "node-1", 194 OwnerReferences: []meta_v1.OwnerReference{ 195 { 196 Kind: cilium_api_v2alpha1.BGPCCKindDefinition, 197 Name: "bgp-cluster-config", 198 }, 199 }, 200 }, 201 Spec: cilium_api_v2alpha1.CiliumBGPNodeSpec{ 202 BGPInstances: []cilium_api_v2alpha1.CiliumBGPNodeInstance{ 203 expectedNode1, 204 }, 205 }, 206 }, 207 }, 208 { 209 description: "node with label and cluster config with nil node selector", 210 node: &cilium_api_v2.CiliumNode{ 211 ObjectMeta: meta_v1.ObjectMeta{ 212 Name: "node-1", 213 Labels: map[string]string{ 214 "bgp": "rack1", 215 }, 216 }, 217 }, 218 clusterConfig: &cilium_api_v2alpha1.CiliumBGPClusterConfig{ 219 ObjectMeta: meta_v1.ObjectMeta{ 220 Name: "bgp-cluster-config", 221 }, 222 Spec: cilium_api_v2alpha1.CiliumBGPClusterConfigSpec{ 223 NodeSelector: nil, 224 BGPInstances: []cilium_api_v2alpha1.CiliumBGPInstance{ 225 cluster1, 226 }, 227 }, 228 }, 229 expectedNodeConfig: &cilium_api_v2alpha1.CiliumBGPNodeConfig{ 230 ObjectMeta: meta_v1.ObjectMeta{ 231 Name: "node-1", 232 OwnerReferences: []meta_v1.OwnerReference{ 233 { 234 Kind: cilium_api_v2alpha1.BGPCCKindDefinition, 235 Name: "bgp-cluster-config", 236 }, 237 }, 238 }, 239 Spec: cilium_api_v2alpha1.CiliumBGPNodeSpec{ 240 BGPInstances: []cilium_api_v2alpha1.CiliumBGPNodeInstance{ 241 expectedNode1, 242 }, 243 }, 244 }, 245 }, 246 } 247 248 ctx, cancel := context.WithTimeout(context.Background(), TestTimeout) 249 defer cancel() 250 251 for _, tt := range tests { 252 t.Run(tt.description, func(t *testing.T) { 253 req := require.New(t) 254 255 f, watcherReady := newFixture(ctx, req) 256 257 tlog := hivetest.Logger(t) 258 f.hive.Start(tlog, ctx) 259 defer f.hive.Stop(tlog, ctx) 260 261 // blocking till all watchers are ready 262 watcherReady() 263 264 // setup node 265 upsertNode(req, ctx, f, tt.node) 266 267 // upsert BGP cluster config 268 upsertBGPCC(req, ctx, f, tt.clusterConfig) 269 270 // validate node configs 271 assert.EventuallyWithT(t, func(c *assert.CollectT) { 272 nodeConfigs, err := f.bgpnClient.List(ctx, meta_v1.ListOptions{}) 273 if err != nil { 274 assert.NoError(c, err) 275 return 276 } 277 278 if tt.expectedNodeConfig == nil { 279 assert.Len(c, nodeConfigs.Items, 0) 280 return 281 } 282 283 nodeConfig, err := f.bgpnClient.Get(ctx, tt.expectedNodeConfig.Name, meta_v1.GetOptions{}) 284 if err != nil { 285 assert.NoError(c, err) 286 return 287 } 288 289 assert.True(c, isSameOwner(tt.expectedNodeConfig.GetOwnerReferences(), nodeConfig.GetOwnerReferences())) 290 assert.Equal(c, tt.expectedNodeConfig.Spec, nodeConfig.Spec) 291 292 }, TestTimeout, 50*time.Millisecond) 293 }) 294 } 295 } 296 297 // Test_ClusterConfigSteps is step based test to validate the BGP node config controller 298 func Test_ClusterConfigSteps(t *testing.T) { 299 steps := []struct { 300 description string 301 clusterConfig *cilium_api_v2alpha1.CiliumBGPClusterConfig 302 nodes []*cilium_api_v2.CiliumNode 303 nodeOverrides []*cilium_api_v2alpha1.CiliumBGPNodeConfigOverride 304 expectedNodeConfigs []*cilium_api_v2alpha1.CiliumBGPNodeConfig 305 }{ 306 { 307 description: "initial node setup", 308 clusterConfig: nil, 309 nodes: []*cilium_api_v2.CiliumNode{ 310 { 311 ObjectMeta: meta_v1.ObjectMeta{ 312 Name: "node-1", 313 Labels: map[string]string{ 314 "bgp": "rack1", 315 }, 316 }, 317 }, 318 { 319 ObjectMeta: meta_v1.ObjectMeta{ 320 Name: "node-2", 321 Labels: map[string]string{ 322 "bgp": "rack1", 323 }, 324 }, 325 }, 326 }, 327 nodeOverrides: nil, 328 expectedNodeConfigs: nil, 329 }, 330 { 331 description: "initial bgp cluster config", 332 clusterConfig: &cilium_api_v2alpha1.CiliumBGPClusterConfig{ 333 ObjectMeta: meta_v1.ObjectMeta{ 334 Name: "bgp-cluster-config", 335 }, 336 Spec: cilium_api_v2alpha1.CiliumBGPClusterConfigSpec{ 337 NodeSelector: &slim_meta_v1.LabelSelector{ 338 MatchLabels: map[string]string{ 339 "bgp": "rack1", 340 }, 341 }, 342 BGPInstances: []cilium_api_v2alpha1.CiliumBGPInstance{ 343 cluster1, 344 }, 345 }, 346 }, 347 nodes: nil, 348 nodeOverrides: nil, 349 expectedNodeConfigs: []*cilium_api_v2alpha1.CiliumBGPNodeConfig{ 350 { 351 ObjectMeta: meta_v1.ObjectMeta{ 352 Name: "node-1", 353 OwnerReferences: []meta_v1.OwnerReference{ 354 { 355 Name: "bgp-cluster-config", 356 }, 357 }, 358 }, 359 Spec: cilium_api_v2alpha1.CiliumBGPNodeSpec{ 360 BGPInstances: []cilium_api_v2alpha1.CiliumBGPNodeInstance{ 361 expectedNode1, 362 }, 363 }, 364 }, 365 { 366 ObjectMeta: meta_v1.ObjectMeta{ 367 Name: "node-2", 368 OwnerReferences: []meta_v1.OwnerReference{ 369 { 370 Name: "bgp-cluster-config", 371 }, 372 }, 373 }, 374 Spec: cilium_api_v2alpha1.CiliumBGPNodeSpec{ 375 BGPInstances: []cilium_api_v2alpha1.CiliumBGPNodeInstance{ 376 expectedNode1, 377 }, 378 }, 379 }, 380 }, 381 }, 382 { 383 description: "add node override", 384 clusterConfig: nil, 385 nodes: nil, 386 nodeOverrides: []*cilium_api_v2alpha1.CiliumBGPNodeConfigOverride{ 387 { 388 ObjectMeta: meta_v1.ObjectMeta{ 389 Name: "node-1", 390 }, 391 392 Spec: nodeOverride1, 393 }, 394 }, 395 expectedNodeConfigs: []*cilium_api_v2alpha1.CiliumBGPNodeConfig{ 396 { 397 ObjectMeta: meta_v1.ObjectMeta{ 398 Name: "node-1", 399 OwnerReferences: []meta_v1.OwnerReference{ 400 { 401 Name: "bgp-cluster-config", 402 }, 403 }, 404 }, 405 Spec: cilium_api_v2alpha1.CiliumBGPNodeSpec{ 406 BGPInstances: []cilium_api_v2alpha1.CiliumBGPNodeInstance{ 407 expectedNodeWithOverride1, 408 }, 409 }, 410 }, 411 { 412 ObjectMeta: meta_v1.ObjectMeta{ 413 Name: "node-2", 414 OwnerReferences: []meta_v1.OwnerReference{ 415 { 416 Name: "bgp-cluster-config", 417 }, 418 }, 419 }, 420 Spec: cilium_api_v2alpha1.CiliumBGPNodeSpec{ 421 BGPInstances: []cilium_api_v2alpha1.CiliumBGPNodeInstance{ 422 expectedNode1, 423 }, 424 }, 425 }, 426 }, 427 }, 428 { 429 description: "add new node", 430 clusterConfig: nil, 431 nodes: []*cilium_api_v2.CiliumNode{ 432 { 433 ObjectMeta: meta_v1.ObjectMeta{ 434 Name: "node-3", 435 Labels: map[string]string{ 436 "bgp": "rack1", 437 }, 438 }, 439 }, 440 }, 441 nodeOverrides: nil, 442 expectedNodeConfigs: []*cilium_api_v2alpha1.CiliumBGPNodeConfig{ 443 { 444 ObjectMeta: meta_v1.ObjectMeta{ 445 Name: "node-1", 446 OwnerReferences: []meta_v1.OwnerReference{ 447 { 448 Name: "bgp-cluster-config", 449 }, 450 }, 451 }, 452 Spec: cilium_api_v2alpha1.CiliumBGPNodeSpec{ 453 BGPInstances: []cilium_api_v2alpha1.CiliumBGPNodeInstance{ 454 expectedNodeWithOverride1, 455 }, 456 }, 457 }, 458 { 459 ObjectMeta: meta_v1.ObjectMeta{ 460 Name: "node-2", 461 OwnerReferences: []meta_v1.OwnerReference{ 462 { 463 Name: "bgp-cluster-config", 464 }, 465 }, 466 }, 467 Spec: cilium_api_v2alpha1.CiliumBGPNodeSpec{ 468 BGPInstances: []cilium_api_v2alpha1.CiliumBGPNodeInstance{ 469 expectedNode1, 470 }, 471 }, 472 }, 473 { 474 ObjectMeta: meta_v1.ObjectMeta{ 475 Name: "node-3", 476 OwnerReferences: []meta_v1.OwnerReference{ 477 { 478 Name: "bgp-cluster-config", 479 }, 480 }, 481 }, 482 Spec: cilium_api_v2alpha1.CiliumBGPNodeSpec{ 483 BGPInstances: []cilium_api_v2alpha1.CiliumBGPNodeInstance{ 484 expectedNode1, 485 }, 486 }, 487 }, 488 }, 489 }, 490 { 491 description: "remove node labels", 492 clusterConfig: nil, 493 nodes: []*cilium_api_v2.CiliumNode{ 494 { 495 ObjectMeta: meta_v1.ObjectMeta{ 496 Name: "node-1", 497 }, 498 }, 499 { 500 ObjectMeta: meta_v1.ObjectMeta{ 501 Name: "node-2", 502 }, 503 }, 504 { 505 ObjectMeta: meta_v1.ObjectMeta{ 506 Name: "node-3", 507 }, 508 }, 509 }, 510 nodeOverrides: nil, 511 expectedNodeConfigs: nil, 512 }, 513 } 514 515 ctx, cancel := context.WithTimeout(context.Background(), TestTimeout) 516 defer cancel() 517 518 f, watchersReady := newFixture(ctx, require.New(t)) 519 520 tlog := hivetest.Logger(t) 521 f.hive.Start(tlog, ctx) 522 defer f.hive.Stop(tlog, ctx) 523 524 watchersReady() 525 526 for _, step := range steps { 527 t.Run(step.description, func(t *testing.T) { 528 req := require.New(t) 529 530 // setup nodes 531 for _, node := range step.nodes { 532 upsertNode(req, ctx, f, node) 533 } 534 535 // upsert BGP cluster config 536 upsertBGPCC(req, ctx, f, step.clusterConfig) 537 538 // upsert node overrides 539 for _, nodeOverride := range step.nodeOverrides { 540 upsertNodeOverrides(req, ctx, f, nodeOverride) 541 } 542 543 // validate node configs 544 assert.EventuallyWithT(t, func(c *assert.CollectT) { 545 nodes, err := f.bgpnClient.List(ctx, meta_v1.ListOptions{}) 546 if err != nil { 547 assert.NoError(c, err) 548 return 549 } 550 551 assert.Equal(c, len(step.expectedNodeConfigs), len(nodes.Items)) 552 553 for _, expectedNodeConfig := range step.expectedNodeConfigs { 554 nodeConfig, err := f.bgpnClient.Get(ctx, expectedNodeConfig.Name, meta_v1.GetOptions{}) 555 if err != nil { 556 assert.NoError(c, err) 557 return 558 } 559 560 assert.Equal(c, expectedNodeConfig.Name, nodeConfig.Name) 561 assert.Equal(c, expectedNodeConfig.Spec, nodeConfig.Spec) 562 } 563 }, TestTimeout, 50*time.Millisecond) 564 }) 565 } 566 } 567 568 func upsertNode(req *require.Assertions, ctx context.Context, f *fixture, node *cilium_api_v2.CiliumNode) { 569 _, err := f.nodeClient.Get(ctx, node.Name, meta_v1.GetOptions{}) 570 if err != nil && k8sErrors.IsNotFound(err) { 571 _, err = f.nodeClient.Create(ctx, node, meta_v1.CreateOptions{}) 572 } else if err != nil { 573 req.Fail(err.Error()) 574 } else { 575 _, err = f.nodeClient.Update(ctx, node, meta_v1.UpdateOptions{}) 576 } 577 req.NoError(err) 578 } 579 580 func upsertBGPCC(req *require.Assertions, ctx context.Context, f *fixture, bgpcc *cilium_api_v2alpha1.CiliumBGPClusterConfig) { 581 if bgpcc == nil { 582 return 583 } 584 585 _, err := f.bgpcClient.Get(ctx, bgpcc.Name, meta_v1.GetOptions{}) 586 if err != nil && k8sErrors.IsNotFound(err) { 587 _, err = f.bgpcClient.Create(ctx, bgpcc, meta_v1.CreateOptions{}) 588 } else if err != nil { 589 req.Fail(err.Error()) 590 } else { 591 _, err = f.bgpcClient.Update(ctx, bgpcc, meta_v1.UpdateOptions{}) 592 } 593 req.NoError(err) 594 } 595 596 func upsertNodeOverrides(req *require.Assertions, ctx context.Context, f *fixture, nodeOverride *cilium_api_v2alpha1.CiliumBGPNodeConfigOverride) { 597 _, err := f.bgpncoClient.Get(ctx, nodeOverride.Name, meta_v1.GetOptions{}) 598 if err != nil && k8sErrors.IsNotFound(err) { 599 _, err = f.bgpncoClient.Create(ctx, nodeOverride, meta_v1.CreateOptions{}) 600 } else { 601 _, err = f.bgpncoClient.Update(ctx, nodeOverride, meta_v1.UpdateOptions{}) 602 } 603 req.NoError(err) 604 } 605 606 func isSameOwner(expectedOwners, runningOwners []meta_v1.OwnerReference) bool { 607 if len(expectedOwners) != len(runningOwners) { 608 return false 609 } 610 611 for i, owner := range expectedOwners { 612 if runningOwners[i].Kind != owner.Kind || runningOwners[i].Name != owner.Name { 613 return false 614 } 615 } 616 return true 617 }