1package agent
2
3import (
4	"encoding/json"
5	"fmt"
6	"github.com/mitchellh/copystructure"
7	"io/ioutil"
8	"os"
9	"path/filepath"
10	"testing"
11
12	"github.com/hashicorp/consul/agent/structs"
13	"github.com/hashicorp/consul/sdk/testutil/retry"
14	"github.com/hashicorp/consul/testrpc"
15	"github.com/stretchr/testify/assert"
16	"github.com/stretchr/testify/require"
17)
18
19func TestServiceManager_RegisterService(t *testing.T) {
20	if testing.Short() {
21		t.Skip("too slow for testing.Short")
22	}
23
24	require := require.New(t)
25
26	a := NewTestAgent(t, "")
27	defer a.Shutdown()
28
29	testrpc.WaitForLeader(t, a.RPC, "dc1")
30
31	// Register a global proxy and service config
32	testApplyConfigEntries(t, a,
33		&structs.ProxyConfigEntry{
34			Config: map[string]interface{}{
35				"foo": 1,
36			},
37		},
38		&structs.ServiceConfigEntry{
39			Kind:     structs.ServiceDefaults,
40			Name:     "redis",
41			Protocol: "tcp",
42		},
43	)
44
45	// Now register a service locally with no sidecar, it should be a no-op.
46	svc := &structs.NodeService{
47		ID:             "redis",
48		Service:        "redis",
49		Port:           8000,
50		EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
51	}
52	require.NoError(a.addServiceFromSource(svc, nil, false, "", ConfigSourceLocal))
53
54	// Verify both the service and sidecar.
55	redisService := a.State.Service(structs.NewServiceID("redis", nil))
56	require.NotNil(redisService)
57	require.Equal(&structs.NodeService{
58		ID:              "redis",
59		Service:         "redis",
60		Port:            8000,
61		TaggedAddresses: map[string]structs.ServiceAddress{},
62		Weights: &structs.Weights{
63			Passing: 1,
64			Warning: 1,
65		},
66		EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
67	}, redisService)
68}
69
70func TestServiceManager_RegisterSidecar(t *testing.T) {
71	if testing.Short() {
72		t.Skip("too slow for testing.Short")
73	}
74
75	require := require.New(t)
76
77	a := NewTestAgent(t, "")
78	defer a.Shutdown()
79
80	testrpc.WaitForLeader(t, a.RPC, "dc1")
81
82	// Register a global proxy and service config
83	testApplyConfigEntries(t, a,
84		&structs.ProxyConfigEntry{
85			Config: map[string]interface{}{
86				"foo": 1,
87			},
88		},
89		&structs.ServiceConfigEntry{
90			Kind:     structs.ServiceDefaults,
91			Name:     "web",
92			Protocol: "http",
93		},
94		&structs.ServiceConfigEntry{
95			Kind:     structs.ServiceDefaults,
96			Name:     "redis",
97			Protocol: "tcp",
98		},
99	)
100
101	// Now register a sidecar proxy. Note we don't use SidecarService here because
102	// that gets resolved earlier in config handling than the AddService call
103	// here.
104	svc := &structs.NodeService{
105		Kind:    structs.ServiceKindConnectProxy,
106		ID:      "web-sidecar-proxy",
107		Service: "web-sidecar-proxy",
108		Port:    21000,
109		Proxy: structs.ConnectProxyConfig{
110			DestinationServiceName: "web",
111			DestinationServiceID:   "web",
112			LocalServiceAddress:    "127.0.0.1",
113			LocalServicePort:       8000,
114			Upstreams: structs.Upstreams{
115				{
116					DestinationName:      "redis",
117					DestinationNamespace: "default",
118					LocalBindPort:        5000,
119				},
120			},
121		},
122		EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
123	}
124	require.NoError(a.addServiceFromSource(svc, nil, false, "", ConfigSourceLocal))
125
126	// Verify sidecar got global config loaded
127	sidecarService := a.State.Service(structs.NewServiceID("web-sidecar-proxy", nil))
128	require.NotNil(sidecarService)
129	require.Equal(&structs.NodeService{
130		Kind:            structs.ServiceKindConnectProxy,
131		ID:              "web-sidecar-proxy",
132		Service:         "web-sidecar-proxy",
133		Port:            21000,
134		TaggedAddresses: map[string]structs.ServiceAddress{},
135		Proxy: structs.ConnectProxyConfig{
136			DestinationServiceName: "web",
137			DestinationServiceID:   "web",
138			LocalServiceAddress:    "127.0.0.1",
139			LocalServicePort:       8000,
140			Config: map[string]interface{}{
141				"foo":      int64(1),
142				"protocol": "http",
143			},
144			Upstreams: structs.Upstreams{
145				{
146					DestinationName:      "redis",
147					DestinationNamespace: "default",
148					LocalBindPort:        5000,
149					Config: map[string]interface{}{
150						"protocol": "tcp",
151					},
152				},
153			},
154		},
155		Weights: &structs.Weights{
156			Passing: 1,
157			Warning: 1,
158		},
159		EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
160	}, sidecarService)
161}
162
163func TestServiceManager_RegisterMeshGateway(t *testing.T) {
164	if testing.Short() {
165		t.Skip("too slow for testing.Short")
166	}
167
168	require := require.New(t)
169
170	a := NewTestAgent(t, "")
171	defer a.Shutdown()
172
173	testrpc.WaitForLeader(t, a.RPC, "dc1")
174
175	// Register a global proxy and service config
176	testApplyConfigEntries(t, a,
177		&structs.ProxyConfigEntry{
178			Config: map[string]interface{}{
179				"foo": 1,
180			},
181		},
182		&structs.ServiceConfigEntry{
183			Kind:     structs.ServiceDefaults,
184			Name:     "mesh-gateway",
185			Protocol: "http",
186		},
187	)
188
189	// Now register a mesh-gateway.
190	svc := &structs.NodeService{
191		Kind:           structs.ServiceKindMeshGateway,
192		ID:             "mesh-gateway",
193		Service:        "mesh-gateway",
194		Port:           443,
195		EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
196	}
197
198	require.NoError(a.addServiceFromSource(svc, nil, false, "", ConfigSourceLocal))
199
200	// Verify gateway got global config loaded
201	gateway := a.State.Service(structs.NewServiceID("mesh-gateway", nil))
202	require.NotNil(gateway)
203	require.Equal(&structs.NodeService{
204		Kind:            structs.ServiceKindMeshGateway,
205		ID:              "mesh-gateway",
206		Service:         "mesh-gateway",
207		Port:            443,
208		TaggedAddresses: map[string]structs.ServiceAddress{},
209		Proxy: structs.ConnectProxyConfig{
210			Config: map[string]interface{}{
211				"foo":      int64(1),
212				"protocol": "http",
213			},
214		},
215		Weights: &structs.Weights{
216			Passing: 1,
217			Warning: 1,
218		},
219		EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
220	}, gateway)
221}
222
223func TestServiceManager_RegisterTerminatingGateway(t *testing.T) {
224	if testing.Short() {
225		t.Skip("too slow for testing.Short")
226	}
227
228	require := require.New(t)
229
230	a := NewTestAgent(t, "")
231	defer a.Shutdown()
232
233	testrpc.WaitForLeader(t, a.RPC, "dc1")
234
235	// Register a global proxy and service config
236	testApplyConfigEntries(t, a,
237		&structs.ProxyConfigEntry{
238			Config: map[string]interface{}{
239				"foo": 1,
240			},
241		},
242		&structs.ServiceConfigEntry{
243			Kind:     structs.ServiceDefaults,
244			Name:     "terminating-gateway",
245			Protocol: "http",
246		},
247	)
248
249	// Now register a terminating-gateway.
250	svc := &structs.NodeService{
251		Kind:           structs.ServiceKindTerminatingGateway,
252		ID:             "terminating-gateway",
253		Service:        "terminating-gateway",
254		Port:           443,
255		EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
256	}
257
258	require.NoError(a.addServiceFromSource(svc, nil, false, "", ConfigSourceLocal))
259
260	// Verify gateway got global config loaded
261	gateway := a.State.Service(structs.NewServiceID("terminating-gateway", nil))
262	require.NotNil(gateway)
263	require.Equal(&structs.NodeService{
264		Kind:            structs.ServiceKindTerminatingGateway,
265		ID:              "terminating-gateway",
266		Service:         "terminating-gateway",
267		Port:            443,
268		TaggedAddresses: map[string]structs.ServiceAddress{},
269		Proxy: structs.ConnectProxyConfig{
270			Config: map[string]interface{}{
271				"foo":      int64(1),
272				"protocol": "http",
273			},
274		},
275		Weights: &structs.Weights{
276			Passing: 1,
277			Warning: 1,
278		},
279		EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
280	}, gateway)
281}
282
283func TestServiceManager_PersistService_API(t *testing.T) {
284	if testing.Short() {
285		t.Skip("too slow for testing.Short")
286	}
287
288	// This is the ServiceManager version of TestAgent_PersistService  and
289	// TestAgent_PurgeService.
290	t.Parallel()
291
292	require := require.New(t)
293
294	// Launch a server to manage the config entries.
295	serverAgent := NewTestAgent(t, "")
296	defer serverAgent.Shutdown()
297	testrpc.WaitForLeader(t, serverAgent.RPC, "dc1")
298
299	// Register a global proxy and service config
300	testApplyConfigEntries(t, serverAgent,
301		&structs.ProxyConfigEntry{
302			Config: map[string]interface{}{
303				"foo": 1,
304			},
305		},
306		&structs.ServiceConfigEntry{
307			Kind:     structs.ServiceDefaults,
308			Name:     "web",
309			Protocol: "http",
310		},
311		&structs.ServiceConfigEntry{
312			Kind:     structs.ServiceDefaults,
313			Name:     "redis",
314			Protocol: "tcp",
315		},
316	)
317
318	// Now launch a single client agent
319	cfg := `
320		server = false
321		bootstrap = false
322	`
323	a := StartTestAgent(t, TestAgent{HCL: cfg})
324	defer a.Shutdown()
325
326	// Join first
327	_, err := a.JoinLAN([]string{
328		fmt.Sprintf("127.0.0.1:%d", serverAgent.Config.SerfPortLAN),
329	})
330	require.NoError(err)
331
332	testrpc.WaitForLeader(t, a.RPC, "dc1")
333
334	// Now register a sidecar proxy via the API.
335	svc := &structs.NodeService{
336		Kind:    structs.ServiceKindConnectProxy,
337		ID:      "web-sidecar-proxy",
338		Service: "web-sidecar-proxy",
339		Port:    21000,
340		Proxy: structs.ConnectProxyConfig{
341			DestinationServiceName: "web",
342			DestinationServiceID:   "web",
343			LocalServiceAddress:    "127.0.0.1",
344			LocalServicePort:       8000,
345			Upstreams: structs.Upstreams{
346				{
347					DestinationName:      "redis",
348					DestinationNamespace: "default",
349					LocalBindPort:        5000,
350				},
351			},
352		},
353		EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
354	}
355
356	expectState := &structs.NodeService{
357		Kind:            structs.ServiceKindConnectProxy,
358		ID:              "web-sidecar-proxy",
359		Service:         "web-sidecar-proxy",
360		Port:            21000,
361		TaggedAddresses: map[string]structs.ServiceAddress{},
362		Proxy: structs.ConnectProxyConfig{
363			DestinationServiceName: "web",
364			DestinationServiceID:   "web",
365			LocalServiceAddress:    "127.0.0.1",
366			LocalServicePort:       8000,
367			Config: map[string]interface{}{
368				"foo":      int64(1),
369				"protocol": "http",
370			},
371			Upstreams: structs.Upstreams{
372				{
373					DestinationName:      "redis",
374					DestinationNamespace: "default",
375					LocalBindPort:        5000,
376					Config: map[string]interface{}{
377						"protocol": "tcp",
378					},
379				},
380			},
381		},
382		Weights: &structs.Weights{
383			Passing: 1,
384			Warning: 1,
385		},
386		EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
387	}
388
389	svcID := svc.CompoundServiceID()
390
391	svcFile := filepath.Join(a.Config.DataDir, servicesDir, svcID.StringHash())
392	configFile := filepath.Join(a.Config.DataDir, serviceConfigDir, svcID.StringHash())
393
394	// Service is not persisted unless requested, but we always persist service configs.
395	err = a.AddService(AddServiceRequest{Service: svc, Source: ConfigSourceRemote})
396	require.NoError(err)
397	requireFileIsAbsent(t, svcFile)
398	requireFileIsPresent(t, configFile)
399
400	// Persists to file if requested
401	err = a.AddService(AddServiceRequest{
402		Service: svc,
403		persist: true,
404		token:   "mytoken",
405		Source:  ConfigSourceRemote,
406	})
407	require.NoError(err)
408	requireFileIsPresent(t, svcFile)
409	requireFileIsPresent(t, configFile)
410
411	// Service definition file is sane.
412	expectJSONFile(t, svcFile, persistedService{
413		Token:   "mytoken",
414		Service: svc,
415		Source:  "remote",
416	}, nil)
417
418	// Service config file is sane.
419	pcfg := persistedServiceConfig{
420		ServiceID: "web-sidecar-proxy",
421		Defaults: &structs.ServiceConfigResponse{
422			ProxyConfig: map[string]interface{}{
423				"foo":      1,
424				"protocol": "http",
425			},
426			UpstreamIDConfigs: structs.OpaqueUpstreamConfigs{
427				structs.OpaqueUpstreamConfig{
428					Upstream: structs.NewServiceID("redis", nil),
429					Config: map[string]interface{}{
430						"protocol": "tcp",
431					},
432				},
433			},
434		},
435		EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
436	}
437	expectJSONFile(t, configFile, pcfg, resetDefaultsQueryMeta)
438
439	// Verify in memory state.
440	{
441		sidecarService := a.State.Service(structs.NewServiceID("web-sidecar-proxy", nil))
442		require.NotNil(sidecarService)
443		require.Equal(expectState, sidecarService)
444	}
445
446	// Updates service definition on disk
447	svc.Proxy.LocalServicePort = 8001
448	require.NoError(a.addServiceFromSource(svc, nil, true, "mytoken", ConfigSourceRemote))
449	requireFileIsPresent(t, svcFile)
450	requireFileIsPresent(t, configFile)
451
452	// Service definition file is updated.
453	expectJSONFile(t, svcFile, persistedService{
454		Token:   "mytoken",
455		Service: svc,
456		Source:  "remote",
457	}, nil)
458
459	// Service config file is the same.
460	pcfg = persistedServiceConfig{
461		ServiceID: "web-sidecar-proxy",
462		Defaults: &structs.ServiceConfigResponse{
463			ProxyConfig: map[string]interface{}{
464				"foo":      1,
465				"protocol": "http",
466			},
467			UpstreamIDConfigs: structs.OpaqueUpstreamConfigs{
468				structs.OpaqueUpstreamConfig{
469					Upstream: structs.NewServiceID("redis", nil),
470					Config: map[string]interface{}{
471						"protocol": "tcp",
472					},
473				},
474			},
475		},
476		EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
477	}
478	expectJSONFile(t, configFile, pcfg, resetDefaultsQueryMeta)
479
480	// Verify in memory state.
481	expectState.Proxy.LocalServicePort = 8001
482	{
483		sidecarService := a.State.Service(structs.NewServiceID("web-sidecar-proxy", nil))
484		require.NotNil(sidecarService)
485		require.Equal(expectState, sidecarService)
486	}
487
488	// Kill the agent to restart it.
489	a.Shutdown()
490
491	// Kill the server so that it can't phone home and must rely upon the persisted defaults.
492	serverAgent.Shutdown()
493
494	// Should load it back during later start.
495	a2 := StartTestAgent(t, TestAgent{HCL: cfg, DataDir: a.DataDir})
496	defer a2.Shutdown()
497
498	{
499		restored := a.State.Service(structs.NewServiceID("web-sidecar-proxy", nil))
500		require.NotNil(restored)
501		require.Equal(expectState, restored)
502	}
503
504	// Now remove it.
505	require.NoError(a2.RemoveService(structs.NewServiceID("web-sidecar-proxy", nil)))
506	requireFileIsAbsent(t, svcFile)
507	requireFileIsAbsent(t, configFile)
508}
509
510func TestServiceManager_PersistService_ConfigFiles(t *testing.T) {
511	if testing.Short() {
512		t.Skip("too slow for testing.Short")
513	}
514
515	// This is the ServiceManager version of TestAgent_PersistService  and
516	// TestAgent_PurgeService but for config files.
517	t.Parallel()
518
519	// Launch a server to manage the config entries.
520	serverAgent := NewTestAgent(t, "")
521	defer serverAgent.Shutdown()
522	testrpc.WaitForLeader(t, serverAgent.RPC, "dc1")
523
524	// Register a global proxy and service config
525	testApplyConfigEntries(t, serverAgent,
526		&structs.ProxyConfigEntry{
527			Config: map[string]interface{}{
528				"foo": 1,
529			},
530		},
531		&structs.ServiceConfigEntry{
532			Kind:     structs.ServiceDefaults,
533			Name:     "web",
534			Protocol: "http",
535		},
536		&structs.ServiceConfigEntry{
537			Kind:     structs.ServiceDefaults,
538			Name:     "redis",
539			Protocol: "tcp",
540		},
541	)
542
543	// Now launch a single client agent
544	serviceSnippet := `
545		service = {
546		  kind  = "connect-proxy"
547		  id    = "web-sidecar-proxy"
548		  name  = "web-sidecar-proxy"
549		  port  = 21000
550		  token = "mytoken"
551		  proxy {
552			destination_service_name = "web"
553			destination_service_id   = "web"
554			local_service_address    = "127.0.0.1"
555			local_service_port       = 8000
556			upstreams = [{
557			  destination_name = "redis"
558			  destination_namespace = "default"
559			  local_bind_port  = 5000
560			}]
561		  }
562		}
563	`
564
565	cfg := `
566		server = false
567		bootstrap = false
568	` + serviceSnippet
569
570	a := StartTestAgent(t, TestAgent{HCL: cfg})
571	defer a.Shutdown()
572
573	// Join first
574	_, err := a.JoinLAN([]string{
575		fmt.Sprintf("127.0.0.1:%d", serverAgent.Config.SerfPortLAN),
576	})
577	require.NoError(t, err)
578
579	testrpc.WaitForLeader(t, a.RPC, "dc1")
580
581	// Now register a sidecar proxy via the API.
582	svcID := "web-sidecar-proxy"
583
584	expectState := &structs.NodeService{
585		Kind:            structs.ServiceKindConnectProxy,
586		ID:              "web-sidecar-proxy",
587		Service:         "web-sidecar-proxy",
588		Port:            21000,
589		TaggedAddresses: map[string]structs.ServiceAddress{},
590		Proxy: structs.ConnectProxyConfig{
591			DestinationServiceName: "web",
592			DestinationServiceID:   "web",
593			LocalServiceAddress:    "127.0.0.1",
594			LocalServicePort:       8000,
595			Config: map[string]interface{}{
596				"foo":      int64(1),
597				"protocol": "http",
598			},
599			Upstreams: structs.Upstreams{
600				{
601					DestinationType:      "service",
602					DestinationName:      "redis",
603					DestinationNamespace: "default",
604					LocalBindPort:        5000,
605					Config: map[string]interface{}{
606						"protocol": "tcp",
607					},
608				},
609			},
610		},
611		Weights: &structs.Weights{
612			Passing: 1,
613			Warning: 1,
614		},
615		EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
616	}
617
618	// Now wait until we've re-registered using central config updated data.
619	retry.Run(t, func(r *retry.R) {
620		a.stateLock.Lock()
621		defer a.stateLock.Unlock()
622		current := a.State.Service(structs.NewServiceID("web-sidecar-proxy", nil))
623		if current == nil {
624			r.Fatalf("service is missing")
625		}
626		require.Equal(r, expectState, current)
627	})
628
629	svcFile := filepath.Join(a.Config.DataDir, servicesDir, stringHash(svcID))
630	configFile := filepath.Join(a.Config.DataDir, serviceConfigDir, stringHash(svcID))
631
632	// Service is never persisted, but we always persist service configs.
633	requireFileIsAbsent(t, svcFile)
634	requireFileIsPresent(t, configFile)
635
636	// Service config file is sane.
637	expectJSONFile(t, configFile, persistedServiceConfig{
638		ServiceID: "web-sidecar-proxy",
639		Defaults: &structs.ServiceConfigResponse{
640			ProxyConfig: map[string]interface{}{
641				"foo":      1,
642				"protocol": "http",
643			},
644			UpstreamIDConfigs: structs.OpaqueUpstreamConfigs{
645				structs.OpaqueUpstreamConfig{
646					Upstream: structs.NewServiceID("redis", nil),
647					Config: map[string]interface{}{
648						"protocol": "tcp",
649					},
650				},
651			},
652		},
653		EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
654	}, resetDefaultsQueryMeta)
655
656	// Verify in memory state.
657	{
658		sidecarService := a.State.Service(structs.NewServiceID("web-sidecar-proxy", nil))
659		require.NotNil(t, sidecarService)
660		require.Equal(t, expectState, sidecarService)
661	}
662
663	// Kill the agent to restart it.
664	a.Shutdown()
665
666	// Kill the server so that it can't phone home and must rely upon the persisted defaults.
667	serverAgent.Shutdown()
668
669	// Should load it back during later start.
670	a2 := StartTestAgent(t, TestAgent{HCL: cfg, DataDir: a.DataDir})
671	defer a2.Shutdown()
672
673	{
674		restored := a.State.Service(structs.NewServiceID("web-sidecar-proxy", nil))
675		require.NotNil(t, restored)
676		require.Equal(t, expectState, restored)
677	}
678
679	// Now remove it.
680	require.NoError(t, a2.RemoveService(structs.NewServiceID("web-sidecar-proxy", nil)))
681	requireFileIsAbsent(t, svcFile)
682	requireFileIsAbsent(t, configFile)
683}
684
685func TestServiceManager_Disabled(t *testing.T) {
686	if testing.Short() {
687		t.Skip("too slow for testing.Short")
688	}
689
690	require := require.New(t)
691
692	a := NewTestAgent(t, "enable_central_service_config = false")
693	defer a.Shutdown()
694
695	testrpc.WaitForLeader(t, a.RPC, "dc1")
696
697	// Register a global proxy and service config
698	testApplyConfigEntries(t, a,
699		&structs.ProxyConfigEntry{
700			Config: map[string]interface{}{
701				"foo": 1,
702			},
703		},
704		&structs.ServiceConfigEntry{
705			Kind:     structs.ServiceDefaults,
706			Name:     "web",
707			Protocol: "http",
708		},
709		&structs.ServiceConfigEntry{
710			Kind:     structs.ServiceDefaults,
711			Name:     "redis",
712			Protocol: "tcp",
713		},
714	)
715
716	// Now register a sidecar proxy. Note we don't use SidecarService here because
717	// that gets resolved earlier in config handling than the AddService call
718	// here.
719	svc := &structs.NodeService{
720		Kind:    structs.ServiceKindConnectProxy,
721		ID:      "web-sidecar-proxy",
722		Service: "web-sidecar-proxy",
723		Port:    21000,
724		Proxy: structs.ConnectProxyConfig{
725			DestinationServiceName: "web",
726			DestinationServiceID:   "web",
727			LocalServiceAddress:    "127.0.0.1",
728			LocalServicePort:       8000,
729			Upstreams: structs.Upstreams{
730				{
731					DestinationName: "redis",
732					LocalBindPort:   5000,
733				},
734			},
735		},
736		EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
737	}
738	require.NoError(a.addServiceFromSource(svc, nil, false, "", ConfigSourceLocal))
739
740	// Verify sidecar got global config loaded
741	sidecarService := a.State.Service(structs.NewServiceID("web-sidecar-proxy", nil))
742	require.NotNil(sidecarService)
743	require.Equal(&structs.NodeService{
744		Kind:            structs.ServiceKindConnectProxy,
745		ID:              "web-sidecar-proxy",
746		Service:         "web-sidecar-proxy",
747		Port:            21000,
748		TaggedAddresses: map[string]structs.ServiceAddress{},
749		Proxy: structs.ConnectProxyConfig{
750			DestinationServiceName: "web",
751			DestinationServiceID:   "web",
752			LocalServiceAddress:    "127.0.0.1",
753			LocalServicePort:       8000,
754			// No config added
755			Upstreams: structs.Upstreams{
756				{
757					DestinationName: "redis",
758					LocalBindPort:   5000,
759					// No config added
760				},
761			},
762		},
763		Weights: &structs.Weights{
764			Passing: 1,
765			Warning: 1,
766		},
767		EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
768	}, sidecarService)
769}
770
771func testApplyConfigEntries(t *testing.T, a *TestAgent, entries ...structs.ConfigEntry) {
772	t.Helper()
773	for _, entry := range entries {
774		args := &structs.ConfigEntryRequest{
775			Datacenter: "dc1",
776			Entry:      entry,
777		}
778		var out bool
779		require.NoError(t, a.RPC("ConfigEntry.Apply", args, &out))
780	}
781}
782
783func requireFileIsAbsent(t *testing.T, file string) {
784	t.Helper()
785	if _, err := os.Stat(file); !os.IsNotExist(err) {
786		t.Fatalf("should not persist")
787	}
788}
789
790func requireFileIsPresent(t *testing.T, file string) {
791	t.Helper()
792	if _, err := os.Stat(file); err != nil {
793		t.Fatalf("err: %v", err)
794	}
795}
796
797func expectJSONFile(t *testing.T, file string, expect interface{}, fixupContentBeforeCompareFn func([]byte) ([]byte, error)) {
798	t.Helper()
799
800	expected, err := json.Marshal(expect)
801	require.NoError(t, err)
802
803	content, err := ioutil.ReadFile(file)
804	require.NoError(t, err)
805
806	if fixupContentBeforeCompareFn != nil {
807		content, err = fixupContentBeforeCompareFn(content)
808		require.NoError(t, err)
809	}
810
811	require.JSONEq(t, string(expected), string(content))
812}
813
814// resetDefaultsQueryMeta will reset the embedded fields from structs.QueryMeta
815// to their zero values in the json object keyed under 'Defaults'.
816func resetDefaultsQueryMeta(content []byte) ([]byte, error) {
817	var raw map[string]interface{}
818	if err := json.Unmarshal(content, &raw); err != nil {
819		return nil, err
820	}
821	def, ok := raw["Defaults"]
822	if !ok {
823		return content, nil
824	}
825
826	rawDef, ok := def.(map[string]interface{})
827	if !ok {
828		return nil, fmt.Errorf("unexpected structure found in 'Defaults' key")
829	}
830
831	qmZero, err := convertToMap(structs.QueryMeta{})
832	if err != nil {
833		return nil, err
834	}
835
836	for k, v := range qmZero {
837		rawDef[k] = v
838	}
839
840	raw["Defaults"] = rawDef
841
842	return json.Marshal(raw)
843}
844
845func convertToMap(v interface{}) (map[string]interface{}, error) {
846	b, err := json.Marshal(v)
847	if err != nil {
848		return nil, err
849	}
850
851	var raw map[string]interface{}
852	if err := json.Unmarshal(b, &raw); err != nil {
853		return nil, err
854	}
855
856	return raw, nil
857}
858
859func Test_mergeServiceConfig_UpstreamOverrides(t *testing.T) {
860	type args struct {
861		defaults *structs.ServiceConfigResponse
862		service  *structs.NodeService
863	}
864	tests := []struct {
865		name string
866		args args
867		want *structs.NodeService
868	}{
869		{
870			name: "new config fields",
871			args: args{
872				defaults: &structs.ServiceConfigResponse{
873					UpstreamIDConfigs: structs.OpaqueUpstreamConfigs{
874						{
875							Upstream: structs.ServiceID{
876								ID:             "zap",
877								EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
878							},
879							Config: map[string]interface{}{
880								"passive_health_check": map[string]interface{}{
881									"Interval":    int64(10),
882									"MaxFailures": int64(2),
883								},
884								"mesh_gateway": map[string]interface{}{
885									"Mode": "local",
886								},
887								"protocol": "grpc",
888							},
889						},
890					},
891				},
892				service: &structs.NodeService{
893					ID:      "foo-proxy",
894					Service: "foo-proxy",
895					Proxy: structs.ConnectProxyConfig{
896						DestinationServiceName: "foo",
897						DestinationServiceID:   "foo",
898						Upstreams: structs.Upstreams{
899							structs.Upstream{
900								DestinationNamespace: "default",
901								DestinationName:      "zap",
902							},
903						},
904					},
905				},
906			},
907			want: &structs.NodeService{
908				ID:      "foo-proxy",
909				Service: "foo-proxy",
910				Proxy: structs.ConnectProxyConfig{
911					DestinationServiceName: "foo",
912					DestinationServiceID:   "foo",
913					Upstreams: structs.Upstreams{
914						structs.Upstream{
915							DestinationNamespace: "default",
916							DestinationName:      "zap",
917							Config: map[string]interface{}{
918								"passive_health_check": map[string]interface{}{
919									"Interval":    int64(10),
920									"MaxFailures": int64(2),
921								},
922								"protocol": "grpc",
923							},
924							MeshGateway: structs.MeshGatewayConfig{
925								Mode: structs.MeshGatewayModeLocal,
926							},
927						},
928					},
929				},
930			},
931		},
932		{
933			name: "remote upstream config expands local upstream list in transparent mode",
934			args: args{
935				defaults: &structs.ServiceConfigResponse{
936					UpstreamIDConfigs: structs.OpaqueUpstreamConfigs{
937						{
938							Upstream: structs.ServiceID{
939								ID:             "zap",
940								EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
941							},
942							Config: map[string]interface{}{
943								"protocol": "grpc",
944							},
945						},
946					},
947				},
948				service: &structs.NodeService{
949					ID:      "foo-proxy",
950					Service: "foo-proxy",
951					Proxy: structs.ConnectProxyConfig{
952						DestinationServiceName: "foo",
953						DestinationServiceID:   "foo",
954						Mode:                   structs.ProxyModeTransparent,
955						TransparentProxy: structs.TransparentProxyConfig{
956							OutboundListenerPort: 10101,
957							DialedDirectly:       true,
958						},
959						Upstreams: structs.Upstreams{
960							structs.Upstream{
961								DestinationNamespace: "default",
962								DestinationName:      "zip",
963								LocalBindPort:        8080,
964								Config: map[string]interface{}{
965									"protocol": "http",
966								},
967							},
968						},
969					},
970				},
971			},
972			want: &structs.NodeService{
973				ID:      "foo-proxy",
974				Service: "foo-proxy",
975				Proxy: structs.ConnectProxyConfig{
976					DestinationServiceName: "foo",
977					DestinationServiceID:   "foo",
978					Mode:                   structs.ProxyModeTransparent,
979					TransparentProxy: structs.TransparentProxyConfig{
980						OutboundListenerPort: 10101,
981						DialedDirectly:       true,
982					},
983					Upstreams: structs.Upstreams{
984						structs.Upstream{
985							DestinationNamespace: "default",
986							DestinationName:      "zip",
987							LocalBindPort:        8080,
988							Config: map[string]interface{}{
989								"protocol": "http",
990							},
991						},
992						structs.Upstream{
993							DestinationNamespace: "default",
994							DestinationName:      "zap",
995							Config: map[string]interface{}{
996								"protocol": "grpc",
997							},
998							CentrallyConfigured: true,
999						},
1000					},
1001				},
1002			},
1003		},
1004		{
1005			name: "remote upstream config not added to local upstream list outside of transparent mode",
1006			args: args{
1007				defaults: &structs.ServiceConfigResponse{
1008					UpstreamIDConfigs: structs.OpaqueUpstreamConfigs{
1009						{
1010							Upstream: structs.ServiceID{
1011								ID:             "zap",
1012								EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
1013							},
1014							Config: map[string]interface{}{
1015								"protocol": "grpc",
1016							},
1017						},
1018					},
1019				},
1020				service: &structs.NodeService{
1021					ID:      "foo-proxy",
1022					Service: "foo-proxy",
1023					Proxy: structs.ConnectProxyConfig{
1024						DestinationServiceName: "foo",
1025						DestinationServiceID:   "foo",
1026						Mode:                   structs.ProxyModeDirect,
1027						Upstreams: structs.Upstreams{
1028							structs.Upstream{
1029								DestinationNamespace: "default",
1030								DestinationName:      "zip",
1031								LocalBindPort:        8080,
1032								Config: map[string]interface{}{
1033									"protocol": "http",
1034								},
1035							},
1036						},
1037					},
1038				},
1039			},
1040			want: &structs.NodeService{
1041				ID:      "foo-proxy",
1042				Service: "foo-proxy",
1043				Proxy: structs.ConnectProxyConfig{
1044					DestinationServiceName: "foo",
1045					DestinationServiceID:   "foo",
1046					Mode:                   structs.ProxyModeDirect,
1047					Upstreams: structs.Upstreams{
1048						structs.Upstream{
1049							DestinationNamespace: "default",
1050							DestinationName:      "zip",
1051							LocalBindPort:        8080,
1052							Config: map[string]interface{}{
1053								"protocol": "http",
1054							},
1055						},
1056					},
1057				},
1058			},
1059		},
1060		{
1061			name: "upstream mode from remote defaults overrides local default",
1062			args: args{
1063				defaults: &structs.ServiceConfigResponse{
1064					UpstreamIDConfigs: structs.OpaqueUpstreamConfigs{
1065						{
1066							Upstream: structs.ServiceID{
1067								ID:             "zap",
1068								EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
1069							},
1070							Config: map[string]interface{}{
1071								"mesh_gateway": map[string]interface{}{
1072									"Mode": "local",
1073								},
1074							},
1075						},
1076					},
1077				},
1078				service: &structs.NodeService{
1079					ID:      "foo-proxy",
1080					Service: "foo-proxy",
1081					Proxy: structs.ConnectProxyConfig{
1082						DestinationServiceName: "foo",
1083						DestinationServiceID:   "foo",
1084						MeshGateway: structs.MeshGatewayConfig{
1085							Mode: structs.MeshGatewayModeRemote,
1086						},
1087						Upstreams: structs.Upstreams{
1088							structs.Upstream{
1089								DestinationNamespace: "default",
1090								DestinationName:      "zap",
1091							},
1092						},
1093					},
1094				},
1095			},
1096			want: &structs.NodeService{
1097				ID:      "foo-proxy",
1098				Service: "foo-proxy",
1099				Proxy: structs.ConnectProxyConfig{
1100					DestinationServiceName: "foo",
1101					DestinationServiceID:   "foo",
1102					MeshGateway: structs.MeshGatewayConfig{
1103						Mode: structs.MeshGatewayModeRemote,
1104					},
1105					Upstreams: structs.Upstreams{
1106						structs.Upstream{
1107							DestinationNamespace: "default",
1108							DestinationName:      "zap",
1109							Config:               map[string]interface{}{},
1110							MeshGateway: structs.MeshGatewayConfig{
1111								Mode: structs.MeshGatewayModeLocal,
1112							},
1113						},
1114					},
1115				},
1116			},
1117		},
1118		{
1119			name: "mode in local upstream config overrides all",
1120			args: args{
1121				defaults: &structs.ServiceConfigResponse{
1122					UpstreamIDConfigs: structs.OpaqueUpstreamConfigs{
1123						{
1124							Upstream: structs.ServiceID{
1125								ID:             "zap",
1126								EnterpriseMeta: *structs.DefaultEnterpriseMeta(),
1127							},
1128							Config: map[string]interface{}{
1129								"mesh_gateway": map[string]interface{}{
1130									"Mode": "local",
1131								},
1132							},
1133						},
1134					},
1135				},
1136				service: &structs.NodeService{
1137					ID:      "foo-proxy",
1138					Service: "foo-proxy",
1139					Proxy: structs.ConnectProxyConfig{
1140						DestinationServiceName: "foo",
1141						DestinationServiceID:   "foo",
1142						MeshGateway: structs.MeshGatewayConfig{
1143							Mode: structs.MeshGatewayModeRemote,
1144						},
1145						Upstreams: structs.Upstreams{
1146							structs.Upstream{
1147								DestinationNamespace: "default",
1148								DestinationName:      "zap",
1149								MeshGateway: structs.MeshGatewayConfig{
1150									Mode: structs.MeshGatewayModeNone,
1151								},
1152							},
1153						},
1154					},
1155				},
1156			},
1157			want: &structs.NodeService{
1158				ID:      "foo-proxy",
1159				Service: "foo-proxy",
1160				Proxy: structs.ConnectProxyConfig{
1161					DestinationServiceName: "foo",
1162					DestinationServiceID:   "foo",
1163					MeshGateway: structs.MeshGatewayConfig{
1164						Mode: structs.MeshGatewayModeRemote,
1165					},
1166					Upstreams: structs.Upstreams{
1167						structs.Upstream{
1168							DestinationNamespace: "default",
1169							DestinationName:      "zap",
1170							Config:               map[string]interface{}{},
1171							MeshGateway: structs.MeshGatewayConfig{
1172								Mode: structs.MeshGatewayModeNone,
1173							},
1174						},
1175					},
1176				},
1177			},
1178		},
1179	}
1180	for _, tt := range tests {
1181		t.Run(tt.name, func(t *testing.T) {
1182			defaultsCopy, err := copystructure.Copy(tt.args.defaults)
1183			require.NoError(t, err)
1184
1185			got, err := mergeServiceConfig(tt.args.defaults, tt.args.service)
1186			require.NoError(t, err)
1187			assert.Equal(t, tt.want, got)
1188
1189			// The input defaults must not be modified by the merge.
1190			// See PR #10647
1191			assert.Equal(t, tt.args.defaults, defaultsCopy)
1192		})
1193	}
1194}
1195
1196func Test_mergeServiceConfig_TransparentProxy(t *testing.T) {
1197	type args struct {
1198		defaults *structs.ServiceConfigResponse
1199		service  *structs.NodeService
1200	}
1201	tests := []struct {
1202		name string
1203		args args
1204		want *structs.NodeService
1205	}{
1206		{
1207			name: "inherit transparent proxy settings",
1208			args: args{
1209				defaults: &structs.ServiceConfigResponse{
1210					Mode: structs.ProxyModeTransparent,
1211					TransparentProxy: structs.TransparentProxyConfig{
1212						OutboundListenerPort: 10101,
1213						DialedDirectly:       true,
1214					},
1215				},
1216				service: &structs.NodeService{
1217					ID:      "foo-proxy",
1218					Service: "foo-proxy",
1219					Proxy: structs.ConnectProxyConfig{
1220						DestinationServiceName: "foo",
1221						DestinationServiceID:   "foo",
1222						Mode:                   structs.ProxyModeDefault,
1223						TransparentProxy:       structs.TransparentProxyConfig{},
1224					},
1225				},
1226			},
1227			want: &structs.NodeService{
1228				ID:      "foo-proxy",
1229				Service: "foo-proxy",
1230				Proxy: structs.ConnectProxyConfig{
1231					DestinationServiceName: "foo",
1232					DestinationServiceID:   "foo",
1233					Mode:                   structs.ProxyModeTransparent,
1234					TransparentProxy: structs.TransparentProxyConfig{
1235						OutboundListenerPort: 10101,
1236						DialedDirectly:       true,
1237					},
1238				},
1239			},
1240		},
1241		{
1242			name: "override transparent proxy settings",
1243			args: args{
1244				defaults: &structs.ServiceConfigResponse{
1245					Mode: structs.ProxyModeTransparent,
1246					TransparentProxy: structs.TransparentProxyConfig{
1247						OutboundListenerPort: 10101,
1248						DialedDirectly:       false,
1249					},
1250				},
1251				service: &structs.NodeService{
1252					ID:      "foo-proxy",
1253					Service: "foo-proxy",
1254					Proxy: structs.ConnectProxyConfig{
1255						DestinationServiceName: "foo",
1256						DestinationServiceID:   "foo",
1257						Mode:                   structs.ProxyModeDirect,
1258						TransparentProxy: structs.TransparentProxyConfig{
1259							OutboundListenerPort: 808,
1260							DialedDirectly:       true,
1261						},
1262					},
1263				},
1264			},
1265			want: &structs.NodeService{
1266				ID:      "foo-proxy",
1267				Service: "foo-proxy",
1268				Proxy: structs.ConnectProxyConfig{
1269					DestinationServiceName: "foo",
1270					DestinationServiceID:   "foo",
1271					Mode:                   structs.ProxyModeDirect,
1272					TransparentProxy: structs.TransparentProxyConfig{
1273						OutboundListenerPort: 808,
1274						DialedDirectly:       true,
1275					},
1276				},
1277			},
1278		},
1279	}
1280	for _, tt := range tests {
1281		t.Run(tt.name, func(t *testing.T) {
1282			defaultsCopy, err := copystructure.Copy(tt.args.defaults)
1283			require.NoError(t, err)
1284
1285			got, err := mergeServiceConfig(tt.args.defaults, tt.args.service)
1286			require.NoError(t, err)
1287			assert.Equal(t, tt.want, got)
1288
1289			// The input defaults must not be modified by the merge.
1290			// See PR #10647
1291			assert.Equal(t, tt.args.defaults, defaultsCopy)
1292		})
1293	}
1294}
1295