github.com/cilium/cilium@v1.16.2/pkg/bgpv1/agent/controller_test.go (about) 1 // SPDX-License-Identifier: Apache-2.0 2 // Copyright Authors of Cilium 3 4 package agent_test 5 6 import ( 7 "context" 8 "errors" 9 "testing" 10 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/agent" 16 "github.com/cilium/cilium/pkg/bgpv1/agent/mode" 17 "github.com/cilium/cilium/pkg/bgpv1/manager/store" 18 "github.com/cilium/cilium/pkg/bgpv1/mock" 19 v2api "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2" 20 v2alpha1api "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1" 21 v1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1" 22 ) 23 24 // TestControllerSanity ensures that the controller calls the correct methods, 25 // with the correct arguments, during its Reconcile loop. 26 func TestControllerSanity(t *testing.T) { 27 var wantPolicy = &v2alpha1api.CiliumBGPPeeringPolicy{ 28 Spec: v2alpha1api.CiliumBGPPeeringPolicySpec{ 29 NodeSelector: &v1.LabelSelector{ 30 MatchLabels: map[string]string{ 31 "bgp-policy": "a", 32 }, 33 }, 34 }, 35 } 36 37 // Reset to false after each test case 38 fullWithdrawalObserved := false 39 40 var table = []struct { 41 // name of test case 42 name string 43 // mock functions to provide to fakeNodeSpecer 44 labels map[string]string 45 annotations map[string]string 46 // a mock List method for the controller's PolicyLister 47 plist func() ([]*v2alpha1api.CiliumBGPPeeringPolicy, error) 48 // a mock ConfigurePeers method for the controller's BGPRouterManager 49 configurePeers func(context.Context, *v2alpha1api.CiliumBGPPeeringPolicy, *v2api.CiliumNode) error 50 // error nil or not 51 err error 52 // expect route full withdrawal observed 53 fullWithdrawalExpected bool 54 }{ 55 // test the normal control flow of a policy being selected and applied. 56 { 57 name: "successful reconcile", 58 labels: map[string]string{ 59 "bgp-policy": "a", 60 }, 61 annotations: map[string]string{}, 62 plist: func() ([]*v2alpha1api.CiliumBGPPeeringPolicy, error) { 63 return []*v2alpha1api.CiliumBGPPeeringPolicy{wantPolicy}, nil 64 }, 65 configurePeers: func(_ context.Context, p *v2alpha1api.CiliumBGPPeeringPolicy, ciliumNode *v2api.CiliumNode) error { 66 if !p.DeepEqual(wantPolicy) { 67 t.Fatalf("got: %+v, want: %+v", p, wantPolicy) 68 } 69 return nil 70 }, 71 err: nil, 72 }, 73 { 74 name: "multiple policies selects node", 75 labels: map[string]string{ 76 "bgp-policy": "a", 77 }, 78 annotations: map[string]string{}, 79 plist: func() ([]*v2alpha1api.CiliumBGPPeeringPolicy, error) { 80 p0 := wantPolicy.DeepCopy() 81 p0.Name = "policy0" 82 p1 := wantPolicy.DeepCopy() 83 p1.Name = "policy1" 84 return []*v2alpha1api.CiliumBGPPeeringPolicy{p0, p1}, nil 85 }, 86 configurePeers: func(_ context.Context, p *v2alpha1api.CiliumBGPPeeringPolicy, n *v2api.CiliumNode) error { 87 if p == nil && n == nil { 88 fullWithdrawalObserved = true 89 } 90 return nil 91 }, 92 err: errors.New(""), 93 fullWithdrawalExpected: false, 94 }, 95 // test policy defaulting 96 { 97 name: "policy defaulting on successful reconcile", 98 labels: map[string]string{ 99 "bgp-policy": "a", 100 }, 101 annotations: map[string]string{}, 102 plist: func() ([]*v2alpha1api.CiliumBGPPeeringPolicy, error) { 103 p := wantPolicy.DeepCopy() 104 p.Spec.VirtualRouters = []v2alpha1api.CiliumBGPVirtualRouter{ 105 { 106 LocalASN: 65001, 107 Neighbors: []v2alpha1api.CiliumBGPNeighbor{ 108 { 109 PeerASN: 65000, 110 PeerAddress: "172.0.0.1/32", 111 GracefulRestart: &v2alpha1api.CiliumBGPNeighborGracefulRestart{ 112 Enabled: true, 113 }, 114 }, 115 }, 116 }, 117 } 118 return []*v2alpha1api.CiliumBGPPeeringPolicy{p}, nil 119 }, 120 configurePeers: func(_ context.Context, p *v2alpha1api.CiliumBGPPeeringPolicy, _ *v2api.CiliumNode) error { 121 for _, r := range p.Spec.VirtualRouters { 122 for _, n := range r.Neighbors { 123 if n.PeerPort == nil || 124 n.EBGPMultihopTTL == nil || 125 n.ConnectRetryTimeSeconds == nil || 126 n.HoldTimeSeconds == nil || 127 n.KeepAliveTimeSeconds == nil || 128 n.GracefulRestart.RestartTimeSeconds == nil { 129 t.Fatalf("policy: %v not defaulted properly", p) 130 } 131 } 132 } 133 return nil 134 }, 135 err: nil, 136 }, 137 { 138 name: "configure peers error", 139 plist: func() ([]*v2alpha1api.CiliumBGPPeeringPolicy, error) { 140 return []*v2alpha1api.CiliumBGPPeeringPolicy{wantPolicy}, nil 141 }, 142 labels: map[string]string{ 143 "bgp-policy": "a", 144 }, 145 annotations: map[string]string{}, 146 configurePeers: func(_ context.Context, p *v2alpha1api.CiliumBGPPeeringPolicy, _ *v2api.CiliumNode) error { 147 return errors.New("") 148 }, 149 err: errors.New(""), 150 }, 151 { 152 name: "timer validation error", 153 plist: func() ([]*v2alpha1api.CiliumBGPPeeringPolicy, error) { 154 p := wantPolicy.DeepCopy() 155 p.Spec.VirtualRouters = []v2alpha1api.CiliumBGPVirtualRouter{ 156 { 157 LocalASN: 65001, 158 Neighbors: []v2alpha1api.CiliumBGPNeighbor{ 159 { 160 PeerASN: 65000, 161 PeerAddress: "172.0.0.1/32", 162 // KeepAliveTimeSeconds larger than HoldTimeSeconds = error 163 KeepAliveTimeSeconds: ptr.To[int32](10), 164 HoldTimeSeconds: ptr.To[int32](5), 165 }, 166 }, 167 }, 168 } 169 return []*v2alpha1api.CiliumBGPPeeringPolicy{p}, nil 170 }, 171 labels: map[string]string{ 172 "bgp-policy": "a", 173 }, 174 annotations: map[string]string{}, 175 configurePeers: func(_ context.Context, p *v2alpha1api.CiliumBGPPeeringPolicy, _ *v2api.CiliumNode) error { 176 return nil 177 }, 178 err: errors.New(""), 179 }, 180 } 181 for _, tt := range table { 182 t.Run(tt.name, func(t *testing.T) { 183 policyLister := &agent.MockCiliumBGPPeeringPolicyLister{ 184 List_: tt.plist, 185 } 186 rtmgr := &mock.MockBGPRouterManager{ 187 ConfigurePeers_: tt.configurePeers, 188 } 189 190 // create test cilium node 191 node := &v2api.CiliumNode{ 192 ObjectMeta: metav1.ObjectMeta{ 193 Name: "Test Node", 194 Annotations: tt.annotations, 195 Labels: tt.labels, 196 }, 197 } 198 199 c := agent.Controller{ 200 PolicyLister: policyLister, 201 BGPMgr: rtmgr, 202 LocalCiliumNode: node, 203 BGPNodeConfigStore: store.NewMockBGPCPResourceStore[*v2alpha1api.CiliumBGPNodeConfig](), 204 ConfigMode: mode.NewConfigMode(), 205 } 206 207 err := c.Reconcile(context.Background()) 208 if (tt.err == nil) != (err == nil) { 209 t.Fatalf("want: %v, got: %v", tt.err, err) 210 } 211 212 if tt.fullWithdrawalExpected != fullWithdrawalObserved { 213 t.Fatal("full withdrawal not observed") 214 } 215 }) 216 fullWithdrawalObserved = false 217 } 218 } 219 220 // TestDeselection ensures that the deselection of a policy causes a full withdrawal 221 func TestDeselection(t *testing.T) { 222 var policy = &v2alpha1api.CiliumBGPPeeringPolicy{ 223 Spec: v2alpha1api.CiliumBGPPeeringPolicySpec{ 224 NodeSelector: &v1.LabelSelector{ 225 MatchLabels: map[string]string{ 226 "bgp-policy": "a", 227 }, 228 }, 229 }, 230 } 231 232 withPolicy := func() ([]*v2alpha1api.CiliumBGPPeeringPolicy, error) { 233 return []*v2alpha1api.CiliumBGPPeeringPolicy{policy}, nil 234 } 235 236 withoutPolicy := func() ([]*v2alpha1api.CiliumBGPPeeringPolicy, error) { 237 return []*v2alpha1api.CiliumBGPPeeringPolicy{}, nil 238 } 239 240 // Start from empty policy list 241 policyLister := &agent.MockCiliumBGPPeeringPolicyLister{ 242 List_: withoutPolicy, 243 } 244 245 fullWithdrawalObserved := false 246 rtmgr := &mock.MockBGPRouterManager{ 247 ConfigurePeers_: func(_ context.Context, p *v2alpha1api.CiliumBGPPeeringPolicy, n *v2api.CiliumNode) error { 248 if p == nil && n == nil { 249 fullWithdrawalObserved = true 250 } 251 return nil 252 }, 253 } 254 255 // create test cilium node 256 node := &v2api.CiliumNode{ 257 ObjectMeta: metav1.ObjectMeta{ 258 Name: "Test Node", 259 Labels: map[string]string{ 260 "bgp-policy": "a", 261 }, 262 }, 263 } 264 265 c := agent.Controller{ 266 PolicyLister: policyLister, 267 BGPMgr: rtmgr, 268 LocalCiliumNode: node, 269 BGPNodeConfigStore: store.NewMockBGPCPResourceStore[*v2alpha1api.CiliumBGPNodeConfig](), 270 ConfigMode: mode.NewConfigMode(), 271 } 272 273 // First, reconcile with the policy selected 274 err := c.Reconcile(context.Background()) 275 require.NoError(t, err) 276 277 // At this point, we shouldn't see any full withdrawal because 278 // there is no previous policy. 279 require.False(t, fullWithdrawalObserved) 280 281 // Now, reconcile with the policy selected 282 policyLister.List_ = withPolicy 283 err = c.Reconcile(context.Background()) 284 require.NoError(t, err) 285 286 // At this point, we shouldn't see any full withdrawal because 287 // the policy is still selected. 288 require.False(t, fullWithdrawalObserved) 289 290 // Now, reconcile with the policy deselected 291 policyLister.List_ = withoutPolicy 292 err = c.Reconcile(context.Background()) 293 require.NoError(t, err) 294 295 // At this point, we should see a full withdrawal because 296 // the policy is no longer selected. 297 require.True(t, fullWithdrawalObserved) 298 } 299 300 // TestPolicySelection ensure the selection of a policy is performed correctly 301 // and enforces the rule set documented by the PolicySelection function. 302 func TestPolicySelection(t *testing.T) { 303 var table = []struct { 304 // name of test case 305 name string 306 // labels for Node object created during test 307 nodeLabels map[string]string 308 // struct expanded into a CiliumBGPPeeringPolicy during test 309 policies []struct { 310 // if true this is the selected policy the test expects 311 want bool 312 // expanded into a CiliumBGPPeeringPolicy during test 313 selector *v1.LabelSelector 314 } 315 // error nil or not 316 err error 317 }{ 318 { 319 name: "no policies", 320 nodeLabels: map[string]string{ 321 "bgp-peering-policy": "a", 322 }, 323 policies: []struct { 324 want bool 325 selector *v1.LabelSelector 326 }{}, 327 err: nil, 328 }, 329 { 330 name: "nil node label selector", 331 nodeLabels: map[string]string{ 332 "bgp-peering-policy": "a", 333 }, 334 policies: []struct { 335 want bool 336 selector *v1.LabelSelector 337 }{ 338 { 339 want: true, 340 selector: nil, 341 }, 342 }, 343 err: nil, 344 }, 345 { 346 name: "empty node label selector", 347 nodeLabels: map[string]string{ 348 "bgp-peering-policy": "a", 349 }, 350 policies: []struct { 351 want bool 352 selector *v1.LabelSelector 353 }{ 354 { 355 want: true, 356 selector: &v1.LabelSelector{ 357 MatchLabels: map[string]string{}, 358 MatchExpressions: []v1.LabelSelectorRequirement{}, 359 }, 360 }, 361 }, 362 err: nil, 363 }, 364 { 365 name: "nil values in MatchExpressions for node label selector", 366 nodeLabels: map[string]string{ 367 "bgp-peering-policy": "a", 368 }, 369 policies: []struct { 370 want bool 371 selector *v1.LabelSelector 372 }{ 373 { 374 want: false, 375 selector: &v1.LabelSelector{ 376 MatchExpressions: []v1.LabelSelectorRequirement{ 377 { 378 Key: "bgp-peering-policy", 379 Operator: "In", 380 Values: nil, 381 }, 382 }, 383 }, 384 }, 385 }, 386 err: nil, 387 }, 388 { 389 name: "valid value in MatchExpressions for node label selector", 390 nodeLabels: map[string]string{ 391 "bgp-peering-policy": "a", 392 }, 393 policies: []struct { 394 want bool 395 selector *v1.LabelSelector 396 }{ 397 { 398 want: true, 399 selector: &v1.LabelSelector{ 400 MatchExpressions: []v1.LabelSelectorRequirement{ 401 { 402 Key: "bgp-peering-policy", 403 Operator: "In", 404 Values: []string{"a"}, 405 }, 406 }, 407 }, 408 }, 409 }, 410 err: nil, 411 }, 412 { 413 // expect first policy returned, error nil 414 name: "policy match", 415 nodeLabels: map[string]string{ 416 "bgp-peering-policy": "a", 417 }, 418 policies: []struct { 419 want bool 420 selector *v1.LabelSelector 421 }{ 422 { 423 want: true, 424 selector: &v1.LabelSelector{ 425 MatchLabels: map[string]string{ 426 "bgp-peering-policy": "a", 427 }, 428 }, 429 }, 430 }, 431 err: nil, 432 }, 433 { 434 // expect nil policy returned, error nil 435 name: "policy no match", 436 nodeLabels: map[string]string{ 437 "bgp-peering-policy": "a", 438 }, 439 policies: []struct { 440 want bool 441 selector *v1.LabelSelector 442 }{ 443 { 444 want: false, 445 selector: &v1.LabelSelector{ 446 MatchLabels: map[string]string{ 447 "bgp-peering-policy": "b", 448 }, 449 }, 450 }, 451 }, 452 err: nil, 453 }, 454 { 455 // expect first policy returned, error nil 456 name: "multi policy match, no conflict", 457 nodeLabels: map[string]string{ 458 "bgp-peering-policy": "a", 459 }, 460 policies: []struct { 461 want bool 462 selector *v1.LabelSelector 463 }{ 464 { 465 want: true, 466 selector: &v1.LabelSelector{ 467 MatchLabels: map[string]string{ 468 "bgp-peering-policy": "a", 469 }, 470 }, 471 }, 472 { 473 selector: &v1.LabelSelector{ 474 MatchLabels: map[string]string{ 475 "bgp-peering-policy": "b", 476 }, 477 }, 478 }, 479 { 480 selector: &v1.LabelSelector{ 481 MatchLabels: map[string]string{ 482 "bgp-peering-policy": "c", 483 }, 484 }, 485 }, 486 }, 487 err: nil, 488 }, 489 { 490 // expect nil policy returned, error not nil 491 name: "multi policy match, conflict", 492 nodeLabels: map[string]string{ 493 "bgp-peering-policy": "a", 494 }, 495 policies: []struct { 496 want bool 497 selector *v1.LabelSelector 498 }{ 499 { 500 selector: &v1.LabelSelector{ 501 MatchLabels: map[string]string{ 502 "bgp-peering-policy": "a", 503 }, 504 }, 505 }, 506 { 507 selector: &v1.LabelSelector{ 508 MatchLabels: map[string]string{ 509 "bgp-peering-policy": "a", 510 }, 511 }, 512 }, 513 { 514 selector: &v1.LabelSelector{ 515 MatchLabels: map[string]string{ 516 "bgp-peering-policy": "b", 517 }, 518 }, 519 }, 520 }, 521 err: errors.New(""), 522 }, 523 } 524 for _, tt := range table { 525 t.Run(tt.name, func(t *testing.T) { 526 // expand policies into CiliumBGPPeeringPolicies, make note of wanted 527 var policies []*v2alpha1api.CiliumBGPPeeringPolicy 528 var want *v2alpha1api.CiliumBGPPeeringPolicy 529 for _, p := range tt.policies { 530 policy := &v2alpha1api.CiliumBGPPeeringPolicy{ 531 Spec: v2alpha1api.CiliumBGPPeeringPolicySpec{ 532 NodeSelector: p.selector, 533 }, 534 } 535 policies = append(policies, policy) 536 if p.want { 537 want = policy 538 } 539 } 540 // call function under test 541 policy, err := agent.PolicySelection(tt.nodeLabels, policies) 542 if (tt.err == nil) != (err == nil) { 543 t.Fatalf("expected err: %v", (tt.err == nil)) 544 } 545 if want != nil { 546 if policy == nil { 547 t.Fatalf("got: <nil>, want: %+v", *want) 548 } 549 550 // pointer comparison, not a deep equal. 551 if policy != want { 552 t.Fatalf("got: %+v, want: %+v", *policy, *want) 553 } 554 } 555 }) 556 } 557 } 558 559 func TestBGPModeSelection(t *testing.T) { 560 var table = []struct { 561 name string 562 initialMode mode.Mode 563 ciliumNode *v2api.CiliumNode 564 policy *v2alpha1api.CiliumBGPPeeringPolicy 565 bgpNodeConfig *v2alpha1api.CiliumBGPNodeConfig 566 expectedMode mode.Mode 567 }{ 568 { 569 name: "Disabled to BGPv1", 570 initialMode: mode.Disabled, 571 ciliumNode: &v2api.CiliumNode{ 572 ObjectMeta: metav1.ObjectMeta{ 573 Name: "Test Node", 574 Labels: map[string]string{ 575 "bgp-policy": "a", 576 }, 577 }, 578 }, 579 policy: &v2alpha1api.CiliumBGPPeeringPolicy{ 580 Spec: v2alpha1api.CiliumBGPPeeringPolicySpec{ 581 NodeSelector: &v1.LabelSelector{ 582 MatchLabels: map[string]string{ 583 "bgp-policy": "a", 584 }, 585 }, 586 }, 587 }, 588 bgpNodeConfig: nil, 589 expectedMode: mode.BGPv1, 590 }, 591 { 592 name: "Disabled to BGPv2", 593 initialMode: mode.Disabled, 594 ciliumNode: &v2api.CiliumNode{ 595 ObjectMeta: metav1.ObjectMeta{ 596 Name: "Test Node", 597 Labels: map[string]string{ 598 "bgp-policy": "a", 599 }, 600 }, 601 }, 602 policy: nil, 603 bgpNodeConfig: &v2alpha1api.CiliumBGPNodeConfig{ 604 ObjectMeta: metav1.ObjectMeta{ 605 Name: "Test Node", 606 }, 607 }, 608 expectedMode: mode.BGPv2, 609 }, 610 { 611 name: "BGPv1 to BGPv2", 612 initialMode: mode.BGPv1, 613 ciliumNode: &v2api.CiliumNode{ 614 ObjectMeta: metav1.ObjectMeta{ 615 Name: "Test Node", 616 Labels: map[string]string{ 617 "bgp-policy": "a", 618 }, 619 }, 620 }, 621 policy: nil, 622 bgpNodeConfig: &v2alpha1api.CiliumBGPNodeConfig{ 623 ObjectMeta: metav1.ObjectMeta{ 624 Name: "Test Node", 625 }, 626 }, 627 expectedMode: mode.BGPv2, 628 }, 629 { 630 name: "BGPv2 to BGPv1, BGPNodeConfig present", 631 initialMode: mode.BGPv2, 632 ciliumNode: &v2api.CiliumNode{ 633 ObjectMeta: metav1.ObjectMeta{ 634 Name: "Test Node", 635 Labels: map[string]string{ 636 "bgp-policy": "a", 637 }, 638 }, 639 }, 640 policy: &v2alpha1api.CiliumBGPPeeringPolicy{ 641 Spec: v2alpha1api.CiliumBGPPeeringPolicySpec{ 642 NodeSelector: &v1.LabelSelector{ 643 MatchLabels: map[string]string{ 644 "bgp-policy": "a", 645 }, 646 }, 647 }, 648 }, 649 bgpNodeConfig: &v2alpha1api.CiliumBGPNodeConfig{ 650 ObjectMeta: metav1.ObjectMeta{ 651 Name: "Test Node", 652 }, 653 }, 654 expectedMode: mode.BGPv1, 655 }, 656 { 657 name: "BGPv2 to BGPv1, BGPNodeConfig removed", 658 initialMode: mode.BGPv2, 659 ciliumNode: &v2api.CiliumNode{ 660 ObjectMeta: metav1.ObjectMeta{ 661 Name: "Test Node", 662 Labels: map[string]string{ 663 "bgp-policy": "a", 664 }, 665 }, 666 }, 667 policy: &v2alpha1api.CiliumBGPPeeringPolicy{ 668 Spec: v2alpha1api.CiliumBGPPeeringPolicySpec{ 669 NodeSelector: &v1.LabelSelector{ 670 MatchLabels: map[string]string{ 671 "bgp-policy": "a", 672 }, 673 }, 674 }, 675 }, 676 bgpNodeConfig: nil, 677 expectedMode: mode.BGPv1, 678 }, 679 { 680 name: "BGPv1 to disabled", 681 initialMode: mode.BGPv1, 682 ciliumNode: &v2api.CiliumNode{ 683 ObjectMeta: metav1.ObjectMeta{ 684 Name: "Test Node", 685 Labels: map[string]string{ 686 "bgp-policy": "a", 687 }, 688 }, 689 }, 690 policy: nil, 691 bgpNodeConfig: nil, 692 expectedMode: mode.Disabled, 693 }, 694 { 695 name: "BGPv2 to disabled", 696 initialMode: mode.BGPv2, 697 ciliumNode: &v2api.CiliumNode{ 698 ObjectMeta: metav1.ObjectMeta{ 699 Name: "Test Node", 700 Labels: map[string]string{ 701 "bgp-policy": "a", 702 }, 703 }, 704 }, 705 policy: nil, 706 bgpNodeConfig: nil, 707 expectedMode: mode.Disabled, 708 }, 709 } 710 711 for _, tt := range table { 712 t.Run(tt.name, func(t *testing.T) { 713 mockStore := store.NewMockBGPCPResourceStore[*v2alpha1api.CiliumBGPNodeConfig]() 714 if tt.bgpNodeConfig != nil { 715 mockStore.Upsert(tt.bgpNodeConfig) 716 } 717 718 policyLister := func() ([]*v2alpha1api.CiliumBGPPeeringPolicy, error) { 719 if tt.policy == nil { 720 return []*v2alpha1api.CiliumBGPPeeringPolicy{}, nil 721 } 722 return []*v2alpha1api.CiliumBGPPeeringPolicy{tt.policy}, nil 723 } 724 725 cm := mode.NewConfigMode() 726 cm.Set(tt.initialMode) 727 728 c := agent.Controller{ 729 PolicyLister: &agent.MockCiliumBGPPeeringPolicyLister{ 730 List_: policyLister, 731 }, 732 BGPMgr: &mock.MockBGPRouterManager{ 733 ConfigurePeers_: func(_ context.Context, p *v2alpha1api.CiliumBGPPeeringPolicy, n *v2api.CiliumNode) error { 734 return nil 735 }, 736 ReconcileInstances_: func(ctx context.Context, bgpnc *v2alpha1api.CiliumBGPNodeConfig, ciliumNode *v2api.CiliumNode) error { 737 return nil 738 }, 739 }, 740 LocalCiliumNode: tt.ciliumNode, 741 BGPNodeConfigStore: mockStore, 742 ConfigMode: cm, 743 } 744 745 err := c.Reconcile(context.Background()) 746 require.NoError(t, err) 747 748 require.Equal(t, tt.expectedMode, cm.Get()) 749 }) 750 } 751 }