1package api
2
3import (
4	"fmt"
5	"testing"
6
7	"github.com/hashicorp/consul/sdk/testutil"
8	"github.com/hashicorp/consul/sdk/testutil/retry"
9	"github.com/stretchr/testify/require"
10)
11
12func TestAPI_HealthNode(t *testing.T) {
13	t.Parallel()
14	c, s := makeClient(t)
15	defer s.Stop()
16
17	agent := c.Agent()
18	health := c.Health()
19
20	info, err := agent.Self()
21	if err != nil {
22		t.Fatalf("err: %v", err)
23	}
24	name := info["Config"]["NodeName"].(string)
25	retry.Run(t, func(r *retry.R) {
26		checks, meta, err := health.Node(name, nil)
27		if err != nil {
28			r.Fatal(err)
29		}
30		if meta.LastIndex == 0 {
31			r.Fatalf("bad: %v", meta)
32		}
33		if len(checks) == 0 {
34			r.Fatalf("bad: %v", checks)
35		}
36	})
37}
38
39func TestAPI_HealthNode_Filter(t *testing.T) {
40	t.Parallel()
41	c, s := makeClient(t)
42	defer s.Stop()
43
44	// this sets up the catalog entries with things we can filter on
45	testNodeServiceCheckRegistrations(t, c, "dc1")
46
47	health := c.Health()
48
49	// filter for just the redis service checks
50	checks, _, err := health.Node("foo", &QueryOptions{Filter: "ServiceName == redis"})
51	require.NoError(t, err)
52	require.Len(t, checks, 2)
53
54	// filter out service checks
55	checks, _, err = health.Node("foo", &QueryOptions{Filter: "ServiceID == ``"})
56	require.NoError(t, err)
57	require.Len(t, checks, 2)
58}
59
60func TestAPI_HealthChecks_AggregatedStatus(t *testing.T) {
61	t.Parallel()
62
63	cases := []struct {
64		name   string
65		checks HealthChecks
66		exp    string
67	}{
68		{
69			"empty",
70			nil,
71			HealthPassing,
72		},
73		{
74			"passing",
75			HealthChecks{
76				&HealthCheck{
77					Status: HealthPassing,
78				},
79			},
80			HealthPassing,
81		},
82		{
83			"warning",
84			HealthChecks{
85				&HealthCheck{
86					Status: HealthWarning,
87				},
88			},
89			HealthWarning,
90		},
91		{
92			"critical",
93			HealthChecks{
94				&HealthCheck{
95					Status: HealthCritical,
96				},
97			},
98			HealthCritical,
99		},
100		{
101			"node_maintenance",
102			HealthChecks{
103				&HealthCheck{
104					CheckID: NodeMaint,
105				},
106			},
107			HealthMaint,
108		},
109		{
110			"service_maintenance",
111			HealthChecks{
112				&HealthCheck{
113					CheckID: ServiceMaintPrefix + "service",
114				},
115			},
116			HealthMaint,
117		},
118		{
119			"unknown",
120			HealthChecks{
121				&HealthCheck{
122					Status: "nope-nope-noper",
123				},
124			},
125			"",
126		},
127		{
128			"maintenance_over_critical",
129			HealthChecks{
130				&HealthCheck{
131					CheckID: NodeMaint,
132				},
133				&HealthCheck{
134					Status: HealthCritical,
135				},
136			},
137			HealthMaint,
138		},
139		{
140			"critical_over_warning",
141			HealthChecks{
142				&HealthCheck{
143					Status: HealthCritical,
144				},
145				&HealthCheck{
146					Status: HealthWarning,
147				},
148			},
149			HealthCritical,
150		},
151		{
152			"warning_over_passing",
153			HealthChecks{
154				&HealthCheck{
155					Status: HealthWarning,
156				},
157				&HealthCheck{
158					Status: HealthPassing,
159				},
160			},
161			HealthWarning,
162		},
163		{
164			"lots",
165			HealthChecks{
166				&HealthCheck{
167					Status: HealthPassing,
168				},
169				&HealthCheck{
170					Status: HealthPassing,
171				},
172				&HealthCheck{
173					Status: HealthPassing,
174				},
175				&HealthCheck{
176					Status: HealthWarning,
177				},
178			},
179			HealthWarning,
180		},
181	}
182
183	for i, tc := range cases {
184		t.Run(fmt.Sprintf("%d_%s", i, tc.name), func(t *testing.T) {
185			act := tc.checks.AggregatedStatus()
186			if tc.exp != act {
187				t.Errorf("\nexp: %#v\nact: %#v", tc.exp, act)
188			}
189		})
190	}
191}
192
193func TestAPI_HealthChecks(t *testing.T) {
194	t.Parallel()
195	c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
196		conf.NodeName = "node123"
197	})
198	defer s.Stop()
199
200	agent := c.Agent()
201	health := c.Health()
202
203	// Make a service with a check
204	reg := &AgentServiceRegistration{
205		Name: "foo",
206		Tags: []string{"bar"},
207		Check: &AgentServiceCheck{
208			TTL: "15s",
209		},
210	}
211	if err := agent.ServiceRegister(reg); err != nil {
212		t.Fatalf("err: %v", err)
213	}
214
215	retry.Run(t, func(r *retry.R) {
216		checks := HealthChecks{
217			&HealthCheck{
218				Node:        "node123",
219				CheckID:     "service:foo",
220				Name:        "Service 'foo' check",
221				Status:      "critical",
222				ServiceID:   "foo",
223				ServiceName: "foo",
224				ServiceTags: []string{"bar"},
225				Type:        "ttl",
226				Namespace:   defaultNamespace,
227			},
228		}
229
230		out, meta, err := health.Checks("foo", nil)
231		if err != nil {
232			r.Fatal(err)
233		}
234		if meta.LastIndex == 0 {
235			r.Fatalf("bad: %v", meta)
236		}
237		checks[0].CreateIndex = out[0].CreateIndex
238		checks[0].ModifyIndex = out[0].ModifyIndex
239		require.Equal(r, checks, out)
240	})
241}
242
243func TestAPI_HealthChecks_NodeMetaFilter(t *testing.T) {
244	t.Parallel()
245	meta := map[string]string{"somekey": "somevalue"}
246	c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
247		conf.NodeMeta = meta
248	})
249	defer s.Stop()
250
251	agent := c.Agent()
252	health := c.Health()
253
254	s.WaitForSerfCheck(t)
255
256	// Make a service with a check
257	reg := &AgentServiceRegistration{
258		Name: "foo",
259		Check: &AgentServiceCheck{
260			TTL: "15s",
261		},
262	}
263	if err := agent.ServiceRegister(reg); err != nil {
264		t.Fatalf("err: %v", err)
265	}
266
267	retry.Run(t, func(r *retry.R) {
268		checks, meta, err := health.Checks("foo", &QueryOptions{NodeMeta: meta})
269		if err != nil {
270			r.Fatal(err)
271		}
272		if meta.LastIndex == 0 {
273			r.Fatalf("bad: %v", meta)
274		}
275		if len(checks) != 1 {
276			r.Fatalf("expected 1 check, got %d", len(checks))
277		}
278		if checks[0].Type != "ttl" {
279			r.Fatalf("expected type ttl, got %s", checks[0].Type)
280		}
281	})
282}
283
284func TestAPI_HealthChecks_Filter(t *testing.T) {
285	t.Parallel()
286	c, s := makeClient(t)
287	defer s.Stop()
288
289	// this sets up the catalog entries with things we can filter on
290	testNodeServiceCheckRegistrations(t, c, "dc1")
291
292	health := c.Health()
293
294	checks, _, err := health.Checks("redis", &QueryOptions{Filter: "Node == foo"})
295	require.NoError(t, err)
296	// 1 service check for each instance
297	require.Len(t, checks, 2)
298
299	checks, _, err = health.Checks("redis", &QueryOptions{Filter: "Node == bar"})
300	require.NoError(t, err)
301	// 1 service check for each instance
302	require.Len(t, checks, 1)
303
304	checks, _, err = health.Checks("redis", &QueryOptions{Filter: "Node == foo and v1 in ServiceTags"})
305	require.NoError(t, err)
306	// 1 service check for the matching instance
307	require.Len(t, checks, 1)
308}
309
310func TestAPI_HealthService(t *testing.T) {
311	t.Parallel()
312	c, s := makeClient(t)
313	defer s.Stop()
314
315	health := c.Health()
316	retry.Run(t, func(r *retry.R) {
317		// consul service should always exist...
318		checks, meta, err := health.Service("consul", "", true, nil)
319		if err != nil {
320			r.Fatal(err)
321		}
322		if meta.LastIndex == 0 {
323			r.Fatalf("bad: %v", meta)
324		}
325		if len(checks) == 0 {
326			r.Fatalf("Bad: %v", checks)
327		}
328		if _, ok := checks[0].Node.TaggedAddresses["wan"]; !ok {
329			r.Fatalf("Bad: %v", checks[0].Node)
330		}
331		if checks[0].Node.Datacenter != "dc1" {
332			r.Fatalf("Bad datacenter: %v", checks[0].Node)
333		}
334	})
335}
336
337func TestAPI_HealthService_SingleTag(t *testing.T) {
338	t.Parallel()
339	c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
340		conf.NodeName = "node123"
341	})
342	defer s.Stop()
343	agent := c.Agent()
344	health := c.Health()
345	reg := &AgentServiceRegistration{
346		Name: "foo",
347		ID:   "foo1",
348		Tags: []string{"bar"},
349		Check: &AgentServiceCheck{
350			Status: HealthPassing,
351			TTL:    "15s",
352		},
353	}
354	require.NoError(t, agent.ServiceRegister(reg))
355	retry.Run(t, func(r *retry.R) {
356		services, meta, err := health.Service("foo", "bar", true, nil)
357		require.NoError(r, err)
358		require.NotEqual(r, meta.LastIndex, 0)
359		require.Len(r, services, 1)
360		require.Equal(r, services[0].Service.ID, "foo1")
361
362		for _, check := range services[0].Checks {
363			if check.CheckID == "service:foo1" && check.Type != "ttl" {
364				r.Fatalf("expected type ttl, got %s", check.Type)
365			}
366		}
367	})
368}
369func TestAPI_HealthService_MultipleTags(t *testing.T) {
370	t.Parallel()
371	c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
372		conf.NodeName = "node123"
373	})
374	defer s.Stop()
375
376	agent := c.Agent()
377	health := c.Health()
378
379	// Make two services with a check
380	reg := &AgentServiceRegistration{
381		Name: "foo",
382		ID:   "foo1",
383		Tags: []string{"bar"},
384		Check: &AgentServiceCheck{
385			Status: HealthPassing,
386			TTL:    "15s",
387		},
388	}
389	require.NoError(t, agent.ServiceRegister(reg))
390
391	reg2 := &AgentServiceRegistration{
392		Name: "foo",
393		ID:   "foo2",
394		Tags: []string{"bar", "v2"},
395		Check: &AgentServiceCheck{
396			Status: HealthPassing,
397			TTL:    "15s",
398		},
399	}
400	require.NoError(t, agent.ServiceRegister(reg2))
401
402	// Test searching with one tag (two results)
403	retry.Run(t, func(r *retry.R) {
404		services, meta, err := health.ServiceMultipleTags("foo", []string{"bar"}, true, nil)
405
406		require.NoError(r, err)
407		require.NotEqual(r, meta.LastIndex, 0)
408		require.Len(r, services, 2)
409	})
410
411	// Test searching with two tags (one result)
412	retry.Run(t, func(r *retry.R) {
413		services, meta, err := health.ServiceMultipleTags("foo", []string{"bar", "v2"}, true, nil)
414
415		require.NoError(r, err)
416		require.NotEqual(r, meta.LastIndex, 0)
417		require.Len(r, services, 1)
418		require.Equal(r, services[0].Service.ID, "foo2")
419	})
420}
421
422func TestAPI_HealthService_NodeMetaFilter(t *testing.T) {
423	t.Parallel()
424	meta := map[string]string{"somekey": "somevalue"}
425	c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
426		conf.NodeMeta = meta
427	})
428	defer s.Stop()
429
430	s.WaitForSerfCheck(t)
431
432	health := c.Health()
433	retry.Run(t, func(r *retry.R) {
434		// consul service should always exist...
435		checks, meta, err := health.Service("consul", "", true, &QueryOptions{NodeMeta: meta})
436		require.NoError(r, err)
437		require.NotEqual(r, meta.LastIndex, 0)
438		require.NotEqual(r, len(checks), 0)
439		require.Equal(r, checks[0].Node.Datacenter, "dc1")
440		require.Contains(r, checks[0].Node.TaggedAddresses, "wan")
441	})
442}
443
444func TestAPI_HealthService_Filter(t *testing.T) {
445	t.Parallel()
446	c, s := makeClient(t)
447	defer s.Stop()
448
449	// this sets up the catalog entries with things we can filter on
450	testNodeServiceCheckRegistrations(t, c, "dc1")
451
452	health := c.Health()
453
454	services, _, err := health.Service("redis", "", false, &QueryOptions{Filter: "Service.Meta.version == 2"})
455	require.NoError(t, err)
456	require.Len(t, services, 1)
457
458	services, _, err = health.Service("web", "", false, &QueryOptions{Filter: "Node.Meta.os == linux"})
459	require.NoError(t, err)
460	require.Len(t, services, 2)
461	require.Equal(t, "baz", services[0].Node.Node)
462	require.Equal(t, "baz", services[1].Node.Node)
463
464	services, _, err = health.Service("web", "", false, &QueryOptions{Filter: "Node.Meta.os == linux and Service.Meta.version == 1"})
465	require.NoError(t, err)
466	require.Len(t, services, 1)
467}
468
469func TestAPI_HealthConnect(t *testing.T) {
470	t.Parallel()
471	c, s := makeClient(t)
472	defer s.Stop()
473
474	agent := c.Agent()
475	health := c.Health()
476
477	s.WaitForSerfCheck(t)
478
479	// Make a service with a proxy
480	reg := &AgentServiceRegistration{
481		Name: "foo",
482		Port: 8000,
483	}
484	err := agent.ServiceRegister(reg)
485	require.NoError(t, err)
486
487	// Register the proxy
488	proxyReg := &AgentServiceRegistration{
489		Name: "foo-proxy",
490		Port: 8001,
491		Kind: ServiceKindConnectProxy,
492		Proxy: &AgentServiceConnectProxyConfig{
493			DestinationServiceName: "foo",
494		},
495	}
496	err = agent.ServiceRegister(proxyReg)
497	require.NoError(t, err)
498
499	retry.Run(t, func(r *retry.R) {
500		services, meta, err := health.Connect("foo", "", true, nil)
501		if err != nil {
502			r.Fatal(err)
503		}
504		if meta.LastIndex == 0 {
505			r.Fatalf("bad: %v", meta)
506		}
507		// Should be exactly 1 service - the original shouldn't show up as a connect
508		// endpoint, only it's proxy.
509		if len(services) != 1 {
510			r.Fatalf("Bad: %v", services)
511		}
512		if services[0].Node.Datacenter != "dc1" {
513			r.Fatalf("Bad datacenter: %v", services[0].Node)
514		}
515		if services[0].Service.Port != proxyReg.Port {
516			r.Fatalf("Bad port: %v", services[0])
517		}
518	})
519}
520
521func TestAPI_HealthConnect_Filter(t *testing.T) {
522	t.Parallel()
523	c, s := makeClient(t)
524	defer s.Stop()
525
526	// this sets up the catalog entries with things we can filter on
527	testNodeServiceCheckRegistrations(t, c, "dc1")
528
529	health := c.Health()
530
531	services, _, err := health.Connect("web", "", false, &QueryOptions{Filter: "Node.Meta.os == linux"})
532	require.NoError(t, err)
533	require.Len(t, services, 2)
534	require.Equal(t, "baz", services[0].Node.Node)
535	require.Equal(t, "baz", services[1].Node.Node)
536
537	services, _, err = health.Service("web", "", false, &QueryOptions{Filter: "Node.Meta.os == linux and Service.Meta.version == 1"})
538	require.NoError(t, err)
539	require.Len(t, services, 1)
540}
541
542func TestAPI_HealthIngress(t *testing.T) {
543	t.Parallel()
544	c, s := makeClient(t)
545	defer s.Stop()
546
547	agent := c.Agent()
548	health := c.Health()
549
550	s.WaitForSerfCheck(t)
551
552	// Make a service with a proxy
553	reg := &AgentServiceRegistration{
554		Name: "foo",
555		Port: 8000,
556	}
557	err := agent.ServiceRegister(reg)
558	require.NoError(t, err)
559
560	// Register the gateway
561	gatewayReg := &AgentServiceRegistration{
562		Name: "foo-gateway",
563		Port: 8001,
564		Kind: ServiceKindIngressGateway,
565	}
566	err = agent.ServiceRegister(gatewayReg)
567	require.NoError(t, err)
568
569	// Associate service and gateway
570	gatewayConfig := &IngressGatewayConfigEntry{
571		Kind: IngressGateway,
572		Name: "foo-gateway",
573		Listeners: []IngressListener{
574			{
575				Port:     2222,
576				Protocol: "tcp",
577				Services: []IngressService{
578					{
579						Name: "foo",
580					},
581				},
582			},
583		},
584	}
585	_, wm, err := c.ConfigEntries().Set(gatewayConfig, nil)
586	require.NoError(t, err)
587	require.NotNil(t, wm)
588
589	retry.Run(t, func(r *retry.R) {
590		services, meta, err := health.Ingress("foo", true, nil)
591		require.NoError(r, err)
592
593		require.NotZero(r, meta.LastIndex)
594
595		// Should be exactly 1 service - the original shouldn't show up as a connect
596		// endpoint, only it's proxy.
597		require.Len(r, services, 1)
598		require.Equal(r, services[0].Node.Datacenter, "dc1")
599		require.Equal(r, services[0].Service.Service, gatewayReg.Name)
600	})
601}
602
603func TestAPI_HealthState(t *testing.T) {
604	t.Parallel()
605	c, s := makeClient(t)
606	defer s.Stop()
607
608	health := c.Health()
609	retry.Run(t, func(r *retry.R) {
610		checks, meta, err := health.State("any", nil)
611		if err != nil {
612			r.Fatal(err)
613		}
614		if meta.LastIndex == 0 {
615			r.Fatalf("bad: %v", meta)
616		}
617		if len(checks) == 0 {
618			r.Fatalf("Bad: %v", checks)
619		}
620	})
621}
622
623func TestAPI_HealthState_NodeMetaFilter(t *testing.T) {
624	t.Parallel()
625	meta := map[string]string{"somekey": "somevalue"}
626	c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
627		conf.NodeMeta = meta
628	})
629	defer s.Stop()
630
631	health := c.Health()
632	retry.Run(t, func(r *retry.R) {
633		checks, meta, err := health.State("any", &QueryOptions{NodeMeta: meta})
634		if err != nil {
635			r.Fatal(err)
636		}
637		if meta.LastIndex == 0 {
638			r.Fatalf("bad: %v", meta)
639		}
640		if len(checks) == 0 {
641			r.Fatalf("Bad: %v", checks)
642		}
643	})
644}
645
646func TestAPI_HealthState_Filter(t *testing.T) {
647	t.Parallel()
648	c, s := makeClient(t)
649	defer s.Stop()
650
651	// this sets up the catalog entries with things we can filter on
652	testNodeServiceCheckRegistrations(t, c, "dc1")
653
654	health := c.Health()
655
656	checks, _, err := health.State(HealthAny, &QueryOptions{Filter: "Node == baz"})
657	require.NoError(t, err)
658	require.Len(t, checks, 6)
659
660	checks, _, err = health.State(HealthAny, &QueryOptions{Filter: "Status == warning or Status == critical"})
661	require.NoError(t, err)
662	require.Len(t, checks, 2)
663
664	checks, _, err = health.State(HealthCritical, &QueryOptions{Filter: "Node == baz"})
665	require.NoError(t, err)
666	require.Len(t, checks, 1)
667
668	checks, _, err = health.State(HealthWarning, &QueryOptions{Filter: "Node == baz"})
669	require.NoError(t, err)
670	require.Len(t, checks, 1)
671}
672