1
16
17 package endpointslicemirroring
18
19 import (
20 "context"
21 "strings"
22 "testing"
23
24 corev1 "k8s.io/api/core/v1"
25 discovery "k8s.io/api/discovery/v1"
26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 "k8s.io/client-go/kubernetes/fake"
28 "k8s.io/client-go/kubernetes/scheme"
29 "k8s.io/client-go/tools/record"
30 "k8s.io/component-base/metrics/testutil"
31 endpointsliceutil "k8s.io/endpointslice/util"
32 endpointsv1 "k8s.io/kubernetes/pkg/api/v1/endpoints"
33 "k8s.io/kubernetes/pkg/controller/endpointslicemirroring/metrics"
34 "k8s.io/kubernetes/test/utils/ktesting"
35 "k8s.io/utils/pointer"
36 )
37
38 const defaultMaxEndpointsPerSubset = int32(1000)
39
40
41
42 func TestReconcile(t *testing.T) {
43 protoTCP := corev1.ProtocolTCP
44 protoUDP := corev1.ProtocolUDP
45
46 testCases := []struct {
47 testName string
48 subsets []corev1.EndpointSubset
49 epLabels map[string]string
50 epAnnotations map[string]string
51 endpointsDeletionPending bool
52 maxEndpointsPerSubset int32
53 existingEndpointSlices []*discovery.EndpointSlice
54 expectedNumSlices int
55 expectedClientActions int
56 expectedMetrics *expectedMetrics
57 }{{
58 testName: "Endpoints with no subsets",
59 subsets: []corev1.EndpointSubset{},
60 existingEndpointSlices: []*discovery.EndpointSlice{},
61 expectedNumSlices: 0,
62 expectedClientActions: 0,
63 expectedMetrics: &expectedMetrics{},
64 }, {
65 testName: "Endpoints with no addresses",
66 subsets: []corev1.EndpointSubset{{
67 Ports: []corev1.EndpointPort{{
68 Name: "http",
69 Port: 80,
70 Protocol: corev1.ProtocolTCP,
71 }},
72 }},
73 existingEndpointSlices: []*discovery.EndpointSlice{},
74 expectedNumSlices: 0,
75 expectedClientActions: 0,
76 expectedMetrics: &expectedMetrics{},
77 }, {
78 testName: "Endpoints with 1 subset, port, and address",
79 subsets: []corev1.EndpointSubset{{
80 Ports: []corev1.EndpointPort{{
81 Name: "http",
82 Port: 80,
83 Protocol: corev1.ProtocolTCP,
84 }},
85 Addresses: []corev1.EndpointAddress{{
86 IP: "10.0.0.1",
87 Hostname: "pod-1",
88 NodeName: pointer.String("node-1"),
89 }},
90 }},
91 existingEndpointSlices: []*discovery.EndpointSlice{},
92 expectedNumSlices: 1,
93 expectedClientActions: 1,
94 expectedMetrics: &expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 1, addedPerSync: 1, numCreated: 1},
95 }, {
96 testName: "Endpoints with 2 subset, different port and address",
97 subsets: []corev1.EndpointSubset{
98 {
99 Ports: []corev1.EndpointPort{{
100 Name: "http",
101 Port: 80,
102 Protocol: corev1.ProtocolTCP,
103 }},
104 Addresses: []corev1.EndpointAddress{{
105 IP: "10.0.0.1",
106 Hostname: "pod-1",
107 NodeName: pointer.String("node-1"),
108 }},
109 },
110 {
111 Ports: []corev1.EndpointPort{{
112 Name: "https",
113 Port: 443,
114 Protocol: corev1.ProtocolTCP,
115 }},
116 Addresses: []corev1.EndpointAddress{{
117 IP: "10.0.0.2",
118 Hostname: "pod-2",
119 NodeName: pointer.String("node-1"),
120 }},
121 },
122 },
123 existingEndpointSlices: []*discovery.EndpointSlice{},
124 expectedNumSlices: 2,
125 expectedClientActions: 2,
126 expectedMetrics: &expectedMetrics{desiredSlices: 2, actualSlices: 2, desiredEndpoints: 2, addedPerSync: 2, numCreated: 2},
127 }, {
128 testName: "Endpoints with 2 subset, different port and same address",
129 subsets: []corev1.EndpointSubset{
130 {
131 Ports: []corev1.EndpointPort{{
132 Name: "http",
133 Port: 80,
134 Protocol: corev1.ProtocolTCP,
135 }},
136 Addresses: []corev1.EndpointAddress{{
137 IP: "10.0.0.1",
138 Hostname: "pod-1",
139 NodeName: pointer.String("node-1"),
140 }},
141 },
142 {
143 Ports: []corev1.EndpointPort{{
144 Name: "https",
145 Port: 443,
146 Protocol: corev1.ProtocolTCP,
147 }},
148 Addresses: []corev1.EndpointAddress{{
149 IP: "10.0.0.1",
150 Hostname: "pod-1",
151 NodeName: pointer.String("node-1"),
152 }},
153 },
154 },
155 existingEndpointSlices: []*discovery.EndpointSlice{},
156 expectedNumSlices: 1,
157 expectedClientActions: 1,
158 expectedMetrics: &expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 1, addedPerSync: 1, numCreated: 1},
159 }, {
160 testName: "Endpoints with 2 subset, different address and same port",
161 subsets: []corev1.EndpointSubset{
162 {
163 Ports: []corev1.EndpointPort{{
164 Name: "http",
165 Port: 80,
166 Protocol: corev1.ProtocolTCP,
167 }},
168 Addresses: []corev1.EndpointAddress{{
169 IP: "10.0.0.1",
170 Hostname: "pod-1",
171 NodeName: pointer.String("node-1"),
172 }},
173 },
174 {
175 Ports: []corev1.EndpointPort{{
176 Name: "http",
177 Port: 80,
178 Protocol: corev1.ProtocolTCP,
179 }},
180 Addresses: []corev1.EndpointAddress{{
181 IP: "10.0.0.2",
182 Hostname: "pod-2",
183 NodeName: pointer.String("node-1"),
184 }},
185 },
186 },
187 existingEndpointSlices: []*discovery.EndpointSlice{},
188 expectedNumSlices: 1,
189 expectedClientActions: 1,
190 expectedMetrics: &expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 2, addedPerSync: 2, numCreated: 1},
191 }, {
192 testName: "Endpoints with 1 subset, port, and address, pending deletion",
193 subsets: []corev1.EndpointSubset{{
194 Ports: []corev1.EndpointPort{{
195 Name: "http",
196 Port: 80,
197 Protocol: corev1.ProtocolTCP,
198 }},
199 Addresses: []corev1.EndpointAddress{{
200 IP: "10.0.0.1",
201 Hostname: "pod-1",
202 NodeName: pointer.String("node-1"),
203 }},
204 }},
205 endpointsDeletionPending: true,
206 existingEndpointSlices: []*discovery.EndpointSlice{},
207 expectedNumSlices: 0,
208 expectedClientActions: 0,
209 }, {
210 testName: "Endpoints with 1 subset, port, and address and existing slice with same fields",
211 subsets: []corev1.EndpointSubset{{
212 Ports: []corev1.EndpointPort{{
213 Name: "http",
214 Port: 80,
215 Protocol: corev1.ProtocolTCP,
216 }},
217 Addresses: []corev1.EndpointAddress{{
218 IP: "10.0.0.1",
219 Hostname: "pod-1",
220 }},
221 }},
222 existingEndpointSlices: []*discovery.EndpointSlice{{
223 ObjectMeta: metav1.ObjectMeta{
224 Name: "test-ep-1",
225 },
226 AddressType: discovery.AddressTypeIPv4,
227 Ports: []discovery.EndpointPort{{
228 Name: pointer.String("http"),
229 Port: pointer.Int32(80),
230 Protocol: &protoTCP,
231 }},
232 Endpoints: []discovery.Endpoint{{
233 Addresses: []string{"10.0.0.1"},
234 Hostname: pointer.String("pod-1"),
235 Conditions: discovery.EndpointConditions{Ready: pointer.Bool(true)},
236 }},
237 }},
238 expectedNumSlices: 1,
239 expectedClientActions: 0,
240 }, {
241 testName: "Endpoints with 1 subset, port, and address and existing slice with an additional annotation",
242 subsets: []corev1.EndpointSubset{{
243 Ports: []corev1.EndpointPort{{
244 Name: "http",
245 Port: 80,
246 Protocol: corev1.ProtocolTCP,
247 }},
248 Addresses: []corev1.EndpointAddress{{
249 IP: "10.0.0.1",
250 Hostname: "pod-1",
251 }},
252 }},
253 existingEndpointSlices: []*discovery.EndpointSlice{{
254 ObjectMeta: metav1.ObjectMeta{
255 Name: "test-ep-1",
256 Annotations: map[string]string{"foo": "bar"},
257 },
258 AddressType: discovery.AddressTypeIPv4,
259 Ports: []discovery.EndpointPort{{
260 Name: pointer.String("http"),
261 Port: pointer.Int32(80),
262 Protocol: &protoTCP,
263 }},
264 Endpoints: []discovery.Endpoint{{
265 Addresses: []string{"10.0.0.1"},
266 Hostname: pointer.String("pod-1"),
267 Conditions: discovery.EndpointConditions{Ready: pointer.Bool(true)},
268 }},
269 }},
270 expectedNumSlices: 1,
271 expectedClientActions: 1,
272 }, {
273 testName: "Endpoints with 1 subset, port, label and address and existing slice with same fields but the label",
274 subsets: []corev1.EndpointSubset{{
275 Ports: []corev1.EndpointPort{{
276 Name: "http",
277 Port: 80,
278 Protocol: corev1.ProtocolTCP,
279 }},
280 Addresses: []corev1.EndpointAddress{{
281 IP: "10.0.0.1",
282 Hostname: "pod-1",
283 }},
284 }},
285 epLabels: map[string]string{"foo": "bar"},
286 existingEndpointSlices: []*discovery.EndpointSlice{{
287 ObjectMeta: metav1.ObjectMeta{
288 Name: "test-ep-1",
289 Annotations: map[string]string{"foo": "bar"},
290 },
291 AddressType: discovery.AddressTypeIPv4,
292 Ports: []discovery.EndpointPort{{
293 Name: pointer.String("http"),
294 Port: pointer.Int32(80),
295 Protocol: &protoTCP,
296 }},
297 Endpoints: []discovery.Endpoint{{
298 Addresses: []string{"10.0.0.1"},
299 Hostname: pointer.String("pod-1"),
300 Conditions: discovery.EndpointConditions{Ready: pointer.Bool(true)},
301 }},
302 }},
303 expectedNumSlices: 1,
304 expectedClientActions: 1,
305 }, {
306 testName: "Endpoints with 1 subset, 2 ports, and 2 addresses",
307 subsets: []corev1.EndpointSubset{{
308 Ports: []corev1.EndpointPort{{
309 Name: "http",
310 Port: 80,
311 Protocol: corev1.ProtocolTCP,
312 }, {
313 Name: "https",
314 Port: 443,
315 Protocol: corev1.ProtocolUDP,
316 }},
317 Addresses: []corev1.EndpointAddress{{
318 IP: "10.0.0.1",
319 Hostname: "pod-1",
320 NodeName: pointer.String("node-1"),
321 }, {
322 IP: "10.0.0.2",
323 Hostname: "pod-2",
324 NodeName: pointer.String("node-2"),
325 }},
326 }},
327 existingEndpointSlices: []*discovery.EndpointSlice{},
328 expectedNumSlices: 1,
329 expectedClientActions: 1,
330 expectedMetrics: &expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 2, addedPerSync: 2, numCreated: 1},
331 }, {
332 testName: "Endpoints with 1 subset, 2 ports, and 2 not ready addresses",
333 subsets: []corev1.EndpointSubset{{
334 Ports: []corev1.EndpointPort{{
335 Name: "http",
336 Port: 80,
337 Protocol: corev1.ProtocolTCP,
338 }, {
339 Name: "https",
340 Port: 443,
341 Protocol: corev1.ProtocolUDP,
342 }},
343 NotReadyAddresses: []corev1.EndpointAddress{{
344 IP: "10.0.0.1",
345 Hostname: "pod-1",
346 NodeName: pointer.String("node-1"),
347 }, {
348 IP: "10.0.0.2",
349 Hostname: "pod-2",
350 NodeName: pointer.String("node-2"),
351 }},
352 }},
353 existingEndpointSlices: []*discovery.EndpointSlice{},
354 expectedNumSlices: 1,
355 expectedClientActions: 1,
356 expectedMetrics: &expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 2, addedPerSync: 2, numCreated: 1},
357 }, {
358 testName: "Endpoints with 1 subset, 2 ports, and 2 ready and 2 not ready addresses",
359 subsets: []corev1.EndpointSubset{{
360 Ports: []corev1.EndpointPort{{
361 Name: "http",
362 Port: 80,
363 Protocol: corev1.ProtocolTCP,
364 }, {
365 Name: "https",
366 Port: 443,
367 Protocol: corev1.ProtocolUDP,
368 }},
369 Addresses: []corev1.EndpointAddress{{
370 IP: "10.1.1.1",
371 Hostname: "pod-11",
372 NodeName: pointer.String("node-1"),
373 }, {
374 IP: "10.1.1.2",
375 Hostname: "pod-12",
376 NodeName: pointer.String("node-2"),
377 }},
378 NotReadyAddresses: []corev1.EndpointAddress{{
379 IP: "10.0.0.1",
380 Hostname: "pod-1",
381 NodeName: pointer.String("node-1"),
382 }, {
383 IP: "10.0.0.2",
384 Hostname: "pod-2",
385 NodeName: pointer.String("node-2"),
386 }},
387 }},
388 existingEndpointSlices: []*discovery.EndpointSlice{},
389 expectedNumSlices: 1,
390 expectedClientActions: 1,
391 expectedMetrics: &expectedMetrics{desiredSlices: 1, actualSlices: 1, desiredEndpoints: 4, addedPerSync: 4, numCreated: 1},
392 }, {
393 testName: "Endpoints with 2 subsets, multiple ports and addresses",
394 subsets: []corev1.EndpointSubset{{
395 Ports: []corev1.EndpointPort{{
396 Name: "http",
397 Port: 80,
398 Protocol: corev1.ProtocolTCP,
399 }, {
400 Name: "https",
401 Port: 443,
402 Protocol: corev1.ProtocolUDP,
403 }},
404 Addresses: []corev1.EndpointAddress{{
405 IP: "10.0.0.1",
406 Hostname: "pod-1",
407 NodeName: pointer.String("node-1"),
408 }, {
409 IP: "10.0.0.2",
410 Hostname: "pod-2",
411 NodeName: pointer.String("node-2"),
412 }},
413 }, {
414 Ports: []corev1.EndpointPort{{
415 Name: "http",
416 Port: 3000,
417 Protocol: corev1.ProtocolTCP,
418 }, {
419 Name: "https",
420 Port: 3001,
421 Protocol: corev1.ProtocolUDP,
422 }},
423 Addresses: []corev1.EndpointAddress{{
424 IP: "10.0.1.1",
425 Hostname: "pod-11",
426 NodeName: pointer.String("node-1"),
427 }, {
428 IP: "10.0.1.2",
429 Hostname: "pod-12",
430 NodeName: pointer.String("node-2"),
431 }, {
432 IP: "10.0.1.3",
433 Hostname: "pod-13",
434 NodeName: pointer.String("node-3"),
435 }},
436 }},
437 existingEndpointSlices: []*discovery.EndpointSlice{},
438 expectedNumSlices: 2,
439 expectedClientActions: 2,
440 expectedMetrics: &expectedMetrics{desiredSlices: 2, actualSlices: 2, desiredEndpoints: 5, addedPerSync: 5, numCreated: 2},
441 }, {
442 testName: "Endpoints with 2 subsets, multiple ports and addresses, existing empty EndpointSlice",
443 subsets: []corev1.EndpointSubset{{
444 Ports: []corev1.EndpointPort{{
445 Name: "http",
446 Port: 80,
447 Protocol: corev1.ProtocolTCP,
448 }, {
449 Name: "https",
450 Port: 443,
451 Protocol: corev1.ProtocolUDP,
452 }},
453 Addresses: []corev1.EndpointAddress{{
454 IP: "10.0.0.1",
455 Hostname: "pod-1",
456 NodeName: pointer.String("node-1"),
457 }, {
458 IP: "10.0.0.2",
459 Hostname: "pod-2",
460 NodeName: pointer.String("node-2"),
461 }},
462 }, {
463 Ports: []corev1.EndpointPort{{
464 Name: "http",
465 Port: 3000,
466 Protocol: corev1.ProtocolTCP,
467 }, {
468 Name: "https",
469 Port: 3001,
470 Protocol: corev1.ProtocolUDP,
471 }},
472 Addresses: []corev1.EndpointAddress{{
473 IP: "10.0.1.1",
474 Hostname: "pod-11",
475 NodeName: pointer.String("node-1"),
476 }, {
477 IP: "10.0.1.2",
478 Hostname: "pod-12",
479 NodeName: pointer.String("node-2"),
480 }, {
481 IP: "10.0.1.3",
482 Hostname: "pod-13",
483 NodeName: pointer.String("node-3"),
484 }},
485 }},
486 existingEndpointSlices: []*discovery.EndpointSlice{{
487 ObjectMeta: metav1.ObjectMeta{
488 Name: "test-ep-1",
489 },
490 AddressType: discovery.AddressTypeIPv4,
491 Ports: []discovery.EndpointPort{{
492 Name: pointer.String("http"),
493 Port: pointer.Int32(80),
494 Protocol: &protoTCP,
495 }, {
496 Name: pointer.String("https"),
497 Port: pointer.Int32(443),
498 Protocol: &protoUDP,
499 }},
500 }},
501 expectedNumSlices: 2,
502 expectedClientActions: 2,
503 expectedMetrics: &expectedMetrics{desiredSlices: 2, actualSlices: 2, desiredEndpoints: 5, addedPerSync: 5, numCreated: 1, numUpdated: 1},
504 }, {
505 testName: "Endpoints with 2 subsets, multiple ports and addresses, existing EndpointSlice with some addresses",
506 subsets: []corev1.EndpointSubset{{
507 Ports: []corev1.EndpointPort{{
508 Name: "http",
509 Port: 80,
510 Protocol: corev1.ProtocolTCP,
511 }, {
512 Name: "https",
513 Port: 443,
514 Protocol: corev1.ProtocolUDP,
515 }},
516 Addresses: []corev1.EndpointAddress{{
517 IP: "10.0.0.1",
518 Hostname: "pod-1",
519 NodeName: pointer.String("node-1"),
520 }, {
521 IP: "10.0.0.2",
522 Hostname: "pod-2",
523 NodeName: pointer.String("node-2"),
524 }},
525 }, {
526 Ports: []corev1.EndpointPort{{
527 Name: "http",
528 Port: 3000,
529 Protocol: corev1.ProtocolTCP,
530 }, {
531 Name: "https",
532 Port: 3001,
533 Protocol: corev1.ProtocolUDP,
534 }},
535 Addresses: []corev1.EndpointAddress{{
536 IP: "10.0.1.1",
537 Hostname: "pod-11",
538 NodeName: pointer.String("node-1"),
539 }, {
540 IP: "10.0.1.2",
541 Hostname: "pod-12",
542 NodeName: pointer.String("node-2"),
543 }, {
544 IP: "10.0.1.3",
545 Hostname: "pod-13",
546 NodeName: pointer.String("node-3"),
547 }},
548 }},
549 existingEndpointSlices: []*discovery.EndpointSlice{{
550 ObjectMeta: metav1.ObjectMeta{
551 Name: "test-ep-1",
552 },
553 AddressType: discovery.AddressTypeIPv4,
554 Ports: []discovery.EndpointPort{{
555 Name: pointer.String("http"),
556 Port: pointer.Int32(80),
557 Protocol: &protoTCP,
558 }, {
559 Name: pointer.String("https"),
560 Port: pointer.Int32(443),
561 Protocol: &protoUDP,
562 }},
563 Endpoints: []discovery.Endpoint{{
564 Addresses: []string{"10.0.0.2"},
565 Hostname: pointer.String("pod-2"),
566 }, {
567 Addresses: []string{"10.0.0.1", "10.0.0.3"},
568 Hostname: pointer.String("pod-1"),
569 }},
570 }},
571 expectedNumSlices: 2,
572 expectedClientActions: 2,
573 expectedMetrics: &expectedMetrics{desiredSlices: 2, actualSlices: 2, desiredEndpoints: 5, addedPerSync: 4, updatedPerSync: 1, removedPerSync: 1, numCreated: 1, numUpdated: 1},
574 }, {
575 testName: "Endpoints with 2 subsets, multiple ports and addresses, existing EndpointSlice identical to subset",
576 subsets: []corev1.EndpointSubset{{
577 Ports: []corev1.EndpointPort{{
578 Name: "http",
579 Port: 80,
580 Protocol: corev1.ProtocolTCP,
581 }, {
582 Name: "https",
583 Port: 443,
584 Protocol: corev1.ProtocolUDP,
585 }},
586 Addresses: []corev1.EndpointAddress{{
587 IP: "10.0.0.1",
588 Hostname: "pod-1",
589 NodeName: pointer.String("node-1"),
590 }, {
591 IP: "10.0.0.2",
592 Hostname: "pod-2",
593 NodeName: pointer.String("node-2"),
594 }},
595 }, {
596 Ports: []corev1.EndpointPort{{
597 Name: "http",
598 Port: 3000,
599 Protocol: corev1.ProtocolTCP,
600 }, {
601 Name: "https",
602 Port: 3001,
603 Protocol: corev1.ProtocolUDP,
604 }},
605 Addresses: []corev1.EndpointAddress{{
606 IP: "10.0.1.1",
607 Hostname: "pod-11",
608 NodeName: pointer.String("node-1"),
609 }, {
610 IP: "10.0.1.2",
611 Hostname: "pod-12",
612 NodeName: pointer.String("node-2"),
613 }, {
614 IP: "10.0.1.3",
615 Hostname: "pod-13",
616 NodeName: pointer.String("node-3"),
617 }},
618 }},
619 existingEndpointSlices: []*discovery.EndpointSlice{{
620 ObjectMeta: metav1.ObjectMeta{
621 Name: "test-ep-1",
622 },
623 AddressType: discovery.AddressTypeIPv4,
624 Ports: []discovery.EndpointPort{{
625 Name: pointer.String("http"),
626 Port: pointer.Int32(80),
627 Protocol: &protoTCP,
628 }, {
629 Name: pointer.String("https"),
630 Port: pointer.Int32(443),
631 Protocol: &protoUDP,
632 }},
633 Endpoints: []discovery.Endpoint{{
634 Addresses: []string{"10.0.0.1"},
635 Hostname: pointer.String("pod-1"),
636 NodeName: pointer.String("node-1"),
637 Conditions: discovery.EndpointConditions{Ready: pointer.Bool(true)},
638 }, {
639 Addresses: []string{"10.0.0.2"},
640 Hostname: pointer.String("pod-2"),
641 NodeName: pointer.String("node-2"),
642 Conditions: discovery.EndpointConditions{Ready: pointer.Bool(true)},
643 }},
644 }},
645 expectedNumSlices: 2,
646 expectedClientActions: 1,
647 expectedMetrics: &expectedMetrics{desiredSlices: 2, actualSlices: 2, desiredEndpoints: 5, addedPerSync: 3, numCreated: 1},
648 }, {
649 testName: "Endpoints with 2 subsets, multiple ports, and dual stack addresses",
650 subsets: []corev1.EndpointSubset{{
651 Ports: []corev1.EndpointPort{{
652 Name: "http",
653 Port: 80,
654 Protocol: corev1.ProtocolTCP,
655 }, {
656 Name: "https",
657 Port: 443,
658 Protocol: corev1.ProtocolUDP,
659 }},
660 Addresses: []corev1.EndpointAddress{{
661 IP: "2001:db8:2222:3333:4444:5555:6666:7777",
662 Hostname: "pod-1",
663 NodeName: pointer.String("node-1"),
664 }, {
665 IP: "10.0.0.2",
666 Hostname: "pod-2",
667 NodeName: pointer.String("node-2"),
668 }},
669 }, {
670 Ports: []corev1.EndpointPort{{
671 Name: "http",
672 Port: 3000,
673 Protocol: corev1.ProtocolTCP,
674 }, {
675 Name: "https",
676 Port: 3001,
677 Protocol: corev1.ProtocolUDP,
678 }},
679 Addresses: []corev1.EndpointAddress{{
680 IP: "10.0.1.1",
681 Hostname: "pod-11",
682 NodeName: pointer.String("node-1"),
683 }, {
684 IP: "10.0.1.2",
685 Hostname: "pod-12",
686 NodeName: pointer.String("node-2"),
687 }, {
688 IP: "2001:db8:3333:4444:5555:6666:7777:8888",
689 Hostname: "pod-13",
690 NodeName: pointer.String("node-3"),
691 }},
692 }},
693 existingEndpointSlices: []*discovery.EndpointSlice{},
694 expectedNumSlices: 4,
695 expectedClientActions: 4,
696 expectedMetrics: &expectedMetrics{desiredSlices: 4, actualSlices: 4, desiredEndpoints: 5, addedPerSync: 5, numCreated: 4},
697 }, {
698 testName: "Endpoints with 2 subsets, multiple ports, ipv6 only addresses",
699 subsets: []corev1.EndpointSubset{{
700 Ports: []corev1.EndpointPort{{
701 Name: "http",
702 Port: 80,
703 Protocol: corev1.ProtocolTCP,
704 }, {
705 Name: "https",
706 Port: 443,
707 Protocol: corev1.ProtocolUDP,
708 }},
709 Addresses: []corev1.EndpointAddress{{
710 IP: "2001:db8:1111:3333:4444:5555:6666:7777",
711 Hostname: "pod-1",
712 NodeName: pointer.String("node-1"),
713 }, {
714 IP: "2001:db8:2222:3333:4444:5555:6666:7777",
715 Hostname: "pod-2",
716 NodeName: pointer.String("node-2"),
717 }},
718 }, {
719 Ports: []corev1.EndpointPort{{
720 Name: "http",
721 Port: 3000,
722 Protocol: corev1.ProtocolTCP,
723 }, {
724 Name: "https",
725 Port: 3001,
726 Protocol: corev1.ProtocolUDP,
727 }},
728 Addresses: []corev1.EndpointAddress{{
729 IP: "2001:db8:3333:3333:4444:5555:6666:7777",
730 Hostname: "pod-11",
731 NodeName: pointer.String("node-1"),
732 }, {
733 IP: "2001:db8:4444:3333:4444:5555:6666:7777",
734 Hostname: "pod-12",
735 NodeName: pointer.String("node-2"),
736 }, {
737 IP: "2001:db8:5555:3333:4444:5555:6666:7777",
738 Hostname: "pod-13",
739 NodeName: pointer.String("node-3"),
740 }},
741 }},
742 existingEndpointSlices: []*discovery.EndpointSlice{},
743 expectedNumSlices: 2,
744 expectedClientActions: 2,
745 expectedMetrics: &expectedMetrics{desiredSlices: 2, actualSlices: 2, desiredEndpoints: 5, addedPerSync: 5, numCreated: 2},
746 }, {
747 testName: "Endpoints with 2 subsets, multiple ports, some invalid addresses",
748 subsets: []corev1.EndpointSubset{{
749 Ports: []corev1.EndpointPort{{
750 Name: "http",
751 Port: 80,
752 Protocol: corev1.ProtocolTCP,
753 }, {
754 Name: "https",
755 Port: 443,
756 Protocol: corev1.ProtocolUDP,
757 }},
758 Addresses: []corev1.EndpointAddress{{
759 IP: "2001:db8:1111:3333:4444:5555:6666:7777",
760 Hostname: "pod-1",
761 NodeName: pointer.String("node-1"),
762 }, {
763 IP: "this-is-not-an-ip",
764 Hostname: "pod-2",
765 NodeName: pointer.String("node-2"),
766 }},
767 }, {
768 Ports: []corev1.EndpointPort{{
769 Name: "http",
770 Port: 3000,
771 Protocol: corev1.ProtocolTCP,
772 }, {
773 Name: "https",
774 Port: 3001,
775 Protocol: corev1.ProtocolUDP,
776 }},
777 Addresses: []corev1.EndpointAddress{{
778 IP: "this-is-also-not-an-ip",
779 Hostname: "pod-11",
780 NodeName: pointer.String("node-1"),
781 }, {
782 IP: "2001:db8:4444:3333:4444:5555:6666:7777",
783 Hostname: "pod-12",
784 NodeName: pointer.String("node-2"),
785 }, {
786 IP: "2001:db8:5555:3333:4444:5555:6666:7777",
787 Hostname: "pod-13",
788 NodeName: pointer.String("node-3"),
789 }},
790 }},
791 existingEndpointSlices: []*discovery.EndpointSlice{},
792 expectedNumSlices: 2,
793 expectedClientActions: 2,
794 expectedMetrics: &expectedMetrics{desiredSlices: 2, actualSlices: 2, desiredEndpoints: 3, addedPerSync: 3, skippedPerSync: 2, numCreated: 2},
795 }, {
796 testName: "Endpoints with 2 subsets, multiple ports, all invalid addresses",
797 subsets: []corev1.EndpointSubset{{
798 Ports: []corev1.EndpointPort{{
799 Name: "http",
800 Port: 80,
801 Protocol: corev1.ProtocolTCP,
802 }, {
803 Name: "https",
804 Port: 443,
805 Protocol: corev1.ProtocolUDP,
806 }},
807 Addresses: []corev1.EndpointAddress{{
808 IP: "this-is-not-an-ip1",
809 Hostname: "pod-1",
810 NodeName: pointer.String("node-1"),
811 }, {
812 IP: "this-is-not-an-ip12",
813 Hostname: "pod-2",
814 NodeName: pointer.String("node-2"),
815 }},
816 }, {
817 Ports: []corev1.EndpointPort{{
818 Name: "http",
819 Port: 3000,
820 Protocol: corev1.ProtocolTCP,
821 }, {
822 Name: "https",
823 Port: 3001,
824 Protocol: corev1.ProtocolUDP,
825 }},
826 Addresses: []corev1.EndpointAddress{{
827 IP: "this-is-not-an-ip11",
828 Hostname: "pod-11",
829 NodeName: pointer.String("node-1"),
830 }, {
831 IP: "this-is-not-an-ip12",
832 Hostname: "pod-12",
833 NodeName: pointer.String("node-2"),
834 }, {
835 IP: "this-is-not-an-ip3",
836 Hostname: "pod-13",
837 NodeName: pointer.String("node-3"),
838 }},
839 }},
840 existingEndpointSlices: []*discovery.EndpointSlice{},
841 expectedNumSlices: 0,
842 expectedClientActions: 0,
843 expectedMetrics: &expectedMetrics{desiredSlices: 0, actualSlices: 0, desiredEndpoints: 0, addedPerSync: 0, skippedPerSync: 5, numCreated: 0},
844 }, {
845 testName: "Endpoints with 2 subsets, 1 exceeding maxEndpointsPerSubset",
846 subsets: []corev1.EndpointSubset{{
847 Ports: []corev1.EndpointPort{{
848 Name: "http",
849 Port: 80,
850 Protocol: corev1.ProtocolTCP,
851 }, {
852 Name: "https",
853 Port: 443,
854 Protocol: corev1.ProtocolUDP,
855 }},
856 Addresses: []corev1.EndpointAddress{{
857 IP: "10.0.0.1",
858 Hostname: "pod-1",
859 NodeName: pointer.String("node-1"),
860 }, {
861 IP: "10.0.0.2",
862 Hostname: "pod-2",
863 NodeName: pointer.String("node-2"),
864 }},
865 }, {
866 Ports: []corev1.EndpointPort{{
867 Name: "http",
868 Port: 3000,
869 Protocol: corev1.ProtocolTCP,
870 }, {
871 Name: "https",
872 Port: 3001,
873 Protocol: corev1.ProtocolUDP,
874 }},
875 Addresses: []corev1.EndpointAddress{{
876 IP: "10.0.1.1",
877 Hostname: "pod-11",
878 NodeName: pointer.String("node-1"),
879 }, {
880 IP: "10.0.1.2",
881 Hostname: "pod-12",
882 NodeName: pointer.String("node-2"),
883 }, {
884 IP: "10.0.1.3",
885 Hostname: "pod-13",
886 NodeName: pointer.String("node-3"),
887 }},
888 }},
889 existingEndpointSlices: []*discovery.EndpointSlice{},
890 expectedNumSlices: 2,
891 expectedClientActions: 2,
892 maxEndpointsPerSubset: 2,
893 expectedMetrics: &expectedMetrics{desiredSlices: 2, actualSlices: 2, desiredEndpoints: 4, addedPerSync: 4, updatedPerSync: 0, removedPerSync: 0, skippedPerSync: 1, numCreated: 2, numUpdated: 0},
894 }, {
895 testName: "The last-applied-configuration annotation should not get mirrored to created or updated endpoint slices",
896 epAnnotations: map[string]string{
897 corev1.LastAppliedConfigAnnotation: "{\"apiVersion\":\"v1\",\"kind\":\"Endpoints\",\"subsets\":[]}",
898 },
899 subsets: []corev1.EndpointSubset{{
900 Ports: []corev1.EndpointPort{{
901 Name: "http",
902 Port: 80,
903 Protocol: corev1.ProtocolTCP,
904 }},
905 Addresses: []corev1.EndpointAddress{{
906 IP: "10.0.0.1",
907 Hostname: "pod-1",
908 }},
909 }},
910 existingEndpointSlices: []*discovery.EndpointSlice{},
911 expectedNumSlices: 1,
912 expectedClientActions: 1,
913 expectedMetrics: &expectedMetrics{addedPerSync: 1, numCreated: 1, desiredEndpoints: 1, desiredSlices: 1, actualSlices: 1},
914 }, {
915 testName: "The last-applied-configuration annotation shouldn't get added to created endpoint slices",
916 subsets: []corev1.EndpointSubset{{
917 Ports: []corev1.EndpointPort{{
918 Name: "http",
919 Port: 80,
920 Protocol: corev1.ProtocolTCP,
921 }},
922 Addresses: []corev1.EndpointAddress{{
923 IP: "10.0.0.1",
924 Hostname: "pod-1",
925 }},
926 }},
927 existingEndpointSlices: []*discovery.EndpointSlice{},
928 expectedNumSlices: 1,
929 expectedClientActions: 1,
930 expectedMetrics: &expectedMetrics{addedPerSync: 1, numCreated: 1, desiredEndpoints: 1, desiredSlices: 1, actualSlices: 1},
931 }, {
932 testName: "The last-applied-configuration shouldn't get mirrored to endpoint slices when it's value is empty",
933 epAnnotations: map[string]string{
934 corev1.LastAppliedConfigAnnotation: "",
935 },
936 subsets: []corev1.EndpointSubset{{
937 Ports: []corev1.EndpointPort{{
938 Name: "http",
939 Port: 80,
940 Protocol: corev1.ProtocolTCP,
941 }},
942 Addresses: []corev1.EndpointAddress{{
943 IP: "10.0.0.1",
944 Hostname: "pod-1",
945 }},
946 }},
947 existingEndpointSlices: []*discovery.EndpointSlice{},
948 expectedNumSlices: 1,
949 expectedClientActions: 1,
950 expectedMetrics: &expectedMetrics{addedPerSync: 1, numCreated: 1, desiredEndpoints: 1, desiredSlices: 1, actualSlices: 1},
951 }, {
952 testName: "Annotations other than last-applied-configuration should get correctly mirrored",
953 epAnnotations: map[string]string{
954 corev1.LastAppliedConfigAnnotation: "{\"apiVersion\":\"v1\",\"kind\":\"Endpoints\",\"subsets\":[]}",
955 "foo": "bar",
956 },
957 subsets: []corev1.EndpointSubset{{
958 Ports: []corev1.EndpointPort{{
959 Name: "http",
960 Port: 80,
961 Protocol: corev1.ProtocolTCP,
962 }},
963 Addresses: []corev1.EndpointAddress{{
964 IP: "10.0.0.1",
965 Hostname: "pod-1",
966 }},
967 }},
968 existingEndpointSlices: []*discovery.EndpointSlice{},
969 expectedNumSlices: 1,
970 expectedClientActions: 1,
971 expectedMetrics: &expectedMetrics{addedPerSync: 1, numCreated: 1, desiredEndpoints: 1, desiredSlices: 1, actualSlices: 1},
972 }, {
973 testName: "Annotation mirroring should remove the last-applied-configuration annotation from existing endpoint slices",
974 subsets: []corev1.EndpointSubset{{
975 Ports: []corev1.EndpointPort{{
976 Name: "http",
977 Port: 80,
978 Protocol: corev1.ProtocolTCP,
979 }},
980 Addresses: []corev1.EndpointAddress{{
981 IP: "10.0.0.1",
982 Hostname: "pod-1",
983 }},
984 }},
985 existingEndpointSlices: []*discovery.EndpointSlice{{
986 ObjectMeta: metav1.ObjectMeta{
987 Name: "test-ep-1",
988 Annotations: map[string]string{
989 corev1.LastAppliedConfigAnnotation: "{\"apiVersion\":\"v1\",\"kind\":\"Endpoints\",\"subsets\":[]}",
990 },
991 },
992 AddressType: discovery.AddressTypeIPv4,
993 Ports: []discovery.EndpointPort{{
994 Name: pointer.String("http"),
995 Port: pointer.Int32(80),
996 Protocol: &protoTCP,
997 }},
998 Endpoints: []discovery.Endpoint{{
999 Addresses: []string{"10.0.0.1"},
1000 Hostname: pointer.String("pod-1"),
1001 Conditions: discovery.EndpointConditions{Ready: pointer.Bool(true)},
1002 }},
1003 }},
1004 expectedNumSlices: 1,
1005 expectedClientActions: 1,
1006 }}
1007
1008 for _, tc := range testCases {
1009 t.Run(tc.testName, func(t *testing.T) {
1010 tCtx := ktesting.Init(t)
1011 client := newClientset()
1012 setupMetrics()
1013 namespace := "test"
1014 endpoints := corev1.Endpoints{
1015 ObjectMeta: metav1.ObjectMeta{Name: "test-ep", Namespace: namespace, Labels: tc.epLabels, Annotations: tc.epAnnotations},
1016 Subsets: tc.subsets,
1017 }
1018
1019 if tc.endpointsDeletionPending {
1020 now := metav1.Now()
1021 endpoints.DeletionTimestamp = &now
1022 }
1023
1024 numInitialActions := 0
1025 for _, epSlice := range tc.existingEndpointSlices {
1026 epSlice.Labels = map[string]string{
1027 discovery.LabelServiceName: endpoints.Name,
1028 discovery.LabelManagedBy: controllerName,
1029 }
1030 _, err := client.DiscoveryV1().EndpointSlices(namespace).Create(context.TODO(), epSlice, metav1.CreateOptions{})
1031 if err != nil {
1032 t.Fatalf("Expected no error creating EndpointSlice, got %v", err)
1033 }
1034 numInitialActions++
1035 }
1036
1037 maxEndpointsPerSubset := tc.maxEndpointsPerSubset
1038 if maxEndpointsPerSubset == 0 {
1039 maxEndpointsPerSubset = defaultMaxEndpointsPerSubset
1040 }
1041 r := newReconciler(tCtx, client, maxEndpointsPerSubset)
1042 reconcileHelper(t, r, &endpoints, tc.existingEndpointSlices)
1043
1044 numExtraActions := len(client.Actions()) - numInitialActions
1045 if numExtraActions != tc.expectedClientActions {
1046 t.Fatalf("Expected %d additional client actions, got %d: %#v", tc.expectedClientActions, numExtraActions, client.Actions()[numInitialActions:])
1047 }
1048
1049 if tc.expectedMetrics != nil {
1050 expectMetrics(t, *tc.expectedMetrics)
1051 }
1052
1053 endpointSlices := fetchEndpointSlices(t, client, namespace)
1054 expectEndpointSlices(t, tc.expectedNumSlices, int(maxEndpointsPerSubset), endpoints, endpointSlices)
1055 })
1056 }
1057 }
1058
1059
1060
1061 func newReconciler(ctx context.Context, client *fake.Clientset, maxEndpointsPerSubset int32) *reconciler {
1062 broadcaster := record.NewBroadcaster(record.WithContext(ctx))
1063 recorder := broadcaster.NewRecorder(scheme.Scheme, corev1.EventSource{Component: "endpoint-slice-mirroring-controller"})
1064
1065 return &reconciler{
1066 client: client,
1067 maxEndpointsPerSubset: maxEndpointsPerSubset,
1068 endpointSliceTracker: endpointsliceutil.NewEndpointSliceTracker(),
1069 metricsCache: metrics.NewCache(maxEndpointsPerSubset),
1070 eventRecorder: recorder,
1071 }
1072 }
1073
1074 func expectEndpointSlices(t *testing.T, num, maxEndpointsPerSubset int, endpoints corev1.Endpoints, endpointSlices []discovery.EndpointSlice) {
1075 t.Helper()
1076 if len(endpointSlices) != num {
1077 t.Fatalf("Expected %d EndpointSlices, got %d", num, len(endpointSlices))
1078 }
1079
1080 if num == 0 {
1081 return
1082 }
1083
1084 for _, epSlice := range endpointSlices {
1085 if !strings.HasPrefix(epSlice.Name, endpoints.Name) {
1086 t.Errorf("Expected EndpointSlice name to start with %s, got %s", endpoints.Name, epSlice.Name)
1087 }
1088
1089 serviceNameVal, ok := epSlice.Labels[discovery.LabelServiceName]
1090 if !ok {
1091 t.Errorf("Expected EndpointSlice to have %s label set", discovery.LabelServiceName)
1092 }
1093 if serviceNameVal != endpoints.Name {
1094 t.Errorf("Expected EndpointSlice to have %s label set to %s, got %s", discovery.LabelServiceName, endpoints.Name, serviceNameVal)
1095 }
1096
1097 _, ok = epSlice.Annotations[corev1.LastAppliedConfigAnnotation]
1098 if ok {
1099 t.Errorf("Expected LastAppliedConfigAnnotation to be unset, got %s", epSlice.Annotations[corev1.LastAppliedConfigAnnotation])
1100 }
1101
1102 _, ok = epSlice.Annotations[corev1.EndpointsLastChangeTriggerTime]
1103 if ok {
1104 t.Errorf("Expected EndpointsLastChangeTriggerTime to be unset, got %s", epSlice.Annotations[corev1.EndpointsLastChangeTriggerTime])
1105 }
1106
1107 for annotation, val := range endpoints.Annotations {
1108 if annotation == corev1.EndpointsLastChangeTriggerTime || annotation == corev1.LastAppliedConfigAnnotation {
1109 continue
1110 }
1111 if epSlice.Annotations[annotation] != val {
1112 t.Errorf("Expected endpoint annotation %s to be mirrored correctly, got %s", annotation, epSlice.Annotations[annotation])
1113 }
1114 }
1115 }
1116
1117
1118
1119
1120 for _, epSubset := range endpointsv1.RepackSubsets(endpoints.Subsets) {
1121 if len(epSubset.Addresses) == 0 && len(epSubset.NotReadyAddresses) == 0 {
1122 continue
1123 }
1124
1125 var matchingEndpointsV4, matchingEndpointsV6 []discovery.Endpoint
1126
1127 for _, epSlice := range endpointSlices {
1128 if portsMatch(epSubset.Ports, epSlice.Ports) {
1129 switch epSlice.AddressType {
1130 case discovery.AddressTypeIPv4:
1131 matchingEndpointsV4 = append(matchingEndpointsV4, epSlice.Endpoints...)
1132 case discovery.AddressTypeIPv6:
1133 matchingEndpointsV6 = append(matchingEndpointsV6, epSlice.Endpoints...)
1134 default:
1135 t.Fatalf("Unexpected EndpointSlice address type found: %v", epSlice.AddressType)
1136 }
1137 }
1138 }
1139
1140 if len(matchingEndpointsV4) == 0 && len(matchingEndpointsV6) == 0 {
1141 t.Fatalf("No EndpointSlices match Endpoints subset: %#v", epSubset.Ports)
1142 }
1143
1144 expectMatchingAddresses(t, epSubset, matchingEndpointsV4, discovery.AddressTypeIPv4, maxEndpointsPerSubset)
1145 expectMatchingAddresses(t, epSubset, matchingEndpointsV6, discovery.AddressTypeIPv6, maxEndpointsPerSubset)
1146 }
1147 }
1148
1149 func portsMatch(epPorts []corev1.EndpointPort, epsPorts []discovery.EndpointPort) bool {
1150 if len(epPorts) != len(epsPorts) {
1151 return false
1152 }
1153
1154 portsToBeMatched := map[int32]corev1.EndpointPort{}
1155
1156 for _, epPort := range epPorts {
1157 portsToBeMatched[epPort.Port] = epPort
1158 }
1159
1160 for _, epsPort := range epsPorts {
1161 epPort, ok := portsToBeMatched[*epsPort.Port]
1162 if !ok {
1163 return false
1164 }
1165 delete(portsToBeMatched, *epsPort.Port)
1166
1167 if epPort.Name != *epsPort.Name {
1168 return false
1169 }
1170 if epPort.Port != *epsPort.Port {
1171 return false
1172 }
1173 if epPort.Protocol != *epsPort.Protocol {
1174 return false
1175 }
1176 if epPort.AppProtocol != epsPort.AppProtocol {
1177 return false
1178 }
1179 }
1180
1181 return true
1182 }
1183
1184 func expectMatchingAddresses(t *testing.T, epSubset corev1.EndpointSubset, esEndpoints []discovery.Endpoint, addrType discovery.AddressType, maxEndpointsPerSubset int) {
1185 t.Helper()
1186 type addressInfo struct {
1187 ready bool
1188 epAddress corev1.EndpointAddress
1189 }
1190
1191
1192 expectedEndpoints := map[string]addressInfo{}
1193
1194 for _, address := range epSubset.Addresses {
1195 at := getAddressType(address.IP)
1196 if at != nil && *at == addrType && len(expectedEndpoints) < maxEndpointsPerSubset {
1197 expectedEndpoints[address.IP] = addressInfo{
1198 ready: true,
1199 epAddress: address,
1200 }
1201 }
1202 }
1203
1204 for _, address := range epSubset.NotReadyAddresses {
1205 at := getAddressType(address.IP)
1206 if at != nil && *at == addrType && len(expectedEndpoints) < maxEndpointsPerSubset {
1207 expectedEndpoints[address.IP] = addressInfo{
1208 ready: false,
1209 epAddress: address,
1210 }
1211 }
1212 }
1213
1214 if len(expectedEndpoints) != len(esEndpoints) {
1215 t.Errorf("Expected %d endpoints, got %d", len(expectedEndpoints), len(esEndpoints))
1216 }
1217
1218 for _, endpoint := range esEndpoints {
1219 if len(endpoint.Addresses) != 1 {
1220 t.Fatalf("Expected endpoint to have 1 address, got %d", len(endpoint.Addresses))
1221 }
1222 address := endpoint.Addresses[0]
1223 expectedEndpoint, ok := expectedEndpoints[address]
1224
1225 if !ok {
1226 t.Fatalf("EndpointSlice has endpoint with unexpected address: %s", address)
1227 }
1228
1229 if expectedEndpoint.ready != *endpoint.Conditions.Ready {
1230 t.Errorf("Expected ready to be %t, got %t", expectedEndpoint.ready, *endpoint.Conditions.Ready)
1231 }
1232
1233 if endpoint.Hostname == nil {
1234 if expectedEndpoint.epAddress.Hostname != "" {
1235 t.Errorf("Expected hostname to be %s, got nil", expectedEndpoint.epAddress.Hostname)
1236 }
1237 } else if expectedEndpoint.epAddress.Hostname != *endpoint.Hostname {
1238 t.Errorf("Expected hostname to be %s, got %s", expectedEndpoint.epAddress.Hostname, *endpoint.Hostname)
1239 }
1240
1241 if expectedEndpoint.epAddress.NodeName != nil {
1242 if endpoint.NodeName == nil {
1243 t.Errorf("Expected nodeName to be set")
1244 }
1245 if *expectedEndpoint.epAddress.NodeName != *endpoint.NodeName {
1246 t.Errorf("Expected nodeName to be %s, got %s", *expectedEndpoint.epAddress.NodeName, *endpoint.NodeName)
1247 }
1248 }
1249 }
1250 }
1251
1252 func fetchEndpointSlices(t *testing.T, client *fake.Clientset, namespace string) []discovery.EndpointSlice {
1253 t.Helper()
1254 fetchedSlices, err := client.DiscoveryV1().EndpointSlices(namespace).List(context.TODO(), metav1.ListOptions{
1255 LabelSelector: discovery.LabelManagedBy + "=" + controllerName,
1256 })
1257 if err != nil {
1258 t.Fatalf("Expected no error fetching Endpoint Slices, got: %v", err)
1259 return []discovery.EndpointSlice{}
1260 }
1261 return fetchedSlices.Items
1262 }
1263
1264 func reconcileHelper(t *testing.T, r *reconciler, endpoints *corev1.Endpoints, existingSlices []*discovery.EndpointSlice) {
1265 t.Helper()
1266 logger, _ := ktesting.NewTestContext(t)
1267 err := r.reconcile(logger, endpoints, existingSlices)
1268 if err != nil {
1269 t.Fatalf("Expected no error reconciling Endpoint Slices, got: %v", err)
1270 }
1271 }
1272
1273
1274
1275 type expectedMetrics struct {
1276 desiredSlices int
1277 actualSlices int
1278 desiredEndpoints int
1279 addedPerSync int
1280 updatedPerSync int
1281 removedPerSync int
1282 skippedPerSync int
1283 numCreated int
1284 numUpdated int
1285 numDeleted int
1286 }
1287
1288 func expectMetrics(t *testing.T, em expectedMetrics) {
1289 t.Helper()
1290
1291 actualDesiredSlices, err := testutil.GetGaugeMetricValue(metrics.DesiredEndpointSlices.WithLabelValues())
1292 handleErr(t, err, "desiredEndpointSlices")
1293 if actualDesiredSlices != float64(em.desiredSlices) {
1294 t.Errorf("Expected desiredEndpointSlices to be %d, got %v", em.desiredSlices, actualDesiredSlices)
1295 }
1296
1297 actualNumSlices, err := testutil.GetGaugeMetricValue(metrics.NumEndpointSlices.WithLabelValues())
1298 handleErr(t, err, "numEndpointSlices")
1299 if actualNumSlices != float64(em.actualSlices) {
1300 t.Errorf("Expected numEndpointSlices to be %d, got %v", em.actualSlices, actualNumSlices)
1301 }
1302
1303 actualEndpointsDesired, err := testutil.GetGaugeMetricValue(metrics.EndpointsDesired.WithLabelValues())
1304 handleErr(t, err, "desiredEndpoints")
1305 if actualEndpointsDesired != float64(em.desiredEndpoints) {
1306 t.Errorf("Expected desiredEndpoints to be %d, got %v", em.desiredEndpoints, actualEndpointsDesired)
1307 }
1308
1309 actualAddedPerSync, err := testutil.GetHistogramMetricValue(metrics.EndpointsAddedPerSync.WithLabelValues())
1310 handleErr(t, err, "endpointsAddedPerSync")
1311 if actualAddedPerSync != float64(em.addedPerSync) {
1312 t.Errorf("Expected endpointsAddedPerSync to be %d, got %v", em.addedPerSync, actualAddedPerSync)
1313 }
1314
1315 actualUpdatedPerSync, err := testutil.GetHistogramMetricValue(metrics.EndpointsUpdatedPerSync.WithLabelValues())
1316 handleErr(t, err, "endpointsUpdatedPerSync")
1317 if actualUpdatedPerSync != float64(em.updatedPerSync) {
1318 t.Errorf("Expected endpointsUpdatedPerSync to be %d, got %v", em.updatedPerSync, actualUpdatedPerSync)
1319 }
1320
1321 actualRemovedPerSync, err := testutil.GetHistogramMetricValue(metrics.EndpointsRemovedPerSync.WithLabelValues())
1322 handleErr(t, err, "endpointsRemovedPerSync")
1323 if actualRemovedPerSync != float64(em.removedPerSync) {
1324 t.Errorf("Expected endpointsRemovedPerSync to be %d, got %v", em.removedPerSync, actualRemovedPerSync)
1325 }
1326
1327 actualSkippedPerSync, err := testutil.GetHistogramMetricValue(metrics.AddressesSkippedPerSync.WithLabelValues())
1328 handleErr(t, err, "addressesSkippedPerSync")
1329 if actualSkippedPerSync != float64(em.skippedPerSync) {
1330 t.Errorf("Expected addressesSkippedPerSync to be %d, got %v", em.skippedPerSync, actualSkippedPerSync)
1331 }
1332
1333 actualCreated, err := testutil.GetCounterMetricValue(metrics.EndpointSliceChanges.WithLabelValues("create"))
1334 handleErr(t, err, "endpointSliceChangesCreated")
1335 if actualCreated != float64(em.numCreated) {
1336 t.Errorf("Expected endpointSliceChangesCreated to be %d, got %v", em.numCreated, actualCreated)
1337 }
1338
1339 actualUpdated, err := testutil.GetCounterMetricValue(metrics.EndpointSliceChanges.WithLabelValues("update"))
1340 handleErr(t, err, "endpointSliceChangesUpdated")
1341 if actualUpdated != float64(em.numUpdated) {
1342 t.Errorf("Expected endpointSliceChangesUpdated to be %d, got %v", em.numUpdated, actualUpdated)
1343 }
1344
1345 actualDeleted, err := testutil.GetCounterMetricValue(metrics.EndpointSliceChanges.WithLabelValues("delete"))
1346 handleErr(t, err, "desiredEndpointSlices")
1347 if actualDeleted != float64(em.numDeleted) {
1348 t.Errorf("Expected endpointSliceChangesDeleted to be %d, got %v", em.numDeleted, actualDeleted)
1349 }
1350 }
1351
1352 func handleErr(t *testing.T, err error, metricName string) {
1353 if err != nil {
1354 t.Errorf("Failed to get %s value, err: %v", metricName, err)
1355 }
1356 }
1357
1358 func setupMetrics() {
1359 metrics.RegisterMetrics()
1360 metrics.NumEndpointSlices.Delete(map[string]string{})
1361 metrics.DesiredEndpointSlices.Delete(map[string]string{})
1362 metrics.EndpointsDesired.Delete(map[string]string{})
1363 metrics.EndpointsAddedPerSync.Delete(map[string]string{})
1364 metrics.EndpointsUpdatedPerSync.Delete(map[string]string{})
1365 metrics.EndpointsRemovedPerSync.Delete(map[string]string{})
1366 metrics.AddressesSkippedPerSync.Delete(map[string]string{})
1367 metrics.EndpointSliceChanges.Delete(map[string]string{"operation": "create"})
1368 metrics.EndpointSliceChanges.Delete(map[string]string{"operation": "update"})
1369 metrics.EndpointSliceChanges.Delete(map[string]string{"operation": "delete"})
1370 }
1371
View as plain text