1package agent
2
3import (
4	"fmt"
5	"io/ioutil"
6	"net"
7	"os"
8	"testing"
9
10	"github.com/hashicorp/serf/serf"
11)
12
13const eventScript = `#!/bin/sh
14RESULT_FILE="%s"
15echo $SERF_SELF_NAME $SERF_SELF_ROLE >>${RESULT_FILE}
16echo $SERF_TAG_DC >> ${RESULT_FILE}
17echo $SERF_TAG_BAD_TAG >> ${RESULT_FILE}
18echo $SERF_EVENT $SERF_USER_EVENT "$@" >>${RESULT_FILE}
19echo $os_env_var >> ${RESULT_FILE}
20while read line; do
21	printf "${line}\n" >>${RESULT_FILE}
22done
23`
24
25const userEventScript = `#!/bin/sh
26RESULT_FILE="%s"
27echo $SERF_SELF_NAME $SERF_SELF_ROLE >>${RESULT_FILE}
28echo $SERF_TAG_DC >> ${RESULT_FILE}
29echo $SERF_EVENT $SERF_USER_EVENT "$@" >>${RESULT_FILE}
30echo $SERF_EVENT $SERF_USER_LTIME "$@" >>${RESULT_FILE}
31while read line; do
32	printf "${line}\n" >>${RESULT_FILE}
33done
34`
35
36const queryScript = `#!/bin/sh
37RESULT_FILE="%s"
38echo $SERF_SELF_NAME $SERF_SELF_ROLE >>${RESULT_FILE}
39echo $SERF_TAG_DC >> ${RESULT_FILE}
40echo $SERF_EVENT $SERF_QUERY_NAME "$@" >>${RESULT_FILE}
41echo $SERF_EVENT $SERF_QUERY_LTIME "$@" >>${RESULT_FILE}
42while read line; do
43	printf "${line}\n" >>${RESULT_FILE}
44done
45`
46
47// testEventScript creates an event script that can be used with the
48// agent. It returns the path to the event script itself and a path to
49// the file that will contain the events that that script receives.
50func testEventScript(t *testing.T, script string) (string, string) {
51	scriptFile, err := ioutil.TempFile("", "serf")
52	if err != nil {
53		t.Fatalf("err: %v", err)
54	}
55	defer scriptFile.Close()
56
57	if err := scriptFile.Chmod(0755); err != nil {
58		t.Fatalf("err: %v", err)
59	}
60
61	resultFile, err := ioutil.TempFile("", "serf-result")
62	if err != nil {
63		t.Fatalf("err: %v", err)
64	}
65	defer resultFile.Close()
66
67	_, err = scriptFile.Write([]byte(
68		fmt.Sprintf(script, resultFile.Name())))
69	if err != nil {
70		t.Fatalf("err: %v", err)
71	}
72
73	return scriptFile.Name(), resultFile.Name()
74}
75
76func TestScriptEventHandler(t *testing.T) {
77	os.Setenv("os_env_var", "os-env-foo")
78
79	script, results := testEventScript(t, eventScript)
80
81	h := &ScriptEventHandler{
82		SelfFunc: func() serf.Member {
83			return serf.Member{
84				Name: "ourname",
85				Tags: map[string]string{"role": "ourrole", "dc": "east-aws", "bad-tag": "bad"},
86			}
87		},
88		Scripts: []EventScript{
89			{
90				EventFilter: EventFilter{
91					Event: "*",
92				},
93				Script: script,
94			},
95		},
96	}
97
98	event := serf.MemberEvent{
99		Type: serf.EventMemberJoin,
100		Members: []serf.Member{
101			{
102				Name: "foo",
103				Addr: net.ParseIP("1.2.3.4"),
104				Tags: map[string]string{"role": "bar", "foo": "bar"},
105			},
106		},
107	}
108
109	h.HandleEvent(event)
110
111	result, err := ioutil.ReadFile(results)
112	if err != nil {
113		t.Fatalf("err: %v", err)
114	}
115
116	expected1 := "ourname ourrole\neast-aws\nbad\nmember-join\nos-env-foo\nfoo\t1.2.3.4\tbar\trole=bar,foo=bar\n"
117	expected2 := "ourname ourrole\neast-aws\nbad\nmember-join\nos-env-foo\nfoo\t1.2.3.4\tbar\tfoo=bar,role=bar\n"
118	if string(result) != expected1 && string(result) != expected2 {
119		t.Fatalf("bad: %#v. Expected: %#v or %v", string(result), expected1, expected2)
120	}
121}
122
123func TestScriptUserEventHandler(t *testing.T) {
124	script, results := testEventScript(t, userEventScript)
125
126	h := &ScriptEventHandler{
127		SelfFunc: func() serf.Member {
128			return serf.Member{
129				Name: "ourname",
130				Tags: map[string]string{"role": "ourrole", "dc": "east-aws"},
131			}
132		},
133		Scripts: []EventScript{
134			{
135				EventFilter: EventFilter{
136					Event: "*",
137				},
138				Script: script,
139			},
140		},
141	}
142
143	userEvent := serf.UserEvent{
144		LTime:    1,
145		Name:     "baz",
146		Payload:  []byte("foobar"),
147		Coalesce: true,
148	}
149
150	h.HandleEvent(userEvent)
151
152	result, err := ioutil.ReadFile(results)
153	if err != nil {
154		t.Fatalf("err: %v", err)
155	}
156
157	expected := "ourname ourrole\neast-aws\nuser baz\nuser 1\nfoobar\n"
158	if string(result) != expected {
159		t.Fatalf("bad: %#v. Expected: %#v", string(result), expected)
160	}
161}
162
163func TestScriptQueryEventHandler(t *testing.T) {
164	script, results := testEventScript(t, queryScript)
165
166	h := &ScriptEventHandler{
167		SelfFunc: func() serf.Member {
168			return serf.Member{
169				Name: "ourname",
170				Tags: map[string]string{"role": "ourrole", "dc": "east-aws"},
171			}
172		},
173		Scripts: []EventScript{
174			{
175				EventFilter: EventFilter{
176					Event: "*",
177				},
178				Script: script,
179			},
180		},
181	}
182
183	query := &serf.Query{
184		LTime:   42,
185		Name:    "uptime",
186		Payload: []byte("load average"),
187	}
188
189	h.HandleEvent(query)
190
191	result, err := ioutil.ReadFile(results)
192	if err != nil {
193		t.Fatalf("err: %v", err)
194	}
195
196	expected := "ourname ourrole\neast-aws\nquery uptime\nquery 42\nload average\n"
197	if string(result) != expected {
198		t.Fatalf("bad: %#v. Expected: %#v", string(result), expected)
199	}
200}
201
202func TestEventScriptInvoke(t *testing.T) {
203	testCases := []struct {
204		script EventScript
205		event  serf.Event
206		invoke bool
207	}{
208		{
209			EventScript{EventFilter{"*", ""}, "script.sh"},
210			serf.MemberEvent{},
211			true,
212		},
213		{
214			EventScript{EventFilter{"user", ""}, "script.sh"},
215			serf.MemberEvent{},
216			false,
217		},
218		{
219			EventScript{EventFilter{"user", "deploy"}, "script.sh"},
220			serf.UserEvent{Name: "deploy"},
221			true,
222		},
223		{
224			EventScript{EventFilter{"user", "deploy"}, "script.sh"},
225			serf.UserEvent{Name: "restart"},
226			false,
227		},
228		{
229			EventScript{EventFilter{"member-join", ""}, "script.sh"},
230			serf.MemberEvent{Type: serf.EventMemberJoin},
231			true,
232		},
233		{
234			EventScript{EventFilter{"member-join", ""}, "script.sh"},
235			serf.MemberEvent{Type: serf.EventMemberLeave},
236			false,
237		},
238		{
239			EventScript{EventFilter{"member-reap", ""}, "script.sh"},
240			serf.MemberEvent{Type: serf.EventMemberReap},
241			true,
242		},
243		{
244			EventScript{EventFilter{"query", "deploy"}, "script.sh"},
245			&serf.Query{Name: "deploy"},
246			true,
247		},
248		{
249			EventScript{EventFilter{"query", "uptime"}, "script.sh"},
250			&serf.Query{Name: "deploy"},
251			false,
252		},
253		{
254			EventScript{EventFilter{"query", ""}, "script.sh"},
255			&serf.Query{Name: "deploy"},
256			true,
257		},
258	}
259
260	for _, tc := range testCases {
261		result := tc.script.Invoke(tc.event)
262		if result != tc.invoke {
263			t.Errorf("bad: %#v", tc)
264		}
265	}
266}
267
268func TestEventScriptValid(t *testing.T) {
269	testCases := []struct {
270		Event string
271		Valid bool
272	}{
273		{"member-join", true},
274		{"member-leave", true},
275		{"member-failed", true},
276		{"member-update", true},
277		{"member-reap", true},
278		{"user", true},
279		{"User", false},
280		{"member", false},
281		{"query", true},
282		{"Query", false},
283		{"*", true},
284	}
285
286	for _, tc := range testCases {
287		script := EventScript{EventFilter: EventFilter{Event: tc.Event}}
288		if script.Valid() != tc.Valid {
289			t.Errorf("bad: %#v", tc)
290		}
291	}
292}
293
294func TestParseEventScript(t *testing.T) {
295	testCases := []struct {
296		v       string
297		err     bool
298		results []EventScript
299	}{
300		{
301			"script.sh",
302			false,
303			[]EventScript{{EventFilter{"*", ""}, "script.sh"}},
304		},
305
306		{
307			"member-join=script.sh",
308			false,
309			[]EventScript{{EventFilter{"member-join", ""}, "script.sh"}},
310		},
311
312		{
313			"foo,bar=script.sh",
314			false,
315			[]EventScript{
316				{EventFilter{"foo", ""}, "script.sh"},
317				{EventFilter{"bar", ""}, "script.sh"},
318			},
319		},
320
321		{
322			"user:deploy=script.sh",
323			false,
324			[]EventScript{{EventFilter{"user", "deploy"}, "script.sh"}},
325		},
326
327		{
328			"foo,user:blah,bar,query:tubez=script.sh",
329			false,
330			[]EventScript{
331				{EventFilter{"foo", ""}, "script.sh"},
332				{EventFilter{"user", "blah"}, "script.sh"},
333				{EventFilter{"bar", ""}, "script.sh"},
334				{EventFilter{"query", "tubez"}, "script.sh"},
335			},
336		},
337
338		{
339			"query:load=script.sh",
340			false,
341			[]EventScript{{EventFilter{"query", "load"}, "script.sh"}},
342		},
343
344		{
345			"query=script.sh",
346			false,
347			[]EventScript{{EventFilter{"query", ""}, "script.sh"}},
348		},
349	}
350
351	for _, tc := range testCases {
352		results := ParseEventScript(tc.v)
353		if results == nil {
354			t.Errorf("result should not be nil")
355			continue
356		}
357
358		if len(results) != len(tc.results) {
359			t.Errorf("bad: %#v", results)
360			continue
361		}
362
363		for i, r := range results {
364			expected := tc.results[i]
365
366			if r.Event != expected.Event {
367				t.Errorf("Events not equal: %s %s", r.Event, expected.Event)
368			}
369
370			if r.Name != expected.Name {
371				t.Errorf("User events not equal: %s %s", r.Name, expected.Name)
372			}
373
374			if r.Script != expected.Script {
375				t.Errorf("Scripts not equal: %s %s", r.Script, expected.Script)
376			}
377		}
378	}
379}
380
381func TestParseEventFilter(t *testing.T) {
382	testCases := []struct {
383		v       string
384		results []EventFilter
385	}{
386		{
387			"",
388			[]EventFilter{EventFilter{"*", ""}},
389		},
390
391		{
392			"member-join",
393			[]EventFilter{EventFilter{"member-join", ""}},
394		},
395
396		{
397			"member-reap",
398			[]EventFilter{EventFilter{"member-reap", ""}},
399		},
400
401		{
402			"foo,bar",
403			[]EventFilter{
404				EventFilter{"foo", ""},
405				EventFilter{"bar", ""},
406			},
407		},
408
409		{
410			"user:deploy",
411			[]EventFilter{EventFilter{"user", "deploy"}},
412		},
413
414		{
415			"foo,user:blah,bar",
416			[]EventFilter{
417				EventFilter{"foo", ""},
418				EventFilter{"user", "blah"},
419				EventFilter{"bar", ""},
420			},
421		},
422
423		{
424			"query:load",
425			[]EventFilter{EventFilter{"query", "load"}},
426		},
427	}
428
429	for _, tc := range testCases {
430		results := ParseEventFilter(tc.v)
431		if results == nil {
432			t.Errorf("result should not be nil")
433			continue
434		}
435
436		if len(results) != len(tc.results) {
437			t.Errorf("bad: %#v", results)
438			continue
439		}
440
441		for i, r := range results {
442			expected := tc.results[i]
443
444			if r.Event != expected.Event {
445				t.Errorf("Events not equal: %s %s", r.Event, expected.Event)
446			}
447
448			if r.Name != expected.Name {
449				t.Errorf("User events not equal: %s %s", r.Name, expected.Name)
450			}
451		}
452	}
453}
454