1package agent
2
3import (
4	"bytes"
5	"log"
6	"os"
7	"testing"
8	"time"
9
10	"github.com/hashicorp/serf/client"
11	"github.com/hashicorp/serf/testutil"
12	"github.com/mitchellh/cli"
13)
14
15func TestCommandRun(t *testing.T) {
16	shutdownCh := make(chan struct{})
17	defer close(shutdownCh)
18
19	ui := new(cli.MockUi)
20	c := &Command{
21		ShutdownCh: shutdownCh,
22		Ui:         ui,
23	}
24
25	ip1, returnFn1 := testutil.TakeIP()
26	defer returnFn1()
27
28	ip2, returnFn2 := testutil.TakeIP()
29	defer returnFn2()
30
31	rpcAddr := ip2.String() + ":11111"
32
33	args := []string{
34		"-bind", ip1.String(),
35		"-rpc-addr", rpcAddr,
36	}
37
38	resultCh := make(chan int)
39	go func() {
40		resultCh <- c.Run(args)
41	}()
42
43	testutil.Yield()
44
45	// Verify it runs "forever"
46	select {
47	case <-resultCh:
48		t.Fatalf("ended too soon, err: %v", ui.ErrorWriter.String())
49	case <-time.After(50 * time.Millisecond):
50	}
51
52	// Send a shutdown request
53	shutdownCh <- struct{}{}
54
55	select {
56	case code := <-resultCh:
57		if code != 0 {
58			t.Fatalf("bad code: %d", code)
59		}
60	case <-time.After(2 * time.Second):
61		t.Fatalf("timeout")
62	}
63}
64
65func TestCommandRun_rpc(t *testing.T) {
66	doneCh := make(chan struct{})
67	shutdownCh := make(chan struct{})
68	defer func() {
69		close(shutdownCh)
70		<-doneCh
71	}()
72
73	c := &Command{
74		ShutdownCh: shutdownCh,
75		Ui:         new(cli.MockUi),
76	}
77
78	ip1, returnFn1 := testutil.TakeIP()
79	defer returnFn1()
80
81	ip2, returnFn2 := testutil.TakeIP()
82	defer returnFn2()
83
84	rpcAddr := ip2.String() + ":11111"
85
86	args := []string{
87		"-bind", ip1.String(),
88		"-rpc-addr", rpcAddr,
89	}
90
91	go func() {
92		code := c.Run(args)
93		if code != 0 {
94			log.Printf("bad: %d", code)
95		}
96
97		close(doneCh)
98	}()
99
100	testutil.Yield()
101
102	client, err := client.NewRPCClient(rpcAddr)
103	if err != nil {
104		t.Fatalf("err: %v", err)
105	}
106	defer client.Close()
107
108	members, err := client.Members()
109	if err != nil {
110		t.Fatalf("err: %v", err)
111	}
112
113	if len(members) != 1 {
114		t.Fatalf("bad: %#v", members)
115	}
116}
117
118func TestCommandRun_join(t *testing.T) {
119	ip1, returnFn1 := testutil.TakeIP()
120	defer returnFn1()
121
122	ip2, returnFn2 := testutil.TakeIP()
123	defer returnFn2()
124
125	a1 := testAgent(t, ip1, nil)
126	if err := a1.Start(); err != nil {
127		t.Fatalf("err: %v", err)
128	}
129	defer a1.Shutdown()
130
131	doneCh := make(chan struct{})
132	shutdownCh := make(chan struct{})
133	defer func() {
134		close(shutdownCh)
135		<-doneCh
136	}()
137
138	c := &Command{
139		ShutdownCh: shutdownCh,
140		Ui:         new(cli.MockUi),
141	}
142
143	args := []string{
144		"-bind", ip2.String(),
145		"-join", a1.conf.MemberlistConfig.BindAddr,
146		"-replay",
147	}
148
149	go func() {
150		code := c.Run(args)
151		if code != 0 {
152			log.Printf("bad: %d", code)
153		}
154
155		close(doneCh)
156	}()
157
158	testutil.Yield()
159
160	if len(a1.Serf().Members()) != 2 {
161		t.Fatalf("bad: %#v", a1.Serf().Members())
162	}
163}
164
165func TestCommandRun_joinFail(t *testing.T) {
166	ip1, returnFn1 := testutil.TakeIP()
167	defer returnFn1()
168
169	ip2, returnFn2 := testutil.TakeIP()
170	defer returnFn2()
171
172	shutdownCh := make(chan struct{})
173	defer close(shutdownCh)
174
175	c := &Command{
176		ShutdownCh: shutdownCh,
177		Ui:         new(cli.MockUi),
178	}
179
180	args := []string{
181		"-bind", ip1.String(),
182		"-join", ip2.String(),
183	}
184
185	code := c.Run(args)
186	if code == 0 {
187		t.Fatal("should fail")
188	}
189}
190
191func TestCommandRun_advertiseAddr(t *testing.T) {
192	doneCh := make(chan struct{})
193	shutdownCh := make(chan struct{})
194	defer func() {
195		close(shutdownCh)
196		<-doneCh
197	}()
198
199	c := &Command{
200		ShutdownCh: shutdownCh,
201		Ui:         new(cli.MockUi),
202	}
203
204	ip1, returnFn1 := testutil.TakeIP()
205	defer returnFn1()
206
207	ip2, returnFn2 := testutil.TakeIP()
208	defer returnFn2()
209
210	rpcAddr := ip2.String() + ":11111"
211	args := []string{
212		"-bind", ip1.String(),
213		"-rpc-addr", rpcAddr,
214		"-advertise", "127.0.0.10:12345",
215	}
216
217	go func() {
218		code := c.Run(args)
219		if code != 0 {
220			log.Printf("bad: %d", code)
221		}
222
223		close(doneCh)
224	}()
225
226	testutil.Yield()
227
228	client, err := client.NewRPCClient(rpcAddr)
229	if err != nil {
230		t.Fatalf("err: %v", err)
231	}
232	defer client.Close()
233
234	members, err := client.Members()
235	if err != nil {
236		t.Fatalf("err: %v", err)
237	}
238
239	if len(members) != 1 {
240		t.Fatalf("bad: %#v", members)
241	}
242
243	// Check the addr and port is as advertised!
244	m := members[0]
245	if bytes.Compare(m.Addr, []byte{127, 0, 0, 10}) != 0 {
246		t.Fatalf("bad: %#v", m)
247	}
248	if m.Port != 12345 {
249		t.Fatalf("bad: %#v", m)
250	}
251}
252
253func TestCommandRun_mDNS(t *testing.T) {
254	// mDNS does not work in travis
255	if os.Getenv("TRAVIS") != "" {
256		t.SkipNow()
257	}
258
259	// Start an agent
260	doneCh := make(chan struct{})
261	shutdownCh := make(chan struct{})
262	defer func() {
263		close(shutdownCh)
264		<-doneCh
265	}()
266
267	c := &Command{
268		ShutdownCh: shutdownCh,
269		Ui:         new(cli.MockUi),
270	}
271
272	ip1, returnFn1 := testutil.TakeIP()
273	defer returnFn1()
274
275	ip2, returnFn2 := testutil.TakeIP()
276	defer returnFn2()
277
278	ip3, returnFn3 := testutil.TakeIP()
279	defer returnFn3()
280
281	ip4, returnFn4 := testutil.TakeIP()
282	defer returnFn4()
283
284	rpcAddr1 := ip2.String() + ":11111"
285	rpcAddr2 := ip4.String() + ":11111"
286
287	args := []string{
288		"-node", "foo",
289		"-bind", ip1.String(),
290		"-discover", "test",
291		"-rpc-addr", rpcAddr1,
292	}
293
294	go func() {
295		code := c.Run(args)
296		if code != 0 {
297			log.Printf("bad: %d", code)
298		}
299		close(doneCh)
300	}()
301
302	// Start a second agent
303	doneCh2 := make(chan struct{})
304	shutdownCh2 := make(chan struct{})
305	defer func() {
306		close(shutdownCh2)
307		<-doneCh2
308	}()
309
310	c2 := &Command{
311		ShutdownCh: shutdownCh2,
312		Ui:         new(cli.MockUi),
313	}
314
315	args2 := []string{
316		"-node", "bar",
317		"-bind", ip3.String(),
318		"-discover", "test",
319		"-rpc-addr", rpcAddr2,
320	}
321
322	go func() {
323		code := c2.Run(args2)
324		if code != 0 {
325			log.Printf("bad: %d", code)
326		}
327		close(doneCh2)
328	}()
329
330	time.Sleep(150 * time.Millisecond)
331
332	client, err := client.NewRPCClient(rpcAddr2)
333	if err != nil {
334		t.Fatalf("err: %v", err)
335	}
336	defer client.Close()
337
338	members, err := client.Members()
339	if err != nil {
340		t.Fatalf("err: %v", err)
341	}
342
343	if len(members) != 2 {
344		t.Fatalf("bad: %#v", members)
345	}
346}
347
348func TestCommandRun_retry_join(t *testing.T) {
349	ip1, returnFn1 := testutil.TakeIP()
350	defer returnFn1()
351
352	ip2, returnFn2 := testutil.TakeIP()
353	defer returnFn2()
354
355	a1 := testAgent(t, ip1, nil)
356	if err := a1.Start(); err != nil {
357		t.Fatalf("err: %v", err)
358	}
359	defer a1.Shutdown()
360
361	doneCh := make(chan struct{})
362	shutdownCh := make(chan struct{})
363	defer func() {
364		close(shutdownCh)
365		<-doneCh
366	}()
367
368	c := &Command{
369		ShutdownCh: shutdownCh,
370		Ui:         new(cli.MockUi),
371	}
372
373	args := []string{
374		"-bind", ip2.String(),
375		"-retry-join", a1.conf.MemberlistConfig.BindAddr,
376		"-replay",
377	}
378
379	go func() {
380		code := c.Run(args)
381		if code != 0 {
382			log.Printf("bad: %d", code)
383		}
384
385		close(doneCh)
386	}()
387
388	testutil.Yield()
389
390	if len(a1.Serf().Members()) != 2 {
391		t.Fatalf("bad: %#v", a1.Serf().Members())
392	}
393}
394
395func TestCommandRun_retry_joinFail(t *testing.T) {
396	shutdownCh := make(chan struct{})
397	defer close(shutdownCh)
398
399	c := &Command{
400		ShutdownCh: shutdownCh,
401		Ui:         new(cli.MockUi),
402	}
403
404	ip1, returnFn1 := testutil.TakeIP()
405	defer returnFn1()
406
407	ip2, returnFn2 := testutil.TakeIP()
408	defer returnFn2()
409
410	args := []string{
411		"-bind", ip1.String(),
412		"-retry-join", ip2.String(),
413		"-retry-interval", "1s",
414		"-retry-max", "1",
415	}
416
417	code := c.Run(args)
418	if code == 0 {
419		t.Fatal("should fail")
420	}
421}
422