1package exec
2
3import (
4	"strings"
5	"testing"
6	"time"
7
8	"github.com/hashicorp/consul/testrpc"
9
10	"github.com/hashicorp/consul/agent"
11	consulapi "github.com/hashicorp/consul/api"
12	"github.com/hashicorp/consul/testutil/retry"
13	"github.com/mitchellh/cli"
14)
15
16func TestExecCommand_noTabs(t *testing.T) {
17	t.Parallel()
18	if strings.ContainsRune(New(nil, nil).Help(), '\t') {
19		t.Fatal("help has tabs")
20	}
21}
22
23func TestExecCommand(t *testing.T) {
24	t.Parallel()
25	a := agent.NewTestAgent(t.Name(), `
26		disable_remote_exec = false
27	`)
28	defer a.Shutdown()
29
30	testrpc.WaitForTestAgent(t, a.RPC, "dc1")
31
32	ui := cli.NewMockUi()
33	c := New(ui, nil)
34	args := []string{"-http-addr=" + a.HTTPAddr(), "-wait=1s", "uptime"}
35
36	code := c.Run(args)
37	if code != 0 {
38		t.Fatalf("bad: %d. Error:%#v  (std)Output:%#v", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
39	}
40
41	if !strings.Contains(ui.OutputWriter.String(), "load") {
42		t.Fatalf("bad: %#v", ui.OutputWriter.String())
43	}
44}
45
46func TestExecCommand_NoShell(t *testing.T) {
47	t.Parallel()
48	a := agent.NewTestAgent(t.Name(), `
49		disable_remote_exec = false
50	`)
51	defer a.Shutdown()
52
53	testrpc.WaitForTestAgent(t, a.RPC, "dc1")
54
55	ui := cli.NewMockUi()
56	c := New(ui, nil)
57	args := []string{"-http-addr=" + a.HTTPAddr(), "-shell=false", "-wait=1s", "uptime"}
58
59	code := c.Run(args)
60	if code != 0 {
61		t.Fatalf("bad: %d. Error:%#v  (std)Output:%#v", code, ui.ErrorWriter.String(), ui.OutputWriter.String())
62	}
63
64	if !strings.Contains(ui.OutputWriter.String(), "load") {
65		t.Fatalf("bad: %#v", ui.OutputWriter.String())
66	}
67}
68
69func TestExecCommand_CrossDC(t *testing.T) {
70	t.Parallel()
71	a1 := agent.NewTestAgent(t.Name(), `
72		disable_remote_exec = false
73	`)
74	defer a1.Shutdown()
75
76	testrpc.WaitForTestAgent(t, a1.RPC, "dc1")
77
78	a2 := agent.NewTestAgent(t.Name(), `
79		datacenter = "dc2"
80		disable_remote_exec = false
81	`)
82	defer a2.Shutdown()
83
84	testrpc.WaitForTestAgent(t, a2.RPC, "dc2")
85
86	// Join over the WAN
87	_, err := a2.JoinWAN([]string{a1.Config.SerfBindAddrWAN.String()})
88	if err != nil {
89		t.Fatalf("err: %v", err)
90	}
91
92	if got, want := len(a1.WANMembers()), 2; got != want {
93		t.Fatalf("got %d WAN members on a1 want %d", got, want)
94	}
95	if got, want := len(a2.WANMembers()), 2; got != want {
96		t.Fatalf("got %d WAN members on a2 want %d", got, want)
97	}
98
99	ui := cli.NewMockUi()
100	c := New(ui, nil)
101	args := []string{"-http-addr=" + a1.HTTPAddr(), "-wait=500ms", "-datacenter=dc2", "uptime"}
102
103	code := c.Run(args)
104	if code != 0 {
105		t.Fatalf("bad: %d. %#v", code, ui.ErrorWriter.String())
106	}
107
108	if !strings.Contains(ui.OutputWriter.String(), "load") {
109		t.Fatalf("bad: %#v", ui.OutputWriter.String())
110	}
111}
112
113func TestExecCommand_Validate(t *testing.T) {
114	t.Parallel()
115	conf := &rExecConf{}
116	err := conf.validate()
117	if err != nil {
118		t.Fatalf("err: %v", err)
119	}
120
121	conf.node = "("
122	err = conf.validate()
123	if err == nil {
124		t.Fatalf("err: %v", err)
125	}
126
127	conf.node = ""
128	conf.service = "("
129	err = conf.validate()
130	if err == nil {
131		t.Fatalf("err: %v", err)
132	}
133
134	conf.service = "()"
135	conf.tag = "("
136	err = conf.validate()
137	if err == nil {
138		t.Fatalf("err: %v", err)
139	}
140
141	conf.service = ""
142	conf.tag = "foo"
143	err = conf.validate()
144	if err == nil {
145		t.Fatalf("err: %v", err)
146	}
147}
148
149func TestExecCommand_Sessions(t *testing.T) {
150	t.Parallel()
151	a := agent.NewTestAgent(t.Name(), `
152		disable_remote_exec = false
153	`)
154	defer a.Shutdown()
155
156	testrpc.WaitForTestAgent(t, a.RPC, "dc1")
157
158	ui := cli.NewMockUi()
159	c := New(ui, nil)
160	c.apiclient = a.Client()
161	id, err := c.createSession()
162	if err != nil {
163		t.Fatalf("err: %v", err)
164	}
165
166	se, _, err := a.Client().Session().Info(id, nil)
167	if err != nil {
168		t.Fatalf("err: %v", err)
169	}
170	if se == nil || se.Name != "Remote Exec" {
171		t.Fatalf("bad: %v", se)
172	}
173
174	c.sessionID = id
175	err = c.destroySession()
176	if err != nil {
177		t.Fatalf("err: %v", err)
178	}
179
180	se, _, err = a.Client().Session().Info(id, nil)
181	if err != nil {
182		t.Fatalf("err: %v", err)
183	}
184	if se != nil {
185		t.Fatalf("bad: %v", se)
186	}
187}
188
189func TestExecCommand_Sessions_Foreign(t *testing.T) {
190	t.Parallel()
191	a := agent.NewTestAgent(t.Name(), `
192		disable_remote_exec = false
193	`)
194	defer a.Shutdown()
195
196	ui := cli.NewMockUi()
197	c := New(ui, nil)
198	c.apiclient = a.Client()
199
200	c.conf.foreignDC = true
201	c.conf.localDC = "dc1"
202	c.conf.localNode = "foo"
203
204	var id string
205	retry.Run(t, func(r *retry.R) {
206		var err error
207		id, err = c.createSession()
208		if err != nil {
209			r.Fatal(err)
210		}
211		if id == "" {
212			r.Fatal("no id")
213		}
214	})
215
216	se, _, err := a.Client().Session().Info(id, nil)
217	if err != nil {
218		t.Fatalf("err: %v", err)
219	}
220	if se == nil || se.Name != "Remote Exec via foo@dc1" {
221		t.Fatalf("bad: %v", se)
222	}
223
224	c.sessionID = id
225	err = c.destroySession()
226	if err != nil {
227		t.Fatalf("err: %v", err)
228	}
229
230	se, _, err = a.Client().Session().Info(id, nil)
231	if err != nil {
232		t.Fatalf("err: %v", err)
233	}
234	if se != nil {
235		t.Fatalf("bad: %v", se)
236	}
237}
238
239func TestExecCommand_UploadDestroy(t *testing.T) {
240	t.Parallel()
241	a := agent.NewTestAgent(t.Name(), `
242		disable_remote_exec = false
243	`)
244	defer a.Shutdown()
245
246	testrpc.WaitForTestAgent(t, a.RPC, "dc1")
247
248	ui := cli.NewMockUi()
249
250	c := New(ui, nil)
251	c.apiclient = a.Client()
252	id, err := c.createSession()
253	if err != nil {
254		t.Fatalf("err: %v", err)
255	}
256	c.sessionID = id
257
258	c.conf.prefix = "_rexec"
259	c.conf.cmd = "uptime"
260	c.conf.wait = time.Second
261
262	buf, err := c.makeRExecSpec()
263	if err != nil {
264		t.Fatalf("err: %v", err)
265	}
266
267	err = c.uploadPayload(buf)
268	if err != nil {
269		t.Fatalf("err: %v", err)
270	}
271
272	pair, _, err := a.Client().KV().Get("_rexec/"+id+"/job", nil)
273	if err != nil {
274		t.Fatalf("err: %v", err)
275	}
276
277	if pair == nil || len(pair.Value) == 0 {
278		t.Fatalf("missing job spec")
279	}
280
281	err = c.destroyData()
282	if err != nil {
283		t.Fatalf("err: %v", err)
284	}
285
286	pair, _, err = a.Client().KV().Get("_rexec/"+id+"/job", nil)
287	if err != nil {
288		t.Fatalf("err: %v", err)
289	}
290
291	if pair != nil {
292		t.Fatalf("should be destroyed")
293	}
294}
295
296func TestExecCommand_StreamResults(t *testing.T) {
297	t.Parallel()
298	a := agent.NewTestAgent(t.Name(), `
299		disable_remote_exec = false
300	`)
301	defer a.Shutdown()
302
303	testrpc.WaitForTestAgent(t, a.RPC, "dc1")
304
305	ui := cli.NewMockUi()
306	c := New(ui, nil)
307	c.apiclient = a.Client()
308	c.conf.prefix = "_rexec"
309
310	id, err := c.createSession()
311	if err != nil {
312		t.Fatalf("err: %v", err)
313	}
314	c.sessionID = id
315
316	ackCh := make(chan rExecAck, 128)
317	heartCh := make(chan rExecHeart, 128)
318	outputCh := make(chan rExecOutput, 128)
319	exitCh := make(chan rExecExit, 128)
320	doneCh := make(chan struct{})
321	errCh := make(chan struct{}, 1)
322	defer close(doneCh)
323	go c.streamResults(doneCh, ackCh, heartCh, outputCh, exitCh, errCh)
324
325	prefix := "_rexec/" + id + "/"
326	ok, _, err := a.Client().KV().Acquire(&consulapi.KVPair{
327		Key:     prefix + "foo/ack",
328		Session: id,
329	}, nil)
330	if err != nil {
331		t.Fatalf("err: %v", err)
332	}
333	if !ok {
334		t.Fatalf("should be ok bro")
335	}
336
337	retry.Run(t, func(r *retry.R) {
338		select {
339		case a := <-ackCh:
340			if a.Node != "foo" {
341				r.Fatalf("bad: %#v", a)
342			}
343		case <-time.After(50 * time.Millisecond):
344			r.Fatalf("timeout")
345		}
346	})
347
348	ok, _, err = a.Client().KV().Acquire(&consulapi.KVPair{
349		Key:     prefix + "foo/exit",
350		Value:   []byte("127"),
351		Session: id,
352	}, nil)
353	if err != nil {
354		t.Fatalf("err: %v", err)
355	}
356	if !ok {
357		t.Fatalf("should be ok bro")
358	}
359
360	retry.Run(t, func(r *retry.R) {
361		select {
362		case e := <-exitCh:
363			if e.Node != "foo" || e.Code != 127 {
364				r.Fatalf("bad: %#v", e)
365			}
366		case <-time.After(50 * time.Millisecond):
367			r.Fatalf("timeout")
368		}
369	})
370
371	// Random key, should ignore
372	ok, _, err = a.Client().KV().Acquire(&consulapi.KVPair{
373		Key:     prefix + "foo/random",
374		Session: id,
375	}, nil)
376	if err != nil {
377		t.Fatalf("err: %v", err)
378	}
379	if !ok {
380		t.Fatalf("should be ok bro")
381	}
382
383	// Output heartbeat
384	ok, _, err = a.Client().KV().Acquire(&consulapi.KVPair{
385		Key:     prefix + "foo/out/00000",
386		Session: id,
387	}, nil)
388	if err != nil {
389		t.Fatalf("err: %v", err)
390	}
391	if !ok {
392		t.Fatalf("should be ok bro")
393	}
394
395	retry.Run(t, func(r *retry.R) {
396		select {
397		case h := <-heartCh:
398			if h.Node != "foo" {
399				r.Fatalf("bad: %#v", h)
400			}
401		case <-time.After(50 * time.Millisecond):
402			r.Fatalf("timeout")
403		}
404	})
405
406	// Output value
407	ok, _, err = a.Client().KV().Acquire(&consulapi.KVPair{
408		Key:     prefix + "foo/out/00001",
409		Value:   []byte("test"),
410		Session: id,
411	}, nil)
412	if err != nil {
413		t.Fatalf("err: %v", err)
414	}
415	if !ok {
416		t.Fatalf("should be ok bro")
417	}
418
419	retry.Run(t, func(r *retry.R) {
420		select {
421		case o := <-outputCh:
422			if o.Node != "foo" || string(o.Output) != "test" {
423				r.Fatalf("bad: %#v", o)
424			}
425		case <-time.After(50 * time.Millisecond):
426			r.Fatalf("timeout")
427		}
428	})
429}
430