master
go 457 lines 12.2 KB
Raw
1 // SPDX-License-Identifier: GPL-3.0-or-later
2
3 package k8ssd
4
5 import (
6 "context"
7 "net"
8 "strconv"
9 "testing"
10 "time"
11
12 "github.com/netdata/netdata/go/plugins/plugin/agent/discovery/sd/model"
13
14 "github.com/stretchr/testify/assert"
15 corev1 "k8s.io/api/core/v1"
16 metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
17 "k8s.io/apimachinery/pkg/runtime"
18 "k8s.io/client-go/kubernetes"
19 "k8s.io/client-go/tools/cache"
20 )
21
22 func TestServiceTargetGroup_Provider(t *testing.T) {
23 var s serviceTargetGroup
24 assert.NotEmpty(t, s.Provider())
25 }
26
27 func TestServiceTargetGroup_Source(t *testing.T) {
28 tests := map[string]struct {
29 createSim func() discoverySim
30 wantSources []string
31 }{
32 "ClusterIP svc with multiple ports": {
33 createSim: func() discoverySim {
34 httpd, nginx := newHTTPDClusterIPService(), newNGINXClusterIPService()
35 disc, _ := prepareAllNsSvcDiscoverer(httpd, nginx)
36
37 return discoverySim{
38 td: disc,
39 wantTargetGroups: []model.TargetGroup{
40 prepareSvcTargetGroup(httpd),
41 prepareSvcTargetGroup(nginx),
42 },
43 }
44 },
45 wantSources: []string{
46 "discoverer=k8s,kind=service,namespace=default,service_name=httpd-cluster-ip-service,test=test",
47 "discoverer=k8s,kind=service,namespace=default,service_name=nginx-cluster-ip-service,test=test",
48 },
49 },
50 }
51
52 for name, test := range tests {
53 t.Run(name, func(t *testing.T) {
54 sim := test.createSim()
55
56 var sources []string
57 for _, tgg := range sim.run(t) {
58 sources = append(sources, tgg.Source())
59 }
60
61 assert.Equal(t, test.wantSources, sources)
62 })
63 }
64 }
65
66 func TestServiceTargetGroup_Targets(t *testing.T) {
67 tests := map[string]struct {
68 createSim func() discoverySim
69 wantTargets int
70 }{
71 "ClusterIP svc with multiple ports": {
72 createSim: func() discoverySim {
73 httpd, nginx := newHTTPDClusterIPService(), newNGINXClusterIPService()
74 disc, _ := prepareAllNsSvcDiscoverer(httpd, nginx)
75
76 return discoverySim{
77 td: disc,
78 wantTargetGroups: []model.TargetGroup{
79 prepareSvcTargetGroup(httpd),
80 prepareSvcTargetGroup(nginx),
81 },
82 }
83 },
84 wantTargets: 4,
85 },
86 }
87
88 for name, test := range tests {
89 t.Run(name, func(t *testing.T) {
90 sim := test.createSim()
91
92 var targets int
93 for _, tgg := range sim.run(t) {
94 targets += len(tgg.Targets())
95 }
96
97 assert.Equal(t, test.wantTargets, targets)
98 })
99 }
100 }
101
102 func TestServiceTarget_Hash(t *testing.T) {
103 tests := map[string]struct {
104 createSim func() discoverySim
105 wantHashes []uint64
106 }{
107 "ClusterIP svc with multiple ports": {
108 createSim: func() discoverySim {
109 httpd, nginx := newHTTPDClusterIPService(), newNGINXClusterIPService()
110 disc, _ := prepareAllNsSvcDiscoverer(httpd, nginx)
111
112 return discoverySim{
113 td: disc,
114 wantTargetGroups: []model.TargetGroup{
115 prepareSvcTargetGroup(httpd),
116 prepareSvcTargetGroup(nginx),
117 },
118 }
119 },
120 wantHashes: []uint64{
121 7927590792385601508,
122 11876820484195315354,
123 12042303898632345558,
124 12464836168762314838,
125 },
126 },
127 }
128
129 for name, test := range tests {
130 t.Run(name, func(t *testing.T) {
131 sim := test.createSim()
132
133 var hashes []uint64
134 for _, tgg := range sim.run(t) {
135 for _, tgt := range tgg.Targets() {
136 hashes = append(hashes, tgt.Hash())
137 }
138 }
139
140 assert.Equal(t, test.wantHashes, hashes)
141 })
142 }
143 }
144
145 func TestServiceTarget_TUID(t *testing.T) {
146 tests := map[string]struct {
147 createSim func() discoverySim
148 wantTUID []string
149 }{
150 "ClusterIP svc with multiple ports": {
151 createSim: func() discoverySim {
152 httpd, nginx := newHTTPDClusterIPService(), newNGINXClusterIPService()
153 disc, _ := prepareAllNsSvcDiscoverer(httpd, nginx)
154
155 return discoverySim{
156 td: disc,
157 wantTargetGroups: []model.TargetGroup{
158 prepareSvcTargetGroup(httpd),
159 prepareSvcTargetGroup(nginx),
160 },
161 }
162 },
163 wantTUID: []string{
164 "default_httpd-cluster-ip-service_tcp_80",
165 "default_httpd-cluster-ip-service_tcp_443",
166 "default_nginx-cluster-ip-service_tcp_80",
167 "default_nginx-cluster-ip-service_tcp_443",
168 },
169 },
170 }
171
172 for name, test := range tests {
173 t.Run(name, func(t *testing.T) {
174 sim := test.createSim()
175
176 var tuid []string
177 for _, tgg := range sim.run(t) {
178 for _, tgt := range tgg.Targets() {
179 tuid = append(tuid, tgt.TUID())
180 }
181 }
182
183 assert.Equal(t, test.wantTUID, tuid)
184 })
185 }
186 }
187
188 func TestNewServiceDiscoverer(t *testing.T) {
189 tests := map[string]struct {
190 informer cache.SharedInformer
191 wantPanic bool
192 }{
193 "valid informer": {
194 wantPanic: false,
195 informer: cache.NewSharedInformer(nil, &corev1.Service{}, resyncPeriod),
196 },
197 "nil informer": {
198 wantPanic: true,
199 informer: nil,
200 },
201 }
202
203 for name, test := range tests {
204 t.Run(name, func(t *testing.T) {
205 f := func() { newServiceDiscoverer(test.informer) }
206
207 if test.wantPanic {
208 assert.Panics(t, f)
209 } else {
210 assert.NotPanics(t, f)
211 }
212 })
213 }
214 }
215
216 func TestServiceDiscoverer_String(t *testing.T) {
217 var s serviceDiscoverer
218 assert.NotEmpty(t, s.String())
219 }
220
221 func TestServiceDiscoverer_Discover(t *testing.T) {
222 tests := map[string]struct {
223 createSim func() discoverySim
224 }{
225 "ADD: ClusterIP svc exist before run": {
226 createSim: func() discoverySim {
227 httpd, nginx := newHTTPDClusterIPService(), newNGINXClusterIPService()
228 disc, _ := prepareAllNsSvcDiscoverer(httpd, nginx)
229
230 return discoverySim{
231 td: disc,
232 wantTargetGroups: []model.TargetGroup{
233 prepareSvcTargetGroup(httpd),
234 prepareSvcTargetGroup(nginx),
235 },
236 }
237 },
238 },
239 "ADD: ClusterIP svc exist before run and add after sync": {
240 createSim: func() discoverySim {
241 httpd, nginx := newHTTPDClusterIPService(), newNGINXClusterIPService()
242 disc, client := prepareAllNsSvcDiscoverer(httpd)
243 svcClient := client.CoreV1().Services("default")
244
245 return discoverySim{
246 td: disc,
247 runAfterSync: func(ctx context.Context) {
248 _, _ = svcClient.Create(ctx, nginx, metav1.CreateOptions{})
249 },
250 wantTargetGroups: []model.TargetGroup{
251 prepareSvcTargetGroup(httpd),
252 prepareSvcTargetGroup(nginx),
253 },
254 }
255 },
256 },
257 "DELETE: ClusterIP svc remove after sync": {
258 createSim: func() discoverySim {
259 httpd, nginx := newHTTPDClusterIPService(), newNGINXClusterIPService()
260 disc, client := prepareAllNsSvcDiscoverer(httpd, nginx)
261 svcClient := client.CoreV1().Services("default")
262
263 return discoverySim{
264 td: disc,
265 runAfterSync: func(ctx context.Context) {
266 time.Sleep(time.Millisecond * 50)
267 _ = svcClient.Delete(ctx, httpd.Name, metav1.DeleteOptions{})
268 _ = svcClient.Delete(ctx, nginx.Name, metav1.DeleteOptions{})
269 },
270 wantTargetGroups: []model.TargetGroup{
271 prepareSvcTargetGroup(httpd),
272 prepareSvcTargetGroup(nginx),
273 prepareEmptySvcTargetGroup(httpd),
274 prepareEmptySvcTargetGroup(nginx),
275 },
276 }
277 },
278 },
279 "ADD,DELETE: ClusterIP svc remove and add after sync": {
280 createSim: func() discoverySim {
281 httpd, nginx := newHTTPDClusterIPService(), newNGINXClusterIPService()
282 disc, client := prepareAllNsSvcDiscoverer(httpd)
283 svcClient := client.CoreV1().Services("default")
284
285 return discoverySim{
286 td: disc,
287 runAfterSync: func(ctx context.Context) {
288 time.Sleep(time.Millisecond * 50)
289 _ = svcClient.Delete(ctx, httpd.Name, metav1.DeleteOptions{})
290 _, _ = svcClient.Create(ctx, nginx, metav1.CreateOptions{})
291 },
292 wantTargetGroups: []model.TargetGroup{
293 prepareSvcTargetGroup(httpd),
294 prepareEmptySvcTargetGroup(httpd),
295 prepareSvcTargetGroup(nginx),
296 },
297 }
298 },
299 },
300 "ADD: Headless svc exist before run": {
301 createSim: func() discoverySim {
302 httpd, nginx := newHTTPDHeadlessService(), newNGINXHeadlessService()
303 disc, _ := prepareAllNsSvcDiscoverer(httpd, nginx)
304
305 return discoverySim{
306 td: disc,
307 wantTargetGroups: []model.TargetGroup{
308 prepareEmptySvcTargetGroup(httpd),
309 prepareEmptySvcTargetGroup(nginx),
310 },
311 }
312 },
313 },
314 "UPDATE: Headless => ClusterIP svc after sync": {
315 createSim: func() discoverySim {
316 httpd, nginx := newHTTPDHeadlessService(), newNGINXHeadlessService()
317 httpdUpd, nginxUpd := *httpd, *nginx
318 httpdUpd.Spec.ClusterIP = "10.100.0.1"
319 nginxUpd.Spec.ClusterIP = "10.100.0.2"
320 disc, client := prepareAllNsSvcDiscoverer(httpd, nginx)
321 svcClient := client.CoreV1().Services("default")
322
323 return discoverySim{
324 td: disc,
325 runAfterSync: func(ctx context.Context) {
326 time.Sleep(time.Millisecond * 50)
327 _, _ = svcClient.Update(ctx, &httpdUpd, metav1.UpdateOptions{})
328 _, _ = svcClient.Update(ctx, &nginxUpd, metav1.UpdateOptions{})
329 },
330 wantTargetGroups: []model.TargetGroup{
331 prepareEmptySvcTargetGroup(httpd),
332 prepareEmptySvcTargetGroup(nginx),
333 prepareSvcTargetGroup(&httpdUpd),
334 prepareSvcTargetGroup(&nginxUpd),
335 },
336 }
337 },
338 },
339 "ADD: ClusterIP svc with zero exposed ports": {
340 createSim: func() discoverySim {
341 httpd, nginx := newHTTPDClusterIPService(), newNGINXClusterIPService()
342 httpd.Spec.Ports = httpd.Spec.Ports[:0]
343 nginx.Spec.Ports = httpd.Spec.Ports[:0]
344 disc, _ := prepareAllNsSvcDiscoverer(httpd, nginx)
345
346 return discoverySim{
347 td: disc,
348 wantTargetGroups: []model.TargetGroup{
349 prepareEmptySvcTargetGroup(httpd),
350 prepareEmptySvcTargetGroup(nginx),
351 },
352 }
353 },
354 },
355 }
356
357 for name, test := range tests {
358 t.Run(name, func(t *testing.T) {
359 sim := test.createSim()
360 sim.run(t)
361 })
362 }
363 }
364
365 func prepareAllNsSvcDiscoverer(objects ...runtime.Object) (*KubeDiscoverer, kubernetes.Interface) {
366 return prepareDiscoverer(roleService, []string{corev1.NamespaceAll}, objects...)
367 }
368
369 func prepareSvcDiscoverer(namespaces []string, objects ...runtime.Object) (*KubeDiscoverer, kubernetes.Interface) {
370 return prepareDiscoverer(roleService, namespaces, objects...)
371 }
372
373 func newHTTPDClusterIPService() *corev1.Service {
374 return &corev1.Service{
375 ObjectMeta: metav1.ObjectMeta{
376 Name: "httpd-cluster-ip-service",
377 Namespace: "default",
378 Annotations: map[string]string{"phase": "prod"},
379 Labels: map[string]string{"app": "httpd", "tier": "frontend"},
380 },
381 Spec: corev1.ServiceSpec{
382 Ports: []corev1.ServicePort{
383 {Name: "http", Protocol: corev1.ProtocolTCP, Port: 80},
384 {Name: "https", Protocol: corev1.ProtocolTCP, Port: 443},
385 },
386 Type: corev1.ServiceTypeClusterIP,
387 ClusterIP: "10.100.0.1",
388 Selector: map[string]string{"app": "httpd", "tier": "frontend"},
389 },
390 }
391 }
392
393 func newNGINXClusterIPService() *corev1.Service {
394 return &corev1.Service{
395 ObjectMeta: metav1.ObjectMeta{
396 Name: "nginx-cluster-ip-service",
397 Namespace: "default",
398 Annotations: map[string]string{"phase": "prod"},
399 Labels: map[string]string{"app": "nginx", "tier": "frontend"},
400 },
401 Spec: corev1.ServiceSpec{
402 Ports: []corev1.ServicePort{
403 {Name: "http", Protocol: corev1.ProtocolTCP, Port: 80},
404 {Name: "https", Protocol: corev1.ProtocolTCP, Port: 443},
405 },
406 Type: corev1.ServiceTypeClusterIP,
407 ClusterIP: "10.100.0.2",
408 Selector: map[string]string{"app": "nginx", "tier": "frontend"},
409 },
410 }
411 }
412
413 func newHTTPDHeadlessService() *corev1.Service {
414 svc := newHTTPDClusterIPService()
415 svc.Name = "httpd-headless-service"
416 svc.Spec.ClusterIP = ""
417 return svc
418 }
419
420 func newNGINXHeadlessService() *corev1.Service {
421 svc := newNGINXClusterIPService()
422 svc.Name = "nginx-headless-service"
423 svc.Spec.ClusterIP = ""
424 return svc
425 }
426
427 func prepareEmptySvcTargetGroup(svc *corev1.Service) *serviceTargetGroup {
428 tgg := &serviceTargetGroup{source: serviceSource(svc)}
429 tgg.source += ",test=test"
430 return tgg
431 }
432
433 func prepareSvcTargetGroup(svc *corev1.Service) *serviceTargetGroup {
434 tgg := prepareEmptySvcTargetGroup(svc)
435
436 for _, port := range svc.Spec.Ports {
437 portNum := strconv.FormatInt(int64(port.Port), 10)
438 tgt := &ServiceTarget{
439 tuid: serviceTUID(svc, port),
440 Address: net.JoinHostPort(svc.Name+"."+svc.Namespace+".svc", portNum),
441 Namespace: svc.Namespace,
442 Name: svc.Name,
443 Annotations: model.MapAny(svc.Annotations),
444 Labels: model.MapAny(svc.Labels),
445 Port: portNum,
446 PortName: port.Name,
447 PortProtocol: string(port.Protocol),
448 ClusterIP: svc.Spec.ClusterIP,
449 ExternalName: svc.Spec.ExternalName,
450 Type: string(svc.Spec.Type),
451 }
452 tgt.hash = mustCalcHash(tgt)
453 tgg.targets = append(tgg.targets, tgt)
454 }
455
456 return tgg
457 }