1package api
2
3import (
4	"reflect"
5	"testing"
6	"time"
7
8	"github.com/hashicorp/consul/sdk/testutil"
9	"github.com/hashicorp/consul/sdk/testutil/retry"
10	"github.com/stretchr/testify/assert"
11	"github.com/stretchr/testify/require"
12)
13
14func TestAPI_CatalogDatacenters(t *testing.T) {
15	t.Parallel()
16	c, s := makeClient(t)
17	defer s.Stop()
18
19	catalog := c.Catalog()
20	retry.Run(t, func(r *retry.R) {
21		datacenters, err := catalog.Datacenters()
22		if err != nil {
23			r.Fatal(err)
24		}
25		if len(datacenters) < 1 {
26			r.Fatal("got 0 datacenters want at least one")
27		}
28	})
29}
30
31func TestAPI_CatalogNodes(t *testing.T) {
32	t.Parallel()
33	c, s := makeClient(t)
34	defer s.Stop()
35
36	s.WaitForSerfCheck(t)
37	catalog := c.Catalog()
38	retry.Run(t, func(r *retry.R) {
39		nodes, meta, err := catalog.Nodes(nil)
40		// We're not concerned about the createIndex of an agent
41		// Hence we're setting it to the default value
42		nodes[0].CreateIndex = 0
43		if err != nil {
44			r.Fatal(err)
45		}
46		if meta.LastIndex < 2 {
47			r.Fatal("Last index must be greater than 1")
48		}
49		want := []*Node{
50			{
51				ID:         s.Config.NodeID,
52				Node:       s.Config.NodeName,
53				Address:    "127.0.0.1",
54				Datacenter: "dc1",
55				TaggedAddresses: map[string]string{
56					"lan":      "127.0.0.1",
57					"lan_ipv4": "127.0.0.1",
58					"wan":      "127.0.0.1",
59					"wan_ipv4": "127.0.0.1",
60				},
61				Meta: map[string]string{
62					"consul-network-segment": "",
63				},
64				// CreateIndex will never always be meta.LastIndex - 1
65				// The purpose of this test is not to test CreateIndex value of an agent
66				// rather to check if the client agent can get the correct number
67				// of agents with a particular service, KV pair, etc...
68				// Hence reverting this to the default value here.
69				CreateIndex: 0,
70				ModifyIndex: meta.LastIndex,
71			},
72		}
73		require.Equal(r, want, nodes)
74	})
75}
76
77func TestAPI_CatalogNodes_MetaFilter(t *testing.T) {
78	t.Parallel()
79	meta := map[string]string{"somekey": "somevalue"}
80	c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
81		conf.NodeMeta = meta
82	})
83	defer s.Stop()
84
85	catalog := c.Catalog()
86	// Make sure we get the node back when filtering by its metadata
87	retry.Run(t, func(r *retry.R) {
88		nodes, meta, err := catalog.Nodes(&QueryOptions{NodeMeta: meta})
89		if err != nil {
90			r.Fatal(err)
91		}
92
93		if meta.LastIndex == 0 {
94			r.Fatalf("Bad: %v", meta)
95		}
96
97		if len(nodes) == 0 {
98			r.Fatalf("Bad: %v", nodes)
99		}
100
101		if _, ok := nodes[0].TaggedAddresses["wan"]; !ok {
102			r.Fatalf("Bad: %v", nodes[0])
103		}
104
105		if v, ok := nodes[0].Meta["somekey"]; !ok || v != "somevalue" {
106			r.Fatalf("Bad: %v", nodes[0].Meta)
107		}
108
109		if nodes[0].Datacenter != "dc1" {
110			r.Fatalf("Bad datacenter: %v", nodes[0])
111		}
112	})
113
114	retry.Run(t, func(r *retry.R) {
115		// Get nothing back when we use an invalid filter
116		nodes, meta, err := catalog.Nodes(&QueryOptions{NodeMeta: map[string]string{"nope": "nope"}})
117		if err != nil {
118			r.Fatal(err)
119		}
120
121		if meta.LastIndex == 0 {
122			r.Fatalf("Bad: %v", meta)
123		}
124
125		if len(nodes) != 0 {
126			r.Fatalf("Bad: %v", nodes)
127		}
128	})
129}
130
131func TestAPI_CatalogNodes_Filter(t *testing.T) {
132	t.Parallel()
133	c, s := makeClient(t)
134	defer s.Stop()
135
136	// this sets up the catalog entries with things we can filter on
137	testNodeServiceCheckRegistrations(t, c, "dc1")
138
139	catalog := c.Catalog()
140	nodes, _, err := catalog.Nodes(nil)
141	require.NoError(t, err)
142	// 3 nodes inserted by the setup func above plus the agent itself
143	require.Len(t, nodes, 4)
144
145	// now filter down to just a couple nodes with a specific meta entry
146	nodes, _, err = catalog.Nodes(&QueryOptions{Filter: "Meta.env == production"})
147	require.NoError(t, err)
148	require.Len(t, nodes, 2)
149
150	// filter out everything that isn't bar or baz
151	nodes, _, err = catalog.Nodes(&QueryOptions{Filter: "Node == bar or Node == baz"})
152	require.NoError(t, err)
153	require.Len(t, nodes, 2)
154
155	// check for non-existent ip for the node addr
156	nodes, _, err = catalog.Nodes(&QueryOptions{Filter: "Address == `10.0.0.1`"})
157	require.NoError(t, err)
158	require.Empty(t, nodes)
159}
160
161func TestAPI_CatalogServices(t *testing.T) {
162	t.Parallel()
163	c, s := makeClient(t)
164	defer s.Stop()
165
166	catalog := c.Catalog()
167	retry.Run(t, func(r *retry.R) {
168		services, meta, err := catalog.Services(nil)
169		if err != nil {
170			r.Fatal(err)
171		}
172
173		if meta.LastIndex == 0 {
174			r.Fatalf("Bad: %v", meta)
175		}
176
177		if len(services) == 0 {
178			r.Fatalf("Bad: %v", services)
179		}
180	})
181}
182
183func TestAPI_CatalogServices_NodeMetaFilter(t *testing.T) {
184	t.Parallel()
185	meta := map[string]string{"somekey": "somevalue"}
186	c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
187		conf.NodeMeta = meta
188	})
189	defer s.Stop()
190
191	catalog := c.Catalog()
192	// Make sure we get the service back when filtering by the node's metadata
193	retry.Run(t, func(r *retry.R) {
194		services, meta, err := catalog.Services(&QueryOptions{NodeMeta: meta})
195		if err != nil {
196			r.Fatal(err)
197		}
198
199		if meta.LastIndex == 0 {
200			r.Fatalf("Bad: %v", meta)
201		}
202
203		if len(services) == 0 {
204			r.Fatalf("Bad: %v", services)
205		}
206	})
207
208	retry.Run(t, func(r *retry.R) {
209		// Get nothing back when using an invalid filter
210		services, meta, err := catalog.Services(&QueryOptions{NodeMeta: map[string]string{"nope": "nope"}})
211		if err != nil {
212			r.Fatal(err)
213		}
214
215		if meta.LastIndex == 0 {
216			r.Fatalf("Bad: %v", meta)
217		}
218
219		if len(services) != 0 {
220			r.Fatalf("Bad: %v", services)
221		}
222	})
223}
224
225func TestAPI_CatalogService(t *testing.T) {
226	t.Parallel()
227	c, s := makeClient(t)
228	defer s.Stop()
229
230	catalog := c.Catalog()
231
232	retry.Run(t, func(r *retry.R) {
233		services, meta, err := catalog.Service("consul", "", nil)
234		if err != nil {
235			r.Fatal(err)
236		}
237
238		if meta.LastIndex == 0 {
239			r.Fatalf("Bad: %v", meta)
240		}
241
242		if len(services) == 0 {
243			r.Fatalf("Bad: %v", services)
244		}
245
246		if services[0].Datacenter != "dc1" {
247			r.Fatalf("Bad datacenter: %v", services[0])
248		}
249	})
250}
251
252func TestAPI_CatalogServiceUnmanagedProxy(t *testing.T) {
253	t.Parallel()
254	c, s := makeClient(t)
255	defer s.Stop()
256
257	catalog := c.Catalog()
258
259	proxyReg := testUnmanagedProxyRegistration(t)
260
261	retry.Run(t, func(r *retry.R) {
262		_, err := catalog.Register(proxyReg, nil)
263		r.Check(err)
264
265		services, meta, err := catalog.Service("web-proxy", "", nil)
266		if err != nil {
267			r.Fatal(err)
268		}
269
270		if meta.LastIndex == 0 {
271			r.Fatalf("Bad: %v", meta)
272		}
273
274		if len(services) == 0 {
275			r.Fatalf("Bad: %v", services)
276		}
277
278		if services[0].Datacenter != "dc1" {
279			r.Fatalf("Bad datacenter: %v", services[0])
280		}
281
282		if !reflect.DeepEqual(services[0].ServiceProxy, proxyReg.Service.Proxy) {
283			r.Fatalf("bad proxy.\nwant: %v\n got: %v", proxyReg.Service.Proxy,
284				services[0].ServiceProxy)
285		}
286	})
287}
288
289func TestAPI_CatalogServiceCached(t *testing.T) {
290	t.Parallel()
291	c, s := makeClient(t)
292	defer s.Stop()
293
294	catalog := c.Catalog()
295
296	q := &QueryOptions{
297		UseCache: true,
298	}
299
300	retry.Run(t, func(r *retry.R) {
301		services, meta, err := catalog.Service("consul", "", q)
302		if err != nil {
303			r.Fatal(err)
304		}
305
306		if meta.LastIndex == 0 {
307			r.Fatalf("Bad: %v", meta)
308		}
309
310		if len(services) == 0 {
311			r.Fatalf("Bad: %v", services)
312		}
313
314		if services[0].Datacenter != "dc1" {
315			r.Fatalf("Bad datacenter: %v", services[0])
316		}
317	})
318
319	require := require.New(t)
320
321	// Got success, next hit must be cache hit
322	_, meta, err := catalog.Service("consul", "", q)
323	require.NoError(err)
324	require.True(meta.CacheHit)
325	require.Equal(time.Duration(0), meta.CacheAge)
326}
327
328func TestAPI_CatalogService_SingleTag(t *testing.T) {
329	t.Parallel()
330	c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
331		conf.NodeName = "node123"
332	})
333	defer s.Stop()
334
335	agent := c.Agent()
336	catalog := c.Catalog()
337
338	reg := &AgentServiceRegistration{
339		Name: "foo",
340		ID:   "foo1",
341		Tags: []string{"bar"},
342	}
343	require.NoError(t, agent.ServiceRegister(reg))
344	defer agent.ServiceDeregister("foo1")
345
346	retry.Run(t, func(r *retry.R) {
347		services, meta, err := catalog.Service("foo", "bar", nil)
348		require.NoError(r, err)
349		require.NotEqual(r, meta.LastIndex, 0)
350		require.Len(r, services, 1)
351		require.Equal(r, services[0].ServiceID, "foo1")
352	})
353}
354
355func TestAPI_CatalogService_MultipleTags(t *testing.T) {
356	t.Parallel()
357	c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
358		conf.NodeName = "node123"
359	})
360	defer s.Stop()
361
362	agent := c.Agent()
363	catalog := c.Catalog()
364
365	// Make two services with a check
366	reg := &AgentServiceRegistration{
367		Name: "foo",
368		ID:   "foo1",
369		Tags: []string{"bar"},
370	}
371	require.NoError(t, agent.ServiceRegister(reg))
372	defer agent.ServiceDeregister("foo1")
373
374	reg2 := &AgentServiceRegistration{
375		Name: "foo",
376		ID:   "foo2",
377		Tags: []string{"bar", "v2"},
378	}
379	require.NoError(t, agent.ServiceRegister(reg2))
380	defer agent.ServiceDeregister("foo2")
381
382	// Test searching with one tag (two results)
383	retry.Run(t, func(r *retry.R) {
384		services, meta, err := catalog.ServiceMultipleTags("foo", []string{"bar"}, nil)
385
386		require.NoError(r, err)
387		require.NotEqual(r, meta.LastIndex, 0)
388
389		// Should be 2 services with the `bar` tag
390		require.Len(r, services, 2)
391	})
392
393	// Test searching with two tags (one result)
394	retry.Run(t, func(r *retry.R) {
395		services, meta, err := catalog.ServiceMultipleTags("foo", []string{"bar", "v2"}, nil)
396
397		require.NoError(r, err)
398		require.NotEqual(r, meta.LastIndex, 0)
399
400		// Should be exactly 1 service, named "foo2"
401		require.Len(r, services, 1)
402		require.Equal(r, services[0].ServiceID, "foo2")
403	})
404}
405
406func TestAPI_CatalogService_NodeMetaFilter(t *testing.T) {
407	t.Parallel()
408	meta := map[string]string{"somekey": "somevalue"}
409	c, s := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
410		conf.NodeMeta = meta
411	})
412	defer s.Stop()
413
414	catalog := c.Catalog()
415	retry.Run(t, func(r *retry.R) {
416		services, meta, err := catalog.Service("consul", "", &QueryOptions{NodeMeta: meta})
417		if err != nil {
418			r.Fatal(err)
419		}
420
421		if meta.LastIndex == 0 {
422			r.Fatalf("Bad: %v", meta)
423		}
424
425		if len(services) == 0 {
426			r.Fatalf("Bad: %v", services)
427		}
428
429		if services[0].Datacenter != "dc1" {
430			r.Fatalf("Bad datacenter: %v", services[0])
431		}
432	})
433}
434
435func TestAPI_CatalogService_Filter(t *testing.T) {
436	t.Parallel()
437	c, s := makeClient(t)
438	defer s.Stop()
439
440	// this sets up the catalog entries with things we can filter on
441	testNodeServiceCheckRegistrations(t, c, "dc1")
442
443	catalog := c.Catalog()
444
445	services, _, err := catalog.Service("redis", "", &QueryOptions{Filter: "ServiceMeta.version == 1"})
446	require.NoError(t, err)
447	// finds it on both foo and bar nodes
448	require.Len(t, services, 2)
449
450	require.Condition(t, func() bool {
451		return (services[0].Node == "foo" && services[1].Node == "bar") ||
452			(services[0].Node == "bar" && services[1].Node == "foo")
453	})
454
455	services, _, err = catalog.Service("redis", "", &QueryOptions{Filter: "NodeMeta.os != windows"})
456	require.NoError(t, err)
457	// finds both service instances on foo
458	require.Len(t, services, 2)
459	require.Equal(t, "foo", services[0].Node)
460	require.Equal(t, "foo", services[1].Node)
461
462	services, _, err = catalog.Service("redis", "", &QueryOptions{Filter: "Address == `10.0.0.1`"})
463	require.NoError(t, err)
464	require.Empty(t, services)
465
466}
467
468func testUpstreams(t *testing.T) []Upstream {
469	return []Upstream{
470		{
471			DestinationName: "db",
472			LocalBindPort:   9191,
473			Config: map[string]interface{}{
474				"connect_timeout_ms": float64(1000),
475			},
476		},
477		{
478			DestinationType: UpstreamDestTypePreparedQuery,
479			DestinationName: "geo-cache",
480			LocalBindPort:   8181,
481		},
482	}
483}
484
485func testExpectUpstreamsWithDefaults(t *testing.T, upstreams []Upstream) []Upstream {
486	ups := make([]Upstream, len(upstreams))
487	for i := range upstreams {
488		ups[i] = upstreams[i]
489		// Fill in default fields we expect to have back explicitly in a response
490		if ups[i].DestinationType == "" {
491			ups[i].DestinationType = UpstreamDestTypeService
492		}
493	}
494	return ups
495}
496
497// testUnmanagedProxy returns a fully configured external proxy service suitable
498// for checking that all the config fields make it back in a response intact.
499func testUnmanagedProxy(t *testing.T) *AgentService {
500	return &AgentService{
501		Kind: ServiceKindConnectProxy,
502		Proxy: &AgentServiceConnectProxyConfig{
503			DestinationServiceName: "web",
504			DestinationServiceID:   "web1",
505			LocalServiceAddress:    "127.0.0.2",
506			LocalServicePort:       8080,
507			Upstreams:              testUpstreams(t),
508			Mode:                   ProxyModeTransparent,
509			TransparentProxy: &TransparentProxyConfig{
510				OutboundListenerPort: 808,
511			},
512		},
513		ID:      "web-proxy1",
514		Service: "web-proxy",
515		Port:    8001,
516	}
517}
518
519// testUnmanagedProxyRegistration returns a *CatalogRegistration for a fully
520// configured external proxy.
521func testUnmanagedProxyRegistration(t *testing.T) *CatalogRegistration {
522	return &CatalogRegistration{
523		Datacenter: "dc1",
524		Node:       "foobar",
525		Address:    "192.168.10.10",
526		Service:    testUnmanagedProxy(t),
527	}
528}
529
530func TestAPI_CatalogConnect(t *testing.T) {
531	t.Parallel()
532	c, s := makeClient(t)
533	defer s.Stop()
534
535	catalog := c.Catalog()
536
537	// Register service and proxy instances to test against.
538	proxyReg := testUnmanagedProxyRegistration(t)
539
540	proxy := proxyReg.Service
541
542	service := &AgentService{
543		ID:      proxyReg.Service.Proxy.DestinationServiceID,
544		Service: proxyReg.Service.Proxy.DestinationServiceName,
545		Port:    8000,
546	}
547	check := &AgentCheck{
548		Node:      "foobar",
549		CheckID:   "service:" + service.ID,
550		Name:      "Redis health check",
551		Notes:     "Script based health check",
552		Status:    HealthPassing,
553		ServiceID: service.ID,
554	}
555
556	reg := &CatalogRegistration{
557		Datacenter: "dc1",
558		Node:       "foobar",
559		Address:    "192.168.10.10",
560		Service:    service,
561		Check:      check,
562	}
563
564	retry.Run(t, func(r *retry.R) {
565		if _, err := catalog.Register(reg, nil); err != nil {
566			r.Fatal(err)
567		}
568		if _, err := catalog.Register(proxyReg, nil); err != nil {
569			r.Fatal(err)
570		}
571
572		services, meta, err := catalog.Connect(proxyReg.Service.Proxy.DestinationServiceName, "", nil)
573		if err != nil {
574			r.Fatal(err)
575		}
576
577		if meta.LastIndex == 0 {
578			r.Fatalf("Bad: %v", meta)
579		}
580
581		if len(services) == 0 {
582			r.Fatalf("Bad: %v", services)
583		}
584
585		if services[0].Datacenter != "dc1" {
586			r.Fatalf("Bad datacenter: %v", services[0])
587		}
588
589		if services[0].ServicePort != proxy.Port {
590			r.Fatalf("Returned port should be for proxy: %v", services[0])
591		}
592
593		if !reflect.DeepEqual(services[0].ServiceProxy, proxy.Proxy) {
594			r.Fatalf("Returned proxy config should match:\nWant: %v\n Got: %v",
595				proxy.Proxy, services[0].ServiceProxy)
596		}
597	})
598}
599
600func TestAPI_CatalogConnectNative(t *testing.T) {
601	t.Parallel()
602	c, s := makeClient(t)
603	defer s.Stop()
604
605	catalog := c.Catalog()
606
607	// Register service and proxy instances to test against.
608	service := &AgentService{
609		ID:      "redis1",
610		Service: "redis",
611		Port:    8000,
612		Connect: &AgentServiceConnect{Native: true},
613	}
614	check := &AgentCheck{
615		Node:      "foobar",
616		CheckID:   "service:redis1",
617		Name:      "Redis health check",
618		Notes:     "Script based health check",
619		Status:    HealthPassing,
620		ServiceID: "redis1",
621	}
622
623	reg := &CatalogRegistration{
624		Datacenter: "dc1",
625		Node:       "foobar",
626		Address:    "192.168.10.10",
627		Service:    service,
628		Check:      check,
629	}
630
631	retry.Run(t, func(r *retry.R) {
632		if _, err := catalog.Register(reg, nil); err != nil {
633			r.Fatal(err)
634		}
635
636		services, meta, err := catalog.Connect("redis", "", nil)
637		if err != nil {
638			r.Fatal(err)
639		}
640
641		if meta.LastIndex == 0 {
642			r.Fatalf("Bad: %v", meta)
643		}
644
645		if len(services) == 0 {
646			r.Fatalf("Bad: %v", services)
647		}
648
649		if services[0].Datacenter != "dc1" {
650			r.Fatalf("Bad datacenter: %v", services[0])
651		}
652
653		if services[0].ServicePort != service.Port {
654			r.Fatalf("Returned port should be for proxy: %v", services[0])
655		}
656	})
657}
658
659func TestAPI_CatalogConnect_Filter(t *testing.T) {
660	t.Parallel()
661	c, s := makeClient(t)
662	defer s.Stop()
663
664	// this sets up the catalog entries with things we can filter on
665	testNodeServiceCheckRegistrations(t, c, "dc1")
666
667	catalog := c.Catalog()
668
669	services, _, err := catalog.Connect("web", "", &QueryOptions{Filter: "ServicePort == 443"})
670	require.NoError(t, err)
671	require.Len(t, services, 2)
672	require.Condition(t, func() bool {
673		return (services[0].Node == "bar" && services[1].Node == "baz") ||
674			(services[0].Node == "baz" && services[1].Node == "bar")
675	})
676
677	// All the web-connect services are native
678	services, _, err = catalog.Connect("web", "", &QueryOptions{Filter: "ServiceConnect.Native != true"})
679	require.NoError(t, err)
680	require.Empty(t, services)
681}
682
683func TestAPI_CatalogNode(t *testing.T) {
684	t.Parallel()
685	c, s := makeClient(t)
686	defer s.Stop()
687
688	catalog := c.Catalog()
689
690	name, err := c.Agent().NodeName()
691	require.NoError(t, err)
692
693	proxyReg := testUnmanagedProxyRegistration(t)
694	proxyReg.Node = name
695	proxyReg.SkipNodeUpdate = true
696
697	retry.Run(t, func(r *retry.R) {
698		// Register a connect proxy to ensure all it's config fields are returned
699		_, err := catalog.Register(proxyReg, nil)
700		r.Check(err)
701
702		info, meta, err := catalog.Node(name, nil)
703		if err != nil {
704			r.Fatal(err)
705		}
706
707		if meta.LastIndex == 0 {
708			r.Fatalf("Bad: %v", meta)
709		}
710
711		if len(info.Services) != 2 {
712			r.Fatalf("Bad: %v (len %d)", info, len(info.Services))
713		}
714
715		if _, ok := info.Node.TaggedAddresses["wan"]; !ok {
716			r.Fatalf("Bad: %v", info.Node.TaggedAddresses)
717		}
718
719		if info.Node.Datacenter != "dc1" {
720			r.Fatalf("Bad datacenter: %v", info)
721		}
722
723		if _, ok := info.Services["web-proxy1"]; !ok {
724			r.Fatalf("Missing proxy service: %v", info.Services)
725		}
726
727		if !reflect.DeepEqual(proxyReg.Service.Proxy, info.Services["web-proxy1"].Proxy) {
728			r.Fatalf("Bad proxy config:\nwant %v\n got: %v", proxyReg.Service.Proxy,
729				info.Services["web-proxy"].Proxy)
730		}
731	})
732}
733
734func TestAPI_CatalogNodeServiceList(t *testing.T) {
735	t.Parallel()
736	c, s := makeClient(t)
737	defer s.Stop()
738
739	catalog := c.Catalog()
740
741	name, err := c.Agent().NodeName()
742	require.NoError(t, err)
743
744	proxyReg := testUnmanagedProxyRegistration(t)
745	proxyReg.Node = name
746	proxyReg.SkipNodeUpdate = true
747
748	retry.Run(t, func(r *retry.R) {
749		// Register a connect proxy to ensure all it's config fields are returned
750		_, err := catalog.Register(proxyReg, nil)
751		r.Check(err)
752
753		info, meta, err := catalog.NodeServiceList(name, nil)
754		if err != nil {
755			r.Fatal(err)
756		}
757
758		if meta.LastIndex == 0 {
759			r.Fatalf("Bad: %v", meta)
760		}
761
762		if len(info.Services) != 2 {
763			r.Fatalf("Bad: %v (len %d)", info, len(info.Services))
764		}
765
766		if _, ok := info.Node.TaggedAddresses["wan"]; !ok {
767			r.Fatalf("Bad: %v", info.Node.TaggedAddresses)
768		}
769
770		if info.Node.Datacenter != "dc1" {
771			r.Fatalf("Bad datacenter: %v", info)
772		}
773
774		var proxySvc *AgentService
775		for _, svc := range info.Services {
776			if svc.ID == "web-proxy1" {
777				proxySvc = svc
778				break
779			}
780		}
781
782		if proxySvc == nil {
783			r.Fatalf("Missing proxy service: %v", info.Services)
784		}
785
786		if !reflect.DeepEqual(proxyReg.Service.Proxy, proxySvc.Proxy) {
787			r.Fatalf("Bad proxy config:\nwant %v\n got: %v", proxyReg.Service.Proxy,
788				proxySvc.Proxy)
789		}
790	})
791}
792
793func TestAPI_CatalogNode_Filter(t *testing.T) {
794	t.Parallel()
795	c, s := makeClient(t)
796	defer s.Stop()
797
798	// this sets up the catalog entries with things we can filter on
799	testNodeServiceCheckRegistrations(t, c, "dc1")
800
801	catalog := c.Catalog()
802
803	// should have only 1 matching service
804	info, _, err := catalog.Node("bar", &QueryOptions{Filter: "connect in Tags"})
805	require.NoError(t, err)
806	require.Len(t, info.Services, 1)
807	require.Contains(t, info.Services, "webV1")
808	require.Equal(t, "web", info.Services["webV1"].Service)
809
810	// should get two services for the node
811	info, _, err = catalog.Node("baz", &QueryOptions{Filter: "connect in Tags"})
812	require.NoError(t, err)
813	require.Len(t, info.Services, 2)
814}
815
816func TestAPI_CatalogRegistration(t *testing.T) {
817	t.Parallel()
818	c, s := makeClient(t)
819	defer s.Stop()
820
821	catalog := c.Catalog()
822
823	service := &AgentService{
824		ID:      "redis1",
825		Service: "redis",
826		Tags:    []string{"master", "v1"},
827		Port:    8000,
828	}
829
830	check := &AgentCheck{
831		Node:      "foobar",
832		CheckID:   "service:redis1-a",
833		Name:      "Redis health check",
834		Notes:     "Script based health check",
835		Status:    HealthPassing,
836		ServiceID: "redis1",
837	}
838
839	checks := HealthChecks{
840		&HealthCheck{
841			Node:      "foobar",
842			CheckID:   "service:redis1-b",
843			Name:      "Redis health check",
844			Notes:     "Script based health check",
845			Status:    HealthPassing,
846			ServiceID: "redis1",
847		},
848	}
849
850	reg := &CatalogRegistration{
851		Datacenter: "dc1",
852		Node:       "foobar",
853		Address:    "192.168.10.10",
854		NodeMeta:   map[string]string{"somekey": "somevalue"},
855		Service:    service,
856		// Specifying both Check and Checks is accepted by Consul
857		Check:  check,
858		Checks: checks,
859	}
860	// Register a connect proxy for that service too
861	proxy := &AgentService{
862		ID:      "redis-proxy1",
863		Service: "redis-proxy",
864		Port:    8001,
865		Kind:    ServiceKindConnectProxy,
866		Proxy: &AgentServiceConnectProxyConfig{
867			DestinationServiceName: service.Service,
868		},
869	}
870	proxyReg := &CatalogRegistration{
871		Datacenter: "dc1",
872		Node:       "foobar",
873		Address:    "192.168.10.10",
874		NodeMeta:   map[string]string{"somekey": "somevalue"},
875		Service:    proxy,
876	}
877	retry.Run(t, func(r *retry.R) {
878		if _, err := catalog.Register(reg, nil); err != nil {
879			r.Fatal(err)
880		}
881		if _, err := catalog.Register(proxyReg, nil); err != nil {
882			r.Fatal(err)
883		}
884
885		node, _, err := catalog.Node("foobar", nil)
886		if err != nil {
887			r.Fatal(err)
888		}
889
890		if _, ok := node.Services["redis1"]; !ok {
891			r.Fatal("missing service: redis1")
892		}
893
894		if _, ok := node.Services["redis-proxy1"]; !ok {
895			r.Fatal("missing service: redis-proxy1")
896		}
897
898		health, _, err := c.Health().Node("foobar", nil)
899		if err != nil {
900			r.Fatal(err)
901		}
902
903		if health[0].CheckID != "service:redis1-a" {
904			r.Fatal("missing checkid service:redis1-a")
905		}
906
907		if health[1].CheckID != "service:redis1-b" {
908			r.Fatal("missing checkid service:redis1-b")
909		}
910
911		if v, ok := node.Node.Meta["somekey"]; !ok || v != "somevalue" {
912			r.Fatal("missing node meta pair somekey:somevalue")
913		}
914	})
915
916	// Test catalog deregistration of the previously registered service
917	dereg := &CatalogDeregistration{
918		Datacenter: "dc1",
919		Node:       "foobar",
920		Address:    "192.168.10.10",
921		ServiceID:  "redis1",
922	}
923
924	// ... and proxy
925	deregProxy := &CatalogDeregistration{
926		Datacenter: "dc1",
927		Node:       "foobar",
928		Address:    "192.168.10.10",
929		ServiceID:  "redis-proxy1",
930	}
931
932	if _, err := catalog.Deregister(dereg, nil); err != nil {
933		t.Fatalf("err: %v", err)
934	}
935
936	if _, err := catalog.Deregister(deregProxy, nil); err != nil {
937		t.Fatalf("err: %v", err)
938	}
939
940	retry.Run(t, func(r *retry.R) {
941		node, _, err := catalog.Node("foobar", nil)
942		if err != nil {
943			r.Fatal(err)
944		}
945
946		if _, ok := node.Services["redis1"]; ok {
947			r.Fatal("ServiceID:redis1 is not deregistered")
948		}
949
950		if _, ok := node.Services["redis-proxy1"]; ok {
951			r.Fatal("ServiceID:redis-proxy1 is not deregistered")
952		}
953	})
954
955	// Test deregistration of the previously registered check
956	dereg = &CatalogDeregistration{
957		Datacenter: "dc1",
958		Node:       "foobar",
959		Address:    "192.168.10.10",
960		CheckID:    "service:redis1-a",
961	}
962
963	if _, err := catalog.Deregister(dereg, nil); err != nil {
964		t.Fatalf("err: %v", err)
965	}
966
967	dereg = &CatalogDeregistration{
968		Datacenter: "dc1",
969		Node:       "foobar",
970		Address:    "192.168.10.10",
971		CheckID:    "service:redis1-b",
972	}
973
974	if _, err := catalog.Deregister(dereg, nil); err != nil {
975		t.Fatalf("err: %v", err)
976	}
977
978	retry.Run(t, func(r *retry.R) {
979		health, _, err := c.Health().Node("foobar", nil)
980		if err != nil {
981			r.Fatal(err)
982		}
983
984		if len(health) != 0 {
985			r.Fatal("CheckID:service:redis1-a or CheckID:service:redis1-a is not deregistered")
986		}
987	})
988
989	// Test node deregistration of the previously registered node
990	dereg = &CatalogDeregistration{
991		Datacenter: "dc1",
992		Node:       "foobar",
993		Address:    "192.168.10.10",
994	}
995
996	if _, err := catalog.Deregister(dereg, nil); err != nil {
997		t.Fatalf("err: %v", err)
998	}
999
1000	retry.Run(t, func(r *retry.R) {
1001		node, _, err := catalog.Node("foobar", nil)
1002		if err != nil {
1003			r.Fatal(err)
1004		}
1005
1006		if node != nil {
1007			r.Fatalf("node is not deregistered: %v", node)
1008		}
1009	})
1010}
1011
1012func TestAPI_CatalogEnableTagOverride(t *testing.T) {
1013	t.Parallel()
1014	c, s := makeClient(t)
1015	defer s.Stop()
1016	s.WaitForSerfCheck(t)
1017
1018	catalog := c.Catalog()
1019
1020	service := &AgentService{
1021		ID:      "redis1",
1022		Service: "redis",
1023		Tags:    []string{"master", "v1"},
1024		Port:    8000,
1025	}
1026
1027	reg := &CatalogRegistration{
1028		Datacenter: "dc1",
1029		Node:       "foobar",
1030		Address:    "192.168.10.10",
1031		Service:    service,
1032	}
1033
1034	retry.Run(t, func(r *retry.R) {
1035		if _, err := catalog.Register(reg, nil); err != nil {
1036			r.Fatal(err)
1037		}
1038
1039		node, _, err := catalog.Node("foobar", nil)
1040		if err != nil {
1041			r.Fatal(err)
1042		}
1043
1044		if _, ok := node.Services["redis1"]; !ok {
1045			r.Fatal("missing service: redis1")
1046		}
1047		if node.Services["redis1"].EnableTagOverride != false {
1048			r.Fatal("tag override set")
1049		}
1050
1051		services, _, err := catalog.Service("redis", "", nil)
1052		if err != nil {
1053			r.Fatal(err)
1054		}
1055
1056		if len(services) < 1 || services[0].ServiceName != "redis" {
1057			r.Fatal("missing service: redis")
1058		}
1059		if services[0].ServiceEnableTagOverride != false {
1060			r.Fatal("tag override set")
1061		}
1062	})
1063
1064	service.EnableTagOverride = true
1065
1066	retry.Run(t, func(r *retry.R) {
1067		if _, err := catalog.Register(reg, nil); err != nil {
1068			r.Fatal(err)
1069		}
1070
1071		node, _, err := catalog.Node("foobar", nil)
1072		if err != nil {
1073			r.Fatal(err)
1074		}
1075
1076		if _, ok := node.Services["redis1"]; !ok {
1077			r.Fatal("missing service: redis1")
1078		}
1079		if node.Services["redis1"].EnableTagOverride != true {
1080			r.Fatal("tag override not set")
1081		}
1082
1083		services, _, err := catalog.Service("redis", "", nil)
1084		if err != nil {
1085			r.Fatal(err)
1086		}
1087
1088		if len(services) < 1 || services[0].ServiceName != "redis" {
1089			r.Fatal("missing service: redis")
1090		}
1091		if services[0].ServiceEnableTagOverride != true {
1092			r.Fatal("tag override not set")
1093		}
1094	})
1095}
1096
1097func TestAPI_CatalogGatewayServices_Terminating(t *testing.T) {
1098	t.Parallel()
1099	c, s := makeClient(t)
1100	defer s.Stop()
1101	s.WaitForSerfCheck(t)
1102
1103	catalog := c.Catalog()
1104
1105	// Register a service to be covered by a wildcard in the config entry
1106	svc := &AgentService{
1107		ID:      "redis",
1108		Service: "redis",
1109		Port:    6379,
1110	}
1111	reg := &CatalogRegistration{
1112		Datacenter: "dc1",
1113		Node:       "bar",
1114		Address:    "192.168.10.11",
1115		Service:    svc,
1116	}
1117	retry.Run(t, func(r *retry.R) {
1118		if _, err := catalog.Register(reg, nil); err != nil {
1119			r.Fatal(err)
1120		}
1121	})
1122
1123	entries := c.ConfigEntries()
1124
1125	// Associate the gateway and api/redis services
1126	gwEntry := TerminatingGatewayConfigEntry{
1127		Kind: TerminatingGateway,
1128		Name: "terminating",
1129		Services: []LinkedService{
1130			{
1131				Name:     "api",
1132				CAFile:   "api/ca.crt",
1133				CertFile: "api/client.crt",
1134				KeyFile:  "api/client.key",
1135				SNI:      "my-domain",
1136			},
1137			{
1138				Name:     "*",
1139				CAFile:   "ca.crt",
1140				CertFile: "client.crt",
1141				KeyFile:  "client.key",
1142				SNI:      "my-alt-domain",
1143			},
1144		},
1145	}
1146	retry.Run(t, func(r *retry.R) {
1147		if success, _, err := entries.Set(&gwEntry, nil); err != nil || !success {
1148			r.Fatal(err)
1149		}
1150	})
1151
1152	expect := []*GatewayService{
1153		{
1154			Service:     CompoundServiceName{"api", defaultNamespace},
1155			Gateway:     CompoundServiceName{"terminating", defaultNamespace},
1156			GatewayKind: ServiceKindTerminatingGateway,
1157			CAFile:      "api/ca.crt",
1158			CertFile:    "api/client.crt",
1159			KeyFile:     "api/client.key",
1160			SNI:         "my-domain",
1161		},
1162		{
1163			Service:      CompoundServiceName{"redis", defaultNamespace},
1164			Gateway:      CompoundServiceName{"terminating", defaultNamespace},
1165			GatewayKind:  ServiceKindTerminatingGateway,
1166			CAFile:       "ca.crt",
1167			CertFile:     "client.crt",
1168			KeyFile:      "client.key",
1169			SNI:          "my-alt-domain",
1170			FromWildcard: true,
1171		},
1172	}
1173	retry.Run(t, func(r *retry.R) {
1174		resp, _, err := catalog.GatewayServices("terminating", nil)
1175		assert.NoError(r, err)
1176		assert.Equal(r, expect, resp)
1177	})
1178}
1179
1180func TestAPI_CatalogGatewayServices_Ingress(t *testing.T) {
1181	t.Parallel()
1182	c, s := makeClient(t)
1183	defer s.Stop()
1184
1185	s.WaitForSerfCheck(t)
1186
1187	entries := c.ConfigEntries()
1188
1189	// Associate the gateway and api/redis services
1190	gwEntry := IngressGatewayConfigEntry{
1191		Kind: "ingress-gateway",
1192		Name: "ingress",
1193		Listeners: []IngressListener{
1194			{
1195				Port: 8888,
1196				Services: []IngressService{
1197					{
1198						Name: "api",
1199					},
1200				},
1201			},
1202			{
1203				Port: 9999,
1204				Services: []IngressService{
1205					{
1206						Name: "redis",
1207					},
1208				},
1209			},
1210		},
1211	}
1212	retry.Run(t, func(r *retry.R) {
1213		if success, _, err := entries.Set(&gwEntry, nil); err != nil || !success {
1214			r.Fatal(err)
1215		}
1216	})
1217
1218	catalog := c.Catalog()
1219
1220	expect := []*GatewayService{
1221		{
1222			Service:     CompoundServiceName{"api", defaultNamespace},
1223			Gateway:     CompoundServiceName{"ingress", defaultNamespace},
1224			GatewayKind: ServiceKindIngressGateway,
1225			Protocol:    "tcp",
1226			Port:        8888,
1227		},
1228		{
1229			Service:     CompoundServiceName{"redis", defaultNamespace},
1230			Gateway:     CompoundServiceName{"ingress", defaultNamespace},
1231			GatewayKind: ServiceKindIngressGateway,
1232			Protocol:    "tcp",
1233			Port:        9999,
1234		},
1235	}
1236	retry.Run(t, func(r *retry.R) {
1237		resp, _, err := catalog.GatewayServices("ingress", nil)
1238		assert.NoError(r, err)
1239		assert.Equal(r, expect, resp)
1240	})
1241}
1242