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  }