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