github.com/cilium/cilium@v1.16.2/pkg/bgpv1/manager/reconcilerv2/preflight_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  	"fmt"
     9  	"testing"
    10  
    11  	"github.com/sirupsen/logrus"
    12  	"github.com/stretchr/testify/require"
    13  	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    14  	"k8s.io/utils/ptr"
    15  
    16  	"github.com/cilium/cilium/pkg/bgpv1/manager/instance"
    17  	"github.com/cilium/cilium/pkg/bgpv1/types"
    18  	v2api "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    19  	v2alpha1api "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1"
    20  	"github.com/cilium/cilium/pkg/node/addressing"
    21  )
    22  
    23  // We use similar local listen ports as the tests in the pkg/bgpv1/test package.
    24  // It is important to NOT use ports from the /proc/sys/net/ipv4/ip_local_port_range
    25  // (defaulted to 32768-60999 on most Linux distributions) to avoid collisions with
    26  // the ephemeral (source) ports. As this range is configurable, ideally, we should
    27  // use the IANA-assigned ports below 1024 (e.g. 179) or mock GoBGP in these tests.
    28  // See https://github.com/cilium/cilium/issues/26209 for more info.
    29  // Note these ports should be different from the ports used in the pkg/bgpv1/manager/reconciler
    30  const (
    31  	localListenPort  = 1780
    32  	localListenPort2 = 1781
    33  	localListenPort3 = 1782
    34  )
    35  
    36  // TestPreflightReconciler ensures if a BgpServer must be recreated, due to
    37  // permanent configuration of the said server changing, its done so correctly.
    38  func TestPreflightReconciler(t *testing.T) {
    39  	req := require.New(t)
    40  
    41  	var table = []struct {
    42  		// modified BGPNodeInstance config
    43  		configModified bool
    44  		// name of test
    45  		name string
    46  		// ASN of original server
    47  		asn int64
    48  		// routerID of original server
    49  		routerID string
    50  		// routerID to reconcile
    51  		newAnnoRouterID string
    52  		// local node IP, from which router id will be generated as last resort
    53  		nodeIP string
    54  		// local annotation listen port of original server
    55  		localPort int32
    56  		// local annotation listen port to reconcile
    57  		newLocalPort int32
    58  		// virtual router configuration to reconcile
    59  		config *v2alpha1api.CiliumBGPNodeInstance
    60  		// should a recreation of the BgpServer
    61  		shouldRecreate bool
    62  		// export a nil error or not
    63  		err error
    64  	}{
    65  		{
    66  			name:            "no change",
    67  			asn:             64125,
    68  			routerID:        "192.168.0.1",
    69  			newAnnoRouterID: "192.168.0.1",
    70  			localPort:       localListenPort,
    71  			newLocalPort:    localListenPort,
    72  			config: &v2alpha1api.CiliumBGPNodeInstance{
    73  				Name:     "test-instance",
    74  				LocalASN: ptr.To[int64](64125),
    75  			},
    76  			shouldRecreate: false,
    77  			err:            nil,
    78  		},
    79  		{
    80  			name:            "router-id annotation change",
    81  			asn:             64125,
    82  			routerID:        "192.168.0.1",
    83  			newAnnoRouterID: "192.168.0.2",
    84  			localPort:       localListenPort,
    85  			newLocalPort:    localListenPort,
    86  			config: &v2alpha1api.CiliumBGPNodeInstance{
    87  				Name:     "test-instance",
    88  				LocalASN: ptr.To[int64](64125),
    89  			},
    90  			shouldRecreate: true,
    91  			err:            nil,
    92  		},
    93  		{
    94  			name:         "router-id from node IP",
    95  			asn:          64125,
    96  			routerID:     "192.168.0.1",
    97  			nodeIP:       "192.168.0.3",
    98  			localPort:    localListenPort,
    99  			newLocalPort: localListenPort,
   100  			config: &v2alpha1api.CiliumBGPNodeInstance{
   101  				Name:     "test-instance",
   102  				LocalASN: ptr.To[int64](64125),
   103  			},
   104  			shouldRecreate: true,
   105  			err:            nil,
   106  		},
   107  		{
   108  			configModified:  true,
   109  			name:            "router-id config change",
   110  			asn:             64125,
   111  			routerID:        "192.168.0.1",
   112  			newAnnoRouterID: "192.168.0.1",
   113  			localPort:       localListenPort,
   114  			newLocalPort:    localListenPort,
   115  			config: &v2alpha1api.CiliumBGPNodeInstance{
   116  				Name:     "test-instance",
   117  				LocalASN: ptr.To[int64](64125),
   118  				RouterID: ptr.To[string]("192.168.0.3"),
   119  			},
   120  			shouldRecreate: true,
   121  			err:            nil,
   122  		},
   123  		{
   124  			configModified:  true,
   125  			name:            "router-id annotation and config change", // config change takes precedence
   126  			asn:             64125,
   127  			routerID:        "192.168.0.1",
   128  			newAnnoRouterID: "192.168.0.2",
   129  			localPort:       localListenPort,
   130  			newLocalPort:    localListenPort,
   131  			config: &v2alpha1api.CiliumBGPNodeInstance{
   132  				Name:     "test-instance",
   133  				LocalASN: ptr.To[int64](64125),
   134  				RouterID: ptr.To[string]("192.168.0.3"),
   135  			},
   136  			shouldRecreate: true,
   137  			err:            nil,
   138  		},
   139  		{
   140  			name:            "local-port annotation change",
   141  			asn:             64125,
   142  			routerID:        "192.168.0.1",
   143  			newAnnoRouterID: "192.168.0.1",
   144  			localPort:       localListenPort,
   145  			newLocalPort:    localListenPort2,
   146  			config: &v2alpha1api.CiliumBGPNodeInstance{
   147  				Name:     "test-instance",
   148  				LocalASN: ptr.To[int64](64125),
   149  			},
   150  			shouldRecreate: true,
   151  			err:            nil,
   152  		},
   153  		{
   154  			configModified:  true,
   155  			name:            "local-port config change",
   156  			asn:             64125,
   157  			routerID:        "192.168.0.1",
   158  			newAnnoRouterID: "192.168.0.1",
   159  			localPort:       localListenPort,
   160  			newLocalPort:    localListenPort,
   161  			config: &v2alpha1api.CiliumBGPNodeInstance{
   162  				Name:      "test-instance",
   163  				LocalASN:  ptr.To[int64](64125),
   164  				LocalPort: ptr.To[int32](localListenPort2),
   165  			},
   166  			shouldRecreate: true,
   167  			err:            nil,
   168  		},
   169  		{
   170  			configModified:  true,
   171  			name:            "local-port annotation and config change", // config change takes precedence
   172  			asn:             64125,
   173  			routerID:        "192.168.0.1",
   174  			newAnnoRouterID: "192.168.0.1",
   175  			localPort:       localListenPort,
   176  			newLocalPort:    localListenPort2,
   177  			config: &v2alpha1api.CiliumBGPNodeInstance{
   178  				Name:      "test-instance",
   179  				LocalASN:  ptr.To[int64](64125),
   180  				LocalPort: ptr.To[int32](localListenPort3),
   181  			},
   182  			shouldRecreate: true,
   183  			err:            nil,
   184  		},
   185  		{
   186  			name:            "local-port, router-id annotation change",
   187  			asn:             64125,
   188  			routerID:        "192.168.0.1",
   189  			newAnnoRouterID: "192.168.0.2",
   190  			localPort:       localListenPort,
   191  			newLocalPort:    localListenPort2,
   192  			config: &v2alpha1api.CiliumBGPNodeInstance{
   193  				Name:     "test-instance",
   194  				LocalASN: ptr.To[int64](64125),
   195  			},
   196  			shouldRecreate: true,
   197  			err:            nil,
   198  		},
   199  		{
   200  			configModified:  true,
   201  			name:            "local-port, router-id config change",
   202  			asn:             64125,
   203  			routerID:        "192.168.0.1",
   204  			newAnnoRouterID: "192.168.0.1",
   205  			localPort:       localListenPort,
   206  			newLocalPort:    localListenPort,
   207  			config: &v2alpha1api.CiliumBGPNodeInstance{
   208  				Name:      "test-instance",
   209  				LocalASN:  ptr.To[int64](64125),
   210  				RouterID:  ptr.To[string]("192.168.0.3"),
   211  				LocalPort: ptr.To[int32](localListenPort2),
   212  			},
   213  			shouldRecreate: true,
   214  			err:            nil,
   215  		},
   216  		{
   217  			configModified:  true,
   218  			name:            "ASN in config change",
   219  			asn:             64125,
   220  			routerID:        "192.168.0.1",
   221  			newAnnoRouterID: "192.168.0.1",
   222  			localPort:       localListenPort,
   223  			newLocalPort:    localListenPort,
   224  			config: &v2alpha1api.CiliumBGPNodeInstance{
   225  				Name:      "test-instance",
   226  				LocalASN:  ptr.To[int64](64126),
   227  				RouterID:  ptr.To[string]("192.168.0.1"),
   228  				LocalPort: ptr.To[int32](localListenPort),
   229  			},
   230  			shouldRecreate: true,
   231  			err:            nil,
   232  		},
   233  	}
   234  	for _, tt := range table {
   235  		t.Run(tt.name, func(t *testing.T) {
   236  			// our test BgpServer with our original router ID and local port
   237  			srvParams := types.ServerParameters{
   238  				Global: types.BGPGlobal{
   239  					ASN:        uint32(tt.asn),
   240  					RouterID:   tt.routerID,
   241  					ListenPort: tt.localPort,
   242  				},
   243  			}
   244  			testInstance, err := instance.NewBGPInstance(context.Background(), logrus.WithField("unit_test", "preflight"), srvParams)
   245  			if err != nil {
   246  				req.NoError(err)
   247  			}
   248  
   249  			// keep a pointer to the original server to avoid gc and to check
   250  			// later
   251  			originalRouter := testInstance.Router
   252  			t.Cleanup(func() {
   253  				originalRouter.Stop()      // stop our test server
   254  				testInstance.Router.Stop() // stop any recreated server
   255  			})
   256  
   257  			preflightReconciler := NewPreflightReconciler(PreflightReconcilerIn{
   258  				Logger: logrus.WithField("unit_test", "preflight"),
   259  			}).Reconciler
   260  
   261  			annotationMap := ""
   262  			if tt.newAnnoRouterID != "" && tt.newLocalPort != 0 {
   263  				annotationMap = fmt.Sprintf("router-id=%s,local-port=%d", tt.newAnnoRouterID, tt.newLocalPort)
   264  			} else if tt.newAnnoRouterID != "" {
   265  				annotationMap = fmt.Sprintf("router-id=%s", tt.newAnnoRouterID)
   266  			} else if tt.newLocalPort != 0 {
   267  				annotationMap = fmt.Sprintf("local-port=%d", tt.newLocalPort)
   268  			}
   269  
   270  			ciliumNode := &v2api.CiliumNode{
   271  				ObjectMeta: meta_v1.ObjectMeta{
   272  					Name: "Test Node",
   273  					Annotations: map[string]string{
   274  						fmt.Sprintf("cilium.io/bgp-virtual-router.%d", tt.asn): annotationMap,
   275  					},
   276  				},
   277  				Spec: v2api.NodeSpec{
   278  					Addresses: []v2api.NodeAddress{
   279  						{
   280  							Type: addressing.NodeInternalIP,
   281  							IP:   tt.nodeIP,
   282  						},
   283  					},
   284  				},
   285  			}
   286  
   287  			testInstance.Config = tt.config
   288  
   289  			params := ReconcileParams{
   290  				BGPInstance:   testInstance,
   291  				DesiredConfig: tt.config,
   292  				CiliumNode:    ciliumNode,
   293  			}
   294  
   295  			err = preflightReconciler.Reconcile(context.Background(), params)
   296  			req.Equal(tt.err == nil, err == nil)
   297  
   298  			if tt.shouldRecreate && testInstance.Router == originalRouter {
   299  				req.Fail("preflightReconciler did not recreate router")
   300  			}
   301  
   302  			getBgpResp, err := testInstance.Router.GetBGP(context.Background())
   303  			req.NoError(err)
   304  
   305  			bgpInfo := getBgpResp.Global
   306  
   307  			if tt.configModified {
   308  				if tt.config.LocalASN != nil {
   309  					req.Equal(*tt.config.LocalASN, int64(bgpInfo.ASN))
   310  				}
   311  				if tt.config.RouterID != nil {
   312  					req.Equal(*tt.config.RouterID, bgpInfo.RouterID)
   313  				}
   314  				if tt.config.LocalPort != nil {
   315  					req.Equal(*tt.config.LocalPort, bgpInfo.ListenPort)
   316  				}
   317  			} else {
   318  				// check router ID is as expected (either from annotation or node IP)
   319  				if tt.newAnnoRouterID == "" && tt.nodeIP != "" {
   320  					req.Equal(tt.nodeIP, bgpInfo.RouterID)
   321  				} else {
   322  					req.Equal(tt.newAnnoRouterID, bgpInfo.RouterID)
   323  				}
   324  				req.Equal(tt.newLocalPort, bgpInfo.ListenPort)
   325  			}
   326  		})
   327  	}
   328  }