1package agent
2
3import (
4	"bytes"
5	"encoding/base64"
6	"io/ioutil"
7	"os"
8	"path/filepath"
9	"reflect"
10	"testing"
11	"time"
12)
13
14func TestConfigBindAddrParts(t *testing.T) {
15	testCases := []struct {
16		Value string
17		IP    string
18		Port  int
19		Error bool
20	}{
21		{"0.0.0.0", "0.0.0.0", DefaultBindPort, false},
22		{"0.0.0.0:1234", "0.0.0.0", 1234, false},
23	}
24
25	for _, tc := range testCases {
26		c := &Config{BindAddr: tc.Value}
27		ip, port, err := c.AddrParts(c.BindAddr)
28		if tc.Error != (err != nil) {
29			t.Errorf("Bad error: %s", err)
30			continue
31		}
32
33		if tc.IP != ip {
34			t.Errorf("%s: Got IP %#v", tc.Value, ip)
35			continue
36		}
37
38		if tc.Port != port {
39			t.Errorf("%s: Got port %d", tc.Value, port)
40			continue
41		}
42	}
43}
44
45func TestConfigEncryptBytes(t *testing.T) {
46	// Test with some input
47	src := []byte("abc")
48	c := &Config{
49		EncryptKey: base64.StdEncoding.EncodeToString(src),
50	}
51
52	result, err := c.EncryptBytes()
53	if err != nil {
54		t.Fatalf("err: %v", err)
55	}
56
57	if !bytes.Equal(src, result) {
58		t.Fatalf("bad: %#v", result)
59	}
60
61	// Test with no input
62	c = &Config{}
63	result, err = c.EncryptBytes()
64	if err != nil {
65		t.Fatalf("err: %v", err)
66	}
67
68	if len(result) > 0 {
69		t.Fatalf("bad: %#v", result)
70	}
71}
72
73func TestConfigEventScripts(t *testing.T) {
74	c := &Config{
75		EventHandlers: []string{
76			"foo.sh",
77			"bar=blah.sh",
78		},
79	}
80
81	result := c.EventScripts()
82	if len(result) != 2 {
83		t.Fatalf("bad: %#v", result)
84	}
85
86	expected := []EventScript{
87		{EventFilter{"*", ""}, "foo.sh"},
88		{EventFilter{"bar", ""}, "blah.sh"},
89	}
90
91	if !reflect.DeepEqual(result, expected) {
92		t.Fatalf("bad: %#v", result)
93	}
94}
95
96func TestDecodeConfig(t *testing.T) {
97	// Without a protocol
98	input := `{"node_name": "foo"}`
99	config, err := DecodeConfig(bytes.NewReader([]byte(input)))
100	if err != nil {
101		t.Fatalf("err: %v", err)
102	}
103
104	if config.NodeName != "foo" {
105		t.Fatalf("bad: %#v", config)
106	}
107
108	if config.Protocol != 0 {
109		t.Fatalf("bad: %#v", config)
110	}
111
112	if config.SkipLeaveOnInt != DefaultConfig().SkipLeaveOnInt {
113		t.Fatalf("bad: %#v", config)
114	}
115
116	if config.LeaveOnTerm != DefaultConfig().LeaveOnTerm {
117		t.Fatalf("bad: %#v", config)
118	}
119
120	// With a protocol
121	input = `{"node_name": "foo", "protocol": 7}`
122	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
123	if err != nil {
124		t.Fatalf("err: %v", err)
125	}
126
127	if config.NodeName != "foo" {
128		t.Fatalf("bad: %#v", config)
129	}
130
131	if config.Protocol != 7 {
132		t.Fatalf("bad: %#v", config)
133	}
134
135	// A bind addr
136	input = `{"bind": "127.0.0.2"}`
137	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
138	if err != nil {
139		t.Fatalf("err: %v", err)
140	}
141
142	if config.BindAddr != "127.0.0.2" {
143		t.Fatalf("bad: %#v", config)
144	}
145
146	// replayOnJoin
147	input = `{"replay_on_join": true}`
148	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
149	if err != nil {
150		t.Fatalf("err: %v", err)
151	}
152
153	if config.ReplayOnJoin != true {
154		t.Fatalf("bad: %#v", config)
155	}
156
157	// leave_on_terminate
158	input = `{"leave_on_terminate": true}`
159	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
160	if err != nil {
161		t.Fatalf("err: %v", err)
162	}
163
164	if config.LeaveOnTerm != true {
165		t.Fatalf("bad: %#v", config)
166	}
167
168	// skip_leave_on_interrupt
169	input = `{"skip_leave_on_interrupt": true}`
170	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
171	if err != nil {
172		t.Fatalf("err: %v", err)
173	}
174
175	if config.SkipLeaveOnInt != true {
176		t.Fatalf("bad: %#v", config)
177	}
178
179	// tags
180	input = `{"tags": {"foo": "bar", "role": "test"}}`
181	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
182	if err != nil {
183		t.Fatalf("err: %v", err)
184	}
185
186	if config.Tags["foo"] != "bar" {
187		t.Fatalf("bad: %#v", config)
188	}
189	if config.Tags["role"] != "test" {
190		t.Fatalf("bad: %#v", config)
191	}
192
193	// tags file
194	input = `{"tags_file": "/some/path"}`
195	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
196	if err != nil {
197		t.Fatalf("err: %v", err)
198	}
199
200	if config.TagsFile != "/some/path" {
201		t.Fatalf("bad: %#v", config)
202	}
203
204	// Discover
205	input = `{"discover": "foobar"}`
206	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
207	if err != nil {
208		t.Fatalf("err: %v", err)
209	}
210
211	if config.Discover != "foobar" {
212		t.Fatalf("bad: %#v", config)
213	}
214
215	// Interface
216	input = `{"interface": "eth0"}`
217	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
218	if err != nil {
219		t.Fatalf("err: %v", err)
220	}
221
222	if config.Interface != "eth0" {
223		t.Fatalf("bad: %#v", config)
224	}
225
226	// Reconnect intervals
227	input = `{"reconnect_interval": "15s", "reconnect_timeout": "48h"}`
228	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
229	if err != nil {
230		t.Fatalf("err: %v", err)
231	}
232
233	if config.ReconnectInterval != 15*time.Second {
234		t.Fatalf("bad: %#v", config)
235	}
236
237	if config.ReconnectTimeout != 48*time.Hour {
238		t.Fatalf("bad: %#v", config)
239	}
240
241	// RPC Auth
242	input = `{"rpc_auth": "foobar"}`
243	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
244	if err != nil {
245		t.Fatalf("err: %v", err)
246	}
247
248	if config.RPCAuthKey != "foobar" {
249		t.Fatalf("bad: %#v", config)
250	}
251
252	// DisableNameResolution
253	input = `{"disable_name_resolution": true}`
254	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
255	if err != nil {
256		t.Fatalf("err: %v", err)
257	}
258
259	if !config.DisableNameResolution {
260		t.Fatalf("bad: %#v", config)
261	}
262
263	// Tombstone intervals
264	input = `{"tombstone_timeout": "48h"}`
265	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
266	if err != nil {
267		t.Fatalf("err: %v", err)
268	}
269
270	if config.TombstoneTimeout != 48*time.Hour {
271		t.Fatalf("bad: %#v", config)
272	}
273
274	// Syslog
275	input = `{"enable_syslog": true, "syslog_facility": "LOCAL4"}`
276	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
277	if err != nil {
278		t.Fatalf("err: %v", err)
279	}
280
281	if !config.EnableSyslog {
282		t.Fatalf("bad: %#v", config)
283	}
284	if config.SyslogFacility != "LOCAL4" {
285		t.Fatalf("bad: %#v", config)
286	}
287
288	// Retry configs
289	input = `{"retry_max_attempts": 5, "retry_interval": "60s"}`
290	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
291	if err != nil {
292		t.Fatalf("err: %v", err)
293	}
294
295	if config.RetryMaxAttempts != 5 {
296		t.Fatalf("bad: %#v", config)
297	}
298
299	if config.RetryInterval != 60*time.Second {
300		t.Fatalf("bad: %#v", config)
301	}
302
303	// Broadcast timeout
304	input = `{"broadcast_timeout": "10s"}`
305	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
306	if err != nil {
307		t.Fatalf("err: %v", err)
308	}
309
310	if config.BroadcastTimeout != 10*time.Second {
311		t.Fatalf("bad: %#v", config)
312	}
313
314	// Retry configs
315	input = `{"retry_join": ["127.0.0.1", "127.0.0.2"]}`
316	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
317	if err != nil {
318		t.Fatalf("err: %v", err)
319	}
320
321	if len(config.RetryJoin) != 2 {
322		t.Fatalf("bad: %#v", config)
323	}
324
325	if config.RetryJoin[0] != "127.0.0.1" {
326		t.Fatalf("bad: %#v", config)
327	}
328
329	if config.RetryJoin[1] != "127.0.0.2" {
330		t.Fatalf("bad: %#v", config)
331	}
332
333	// Rejoin configs
334	input = `{"rejoin_after_leave": true}`
335	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
336	if err != nil {
337		t.Fatalf("err: %v", err)
338	}
339
340	if !config.RejoinAfterLeave {
341		t.Fatalf("bad: %#v", config)
342	}
343
344	// Stats configs
345	input = `{"statsite_addr": "127.0.0.1:8123", "statsd_addr": "127.0.0.1:8125"}`
346	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
347	if err != nil {
348		t.Fatalf("err: %v", err)
349	}
350
351	if config.StatsiteAddr != "127.0.0.1:8123" {
352		t.Fatalf("bad: %#v", config)
353	}
354
355	if config.StatsdAddr != "127.0.0.1:8125" {
356		t.Fatalf("bad: %#v", config)
357	}
358
359	// Query sizes
360	input = `{"query_response_size_limit": 123, "query_size_limit": 456}`
361	config, err = DecodeConfig(bytes.NewReader([]byte(input)))
362	if err != nil {
363		t.Fatalf("err: %v", err)
364	}
365
366	if config.QueryResponseSizeLimit != 123 || config.QuerySizeLimit != 456 {
367		t.Fatalf("bad: %#v", config)
368	}
369}
370
371func TestDecodeConfig_unknownDirective(t *testing.T) {
372	input := `{"unknown_directive": "titi"}`
373	_, err := DecodeConfig(bytes.NewReader([]byte(input)))
374	if err == nil {
375		t.Fatal("should have err")
376	}
377}
378
379func TestMergeConfig(t *testing.T) {
380	a := &Config{
381		NodeName:      "foo",
382		Role:          "bar",
383		Protocol:      7,
384		EventHandlers: []string{"foo"},
385		StartJoin:     []string{"foo"},
386		ReplayOnJoin:  true,
387		RetryJoin:     []string{"zab"},
388	}
389
390	b := &Config{
391		NodeName:               "bname",
392		DisableCoordinates:     true,
393		Protocol:               -1,
394		EncryptKey:             "foo",
395		EventHandlers:          []string{"bar"},
396		StartJoin:              []string{"bar"},
397		LeaveOnTerm:            true,
398		SkipLeaveOnInt:         true,
399		Discover:               "tubez",
400		Interface:              "eth0",
401		ReconnectInterval:      15 * time.Second,
402		ReconnectTimeout:       48 * time.Hour,
403		RPCAuthKey:             "foobar",
404		DisableNameResolution:  true,
405		TombstoneTimeout:       36 * time.Hour,
406		EnableSyslog:           true,
407		RetryJoin:              []string{"zip"},
408		RetryMaxAttempts:       10,
409		RetryInterval:          120 * time.Second,
410		RejoinAfterLeave:       true,
411		StatsiteAddr:           "127.0.0.1:8125",
412		QueryResponseSizeLimit: 123,
413		QuerySizeLimit:         456,
414		BroadcastTimeout:       20 * time.Second,
415		EnableCompression:      true,
416	}
417
418	c := MergeConfig(a, b)
419
420	if c.NodeName != "bname" {
421		t.Fatalf("bad: %#v", c)
422	}
423
424	if c.Role != "bar" {
425		t.Fatalf("bad: %#v", c)
426	}
427
428	if c.DisableCoordinates != true {
429		t.Fatalf("bad: %#v", c)
430	}
431
432	if c.Protocol != 7 {
433		t.Fatalf("bad: %#v", c)
434	}
435
436	if c.EncryptKey != "foo" {
437		t.Fatalf("bad: %#v", c.EncryptKey)
438	}
439
440	if c.ReplayOnJoin != true {
441		t.Fatalf("bad: %#v", c.ReplayOnJoin)
442	}
443
444	if !c.LeaveOnTerm {
445		t.Fatalf("bad: %#v", c.LeaveOnTerm)
446	}
447
448	if !c.SkipLeaveOnInt {
449		t.Fatalf("bad: %#v", c.SkipLeaveOnInt)
450	}
451
452	if c.Discover != "tubez" {
453		t.Fatalf("Bad: %v", c.Discover)
454	}
455
456	if c.Interface != "eth0" {
457		t.Fatalf("Bad: %v", c.Interface)
458	}
459
460	if c.ReconnectInterval != 15*time.Second {
461		t.Fatalf("bad: %#v", c)
462	}
463
464	if c.ReconnectTimeout != 48*time.Hour {
465		t.Fatalf("bad: %#v", c)
466	}
467
468	if c.TombstoneTimeout != 36*time.Hour {
469		t.Fatalf("bad: %#v", c)
470	}
471
472	if c.RPCAuthKey != "foobar" {
473		t.Fatalf("bad: %#v", c)
474	}
475
476	if !c.DisableNameResolution {
477		t.Fatalf("bad: %#v", c)
478	}
479
480	if !c.EnableSyslog {
481		t.Fatalf("bad: %#v", c)
482	}
483
484	if c.RetryMaxAttempts != 10 {
485		t.Fatalf("bad: %#v", c)
486	}
487
488	if c.RetryInterval != 120*time.Second {
489		t.Fatalf("bad: %#v", c)
490	}
491
492	if !c.RejoinAfterLeave {
493		t.Fatalf("bad: %#v", c)
494	}
495
496	if c.StatsiteAddr != "127.0.0.1:8125" {
497		t.Fatalf("bad: %#v", c)
498	}
499
500	expected := []string{"foo", "bar"}
501	if !reflect.DeepEqual(c.EventHandlers, expected) {
502		t.Fatalf("bad: %#v", c)
503	}
504
505	if !reflect.DeepEqual(c.StartJoin, expected) {
506		t.Fatalf("bad: %#v", c)
507	}
508
509	expected = []string{"zab", "zip"}
510	if !reflect.DeepEqual(c.RetryJoin, expected) {
511		t.Fatalf("bad: %#v", c)
512	}
513
514	if c.QueryResponseSizeLimit != 123 || c.QuerySizeLimit != 456 {
515		t.Fatalf("bad: %#v", c)
516	}
517
518	if c.BroadcastTimeout != 20*time.Second {
519		t.Fatalf("bad: %#v", c)
520	}
521
522	if !c.EnableCompression {
523		t.Fatalf("bad: %#v", c)
524	}
525}
526
527func TestReadConfigPaths_badPath(t *testing.T) {
528	_, err := ReadConfigPaths([]string{"/i/shouldnt/exist/ever/rainbows"})
529	if err == nil {
530		t.Fatal("should have err")
531	}
532}
533
534func TestReadConfigPaths_file(t *testing.T) {
535	tf, err := ioutil.TempFile("", "serf")
536	if err != nil {
537		t.Fatalf("err: %v", err)
538	}
539	tf.Write([]byte(`{"node_name":"bar"}`))
540	tf.Close()
541	defer os.Remove(tf.Name())
542
543	config, err := ReadConfigPaths([]string{tf.Name()})
544	if err != nil {
545		t.Fatalf("err: %v", err)
546	}
547
548	if config.NodeName != "bar" {
549		t.Fatalf("bad: %#v", config)
550	}
551}
552
553func TestReadConfigPaths_dir(t *testing.T) {
554	td, err := ioutil.TempDir("", "serf")
555	if err != nil {
556		t.Fatalf("err: %v", err)
557	}
558	defer os.RemoveAll(td)
559
560	err = ioutil.WriteFile(filepath.Join(td, "a.json"),
561		[]byte(`{"node_name": "bar"}`), 0644)
562	if err != nil {
563		t.Fatalf("err: %v", err)
564	}
565
566	err = ioutil.WriteFile(filepath.Join(td, "b.json"),
567		[]byte(`{"node_name": "baz"}`), 0644)
568	if err != nil {
569		t.Fatalf("err: %v", err)
570	}
571
572	// A non-json file, shouldn't be read
573	err = ioutil.WriteFile(filepath.Join(td, "c"),
574		[]byte(`{"node_name": "bad"}`), 0644)
575	if err != nil {
576		t.Fatalf("err: %v", err)
577	}
578
579	config, err := ReadConfigPaths([]string{td})
580	if err != nil {
581		t.Fatalf("err: %v", err)
582	}
583
584	if config.NodeName != "baz" {
585		t.Fatalf("bad: %#v", config)
586	}
587}
588