1package api
2
3import (
4	crand "crypto/rand"
5	"crypto/tls"
6	"fmt"
7	"io/ioutil"
8	"net"
9	"net/http"
10	"net/url"
11	"os"
12	"path/filepath"
13	"reflect"
14	"runtime"
15	"strings"
16	"testing"
17	"time"
18
19	"github.com/hashicorp/consul/sdk/testutil"
20	"github.com/hashicorp/consul/sdk/testutil/retry"
21	"github.com/stretchr/testify/assert"
22	"github.com/stretchr/testify/require"
23)
24
25type configCallback func(c *Config)
26
27func makeClient(t *testing.T) (*Client, *testutil.TestServer) {
28	return makeClientWithConfig(t, nil, nil)
29}
30
31func makeClientWithoutConnect(t *testing.T) (*Client, *testutil.TestServer) {
32	return makeClientWithConfig(t, nil, func(serverConfig *testutil.TestServerConfig) {
33		serverConfig.Connect = nil
34	})
35}
36
37func makeACLClient(t *testing.T) (*Client, *testutil.TestServer) {
38	return makeClientWithConfig(t, func(clientConfig *Config) {
39		clientConfig.Token = "root"
40	}, func(serverConfig *testutil.TestServerConfig) {
41		serverConfig.PrimaryDatacenter = "dc1"
42		serverConfig.ACL.Tokens.Master = "root"
43		serverConfig.ACL.Tokens.Agent = "root"
44		serverConfig.ACL.Enabled = true
45		serverConfig.ACL.DefaultPolicy = "deny"
46	})
47}
48
49func makeClientWithConfig(
50	t *testing.T,
51	cb1 configCallback,
52	cb2 testutil.ServerConfigCallback) (*Client, *testutil.TestServer) {
53
54	// Make client config
55	conf := DefaultConfig()
56	if cb1 != nil {
57		cb1(conf)
58	}
59
60	// Create server
61	var server *testutil.TestServer
62	var err error
63	retry.RunWith(retry.ThreeTimes(), t, func(r *retry.R) {
64		server, err = testutil.NewTestServerConfigT(t, cb2)
65		if err != nil {
66			r.Fatalf("Failed to start server: %v", err.Error())
67		}
68	})
69	if server.Config.Bootstrap {
70		server.WaitForLeader(t)
71	}
72
73	conf.Address = server.HTTPAddr
74
75	// Create client
76	client, err := NewClient(conf)
77	if err != nil {
78		server.Stop()
79		t.Fatalf("err: %v", err)
80	}
81
82	return client, server
83}
84
85func testKey() string {
86	buf := make([]byte, 16)
87	if _, err := crand.Read(buf); err != nil {
88		panic(fmt.Errorf("Failed to read random bytes: %v", err))
89	}
90
91	return fmt.Sprintf("%08x-%04x-%04x-%04x-%12x",
92		buf[0:4],
93		buf[4:6],
94		buf[6:8],
95		buf[8:10],
96		buf[10:16])
97}
98
99func testNodeServiceCheckRegistrations(t *testing.T, client *Client, datacenter string) {
100	t.Helper()
101
102	registrations := map[string]*CatalogRegistration{
103		"Node foo": {
104			Datacenter: datacenter,
105			Node:       "foo",
106			ID:         "e0155642-135d-4739-9853-a1ee6c9f945b",
107			Address:    "127.0.0.2",
108			TaggedAddresses: map[string]string{
109				"lan": "127.0.0.2",
110				"wan": "198.18.0.2",
111			},
112			NodeMeta: map[string]string{
113				"env": "production",
114				"os":  "linux",
115			},
116			Checks: HealthChecks{
117				&HealthCheck{
118					Node:    "foo",
119					CheckID: "foo:alive",
120					Name:    "foo-liveness",
121					Status:  HealthPassing,
122					Notes:   "foo is alive and well",
123				},
124				&HealthCheck{
125					Node:    "foo",
126					CheckID: "foo:ssh",
127					Name:    "foo-remote-ssh",
128					Status:  HealthPassing,
129					Notes:   "foo has ssh access",
130				},
131			},
132		},
133		"Service redis v1 on foo": {
134			Datacenter:     datacenter,
135			Node:           "foo",
136			SkipNodeUpdate: true,
137			Service: &AgentService{
138				Kind:    ServiceKindTypical,
139				ID:      "redisV1",
140				Service: "redis",
141				Tags:    []string{"v1"},
142				Meta:    map[string]string{"version": "1"},
143				Port:    1234,
144				Address: "198.18.1.2",
145			},
146			Checks: HealthChecks{
147				&HealthCheck{
148					Node:        "foo",
149					CheckID:     "foo:redisV1",
150					Name:        "redis-liveness",
151					Status:      HealthPassing,
152					Notes:       "redis v1 is alive and well",
153					ServiceID:   "redisV1",
154					ServiceName: "redis",
155				},
156			},
157		},
158		"Service redis v2 on foo": {
159			Datacenter:     datacenter,
160			Node:           "foo",
161			SkipNodeUpdate: true,
162			Service: &AgentService{
163				Kind:    ServiceKindTypical,
164				ID:      "redisV2",
165				Service: "redis",
166				Tags:    []string{"v2"},
167				Meta:    map[string]string{"version": "2"},
168				Port:    1235,
169				Address: "198.18.1.2",
170			},
171			Checks: HealthChecks{
172				&HealthCheck{
173					Node:        "foo",
174					CheckID:     "foo:redisV2",
175					Name:        "redis-v2-liveness",
176					Status:      HealthPassing,
177					Notes:       "redis v2 is alive and well",
178					ServiceID:   "redisV2",
179					ServiceName: "redis",
180				},
181			},
182		},
183		"Node bar": {
184			Datacenter: datacenter,
185			Node:       "bar",
186			ID:         "c6e7a976-8f4f-44b5-bdd3-631be7e8ecac",
187			Address:    "127.0.0.3",
188			TaggedAddresses: map[string]string{
189				"lan": "127.0.0.3",
190				"wan": "198.18.0.3",
191			},
192			NodeMeta: map[string]string{
193				"env": "production",
194				"os":  "windows",
195			},
196			Checks: HealthChecks{
197				&HealthCheck{
198					Node:    "bar",
199					CheckID: "bar:alive",
200					Name:    "bar-liveness",
201					Status:  HealthPassing,
202					Notes:   "bar is alive and well",
203				},
204			},
205		},
206		"Service redis v1 on bar": {
207			Datacenter:     datacenter,
208			Node:           "bar",
209			SkipNodeUpdate: true,
210			Service: &AgentService{
211				Kind:    ServiceKindTypical,
212				ID:      "redisV1",
213				Service: "redis",
214				Tags:    []string{"v1"},
215				Meta:    map[string]string{"version": "1"},
216				Port:    1234,
217				Address: "198.18.1.3",
218			},
219			Checks: HealthChecks{
220				&HealthCheck{
221					Node:        "bar",
222					CheckID:     "bar:redisV1",
223					Name:        "redis-liveness",
224					Status:      HealthPassing,
225					Notes:       "redis v1 is alive and well",
226					ServiceID:   "redisV1",
227					ServiceName: "redis",
228				},
229			},
230		},
231		"Service web v1 on bar": {
232			Datacenter:     datacenter,
233			Node:           "bar",
234			SkipNodeUpdate: true,
235			Service: &AgentService{
236				Kind:    ServiceKindTypical,
237				ID:      "webV1",
238				Service: "web",
239				Tags:    []string{"v1", "connect"},
240				Meta:    map[string]string{"version": "1", "connect": "enabled"},
241				Port:    443,
242				Address: "198.18.1.4",
243				Connect: &AgentServiceConnect{Native: true},
244			},
245			Checks: HealthChecks{
246				&HealthCheck{
247					Node:        "bar",
248					CheckID:     "bar:web:v1",
249					Name:        "web-v1-liveness",
250					Status:      HealthPassing,
251					Notes:       "web connect v1 is alive and well",
252					ServiceID:   "webV1",
253					ServiceName: "web",
254				},
255			},
256		},
257		"Node baz": {
258			Datacenter: datacenter,
259			Node:       "baz",
260			ID:         "12f96b27-a7b0-47bd-add7-044a2bfc7bfb",
261			Address:    "127.0.0.4",
262			TaggedAddresses: map[string]string{
263				"lan": "127.0.0.4",
264			},
265			NodeMeta: map[string]string{
266				"env": "qa",
267				"os":  "linux",
268			},
269			Checks: HealthChecks{
270				&HealthCheck{
271					Node:    "baz",
272					CheckID: "baz:alive",
273					Name:    "baz-liveness",
274					Status:  HealthPassing,
275					Notes:   "baz is alive and well",
276				},
277				&HealthCheck{
278					Node:    "baz",
279					CheckID: "baz:ssh",
280					Name:    "baz-remote-ssh",
281					Status:  HealthPassing,
282					Notes:   "baz has ssh access",
283				},
284			},
285		},
286		"Service web v1 on baz": {
287			Datacenter:     datacenter,
288			Node:           "baz",
289			SkipNodeUpdate: true,
290			Service: &AgentService{
291				Kind:    ServiceKindTypical,
292				ID:      "webV1",
293				Service: "web",
294				Tags:    []string{"v1", "connect"},
295				Meta:    map[string]string{"version": "1", "connect": "enabled"},
296				Port:    443,
297				Address: "198.18.1.4",
298				Connect: &AgentServiceConnect{Native: true},
299			},
300			Checks: HealthChecks{
301				&HealthCheck{
302					Node:        "baz",
303					CheckID:     "baz:web:v1",
304					Name:        "web-v1-liveness",
305					Status:      HealthPassing,
306					Notes:       "web connect v1 is alive and well",
307					ServiceID:   "webV1",
308					ServiceName: "web",
309				},
310			},
311		},
312		"Service web v2 on baz": {
313			Datacenter:     datacenter,
314			Node:           "baz",
315			SkipNodeUpdate: true,
316			Service: &AgentService{
317				Kind:    ServiceKindTypical,
318				ID:      "webV2",
319				Service: "web",
320				Tags:    []string{"v2", "connect"},
321				Meta:    map[string]string{"version": "2", "connect": "enabled"},
322				Port:    8443,
323				Address: "198.18.1.4",
324				Connect: &AgentServiceConnect{Native: true},
325			},
326			Checks: HealthChecks{
327				&HealthCheck{
328					Node:        "baz",
329					CheckID:     "baz:web:v2",
330					Name:        "web-v2-liveness",
331					Status:      HealthPassing,
332					Notes:       "web connect v2 is alive and well",
333					ServiceID:   "webV2",
334					ServiceName: "web",
335				},
336			},
337		},
338		"Service critical on baz": {
339			Datacenter:     datacenter,
340			Node:           "baz",
341			SkipNodeUpdate: true,
342			Service: &AgentService{
343				Kind:    ServiceKindTypical,
344				ID:      "criticalV2",
345				Service: "critical",
346				Tags:    []string{"v2"},
347				Meta:    map[string]string{"version": "2"},
348				Port:    8080,
349				Address: "198.18.1.4",
350			},
351			Checks: HealthChecks{
352				&HealthCheck{
353					Node:        "baz",
354					CheckID:     "baz:critical:v2",
355					Name:        "critical-v2-liveness",
356					Status:      HealthCritical,
357					Notes:       "critical v2 is in the critical state",
358					ServiceID:   "criticalV2",
359					ServiceName: "critical",
360				},
361			},
362		},
363		"Service warning on baz": {
364			Datacenter:     datacenter,
365			Node:           "baz",
366			SkipNodeUpdate: true,
367			Service: &AgentService{
368				Kind:    ServiceKindTypical,
369				ID:      "warningV2",
370				Service: "warning",
371				Tags:    []string{"v2"},
372				Meta:    map[string]string{"version": "2"},
373				Port:    8081,
374				Address: "198.18.1.4",
375			},
376			Checks: HealthChecks{
377				&HealthCheck{
378					Node:        "baz",
379					CheckID:     "baz:warning:v2",
380					Name:        "warning-v2-liveness",
381					Status:      HealthWarning,
382					Notes:       "warning v2 is in the warning state",
383					ServiceID:   "warningV2",
384					ServiceName: "warning",
385				},
386			},
387		},
388	}
389
390	catalog := client.Catalog()
391	for name, reg := range registrations {
392		_, err := catalog.Register(reg, nil)
393		require.NoError(t, err, "Failed catalog registration for %q: %v", name, err)
394	}
395}
396
397func TestAPI_DefaultConfig_env(t *testing.T) {
398	// t.Parallel() // DO NOT ENABLE !!!
399	// do not enable t.Parallel for this test since it modifies global state
400	// (environment) which has non-deterministic effects on the other tests
401	// which derive their default configuration from the environment
402
403	addr := "1.2.3.4:5678"
404	token := "abcd1234"
405	auth := "username:password"
406
407	os.Setenv(HTTPAddrEnvName, addr)
408	defer os.Setenv(HTTPAddrEnvName, "")
409	os.Setenv(HTTPTokenEnvName, token)
410	defer os.Setenv(HTTPTokenEnvName, "")
411	os.Setenv(HTTPAuthEnvName, auth)
412	defer os.Setenv(HTTPAuthEnvName, "")
413	os.Setenv(HTTPSSLEnvName, "1")
414	defer os.Setenv(HTTPSSLEnvName, "")
415	os.Setenv(HTTPCAFile, "ca.pem")
416	defer os.Setenv(HTTPCAFile, "")
417	os.Setenv(HTTPCAPath, "certs/")
418	defer os.Setenv(HTTPCAPath, "")
419	os.Setenv(HTTPClientCert, "client.crt")
420	defer os.Setenv(HTTPClientCert, "")
421	os.Setenv(HTTPClientKey, "client.key")
422	defer os.Setenv(HTTPClientKey, "")
423	os.Setenv(HTTPTLSServerName, "consul.test")
424	defer os.Setenv(HTTPTLSServerName, "")
425	os.Setenv(HTTPSSLVerifyEnvName, "0")
426	defer os.Setenv(HTTPSSLVerifyEnvName, "")
427
428	for i, config := range []*Config{
429		DefaultConfig(),
430		DefaultConfigWithLogger(testutil.Logger(t)),
431		DefaultNonPooledConfig(),
432	} {
433		if config.Address != addr {
434			t.Errorf("expected %q to be %q", config.Address, addr)
435		}
436		if config.Token != token {
437			t.Errorf("expected %q to be %q", config.Token, token)
438		}
439		if config.HttpAuth == nil {
440			t.Fatalf("expected HttpAuth to be enabled")
441		}
442		if config.HttpAuth.Username != "username" {
443			t.Errorf("expected %q to be %q", config.HttpAuth.Username, "username")
444		}
445		if config.HttpAuth.Password != "password" {
446			t.Errorf("expected %q to be %q", config.HttpAuth.Password, "password")
447		}
448		if config.Scheme != "https" {
449			t.Errorf("expected %q to be %q", config.Scheme, "https")
450		}
451		if config.TLSConfig.CAFile != "ca.pem" {
452			t.Errorf("expected %q to be %q", config.TLSConfig.CAFile, "ca.pem")
453		}
454		if config.TLSConfig.CAPath != "certs/" {
455			t.Errorf("expected %q to be %q", config.TLSConfig.CAPath, "certs/")
456		}
457		if config.TLSConfig.CertFile != "client.crt" {
458			t.Errorf("expected %q to be %q", config.TLSConfig.CertFile, "client.crt")
459		}
460		if config.TLSConfig.KeyFile != "client.key" {
461			t.Errorf("expected %q to be %q", config.TLSConfig.KeyFile, "client.key")
462		}
463		if config.TLSConfig.Address != "consul.test" {
464			t.Errorf("expected %q to be %q", config.TLSConfig.Address, "consul.test")
465		}
466		if !config.TLSConfig.InsecureSkipVerify {
467			t.Errorf("expected SSL verification to be off")
468		}
469
470		// Use keep alives as a check for whether pooling is on or off.
471		if pooled := i != 2; pooled {
472			if config.Transport.DisableKeepAlives != false {
473				t.Errorf("expected keep alives to be enabled")
474			}
475		} else {
476			if config.Transport.DisableKeepAlives != true {
477				t.Errorf("expected keep alives to be disabled")
478			}
479		}
480	}
481}
482
483func TestAPI_SetupTLSConfig(t *testing.T) {
484	t.Parallel()
485	// A default config should result in a clean default client config.
486	tlsConfig := &TLSConfig{}
487	cc, err := SetupTLSConfig(tlsConfig)
488	if err != nil {
489		t.Fatalf("err: %v", err)
490	}
491	expected := &tls.Config{RootCAs: cc.RootCAs}
492	if !reflect.DeepEqual(cc, expected) {
493		t.Fatalf("bad: \n%v, \n%v", cc, expected)
494	}
495
496	// Try some address variations with and without ports.
497	tlsConfig.Address = "127.0.0.1"
498	cc, err = SetupTLSConfig(tlsConfig)
499	if err != nil {
500		t.Fatalf("err: %v", err)
501	}
502	expected.ServerName = "127.0.0.1"
503	if !reflect.DeepEqual(cc, expected) {
504		t.Fatalf("bad: %v", cc)
505	}
506
507	tlsConfig.Address = "127.0.0.1:80"
508	cc, err = SetupTLSConfig(tlsConfig)
509	if err != nil {
510		t.Fatalf("err: %v", err)
511	}
512	expected.ServerName = "127.0.0.1"
513	if !reflect.DeepEqual(cc, expected) {
514		t.Fatalf("bad: %v", cc)
515	}
516
517	tlsConfig.Address = "demo.consul.io:80"
518	cc, err = SetupTLSConfig(tlsConfig)
519	if err != nil {
520		t.Fatalf("err: %v", err)
521	}
522	expected.ServerName = "demo.consul.io"
523	if !reflect.DeepEqual(cc, expected) {
524		t.Fatalf("bad: %v", cc)
525	}
526
527	tlsConfig.Address = "[2001:db8:a0b:12f0::1]"
528	cc, err = SetupTLSConfig(tlsConfig)
529	if err != nil {
530		t.Fatalf("err: %v", err)
531	}
532	expected.ServerName = "[2001:db8:a0b:12f0::1]"
533	if !reflect.DeepEqual(cc, expected) {
534		t.Fatalf("bad: %v", cc)
535	}
536
537	tlsConfig.Address = "[2001:db8:a0b:12f0::1]:80"
538	cc, err = SetupTLSConfig(tlsConfig)
539	if err != nil {
540		t.Fatalf("err: %v", err)
541	}
542	expected.ServerName = "2001:db8:a0b:12f0::1"
543	if !reflect.DeepEqual(cc, expected) {
544		t.Fatalf("bad: %v", cc)
545	}
546
547	// Skip verification.
548	tlsConfig.InsecureSkipVerify = true
549	cc, err = SetupTLSConfig(tlsConfig)
550	if err != nil {
551		t.Fatalf("err: %v", err)
552	}
553	expected.InsecureSkipVerify = true
554	if !reflect.DeepEqual(cc, expected) {
555		t.Fatalf("bad: %v", cc)
556	}
557
558	// Make a new config that hits all the file parsers.
559	tlsConfig = &TLSConfig{
560		CertFile: "../test/hostname/Alice.crt",
561		KeyFile:  "../test/hostname/Alice.key",
562		CAFile:   "../test/hostname/CertAuth.crt",
563	}
564	cc, err = SetupTLSConfig(tlsConfig)
565	if err != nil {
566		t.Fatalf("err: %v", err)
567	}
568	if len(cc.Certificates) != 1 {
569		t.Fatalf("missing certificate: %v", cc.Certificates)
570	}
571	if cc.RootCAs == nil {
572		t.Fatalf("didn't load root CAs")
573	}
574
575	// Use a directory to load the certs instead
576	cc, err = SetupTLSConfig(&TLSConfig{
577		CAPath: "../test/ca_path",
578	})
579	if err != nil {
580		t.Fatalf("err: %v", err)
581	}
582	if len(cc.RootCAs.Subjects()) != 2 {
583		t.Fatalf("didn't load root CAs")
584	}
585
586	// Load certs in-memory
587	certPEM, err := ioutil.ReadFile("../test/hostname/Alice.crt")
588	if err != nil {
589		t.Fatalf("err: %v", err)
590	}
591	keyPEM, err := ioutil.ReadFile("../test/hostname/Alice.key")
592	if err != nil {
593		t.Fatalf("err: %v", err)
594	}
595	caPEM, err := ioutil.ReadFile("../test/hostname/CertAuth.crt")
596	if err != nil {
597		t.Fatalf("err: %v", err)
598	}
599
600	// Setup config with in-memory certs
601	cc, err = SetupTLSConfig(&TLSConfig{
602		CertPEM: certPEM,
603		KeyPEM:  keyPEM,
604		CAPem:   caPEM,
605	})
606	if err != nil {
607		t.Fatalf("err: %v", err)
608	}
609	if len(cc.Certificates) != 1 {
610		t.Fatalf("missing certificate: %v", cc.Certificates)
611	}
612	if cc.RootCAs == nil {
613		t.Fatalf("didn't load root CAs")
614	}
615}
616
617func TestAPI_ClientTLSOptions(t *testing.T) {
618	t.Parallel()
619	// Start a server that verifies incoming HTTPS connections
620	_, srvVerify := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
621		conf.CAFile = "../test/client_certs/rootca.crt"
622		conf.CertFile = "../test/client_certs/server.crt"
623		conf.KeyFile = "../test/client_certs/server.key"
624		conf.VerifyIncomingHTTPS = true
625	})
626	defer srvVerify.Stop()
627
628	// Start a server without VerifyIncomingHTTPS
629	_, srvNoVerify := makeClientWithConfig(t, nil, func(conf *testutil.TestServerConfig) {
630		conf.CAFile = "../test/client_certs/rootca.crt"
631		conf.CertFile = "../test/client_certs/server.crt"
632		conf.KeyFile = "../test/client_certs/server.key"
633		conf.VerifyIncomingHTTPS = false
634	})
635	defer srvNoVerify.Stop()
636
637	// Client without a cert
638	t.Run("client without cert, validation", func(t *testing.T) {
639		client, err := NewClient(&Config{
640			Address: srvVerify.HTTPSAddr,
641			Scheme:  "https",
642			TLSConfig: TLSConfig{
643				Address: "consul.test",
644				CAFile:  "../test/client_certs/rootca.crt",
645			},
646		})
647		if err != nil {
648			t.Fatal(err)
649		}
650
651		// Should fail
652		_, err = client.Agent().Self()
653		if err == nil || !strings.Contains(err.Error(), "bad certificate") {
654			t.Fatal(err)
655		}
656	})
657
658	// Client with a valid cert
659	t.Run("client with cert, validation", func(t *testing.T) {
660		client, err := NewClient(&Config{
661			Address: srvVerify.HTTPSAddr,
662			Scheme:  "https",
663			TLSConfig: TLSConfig{
664				Address:  "consul.test",
665				CAFile:   "../test/client_certs/rootca.crt",
666				CertFile: "../test/client_certs/client.crt",
667				KeyFile:  "../test/client_certs/client.key",
668			},
669		})
670		if err != nil {
671			t.Fatal(err)
672		}
673
674		// Should succeed
675		_, err = client.Agent().Self()
676		if err != nil {
677			t.Fatal(err)
678		}
679	})
680
681	// Client without a cert
682	t.Run("client without cert, no validation", func(t *testing.T) {
683		client, err := NewClient(&Config{
684			Address: srvNoVerify.HTTPSAddr,
685			Scheme:  "https",
686			TLSConfig: TLSConfig{
687				Address: "consul.test",
688				CAFile:  "../test/client_certs/rootca.crt",
689			},
690		})
691		if err != nil {
692			t.Fatal(err)
693		}
694
695		// Should succeed
696		_, err = client.Agent().Self()
697		if err != nil {
698			t.Fatal(err)
699		}
700	})
701
702	// Client with a valid cert
703	t.Run("client with cert, no validation", func(t *testing.T) {
704		client, err := NewClient(&Config{
705			Address: srvNoVerify.HTTPSAddr,
706			Scheme:  "https",
707			TLSConfig: TLSConfig{
708				Address:  "consul.test",
709				CAFile:   "../test/client_certs/rootca.crt",
710				CertFile: "../test/client_certs/client.crt",
711				KeyFile:  "../test/client_certs/client.key",
712			},
713		})
714		if err != nil {
715			t.Fatal(err)
716		}
717
718		// Should succeed
719		_, err = client.Agent().Self()
720		if err != nil {
721			t.Fatal(err)
722		}
723	})
724}
725
726func TestAPI_SetQueryOptions(t *testing.T) {
727	t.Parallel()
728	c, s := makeClient(t)
729	defer s.Stop()
730
731	assert := assert.New(t)
732
733	r := c.newRequest("GET", "/v1/kv/foo")
734	q := &QueryOptions{
735		Namespace:         "operator",
736		Datacenter:        "foo",
737		AllowStale:        true,
738		RequireConsistent: true,
739		WaitIndex:         1000,
740		WaitTime:          100 * time.Second,
741		Token:             "12345",
742		Near:              "nodex",
743		LocalOnly:         true,
744	}
745	r.setQueryOptions(q)
746
747	if r.params.Get("ns") != "operator" {
748		t.Fatalf("bad: %v", r.params)
749	}
750	if r.params.Get("dc") != "foo" {
751		t.Fatalf("bad: %v", r.params)
752	}
753	if _, ok := r.params["stale"]; !ok {
754		t.Fatalf("bad: %v", r.params)
755	}
756	if _, ok := r.params["consistent"]; !ok {
757		t.Fatalf("bad: %v", r.params)
758	}
759	if r.params.Get("index") != "1000" {
760		t.Fatalf("bad: %v", r.params)
761	}
762	if r.params.Get("wait") != "100000ms" {
763		t.Fatalf("bad: %v", r.params)
764	}
765	if r.header.Get("X-Consul-Token") != "12345" {
766		t.Fatalf("bad: %v", r.header)
767	}
768	if r.params.Get("near") != "nodex" {
769		t.Fatalf("bad: %v", r.params)
770	}
771	if r.params.Get("local-only") != "true" {
772		t.Fatalf("bad: %v", r.params)
773	}
774	assert.Equal("", r.header.Get("Cache-Control"))
775
776	r = c.newRequest("GET", "/v1/kv/foo")
777	q = &QueryOptions{
778		UseCache:     true,
779		MaxAge:       30 * time.Second,
780		StaleIfError: 345678 * time.Millisecond, // Fractional seconds should be rounded
781	}
782	r.setQueryOptions(q)
783
784	_, ok := r.params["cached"]
785	assert.True(ok)
786	assert.Equal("max-age=30, stale-if-error=346", r.header.Get("Cache-Control"))
787}
788
789func TestAPI_SetWriteOptions(t *testing.T) {
790	t.Parallel()
791	c, s := makeClient(t)
792	defer s.Stop()
793
794	r := c.newRequest("GET", "/v1/kv/foo")
795	q := &WriteOptions{
796		Namespace:  "operator",
797		Datacenter: "foo",
798		Token:      "23456",
799	}
800	r.setWriteOptions(q)
801	if r.params.Get("ns") != "operator" {
802		t.Fatalf("bad: %v", r.params)
803	}
804	if r.params.Get("dc") != "foo" {
805		t.Fatalf("bad: %v", r.params)
806	}
807	if r.header.Get("X-Consul-Token") != "23456" {
808		t.Fatalf("bad: %v", r.header)
809	}
810}
811
812func TestAPI_Headers(t *testing.T) {
813	t.Parallel()
814
815	var request *http.Request
816	c, s := makeClientWithConfig(t, func(c *Config) {
817		transport := http.DefaultTransport.(*http.Transport).Clone()
818		transport.Proxy = func(r *http.Request) (*url.URL, error) {
819			// Keep track of the last request sent
820			request = r
821			return nil, nil
822		}
823		c.Transport = transport
824	}, nil)
825	defer s.Stop()
826
827	if len(c.Headers()) != 0 {
828		t.Fatalf("expected headers to be empty: %v", c.Headers())
829	}
830
831	c.AddHeader("Hello", "World")
832	r := c.newRequest("GET", "/v1/kv/foo")
833
834	if r.header.Get("Hello") != "World" {
835		t.Fatalf("Hello header not set : %v", r.header)
836	}
837
838	c.SetHeaders(http.Header{
839		"Auth": []string{"Token"},
840	})
841
842	r = c.newRequest("GET", "/v1/kv/foo")
843	if r.header.Get("Hello") != "" {
844		t.Fatalf("Hello header should not be set: %v", r.header)
845	}
846
847	if r.header.Get("Auth") != "Token" {
848		t.Fatalf("Auth header not set: %v", r.header)
849	}
850
851	kv := c.KV()
852	_, err := kv.Put(&KVPair{Key: "test-headers", Value: []byte("foo")}, nil)
853	require.NoError(t, err)
854	require.Equal(t, "application/octet-stream", request.Header.Get("Content-Type"))
855
856	_, _, err = kv.Get("test-headers", nil)
857	require.NoError(t, err)
858	require.Equal(t, "", request.Header.Get("Content-Type"))
859
860	_, err = kv.Delete("test-headers", nil)
861	require.NoError(t, err)
862	require.Equal(t, "", request.Header.Get("Content-Type"))
863
864	err = c.Snapshot().Restore(nil, strings.NewReader("foo"))
865	require.Error(t, err)
866	require.Equal(t, "application/octet-stream", request.Header.Get("Content-Type"))
867
868	_, err = c.ACL().RulesTranslate(strings.NewReader(`
869	agent "" {
870	  policy = "read"
871	}
872	`))
873	// ACL support is disabled
874	require.Error(t, err)
875	require.Equal(t, "text/plain", request.Header.Get("Content-Type"))
876
877	_, _, err = c.Event().Fire(&UserEvent{
878		Name:    "test",
879		Payload: []byte("foo"),
880	}, nil)
881	require.NoError(t, err)
882	require.Equal(t, "application/octet-stream", request.Header.Get("Content-Type"))
883}
884
885func TestAPI_RequestToHTTP(t *testing.T) {
886	t.Parallel()
887	c, s := makeClient(t)
888	defer s.Stop()
889
890	r := c.newRequest("DELETE", "/v1/kv/foo")
891	q := &QueryOptions{
892		Datacenter: "foo",
893	}
894	r.setQueryOptions(q)
895	req, err := r.toHTTP()
896	if err != nil {
897		t.Fatalf("err: %v", err)
898	}
899
900	if req.Method != "DELETE" {
901		t.Fatalf("bad: %v", req)
902	}
903	if req.URL.RequestURI() != "/v1/kv/foo?dc=foo" {
904		t.Fatalf("bad: %v", req)
905	}
906}
907
908func TestAPI_ParseQueryMeta(t *testing.T) {
909	t.Parallel()
910	resp := &http.Response{
911		Header: make(map[string][]string),
912	}
913	resp.Header.Set("X-Consul-Index", "12345")
914	resp.Header.Set("X-Consul-LastContact", "80")
915	resp.Header.Set("X-Consul-KnownLeader", "true")
916	resp.Header.Set("X-Consul-Translate-Addresses", "true")
917	resp.Header.Set("X-Consul-Default-ACL-Policy", "deny")
918
919	qm := &QueryMeta{}
920	if err := parseQueryMeta(resp, qm); err != nil {
921		t.Fatalf("err: %v", err)
922	}
923
924	if qm.LastIndex != 12345 {
925		t.Fatalf("Bad: %v", qm)
926	}
927	if qm.LastContact != 80*time.Millisecond {
928		t.Fatalf("Bad: %v", qm)
929	}
930	if !qm.KnownLeader {
931		t.Fatalf("Bad: %v", qm)
932	}
933	if !qm.AddressTranslationEnabled {
934		t.Fatalf("Bad: %v", qm)
935	}
936	if qm.DefaultACLPolicy != "deny" {
937		t.Fatalf("Bad: %v", qm)
938	}
939}
940
941func TestAPI_UnixSocket(t *testing.T) {
942	t.Parallel()
943	if runtime.GOOS == "windows" {
944		t.SkipNow()
945	}
946
947	tempDir := testutil.TempDir(t, "consul")
948	socket := filepath.Join(tempDir, "test.sock")
949
950	c, s := makeClientWithConfig(t, func(c *Config) {
951		c.Address = "unix://" + socket
952	}, func(c *testutil.TestServerConfig) {
953		c.Addresses = &testutil.TestAddressConfig{
954			HTTP: "unix://" + socket,
955		}
956	})
957	defer s.Stop()
958
959	agent := c.Agent()
960
961	info, err := agent.Self()
962	if err != nil {
963		t.Fatalf("err: %s", err)
964	}
965	if info["Config"]["NodeName"].(string) == "" {
966		t.Fatalf("bad: %v", info)
967	}
968}
969
970func TestAPI_durToMsec(t *testing.T) {
971	t.Parallel()
972	if ms := durToMsec(0); ms != "0ms" {
973		t.Fatalf("bad: %s", ms)
974	}
975
976	if ms := durToMsec(time.Millisecond); ms != "1ms" {
977		t.Fatalf("bad: %s", ms)
978	}
979
980	if ms := durToMsec(time.Microsecond); ms != "1ms" {
981		t.Fatalf("bad: %s", ms)
982	}
983
984	if ms := durToMsec(5 * time.Millisecond); ms != "5ms" {
985		t.Fatalf("bad: %s", ms)
986	}
987}
988
989func TestAPI_IsRetryableError(t *testing.T) {
990	t.Parallel()
991	if IsRetryableError(nil) {
992		t.Fatal("should not be a retryable error")
993	}
994
995	if IsRetryableError(fmt.Errorf("not the error you are looking for")) {
996		t.Fatal("should not be a retryable error")
997	}
998
999	if !IsRetryableError(fmt.Errorf(serverError)) {
1000		t.Fatal("should be a retryable error")
1001	}
1002
1003	if !IsRetryableError(&net.OpError{Err: fmt.Errorf("network conn error")}) {
1004		t.Fatal("should be a retryable error")
1005	}
1006}
1007
1008func TestAPI_GenerateEnv(t *testing.T) {
1009	t.Parallel()
1010
1011	c := &Config{
1012		Address:   "127.0.0.1:8500",
1013		Token:     "test",
1014		TokenFile: "test.file",
1015		Scheme:    "http",
1016		TLSConfig: TLSConfig{
1017			CAFile:             "",
1018			CAPath:             "",
1019			CertFile:           "",
1020			KeyFile:            "",
1021			Address:            "",
1022			InsecureSkipVerify: true,
1023		},
1024	}
1025
1026	expected := []string{
1027		"CONSUL_HTTP_ADDR=127.0.0.1:8500",
1028		"CONSUL_HTTP_TOKEN=test",
1029		"CONSUL_HTTP_TOKEN_FILE=test.file",
1030		"CONSUL_HTTP_SSL=false",
1031		"CONSUL_CACERT=",
1032		"CONSUL_CAPATH=",
1033		"CONSUL_CLIENT_CERT=",
1034		"CONSUL_CLIENT_KEY=",
1035		"CONSUL_TLS_SERVER_NAME=",
1036		"CONSUL_HTTP_SSL_VERIFY=false",
1037		"CONSUL_HTTP_AUTH=",
1038	}
1039
1040	require.Equal(t, expected, c.GenerateEnv())
1041}
1042
1043func TestAPI_GenerateEnvHTTPS(t *testing.T) {
1044	t.Parallel()
1045
1046	c := &Config{
1047		Address:   "127.0.0.1:8500",
1048		Token:     "test",
1049		TokenFile: "test.file",
1050		Scheme:    "https",
1051		TLSConfig: TLSConfig{
1052			CAFile:             "/var/consul/ca.crt",
1053			CAPath:             "/var/consul/ca.dir",
1054			CertFile:           "/var/consul/server.crt",
1055			KeyFile:            "/var/consul/ssl/server.key",
1056			Address:            "127.0.0.1:8500",
1057			InsecureSkipVerify: false,
1058		},
1059		HttpAuth: &HttpBasicAuth{
1060			Username: "user",
1061			Password: "password",
1062		},
1063	}
1064
1065	expected := []string{
1066		"CONSUL_HTTP_ADDR=127.0.0.1:8500",
1067		"CONSUL_HTTP_TOKEN=test",
1068		"CONSUL_HTTP_TOKEN_FILE=test.file",
1069		"CONSUL_HTTP_SSL=true",
1070		"CONSUL_CACERT=/var/consul/ca.crt",
1071		"CONSUL_CAPATH=/var/consul/ca.dir",
1072		"CONSUL_CLIENT_CERT=/var/consul/server.crt",
1073		"CONSUL_CLIENT_KEY=/var/consul/ssl/server.key",
1074		"CONSUL_TLS_SERVER_NAME=127.0.0.1:8500",
1075		"CONSUL_HTTP_SSL_VERIFY=true",
1076		"CONSUL_HTTP_AUTH=user:password",
1077	}
1078
1079	require.Equal(t, expected, c.GenerateEnv())
1080}
1081