github.com/cilium/cilium@v1.16.2/pkg/endpointcleanup/cleanup_test.go (about)

     1  // SPDX-License-Identifier: Apache-2.0
     2  // Copyright Authors of Cilium
     3  
     4  package endpointcleanup
     5  
     6  import (
     7  	"context"
     8  	"fmt"
     9  	"testing"
    10  
    11  	"github.com/cilium/hive/cell"
    12  	"github.com/cilium/hive/hivetest"
    13  	"github.com/sirupsen/logrus"
    14  	"github.com/stretchr/testify/assert"
    15  	"go.uber.org/goleak"
    16  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    17  	"k8s.io/apimachinery/pkg/runtime"
    18  	k8stesting "k8s.io/client-go/testing"
    19  
    20  	"github.com/cilium/cilium/daemon/k8s"
    21  	"github.com/cilium/cilium/pkg/endpoint"
    22  	"github.com/cilium/cilium/pkg/endpointstate"
    23  	"github.com/cilium/cilium/pkg/hive"
    24  	cilium_v2 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2"
    25  	cilium_v2a1 "github.com/cilium/cilium/pkg/k8s/apis/cilium.io/v2alpha1"
    26  	k8sClient "github.com/cilium/cilium/pkg/k8s/client"
    27  	"github.com/cilium/cilium/pkg/k8s/resource"
    28  	slim_metav1 "github.com/cilium/cilium/pkg/k8s/slim/k8s/apis/meta/v1"
    29  	"github.com/cilium/cilium/pkg/k8s/types"
    30  	"github.com/cilium/cilium/pkg/node"
    31  	"github.com/cilium/cilium/pkg/promise"
    32  )
    33  
    34  var testCESs = []cilium_v2a1.CiliumEndpointSlice{
    35  	{
    36  		ObjectMeta: metav1.ObjectMeta{
    37  			Name: "ciliumEndpointSlices-000",
    38  		},
    39  		Namespace: "x",
    40  		Endpoints: []cilium_v2a1.CoreCiliumEndpoint{
    41  			{
    42  				Name: "foo",
    43  				Networking: &cilium_v2.EndpointNetworking{
    44  					Addressing: cilium_v2.AddressPairList{},
    45  					NodeIP:     "<nil>",
    46  				},
    47  			},
    48  		},
    49  	},
    50  }
    51  
    52  func TestGC(t *testing.T) {
    53  	tests := map[string]struct {
    54  		ciliumEndpoints []types.CiliumEndpoint
    55  		// should only be used if disableCEPCRD is true.
    56  		ciliumEndpointSlices []cilium_v2a1.CiliumEndpointSlice
    57  		// if true, simulates running CiliumEndpointSlice watcher instead of CEP.
    58  		enableCES bool
    59  		// endpoints in endpointManaged.
    60  		managedEndpoints map[string]*endpoint.Endpoint
    61  		// expectedDeletedSet contains CiliumEndpoints that are expected to be deleted
    62  		// during test, in the form '<namespace>/<cilium_endpoint>'.
    63  		expectedDeletedSet []string
    64  		// apiserverCEPs is used to mock apiserver get requests when running with CES enabled.
    65  		apiserverCEPs map[string]*cilium_v2.CiliumEndpoint
    66  	}{
    67  		"CEPs with local pods without endpoints should be GCd": {
    68  			ciliumEndpoints:    []types.CiliumEndpoint{cep("foo", "x", "<nil>"), cep("foo", "y", "<nil>")},
    69  			managedEndpoints:   map[string]*endpoint.Endpoint{"y/foo": {}},
    70  			expectedDeletedSet: []string{"x/foo"},
    71  		},
    72  		"CEPs with local pods with endpoints should not be GCd": {
    73  			ciliumEndpoints:    []types.CiliumEndpoint{cep("foo", "x", "")},
    74  			managedEndpoints:   map[string]*endpoint.Endpoint{"x/foo": {}},
    75  			expectedDeletedSet: nil,
    76  		},
    77  		"Non local CEPs should not be GCd": {
    78  			ciliumEndpoints:    []types.CiliumEndpoint{cep("foo", "x", "1.2.3.4")},
    79  			managedEndpoints:   map[string]*endpoint.Endpoint{},
    80  			expectedDeletedSet: nil,
    81  		},
    82  		"Nothing should be deleted if fields are missing": {
    83  			ciliumEndpoints:    []types.CiliumEndpoint{cep("", "", "")},
    84  			managedEndpoints:   map[string]*endpoint.Endpoint{},
    85  			expectedDeletedSet: nil,
    86  		},
    87  		"CES: local CEPs without endpoints should be GCd": {
    88  			ciliumEndpointSlices: testCESs,
    89  			ciliumEndpoints: []types.CiliumEndpoint{
    90  				cep("bar", "x", "<nil>"),
    91  				cep("foo", "x", "<nil>"),
    92  				cep("notlocal", "x", "1.2.3.4"),
    93  			},
    94  			enableCES:          true,
    95  			managedEndpoints:   map[string]*endpoint.Endpoint{"x/bar": {}},
    96  			expectedDeletedSet: []string{"x/foo"},
    97  			apiserverCEPs: map[string]*cilium_v2.CiliumEndpoint{
    98  				"x/foo": {
    99  					ObjectMeta: metav1.ObjectMeta{
   100  						UID: "00001",
   101  					},
   102  					Status: cilium_v2.EndpointStatus{
   103  						Networking: &cilium_v2.EndpointNetworking{
   104  							NodeIP: "<nil>",
   105  						},
   106  					},
   107  				},
   108  			},
   109  		},
   110  		"CES: Test case where IP in apiserver changes and delete should be skipped": {
   111  			ciliumEndpointSlices: testCESs,
   112  			ciliumEndpoints: []types.CiliumEndpoint{
   113  				cep("foo", "x", "<nil>"),
   114  			},
   115  			enableCES:          true,
   116  			managedEndpoints:   map[string]*endpoint.Endpoint{"x/bar": {}},
   117  			expectedDeletedSet: nil,
   118  			apiserverCEPs: map[string]*cilium_v2.CiliumEndpoint{
   119  				"x/foo": {
   120  					ObjectMeta: metav1.ObjectMeta{
   121  						UID: "00001",
   122  					},
   123  					Status: cilium_v2.EndpointStatus{
   124  						Networking: &cilium_v2.EndpointNetworking{
   125  							NodeIP: "1.2.3.4",
   126  						},
   127  					},
   128  				},
   129  			},
   130  		},
   131  	}
   132  	for name, test := range tests {
   133  		t.Run(name, func(t *testing.T) {
   134  			defer goleak.VerifyNone(
   135  				t,
   136  				// Delaying workqueues used by resource.Resource[T].Events leaks this waitingLoop goroutine.
   137  				// It does stop when shutting down but is not guaranteed to before we actually exit.
   138  				goleak.IgnoreTopFunction("k8s.io/client-go/util/workqueue.(*delayingType).waitingLoop"),
   139  			)
   140  
   141  			node.SetTestLocalNodeStore()
   142  			defer node.UnsetTestLocalNodeStore()
   143  
   144  			var (
   145  				testCleanup *cleanup
   146  				deletedSet  []string
   147  			)
   148  
   149  			hive := hive.New(
   150  				k8sClient.FakeClientCell,
   151  				k8s.ResourcesCell,
   152  				cell.ProvidePrivate(func() localEndpointCache {
   153  					return &fakeEPManager{test.managedEndpoints}
   154  				}),
   155  				cell.Provide(func() promise.Promise[endpointstate.Restorer] {
   156  					return &fakeRestorer{}
   157  				}),
   158  				cell.Provide(func() *node.LocalNodeStore {
   159  					// no need to provide a real LocalNodeStore since the one set by
   160  					// SetTestLocalNodeStore will be referenced through the global
   161  					// variable
   162  					return nil
   163  				}),
   164  				cell.Invoke(func(clientset *k8sClient.FakeClientset) error {
   165  					clientset.CiliumFakeClientset.PrependReactor("get", "ciliumendpoints", k8stesting.ReactionFunc(
   166  						func(action k8stesting.Action) (bool, runtime.Object, error) {
   167  							if !test.enableCES {
   168  								t.Fatal("unexpected get on ciliumendpoints in CEP mode, expected only in CES mode")
   169  							}
   170  							name := action.(k8stesting.GetAction).GetName()
   171  							ns := action.(k8stesting.GetActionImpl).Namespace
   172  							cep, ok := test.apiserverCEPs[ns+"/"+name]
   173  							if !ok {
   174  								return true, nil, fmt.Errorf("not found")
   175  							}
   176  							return true, cep, nil
   177  						},
   178  					))
   179  					clientset.CiliumFakeClientset.PrependReactor("delete", "ciliumendpoints", k8stesting.ReactionFunc(
   180  						func(action k8stesting.Action) (bool, runtime.Object, error) {
   181  							a := action.(k8stesting.DeleteAction)
   182  							deletedSet = append(deletedSet, fmt.Sprintf("%s/%s", a.GetNamespace(), a.GetName()))
   183  							return true, nil, nil
   184  						},
   185  					))
   186  					return nil
   187  				}),
   188  				cell.Invoke(func(clientset k8sClient.Clientset) error {
   189  					for _, ces := range test.ciliumEndpointSlices {
   190  						if _, err := clientset.CiliumV2alpha1().CiliumEndpointSlices().
   191  							Create(context.Background(), &ces, metav1.CreateOptions{}); err != nil {
   192  							return fmt.Errorf("failed to create CiliumEndpointSlice %v: %w", ces, err)
   193  						}
   194  					}
   195  					for _, cep := range test.ciliumEndpoints {
   196  						if _, err := clientset.CiliumV2().CiliumEndpoints(cep.Namespace).
   197  							Create(context.Background(), &cilium_v2.CiliumEndpoint{
   198  								ObjectMeta: metav1.ObjectMeta{
   199  									Name:      cep.Name,
   200  									Namespace: cep.Namespace,
   201  								},
   202  								Status: cilium_v2.EndpointStatus{
   203  									Networking: &cilium_v2.EndpointNetworking{
   204  										NodeIP: cep.Networking.NodeIP,
   205  									},
   206  								},
   207  							}, metav1.CreateOptions{}); err != nil {
   208  							return fmt.Errorf("failed to create CiliumEndpoint %v: %w", cep, err)
   209  						}
   210  					}
   211  					return nil
   212  				}),
   213  				cell.Invoke(func(
   214  					logger logrus.FieldLogger,
   215  					ciliumEndpoint resource.Resource[*types.CiliumEndpoint],
   216  					ciliumEndpointSlice resource.Resource[*cilium_v2a1.CiliumEndpointSlice],
   217  					clientset k8sClient.Clientset,
   218  					restorerPromise promise.Promise[endpointstate.Restorer],
   219  					endpointsCache localEndpointCache,
   220  				) *cleanup {
   221  					testCleanup = &cleanup{
   222  						log:                        logger,
   223  						ciliumEndpoint:             ciliumEndpoint,
   224  						ciliumEndpointSlice:        ciliumEndpointSlice,
   225  						ciliumClient:               clientset.CiliumV2(),
   226  						restorerPromise:            restorerPromise,
   227  						endpointsCache:             endpointsCache,
   228  						ciliumEndpointSliceEnabled: test.enableCES,
   229  					}
   230  					return testCleanup
   231  				}),
   232  			)
   233  
   234  			ctx, cancel := context.WithCancel(context.Background())
   235  			defer cancel()
   236  
   237  			tlog := hivetest.Logger(t)
   238  			assert.NoError(t, hive.Start(tlog, ctx))
   239  
   240  			assert.NoError(t, testCleanup.run(ctx))
   241  
   242  			assert.ElementsMatch(t, test.expectedDeletedSet, deletedSet)
   243  
   244  			assert.NoError(t, hive.Stop(tlog, ctx))
   245  		})
   246  	}
   247  }
   248  
   249  type fakeEPManager struct {
   250  	byCEPName map[string]*endpoint.Endpoint
   251  }
   252  
   253  func (epm *fakeEPManager) LookupCEPName(namespacedName string) *endpoint.Endpoint {
   254  	ep, ok := epm.byCEPName[namespacedName]
   255  	if !ok {
   256  		return nil
   257  	}
   258  	return ep
   259  }
   260  
   261  func cep(name, ns, nodeIP string) types.CiliumEndpoint {
   262  	return types.CiliumEndpoint{
   263  		ObjectMeta: slim_metav1.ObjectMeta{
   264  			Name:      name,
   265  			Namespace: ns,
   266  		},
   267  		Networking: &cilium_v2.EndpointNetworking{
   268  			NodeIP: nodeIP,
   269  		},
   270  	}
   271  }
   272  
   273  type fakeRestorer struct {
   274  }
   275  
   276  func (r *fakeRestorer) Await(context.Context) (endpointstate.Restorer, error) {
   277  	return r, nil
   278  }
   279  
   280  func (r *fakeRestorer) WaitForEndpointRestore(_ context.Context) {
   281  }