1
16
17 package reconcilers
18
19 import (
20 "context"
21 "fmt"
22 "testing"
23
24 corev1 "k8s.io/api/core/v1"
25 discovery "k8s.io/api/discovery/v1"
26 apiequality "k8s.io/apimachinery/pkg/api/equality"
27 "k8s.io/apimachinery/pkg/api/errors"
28 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
29 "k8s.io/apimachinery/pkg/runtime"
30 "k8s.io/apimachinery/pkg/runtime/schema"
31 "k8s.io/client-go/kubernetes/fake"
32 )
33
34 func TestEndpointsAdapterGet(t *testing.T) {
35 endpoints1, epSlice1 := generateEndpointsAndSlice("foo", "testing", []int{80, 443}, []string{"10.1.2.3", "10.1.2.4"})
36
37 testCases := map[string]struct {
38 expectedError error
39 expectedEndpoints *corev1.Endpoints
40 initialState []runtime.Object
41 namespaceParam string
42 nameParam string
43 }{
44 "single-existing-endpoints": {
45 expectedError: nil,
46 expectedEndpoints: endpoints1,
47 initialState: []runtime.Object{endpoints1, epSlice1},
48 namespaceParam: "testing",
49 nameParam: "foo",
50 },
51 "endpoints exists, endpointslice does not": {
52 expectedError: nil,
53 expectedEndpoints: endpoints1,
54 initialState: []runtime.Object{endpoints1},
55 namespaceParam: "testing",
56 nameParam: "foo",
57 },
58 "endpointslice exists, endpoints does not": {
59 expectedError: errors.NewNotFound(schema.GroupResource{Group: "", Resource: "endpoints"}, "foo"),
60 expectedEndpoints: nil,
61 initialState: []runtime.Object{epSlice1},
62 namespaceParam: "testing",
63 nameParam: "foo",
64 },
65 "wrong-namespace": {
66 expectedError: errors.NewNotFound(schema.GroupResource{Group: "", Resource: "endpoints"}, "foo"),
67 expectedEndpoints: nil,
68 initialState: []runtime.Object{endpoints1, epSlice1},
69 namespaceParam: "foo",
70 nameParam: "foo",
71 },
72 "wrong-name": {
73 expectedError: errors.NewNotFound(schema.GroupResource{Group: "", Resource: "endpoints"}, "bar"),
74 expectedEndpoints: nil,
75 initialState: []runtime.Object{endpoints1, epSlice1},
76 namespaceParam: "testing",
77 nameParam: "bar",
78 },
79 }
80
81 for name, testCase := range testCases {
82 t.Run(name, func(t *testing.T) {
83 client := fake.NewSimpleClientset(testCase.initialState...)
84 epAdapter := NewEndpointsAdapter(client.CoreV1(), client.DiscoveryV1())
85
86 endpoints, err := epAdapter.Get(testCase.namespaceParam, testCase.nameParam, metav1.GetOptions{})
87
88 if !apiequality.Semantic.DeepEqual(testCase.expectedError, err) {
89 t.Errorf("Expected error: %v, got: %v", testCase.expectedError, err)
90 }
91
92 if !apiequality.Semantic.DeepEqual(endpoints, testCase.expectedEndpoints) {
93 t.Errorf("Expected endpoints: %v, got: %v", testCase.expectedEndpoints, endpoints)
94 }
95 })
96 }
97 }
98
99 func TestEndpointsAdapterCreate(t *testing.T) {
100 endpoints1, epSlice1 := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.3", "10.1.2.4"})
101
102
103
104 endpoints2, _ := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.5", "10.1.2.6", "1234::5678:0000:0000:9abc:def0"})
105 _, epSlice2 := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.5", "10.1.2.6"})
106
107
108
109 endpoints3, epSlice3 := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"1234::5678:0000:0000:9abc:def0"})
110 epSlice3.AddressType = discovery.AddressTypeIPv6
111
112 testCases := map[string]struct {
113 expectedError error
114 expectedResult *corev1.Endpoints
115 expectCreate []runtime.Object
116 expectUpdate []runtime.Object
117 initialState []runtime.Object
118 namespaceParam string
119 endpointsParam *corev1.Endpoints
120 }{
121 "single-endpoint": {
122 expectedError: nil,
123 expectedResult: endpoints1,
124 expectCreate: []runtime.Object{endpoints1, epSlice1},
125 initialState: []runtime.Object{},
126 namespaceParam: endpoints1.Namespace,
127 endpointsParam: endpoints1,
128 },
129 "single-endpoint-partial-ipv6": {
130 expectedError: nil,
131 expectedResult: endpoints2,
132 expectCreate: []runtime.Object{endpoints2, epSlice2},
133 initialState: []runtime.Object{},
134 namespaceParam: endpoints2.Namespace,
135 endpointsParam: endpoints2,
136 },
137 "single-endpoint-full-ipv6": {
138 expectedError: nil,
139 expectedResult: endpoints3,
140 expectCreate: []runtime.Object{endpoints3, epSlice3},
141 initialState: []runtime.Object{},
142 namespaceParam: endpoints3.Namespace,
143 endpointsParam: endpoints3,
144 },
145 "existing-endpoints": {
146 expectedError: errors.NewAlreadyExists(schema.GroupResource{Group: "", Resource: "endpoints"}, "foo"),
147 expectedResult: nil,
148 initialState: []runtime.Object{endpoints1, epSlice1},
149 namespaceParam: endpoints1.Namespace,
150 endpointsParam: endpoints1,
151
152
153 expectCreate: []runtime.Object{endpoints1},
154 },
155 "existing-endpointslice-incorrect": {
156
157
158 expectedError: nil,
159 expectedResult: endpoints1,
160 expectCreate: []runtime.Object{endpoints1},
161 initialState: []runtime.Object{epSlice1},
162 namespaceParam: endpoints1.Namespace,
163 endpointsParam: endpoints1,
164 },
165 "existing-endpointslice-correct": {
166
167
168 expectedError: nil,
169 expectedResult: endpoints2,
170 expectCreate: []runtime.Object{endpoints2},
171 expectUpdate: []runtime.Object{epSlice2},
172 initialState: []runtime.Object{epSlice1},
173 namespaceParam: endpoints2.Namespace,
174 endpointsParam: endpoints2,
175 },
176 }
177
178 for name, testCase := range testCases {
179 t.Run(name, func(t *testing.T) {
180 client := fake.NewSimpleClientset(testCase.initialState...)
181 epAdapter := NewEndpointsAdapter(client.CoreV1(), client.DiscoveryV1())
182
183 endpoints, err := epAdapter.Create(testCase.namespaceParam, testCase.endpointsParam)
184
185 if !apiequality.Semantic.DeepEqual(testCase.expectedError, err) {
186 t.Errorf("Expected error: %v, got: %v", testCase.expectedError, err)
187 }
188
189 if !apiequality.Semantic.DeepEqual(endpoints, testCase.expectedResult) {
190 t.Errorf("Expected endpoints: %v, got: %v", testCase.expectedResult, endpoints)
191 }
192
193 err = verifyCreatesAndUpdates(client, testCase.expectCreate, testCase.expectUpdate)
194 if err != nil {
195 t.Errorf("unexpected error in side effects: %v", err)
196 }
197 })
198 }
199 }
200
201 func TestEndpointsAdapterUpdate(t *testing.T) {
202 endpoints1, epSlice1 := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.3", "10.1.2.4"})
203 endpoints2, epSlice2 := generateEndpointsAndSlice("foo", "testing", []int{80, 443}, []string{"10.1.2.3", "10.1.2.4", "10.1.2.5"})
204 endpoints3, _ := generateEndpointsAndSlice("bar", "testing", []int{80, 443}, []string{"10.1.2.3", "10.1.2.4", "10.1.2.5"})
205
206
207
208 endpoints4, _ := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.7", "10.1.2.8"})
209 _, epSlice4IP := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.7", "10.1.2.8"})
210
211 epSlice4IP.AddressType = discovery.AddressType("IP")
212 _, epSlice4IPv4 := generateEndpointsAndSlice("foo", "testing", []int{80}, []string{"10.1.2.7", "10.1.2.8"})
213
214 testCases := map[string]struct {
215 expectedError error
216 expectedResult *corev1.Endpoints
217 expectCreate []runtime.Object
218 expectUpdate []runtime.Object
219 initialState []runtime.Object
220 namespaceParam string
221 endpointsParam *corev1.Endpoints
222 }{
223 "single-existing-endpoints-no-change": {
224 expectedError: nil,
225 expectedResult: endpoints1,
226 initialState: []runtime.Object{endpoints1, epSlice1},
227 namespaceParam: "testing",
228 endpointsParam: endpoints1,
229
230
231
232 expectUpdate: []runtime.Object{endpoints1},
233 },
234 "existing-endpointslice-replaced-with-updated-ipv4-address-type": {
235 expectedError: nil,
236 expectedResult: endpoints4,
237 initialState: []runtime.Object{endpoints4, epSlice4IP},
238 namespaceParam: "testing",
239 endpointsParam: endpoints4,
240
241
242
243 expectUpdate: []runtime.Object{endpoints4},
244 expectCreate: []runtime.Object{epSlice4IPv4},
245 },
246 "add-ports-and-ips": {
247 expectedError: nil,
248 expectedResult: endpoints2,
249 expectUpdate: []runtime.Object{endpoints2, epSlice2},
250 initialState: []runtime.Object{endpoints1, epSlice1},
251 namespaceParam: "testing",
252 endpointsParam: endpoints2,
253 },
254 "endpoints-correct-endpointslice-wrong": {
255 expectedError: nil,
256 expectedResult: endpoints2,
257 expectUpdate: []runtime.Object{endpoints2, epSlice2},
258 initialState: []runtime.Object{endpoints2, epSlice1},
259 namespaceParam: "testing",
260 endpointsParam: endpoints2,
261 },
262 "endpointslice-correct-endpoints-wrong": {
263 expectedError: nil,
264 expectedResult: endpoints2,
265 expectUpdate: []runtime.Object{endpoints2},
266 initialState: []runtime.Object{endpoints1, epSlice2},
267 namespaceParam: "testing",
268 endpointsParam: endpoints2,
269 },
270 "wrong-endpoints": {
271 expectedError: errors.NewNotFound(schema.GroupResource{Group: "", Resource: "endpoints"}, "bar"),
272 expectedResult: nil,
273 expectUpdate: []runtime.Object{endpoints3},
274 initialState: []runtime.Object{endpoints1, epSlice1},
275 namespaceParam: "testing",
276 endpointsParam: endpoints3,
277 },
278 "missing-endpoints": {
279 expectedError: errors.NewNotFound(schema.GroupResource{Group: "", Resource: "endpoints"}, "bar"),
280 expectedResult: nil,
281 initialState: []runtime.Object{endpoints1, epSlice1},
282 namespaceParam: "testing",
283 endpointsParam: endpoints3,
284
285
286 expectUpdate: []runtime.Object{endpoints3},
287 },
288 "missing-endpointslice": {
289
290
291 expectedError: nil,
292 expectedResult: endpoints1,
293 expectUpdate: []runtime.Object{endpoints1},
294 expectCreate: []runtime.Object{epSlice1},
295 initialState: []runtime.Object{endpoints2},
296 namespaceParam: "testing",
297 endpointsParam: endpoints1,
298 },
299 }
300
301 for name, testCase := range testCases {
302 t.Run(name, func(t *testing.T) {
303 client := fake.NewSimpleClientset(testCase.initialState...)
304 epAdapter := NewEndpointsAdapter(client.CoreV1(), client.DiscoveryV1())
305
306 endpoints, err := epAdapter.Update(testCase.namespaceParam, testCase.endpointsParam)
307
308 if !apiequality.Semantic.DeepEqual(testCase.expectedError, err) {
309 t.Errorf("Expected error: %v, got: %v", testCase.expectedError, err)
310 }
311
312 if !apiequality.Semantic.DeepEqual(endpoints, testCase.expectedResult) {
313 t.Errorf("Expected endpoints: %v, got: %v", testCase.expectedResult, endpoints)
314 }
315
316 err = verifyCreatesAndUpdates(client, testCase.expectCreate, testCase.expectUpdate)
317 if err != nil {
318 t.Errorf("unexpected error in side effects: %v", err)
319 }
320 })
321 }
322 }
323
324 func generateEndpointsAndSlice(name, namespace string, ports []int, addresses []string) (*corev1.Endpoints, *discovery.EndpointSlice) {
325 trueBool := true
326
327 epSlice := &discovery.EndpointSlice{
328 ObjectMeta: metav1.ObjectMeta{Name: name, Namespace: namespace},
329 AddressType: discovery.AddressTypeIPv4,
330 }
331 epSlice.Labels = map[string]string{discovery.LabelServiceName: name}
332 subset := corev1.EndpointSubset{}
333
334 for i, port := range ports {
335 endpointPort := corev1.EndpointPort{
336 Name: fmt.Sprintf("port-%d", i),
337 Port: int32(port),
338 Protocol: corev1.ProtocolTCP,
339 }
340 subset.Ports = append(subset.Ports, endpointPort)
341 epSlice.Ports = append(epSlice.Ports, discovery.EndpointPort{
342 Name: &endpointPort.Name,
343 Port: &endpointPort.Port,
344 Protocol: &endpointPort.Protocol,
345 })
346 }
347
348 for i, address := range addresses {
349 endpointAddress := corev1.EndpointAddress{
350 IP: address,
351 TargetRef: &corev1.ObjectReference{
352 Kind: "Pod",
353 Name: fmt.Sprintf("pod-%d", i),
354 },
355 }
356
357 subset.Addresses = append(subset.Addresses, endpointAddress)
358
359 epSlice.Endpoints = append(epSlice.Endpoints, discovery.Endpoint{
360 Addresses: []string{endpointAddress.IP},
361 TargetRef: endpointAddress.TargetRef,
362 Conditions: discovery.EndpointConditions{Ready: &trueBool},
363 })
364 }
365
366 return &corev1.Endpoints{
367 ObjectMeta: metav1.ObjectMeta{
368 Name: name,
369 Namespace: namespace,
370 Labels: map[string]string{
371 discovery.LabelSkipMirror: "true",
372 },
373 },
374 Subsets: []corev1.EndpointSubset{subset},
375 }, epSlice
376 }
377
378 func TestEndpointManagerEnsureEndpointSliceFromEndpoints(t *testing.T) {
379 endpoints1, epSlice1 := generateEndpointsAndSlice("foo", "testing", []int{80, 443}, []string{"10.1.2.3", "10.1.2.4"})
380 endpoints2, epSlice2 := generateEndpointsAndSlice("foo", "testing", []int{80, 443}, []string{"10.1.2.3", "10.1.2.4", "10.1.2.5"})
381
382 testCases := map[string]struct {
383 expectedError error
384 expectedEndpointSlice *discovery.EndpointSlice
385 initialState []runtime.Object
386 namespaceParam string
387 endpointsParam *corev1.Endpoints
388 }{
389 "existing-endpointslice-no-change": {
390 expectedError: nil,
391 expectedEndpointSlice: epSlice1,
392 initialState: []runtime.Object{epSlice1},
393 namespaceParam: "testing",
394 endpointsParam: endpoints1,
395 },
396 "existing-endpointslice-change": {
397 expectedError: nil,
398 expectedEndpointSlice: epSlice2,
399 initialState: []runtime.Object{epSlice1},
400 namespaceParam: "testing",
401 endpointsParam: endpoints2,
402 },
403 "missing-endpointslice": {
404 expectedError: nil,
405 expectedEndpointSlice: epSlice1,
406 initialState: []runtime.Object{},
407 namespaceParam: "testing",
408 endpointsParam: endpoints1,
409 },
410 }
411
412 for name, testCase := range testCases {
413 t.Run(name, func(t *testing.T) {
414 client := fake.NewSimpleClientset(testCase.initialState...)
415 epAdapter := NewEndpointsAdapter(client.CoreV1(), client.DiscoveryV1())
416
417 err := epAdapter.EnsureEndpointSliceFromEndpoints(testCase.namespaceParam, testCase.endpointsParam)
418 if !apiequality.Semantic.DeepEqual(testCase.expectedError, err) {
419 t.Errorf("Expected error: %v, got: %v", testCase.expectedError, err)
420 }
421
422 endpointSlice, err := client.DiscoveryV1().EndpointSlices(testCase.namespaceParam).Get(context.TODO(), testCase.endpointsParam.Name, metav1.GetOptions{})
423 if err != nil && !errors.IsNotFound(err) {
424 t.Fatalf("Error getting Endpoint Slice: %v", err)
425 }
426
427 if !apiequality.Semantic.DeepEqual(endpointSlice, testCase.expectedEndpointSlice) {
428 t.Errorf("Expected Endpoint Slice: %v, got: %v", testCase.expectedEndpointSlice, endpointSlice)
429 }
430 })
431 }
432 }
433
View as plain text