github.com/cilium/cilium@v1.16.2/pkg/bgpv1/manager/reconciler/preflight_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package reconciler
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"testing"
    10  
    11  	"github.com/stretchr/testify/require"
    12  	meta_v1 "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"
    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  	slim_corev1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/api/core/v1"
    22  	slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
    23  	"github.com/cilium/cilium/pkg/option"
    24  )
    25  
    26  // We use similar local listen ports as the tests in the pkg/bgpv1/test package.
    27  // It is important to NOT use ports from the /proc/sys/net/ipv4/ip_local_port_range
    28  // (defaulted to 32768-60999 on most Linux distributions) to avoid collisions with
    29  // the ephemeral (source) ports. As this range is configurable, ideally, we should
    30  // use the IANA-assigned ports below 1024 (e.g. 179) or mock GoBGP in these tests.
    31  // See https://github.com/cilium/cilium/issues/26209 for more info.
    32  const (
    33  	localListenPort  = 1793
    34  	localListenPort2 = 1794
    35  )
    36  
    37  // TestPreflightReconciler ensures if a BgpServer must be recreated, due to
    38  // permanent configuration of the said server changing, its done so correctly.
    39  func TestPreflightReconciler(t *testing.T) {
    40  	var table = []struct {
    41  		// name of test
    42  		name string
    43  		// routerID of original server
    44  		routerID string
    45  		// routerID to reconcile
    46  		newRouterID string
    47  		// local listen port of original server
    48  		localPort int32
    49  		// local listen port to reconcile
    50  		newLocalPort int32
    51  		// virtual router configuration to reconcile, used mostly for pointer
    52  		// comparison
    53  		config *v2alpha1api.CiliumBGPVirtualRouter
    54  		// should a recreation of the BgpServer
    55  		shouldRecreate bool
    56  		// export a nil error or not
    57  		err error
    58  	}{
    59  		{
    60  			name:           "no change",
    61  			routerID:       "192.168.0.1",
    62  			newRouterID:    "192.168.0.1",
    63  			localPort:      localListenPort,
    64  			newLocalPort:   localListenPort,
    65  			config:         &v2alpha1api.CiliumBGPVirtualRouter{},
    66  			shouldRecreate: false,
    67  			err:            nil,
    68  		},
    69  		{
    70  			name:           "router-id change",
    71  			routerID:       "192.168.0.1",
    72  			newRouterID:    "192.168.0.2",
    73  			localPort:      localListenPort,
    74  			newLocalPort:   localListenPort,
    75  			config:         &v2alpha1api.CiliumBGPVirtualRouter{},
    76  			shouldRecreate: true,
    77  			err:            nil,
    78  		},
    79  		{
    80  			name:           "local-port change",
    81  			routerID:       "192.168.0.1",
    82  			newRouterID:    "192.168.0.1",
    83  			localPort:      localListenPort,
    84  			newLocalPort:   localListenPort2,
    85  			config:         &v2alpha1api.CiliumBGPVirtualRouter{},
    86  			shouldRecreate: true,
    87  			err:            nil,
    88  		},
    89  		{
    90  			name:           "local-port, router-id change",
    91  			routerID:       "192.168.0.1",
    92  			newRouterID:    "192.168.0.2",
    93  			localPort:      localListenPort,
    94  			newLocalPort:   localListenPort2,
    95  			config:         &v2alpha1api.CiliumBGPVirtualRouter{},
    96  			shouldRecreate: true,
    97  			err:            nil,
    98  		},
    99  	}
   100  	for _, tt := range table {
   101  		t.Run(tt.name, func(t *testing.T) {
   102  			// our test BgpServer with our original router ID and local port
   103  			srvParams := types.ServerParameters{
   104  				Global: types.BGPGlobal{
   105  					ASN:        64125,
   106  					RouterID:   tt.routerID,
   107  					ListenPort: tt.localPort,
   108  				},
   109  			}
   110  			testSC, err := instance.NewServerWithConfig(context.Background(), log, srvParams)
   111  			if err != nil {
   112  				t.Fatalf("failed to create test BgpServer: %v", err)
   113  			}
   114  
   115  			// keep a pointer to the original server to avoid gc and to check
   116  			// later
   117  			originalServer := testSC.Server
   118  			t.Cleanup(func() {
   119  				originalServer.Stop() // stop our test server
   120  				testSC.Server.Stop()  // stop any recreated server
   121  			})
   122  
   123  			// attach original config
   124  			testSC.Config = tt.config
   125  			newc := &v2alpha1api.CiliumBGPVirtualRouter{
   126  				LocalASN: 64125,
   127  			}
   128  
   129  			preflightReconciler := NewPreflightReconciler().Reconciler
   130  			params := ReconcileParams{
   131  				CurrentServer: testSC,
   132  				DesiredConfig: newc,
   133  				CiliumNode: &v2api.CiliumNode{
   134  					ObjectMeta: meta_v1.ObjectMeta{
   135  						Name: "Test Node",
   136  						Annotations: map[string]string{
   137  							"cilium.io/bgp-virtual-router.64125": fmt.Sprintf("router-id=%s,local-port=%d", tt.newRouterID, tt.newLocalPort),
   138  						},
   139  					},
   140  				},
   141  			}
   142  
   143  			// Run the reconciler twice to ensure idempotency. This
   144  			// simulates the retrying behavior of the controller.
   145  			for i := 0; i < 2; i++ {
   146  				t.Run(tt.name, func(t *testing.T) {
   147  					err = preflightReconciler.Reconcile(context.Background(), params)
   148  					if (tt.err == nil) != (err == nil) {
   149  						t.Fatalf("wanted error: %v", (tt.err == nil))
   150  					}
   151  				})
   152  			}
   153  			if tt.shouldRecreate && testSC.Server == originalServer {
   154  				t.Fatalf("preflightReconciler did not recreate server")
   155  			}
   156  			getBgpResp, err := testSC.Server.GetBGP(context.Background())
   157  			if err != nil {
   158  				t.Fatalf("failed to retrieve BGP Info for BgpServer under test: %v", err)
   159  			}
   160  			bgpInfo := getBgpResp.Global
   161  			if bgpInfo.RouterID != tt.newRouterID {
   162  				t.Fatalf("got: %v, want: %v", bgpInfo.RouterID, tt.newRouterID)
   163  			}
   164  			if bgpInfo.ListenPort != int32(tt.newLocalPort) {
   165  				t.Fatalf("got: %v, want: %v", bgpInfo.ListenPort, tt.newLocalPort)
   166  			}
   167  		})
   168  	}
   169  }
   170  
   171  // TestReconcileAfterServerReinit reproduces issue #24975, validates service reconcile works after router-id is
   172  // modified.
   173  func TestReconcileAfterServerReinit(t *testing.T) {
   174  	var (
   175  		routerID        = "192.168.0.1"
   176  		localPort       = int32(localListenPort)
   177  		localASN        = int64(64125)
   178  		newRouterID     = "192.168.0.2"
   179  		diffstore       = store.NewFakeDiffStore[*slim_corev1.Service]()
   180  		epDiffStore     = store.NewFakeDiffStore[*k8s.Endpoints]()
   181  		serviceSelector = &slim_metav1.LabelSelector{MatchLabels: map[string]string{"color": "blue"}}
   182  		obj             = &slim_corev1.Service{
   183  			ObjectMeta: slim_metav1.ObjectMeta{
   184  				Name:      "svc-1",
   185  				Namespace: "default",
   186  				Labels: map[string]string{
   187  					"color": "blue",
   188  				},
   189  			},
   190  			Spec: slim_corev1.ServiceSpec{
   191  				Type: slim_corev1.ServiceTypeLoadBalancer,
   192  			},
   193  			Status: slim_corev1.ServiceStatus{
   194  				LoadBalancer: slim_corev1.LoadBalancerStatus{
   195  					Ingress: []slim_corev1.LoadBalancerIngress{
   196  						{
   197  							IP: "1.2.3.4",
   198  						},
   199  					},
   200  				},
   201  			},
   202  		}
   203  	)
   204  
   205  	// Initial router configuration
   206  	srvParams := types.ServerParameters{
   207  		Global: types.BGPGlobal{
   208  			ASN:        64125,
   209  			RouterID:   "127.0.0.1",
   210  			ListenPort: -1,
   211  		},
   212  	}
   213  
   214  	testSC, err := instance.NewServerWithConfig(context.Background(), log, srvParams)
   215  	require.NoError(t, err)
   216  
   217  	originalServer := testSC.Server
   218  	t.Cleanup(func() {
   219  		originalServer.Stop() // stop our test server
   220  		testSC.Server.Stop()  // stop any recreated server
   221  	})
   222  
   223  	// Validate pod CIDR and service announcements work as expected
   224  	newc := &v2alpha1api.CiliumBGPVirtualRouter{
   225  		LocalASN:        localASN,
   226  		ExportPodCIDR:   ptr.To[bool](true),
   227  		Neighbors:       []v2alpha1api.CiliumBGPNeighbor{},
   228  		ServiceSelector: serviceSelector,
   229  	}
   230  
   231  	daemonConfig := &option.DaemonConfig{IPAM: "Kubernetes"}
   232  	exportPodCIDRReconciler := NewExportPodCIDRReconciler(daemonConfig).Reconciler
   233  	params := ReconcileParams{
   234  		CurrentServer: testSC,
   235  		DesiredConfig: newc,
   236  		CiliumNode: &v2api.CiliumNode{
   237  			ObjectMeta: meta_v1.ObjectMeta{
   238  				Name: "Test Node",
   239  				Annotations: map[string]string{
   240  					"cilium.io/bgp-virtual-router.64125": fmt.Sprintf("router-id=%s,local-port=%d", routerID, localPort),
   241  				},
   242  			},
   243  		},
   244  	}
   245  
   246  	err = exportPodCIDRReconciler.Reconcile(context.Background(), params)
   247  	require.NoError(t, err)
   248  
   249  	diffstore.Upsert(obj)
   250  	reconciler := NewServiceReconciler(diffstore, epDiffStore)
   251  	err = reconciler.Reconciler.Reconcile(context.Background(), params)
   252  	require.NoError(t, err)
   253  
   254  	// update server config, this is done outside of reconcilers
   255  	testSC.Config = newc
   256  
   257  	params.CiliumNode.Annotations = map[string]string{
   258  		"cilium.io/bgp-virtual-router.64125": fmt.Sprintf("router-id=%s,local-port=%d", newRouterID, localPort),
   259  	}
   260  
   261  	preflightReconciler := NewPreflightReconciler().Reconciler
   262  
   263  	// Trigger pre flight reconciler
   264  	err = preflightReconciler.Reconcile(context.Background(), params)
   265  	require.NoError(t, err)
   266  
   267  	// Test pod CIDR reconciler is working
   268  	err = exportPodCIDRReconciler.Reconcile(context.Background(), params)
   269  	require.NoError(t, err)
   270  
   271  	// Update LB service
   272  	reconciler = NewServiceReconciler(diffstore, epDiffStore)
   273  	err = reconciler.Reconciler.Reconcile(context.Background(), params)
   274  	require.NoError(t, err)
   275  }