1
16
17 package proxy
18
19 import (
20 "fmt"
21 "reflect"
22 "testing"
23
24 v1 "k8s.io/api/core/v1"
25 discovery "k8s.io/api/discovery/v1"
26 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
27 "k8s.io/apimachinery/pkg/runtime"
28 "k8s.io/apimachinery/pkg/types"
29 "k8s.io/utils/ptr"
30 )
31
32 func TestEndpointsMapFromESC(t *testing.T) {
33 testCases := map[string]struct {
34 endpointSlices []*discovery.EndpointSlice
35 hostname string
36 namespacedName types.NamespacedName
37 expectedMap map[ServicePortName][]*BaseEndpointInfo
38 }{
39 "1 slice, 2 hosts, ports 80,443": {
40 namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
41 hostname: "host1",
42 endpointSlices: []*discovery.EndpointSlice{
43 generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80), ptr.To[int32](443)}),
44 },
45 expectedMap: map[ServicePortName][]*BaseEndpointInfo{
46 makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
47 &BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: false, ready: true, serving: true, terminating: false},
48 &BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: true, ready: true, serving: true, terminating: false},
49 &BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: false, ready: true, serving: true, terminating: false},
50 },
51 makeServicePortName("ns1", "svc1", "port-1", v1.ProtocolTCP): {
52 &BaseEndpointInfo{ip: "10.0.1.1", port: 443, endpoint: "10.0.1.1:443", isLocal: false, ready: true, serving: true, terminating: false},
53 &BaseEndpointInfo{ip: "10.0.1.2", port: 443, endpoint: "10.0.1.2:443", isLocal: true, ready: true, serving: true, terminating: false},
54 &BaseEndpointInfo{ip: "10.0.1.3", port: 443, endpoint: "10.0.1.3:443", isLocal: false, ready: true, serving: true, terminating: false},
55 },
56 },
57 },
58 "2 slices, same port": {
59 namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
60 endpointSlices: []*discovery.EndpointSlice{
61 generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{}, []*int32{ptr.To[int32](80)}),
62 generateEndpointSlice("svc1", "ns1", 2, 3, 999, 999, []string{}, []*int32{ptr.To[int32](80)}),
63 },
64 expectedMap: map[ServicePortName][]*BaseEndpointInfo{
65 makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
66 &BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: false, ready: true, serving: true, terminating: false},
67 &BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: false, ready: true, serving: true, terminating: false},
68 &BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: false, ready: true, serving: true, terminating: false},
69 &BaseEndpointInfo{ip: "10.0.2.1", port: 80, endpoint: "10.0.2.1:80", isLocal: false, ready: true, serving: true, terminating: false},
70 &BaseEndpointInfo{ip: "10.0.2.2", port: 80, endpoint: "10.0.2.2:80", isLocal: false, ready: true, serving: true, terminating: false},
71 &BaseEndpointInfo{ip: "10.0.2.3", port: 80, endpoint: "10.0.2.3:80", isLocal: false, ready: true, serving: true, terminating: false},
72 },
73 },
74 },
75
76
77 "2 overlapping slices, same port": {
78 namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
79 endpointSlices: []*discovery.EndpointSlice{
80 generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{}, []*int32{ptr.To[int32](80)}),
81 generateEndpointSlice("svc1", "ns1", 1, 4, 999, 999, []string{}, []*int32{ptr.To[int32](80)}),
82 },
83 expectedMap: map[ServicePortName][]*BaseEndpointInfo{
84 makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
85 &BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: false, ready: true, serving: true, terminating: false},
86 &BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: false, ready: true, serving: true, terminating: false},
87 &BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: false, ready: true, serving: true, terminating: false},
88 &BaseEndpointInfo{ip: "10.0.1.4", port: 80, endpoint: "10.0.1.4:80", isLocal: false, ready: true, serving: true, terminating: false},
89 },
90 },
91 },
92
93
94
95
96 "2 slices, overlapping endpoints, some endpoints unready in 1 or both": {
97 namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
98 endpointSlices: []*discovery.EndpointSlice{
99 generateEndpointSlice("svc1", "ns1", 1, 10, 3, 999, []string{}, []*int32{ptr.To[int32](80)}),
100 generateEndpointSlice("svc1", "ns1", 1, 10, 6, 999, []string{}, []*int32{ptr.To[int32](80)}),
101 },
102 expectedMap: map[ServicePortName][]*BaseEndpointInfo{
103 makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
104 &BaseEndpointInfo{ip: "10.0.1.10", port: 80, endpoint: "10.0.1.10:80", isLocal: false, ready: true, serving: true, terminating: false},
105 &BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: false, ready: true, serving: true, terminating: false},
106 &BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: false, ready: true, serving: true, terminating: false},
107 &BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: false, ready: true, serving: true, terminating: false},
108 &BaseEndpointInfo{ip: "10.0.1.4", port: 80, endpoint: "10.0.1.4:80", isLocal: false, ready: true, serving: true, terminating: false},
109 &BaseEndpointInfo{ip: "10.0.1.5", port: 80, endpoint: "10.0.1.5:80", isLocal: false, ready: true, serving: true, terminating: false},
110 &BaseEndpointInfo{ip: "10.0.1.6", port: 80, endpoint: "10.0.1.6:80", isLocal: false, ready: false, serving: false, terminating: false},
111 &BaseEndpointInfo{ip: "10.0.1.7", port: 80, endpoint: "10.0.1.7:80", isLocal: false, ready: true, serving: true, terminating: false},
112 &BaseEndpointInfo{ip: "10.0.1.8", port: 80, endpoint: "10.0.1.8:80", isLocal: false, ready: true, serving: true, terminating: false},
113 &BaseEndpointInfo{ip: "10.0.1.9", port: 80, endpoint: "10.0.1.9:80", isLocal: false, ready: true, serving: true, terminating: false},
114 },
115 },
116 },
117
118 "2 slices, overlapping endpoints, some endpoints unready and some endpoints terminating": {
119 namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
120 endpointSlices: []*discovery.EndpointSlice{
121 generateEndpointSlice("svc1", "ns1", 1, 10, 3, 5, []string{}, []*int32{ptr.To[int32](80)}),
122 generateEndpointSlice("svc1", "ns1", 1, 10, 6, 5, []string{}, []*int32{ptr.To[int32](80)}),
123 },
124 expectedMap: map[ServicePortName][]*BaseEndpointInfo{
125 makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
126 &BaseEndpointInfo{ip: "10.0.1.10", port: 80, endpoint: "10.0.1.10:80", isLocal: false, ready: false, serving: true, terminating: true},
127 &BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: false, ready: true, serving: true, terminating: false},
128 &BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: false, ready: true, serving: true, terminating: false},
129 &BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: false, ready: true, serving: true, terminating: false},
130 &BaseEndpointInfo{ip: "10.0.1.4", port: 80, endpoint: "10.0.1.4:80", isLocal: false, ready: true, serving: true, terminating: false},
131 &BaseEndpointInfo{ip: "10.0.1.5", port: 80, endpoint: "10.0.1.5:80", isLocal: false, ready: false, serving: true, terminating: true},
132 &BaseEndpointInfo{ip: "10.0.1.6", port: 80, endpoint: "10.0.1.6:80", isLocal: false, ready: false, serving: false, terminating: false},
133 &BaseEndpointInfo{ip: "10.0.1.7", port: 80, endpoint: "10.0.1.7:80", isLocal: false, ready: true, serving: true, terminating: false},
134 &BaseEndpointInfo{ip: "10.0.1.8", port: 80, endpoint: "10.0.1.8:80", isLocal: false, ready: true, serving: true, terminating: false},
135 &BaseEndpointInfo{ip: "10.0.1.9", port: 80, endpoint: "10.0.1.9:80", isLocal: false, ready: true, serving: true, terminating: false},
136 },
137 },
138 },
139 "2 slices, overlapping endpoints, all unready": {
140 namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
141 endpointSlices: []*discovery.EndpointSlice{
142 generateEndpointSlice("svc1", "ns1", 1, 10, 1, 999, []string{}, []*int32{ptr.To[int32](80)}),
143 generateEndpointSlice("svc1", "ns1", 1, 10, 1, 999, []string{}, []*int32{ptr.To[int32](80)}),
144 },
145 expectedMap: map[ServicePortName][]*BaseEndpointInfo{
146 makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
147 &BaseEndpointInfo{ip: "10.0.1.10", port: 80, endpoint: "10.0.1.10:80", isLocal: false, ready: false, serving: false, terminating: false},
148 &BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: false, ready: false, serving: false, terminating: false},
149 &BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: false, ready: false, serving: false, terminating: false},
150 &BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: false, ready: false, serving: false, terminating: false},
151 &BaseEndpointInfo{ip: "10.0.1.4", port: 80, endpoint: "10.0.1.4:80", isLocal: false, ready: false, serving: false, terminating: false},
152 &BaseEndpointInfo{ip: "10.0.1.5", port: 80, endpoint: "10.0.1.5:80", isLocal: false, ready: false, serving: false, terminating: false},
153 &BaseEndpointInfo{ip: "10.0.1.6", port: 80, endpoint: "10.0.1.6:80", isLocal: false, ready: false, serving: false, terminating: false},
154 &BaseEndpointInfo{ip: "10.0.1.7", port: 80, endpoint: "10.0.1.7:80", isLocal: false, ready: false, serving: false, terminating: false},
155 &BaseEndpointInfo{ip: "10.0.1.8", port: 80, endpoint: "10.0.1.8:80", isLocal: false, ready: false, serving: false, terminating: false},
156 &BaseEndpointInfo{ip: "10.0.1.9", port: 80, endpoint: "10.0.1.9:80", isLocal: false, ready: false, serving: false, terminating: false},
157 },
158 },
159 },
160 "3 slices with different services and namespaces": {
161 namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
162 endpointSlices: []*discovery.EndpointSlice{
163 generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{}, []*int32{ptr.To[int32](80)}),
164 generateEndpointSlice("svc2", "ns1", 2, 3, 999, 999, []string{}, []*int32{ptr.To[int32](80)}),
165 generateEndpointSlice("svc1", "ns2", 3, 3, 999, 999, []string{}, []*int32{ptr.To[int32](80)}),
166 },
167 expectedMap: map[ServicePortName][]*BaseEndpointInfo{
168 makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
169 &BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: false, ready: true, serving: true, terminating: false},
170 &BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: false, ready: true, serving: true, terminating: false},
171 &BaseEndpointInfo{ip: "10.0.1.3", port: 80, endpoint: "10.0.1.3:80", isLocal: false, ready: true, serving: true, terminating: false},
172 },
173 },
174 },
175
176
177
178 "Nil port should not break anything": {
179 namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
180 hostname: "host1",
181 endpointSlices: []*discovery.EndpointSlice{
182 generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{"host1", "host2"}, []*int32{nil}),
183 },
184 expectedMap: map[ServicePortName][]*BaseEndpointInfo{},
185 },
186
187 "Different endpoints with duplicate IPs should not be filtered": {
188 namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
189 hostname: "host1",
190 endpointSlices: []*discovery.EndpointSlice{
191 generateEndpointSliceWithOffset("svc1", "ns1", 1, 1, 2, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80)}),
192 generateEndpointSliceWithOffset("svc1", "ns1", 2, 1, 2, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](8080)}),
193 },
194 expectedMap: map[ServicePortName][]*BaseEndpointInfo{
195 makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
196 &BaseEndpointInfo{ip: "10.0.1.1", port: 80, endpoint: "10.0.1.1:80", isLocal: false, ready: true, serving: true, terminating: false},
197 &BaseEndpointInfo{ip: "10.0.1.1", port: 8080, endpoint: "10.0.1.1:8080", isLocal: false, ready: true, serving: true, terminating: false},
198 &BaseEndpointInfo{ip: "10.0.1.2", port: 80, endpoint: "10.0.1.2:80", isLocal: true, ready: true, serving: true, terminating: false},
199 &BaseEndpointInfo{ip: "10.0.1.2", port: 8080, endpoint: "10.0.1.2:8080", isLocal: true, ready: true, serving: true, terminating: false},
200 },
201 },
202 },
203 }
204
205 for name, tc := range testCases {
206 t.Run(name, func(t *testing.T) {
207 esCache := NewEndpointSliceCache(tc.hostname, v1.IPv4Protocol, nil, nil)
208
209 cmc := newCacheMutationCheck(tc.endpointSlices)
210 for _, endpointSlice := range tc.endpointSlices {
211 esCache.updatePending(endpointSlice, false)
212 }
213
214 compareEndpointsMapsStr(t, esCache.getEndpointsMap(tc.namespacedName, esCache.trackerByServiceMap[tc.namespacedName].pending), tc.expectedMap)
215 cmc.Check(t)
216 })
217 }
218 }
219
220 func TestEndpointInfoByServicePort(t *testing.T) {
221 testCases := map[string]struct {
222 namespacedName types.NamespacedName
223 endpointSlices []*discovery.EndpointSlice
224 hostname string
225 expectedMap spToEndpointMap
226 }{
227 "simple use case with 3 endpoints": {
228 namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
229 hostname: "host1",
230 endpointSlices: []*discovery.EndpointSlice{
231 generateEndpointSlice("svc1", "ns1", 1, 3, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80)}),
232 },
233 expectedMap: spToEndpointMap{
234 makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
235 "10.0.1.1:80": &BaseEndpointInfo{
236 ip: "10.0.1.1",
237 port: 80,
238 endpoint: "10.0.1.1:80",
239 isLocal: false,
240 ready: true,
241 serving: true,
242 terminating: false,
243 },
244 "10.0.1.2:80": &BaseEndpointInfo{
245 ip: "10.0.1.2",
246 port: 80,
247 endpoint: "10.0.1.2:80",
248 isLocal: true,
249 ready: true,
250 serving: true,
251 terminating: false,
252 },
253 "10.0.1.3:80": &BaseEndpointInfo{
254 ip: "10.0.1.3",
255 port: 80,
256 endpoint: "10.0.1.3:80",
257 isLocal: false,
258 ready: true,
259 serving: true,
260 terminating: false,
261 },
262 },
263 },
264 },
265 "4 different slices with duplicate IPs": {
266 namespacedName: types.NamespacedName{Name: "svc1", Namespace: "ns1"},
267 hostname: "host1",
268 endpointSlices: []*discovery.EndpointSlice{
269 generateEndpointSliceWithOffset("svc1", "ns1", 1, 1, 2, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](80)}),
270 generateEndpointSliceWithOffset("svc1", "ns1", 2, 1, 2, 999, 999, []string{"host1", "host2"}, []*int32{ptr.To[int32](8080)}),
271 },
272 expectedMap: spToEndpointMap{
273 makeServicePortName("ns1", "svc1", "port-0", v1.ProtocolTCP): {
274 "10.0.1.1:80": &BaseEndpointInfo{
275 ip: "10.0.1.1",
276 port: 80,
277 endpoint: "10.0.1.1:80",
278 isLocal: false,
279 ready: true,
280 serving: true,
281 terminating: false,
282 },
283 "10.0.1.2:80": &BaseEndpointInfo{
284 ip: "10.0.1.2",
285 port: 80,
286 endpoint: "10.0.1.2:80",
287 isLocal: true,
288 ready: true,
289 serving: true,
290 terminating: false,
291 },
292 "10.0.1.1:8080": &BaseEndpointInfo{
293 ip: "10.0.1.1",
294 port: 8080,
295 endpoint: "10.0.1.1:8080",
296 isLocal: false,
297 ready: true,
298 serving: true,
299 terminating: false,
300 },
301 "10.0.1.2:8080": &BaseEndpointInfo{
302 ip: "10.0.1.2",
303 port: 8080,
304 endpoint: "10.0.1.2:8080",
305 isLocal: true,
306 ready: true,
307 serving: true,
308 terminating: false,
309 },
310 },
311 },
312 },
313 }
314
315 for name, tc := range testCases {
316 t.Run(name, func(t *testing.T) {
317 esCache := NewEndpointSliceCache(tc.hostname, v1.IPv4Protocol, nil, nil)
318
319 for _, endpointSlice := range tc.endpointSlices {
320 esCache.updatePending(endpointSlice, false)
321 }
322
323 got := esCache.endpointInfoByServicePort(tc.namespacedName, esCache.trackerByServiceMap[tc.namespacedName].pending)
324 if !reflect.DeepEqual(got, tc.expectedMap) {
325 t.Errorf("endpointInfoByServicePort does not match. Want: %+v, Got: %+v", tc.expectedMap, got)
326 }
327 })
328 }
329 }
330
331 func TestEsDataChanged(t *testing.T) {
332 p80 := int32(80)
333 p443 := int32(443)
334 port80 := discovery.EndpointPort{Port: &p80, Name: ptr.To("http"), Protocol: ptr.To(v1.ProtocolTCP)}
335 port443 := discovery.EndpointPort{Port: &p443, Name: ptr.To("https"), Protocol: ptr.To(v1.ProtocolTCP)}
336 endpoint1 := discovery.Endpoint{Addresses: []string{"10.0.1.0"}}
337 endpoint2 := discovery.Endpoint{Addresses: []string{"10.0.1.1"}}
338
339 objMeta := metav1.ObjectMeta{
340 Name: "foo",
341 Namespace: "bar",
342 Labels: map[string]string{discovery.LabelServiceName: "svc1"},
343 }
344
345 testCases := map[string]struct {
346 cache *EndpointSliceCache
347 initialSlice *discovery.EndpointSlice
348 updatedSlice *discovery.EndpointSlice
349 expectChanged bool
350 }{
351 "identical slices, ports only": {
352 cache: NewEndpointSliceCache("", v1.IPv4Protocol, nil, nil),
353 initialSlice: &discovery.EndpointSlice{
354 ObjectMeta: objMeta,
355 Ports: []discovery.EndpointPort{port80},
356 },
357 updatedSlice: &discovery.EndpointSlice{
358 ObjectMeta: objMeta,
359 Ports: []discovery.EndpointPort{port80},
360 },
361 expectChanged: false,
362 },
363 "identical slices, ports out of order": {
364 cache: NewEndpointSliceCache("", v1.IPv4Protocol, nil, nil),
365 initialSlice: &discovery.EndpointSlice{
366 ObjectMeta: objMeta,
367 Ports: []discovery.EndpointPort{port443, port80},
368 },
369 updatedSlice: &discovery.EndpointSlice{
370 ObjectMeta: objMeta,
371 Ports: []discovery.EndpointPort{port80, port443},
372 },
373 expectChanged: false,
374 },
375 "port removed": {
376 cache: NewEndpointSliceCache("", v1.IPv4Protocol, nil, nil),
377 initialSlice: &discovery.EndpointSlice{
378 ObjectMeta: objMeta,
379 Ports: []discovery.EndpointPort{port443, port80},
380 },
381 updatedSlice: &discovery.EndpointSlice{
382 ObjectMeta: objMeta,
383 Ports: []discovery.EndpointPort{port443},
384 },
385 expectChanged: true,
386 },
387 "port added": {
388 cache: NewEndpointSliceCache("", v1.IPv4Protocol, nil, nil),
389 initialSlice: &discovery.EndpointSlice{
390 ObjectMeta: objMeta,
391 Ports: []discovery.EndpointPort{port443},
392 },
393 updatedSlice: &discovery.EndpointSlice{
394 ObjectMeta: objMeta,
395 Ports: []discovery.EndpointPort{port443, port80},
396 },
397 expectChanged: true,
398 },
399 "identical with endpoints": {
400 cache: NewEndpointSliceCache("", v1.IPv4Protocol, nil, nil),
401 initialSlice: &discovery.EndpointSlice{
402 ObjectMeta: objMeta,
403 Ports: []discovery.EndpointPort{port443},
404 Endpoints: []discovery.Endpoint{endpoint1, endpoint2},
405 },
406 updatedSlice: &discovery.EndpointSlice{
407 ObjectMeta: objMeta,
408 Ports: []discovery.EndpointPort{port443},
409 Endpoints: []discovery.Endpoint{endpoint1, endpoint2},
410 },
411 expectChanged: false,
412 },
413 "identical with endpoints out of order": {
414 cache: NewEndpointSliceCache("", v1.IPv4Protocol, nil, nil),
415 initialSlice: &discovery.EndpointSlice{
416 ObjectMeta: objMeta,
417 Ports: []discovery.EndpointPort{port443},
418 Endpoints: []discovery.Endpoint{endpoint1, endpoint2},
419 },
420 updatedSlice: &discovery.EndpointSlice{
421 ObjectMeta: objMeta,
422 Ports: []discovery.EndpointPort{port443},
423 Endpoints: []discovery.Endpoint{endpoint2, endpoint1},
424 },
425 expectChanged: false,
426 },
427 "identical with endpoint added": {
428 cache: NewEndpointSliceCache("", v1.IPv4Protocol, nil, nil),
429 initialSlice: &discovery.EndpointSlice{
430 ObjectMeta: objMeta,
431 Ports: []discovery.EndpointPort{port443},
432 Endpoints: []discovery.Endpoint{endpoint1},
433 },
434 updatedSlice: &discovery.EndpointSlice{
435 ObjectMeta: objMeta,
436 Ports: []discovery.EndpointPort{port443},
437 Endpoints: []discovery.Endpoint{endpoint2, endpoint1},
438 },
439 expectChanged: true,
440 },
441 }
442
443 for name, tc := range testCases {
444 t.Run(name, func(t *testing.T) {
445 cmc := newCacheMutationCheck([]*discovery.EndpointSlice{tc.initialSlice})
446
447 if tc.initialSlice != nil {
448 tc.cache.updatePending(tc.initialSlice, false)
449 tc.cache.checkoutChanges()
450 }
451
452 serviceKey, sliceKey, err := endpointSliceCacheKeys(tc.updatedSlice)
453 if err != nil {
454 t.Fatalf("Expected no error calling endpointSliceCacheKeys(): %v", err)
455 }
456
457 esData := newEndpointSliceData(tc.updatedSlice, false)
458 changed := tc.cache.esDataChanged(serviceKey, sliceKey, esData)
459
460 if tc.expectChanged != changed {
461 t.Errorf("Expected esDataChanged() to return %t, got %t", tc.expectChanged, changed)
462 }
463
464 cmc.Check(t)
465 })
466 }
467 }
468
469 func generateEndpointSliceWithOffset(serviceName, namespace string, sliceNum, offset, numEndpoints, unreadyMod int, terminatingMod int, hosts []string, portNums []*int32) *discovery.EndpointSlice {
470 endpointSlice := &discovery.EndpointSlice{
471 ObjectMeta: metav1.ObjectMeta{
472 Name: fmt.Sprintf("%s-%d", serviceName, sliceNum),
473 Namespace: namespace,
474 Labels: map[string]string{discovery.LabelServiceName: serviceName},
475 },
476 Ports: []discovery.EndpointPort{},
477 AddressType: discovery.AddressTypeIPv4,
478 Endpoints: []discovery.Endpoint{},
479 }
480
481 for i, portNum := range portNums {
482 endpointSlice.Ports = append(endpointSlice.Ports, discovery.EndpointPort{
483 Name: ptr.To(fmt.Sprintf("port-%d", i)),
484 Port: portNum,
485 Protocol: ptr.To(v1.ProtocolTCP),
486 })
487 }
488
489 for i := 1; i <= numEndpoints; i++ {
490 readyCondition := i%unreadyMod != 0
491 terminatingCondition := i%terminatingMod == 0
492
493 ready := ptr.To(readyCondition && !terminatingCondition)
494 serving := ptr.To(readyCondition)
495 terminating := ptr.To(terminatingCondition)
496
497 endpoint := discovery.Endpoint{
498 Addresses: []string{fmt.Sprintf("10.0.%d.%d", offset, i)},
499 Conditions: discovery.EndpointConditions{
500 Ready: ready,
501 Serving: serving,
502 Terminating: terminating,
503 },
504 }
505
506 if len(hosts) > 0 {
507 hostname := hosts[i%len(hosts)]
508 endpoint.NodeName = &hostname
509 }
510
511 endpointSlice.Endpoints = append(endpointSlice.Endpoints, endpoint)
512 }
513
514 return endpointSlice
515 }
516
517 func generateEndpointSlice(serviceName, namespace string, sliceNum, numEndpoints, unreadyMod int, terminatingMod int, hosts []string, portNums []*int32) *discovery.EndpointSlice {
518 return generateEndpointSliceWithOffset(serviceName, namespace, sliceNum, sliceNum, numEndpoints, unreadyMod, terminatingMod, hosts, portNums)
519 }
520
521
522
523 type cacheMutationCheck struct {
524 objects []cacheObject
525 }
526
527
528
529 type cacheObject struct {
530 original runtime.Object
531 deepCopy runtime.Object
532 }
533
534
535 func newCacheMutationCheck(endpointSlices []*discovery.EndpointSlice) cacheMutationCheck {
536 cmc := cacheMutationCheck{}
537 for _, endpointSlice := range endpointSlices {
538 cmc.Add(endpointSlice)
539 }
540 return cmc
541 }
542
543
544
545 func (cmc *cacheMutationCheck) Add(o runtime.Object) {
546 cmc.objects = append(cmc.objects, cacheObject{
547 original: o,
548 deepCopy: o.DeepCopyObject(),
549 })
550 }
551
552
553 func (cmc *cacheMutationCheck) Check(t *testing.T) {
554 for _, o := range cmc.objects {
555 if !reflect.DeepEqual(o.original, o.deepCopy) {
556
557
558 t.Errorf("Cached object was unexpectedly mutated. Original: %+v, Mutated: %+v", o.deepCopy, o.original)
559 }
560 }
561 }
562
View as plain text