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 }