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  }