command/exec: Testing exec

This commit is contained in:
Armon Dadgar 2014-09-01 13:02:15 -07:00
parent 4d46817a96
commit 17269f7a83
2 changed files with 281 additions and 4 deletions

View file

@ -125,7 +125,8 @@ func (c *ExecCommand) Run(args []string) int {
c.conf.cmd = strings.Join(cmdFlags.Args(), " ") c.conf.cmd = strings.Join(cmdFlags.Args(), " ")
// If there is no command, read stdin for a script input // If there is no command, read stdin for a script input
if c.conf.cmd == "" { if c.conf.cmd == "-" {
c.conf.cmd = ""
var buf bytes.Buffer var buf bytes.Buffer
_, err := io.Copy(&buf, os.Stdin) _, err := io.Copy(&buf, os.Stdin)
if err != nil { if err != nil {
@ -240,6 +241,11 @@ OUTER:
ackCount++ ackCount++
c.Ui.Output(fmt.Sprintf("Node %s: acknowledged event", e.Node)) c.Ui.Output(fmt.Sprintf("Node %s: acknowledged event", e.Node))
case h := <-heartCh:
if c.conf.verbose {
c.Ui.Output(fmt.Sprintf("Node %s: heartbeated", h.Node))
}
case e := <-outputCh: case e := <-outputCh:
c.Ui.Output(fmt.Sprintf("Node %s: %s", e.Node, e.Output)) c.Ui.Output(fmt.Sprintf("Node %s: %s", e.Node, e.Output))
@ -471,11 +477,11 @@ func (c *ExecCommand) Synopsis() string {
func (c *ExecCommand) Help() string { func (c *ExecCommand) Help() string {
helpText := ` helpText := `
Usage: consul exec [options] [command...] Usage: consul exec [options] [-|command...]
Evaluates a command on remote Consul nodes. The nodes responding can Evaluates a command on remote Consul nodes. The nodes responding can
be filtered using regular expressions on node name, service, and tag be filtered using regular expressions on node name, service, and tag
definitions. If a command is not provided, stdin will be read until EOF definitions. If a command is '-', stdin will be read until EOF
and used as a script input. and used as a script input.
Options: Options:

View file

@ -1,11 +1,13 @@
package command package command
import ( import (
"github.com/mitchellh/cli"
"strings" "strings"
"testing" "testing"
"time"
"github.com/armon/consul-api"
"github.com/hashicorp/consul/testutil" "github.com/hashicorp/consul/testutil"
"github.com/mitchellh/cli"
) )
func TestExecCommand_implements(t *testing.T) { func TestExecCommand_implements(t *testing.T) {
@ -46,3 +48,272 @@ func waitForLeader(t *testing.T, httpAddr string) {
t.Fatalf("failed to find leader: %v", err) t.Fatalf("failed to find leader: %v", err)
}) })
} }
func TestExecCommand_Validate(t *testing.T) {
conf := &rExecConf{}
err := conf.validate()
if err != nil {
t.Fatalf("err: %v", err)
}
conf.node = "("
err = conf.validate()
if err == nil {
t.Fatalf("err: %v", err)
}
conf.node = ""
conf.service = "("
err = conf.validate()
if err == nil {
t.Fatalf("err: %v", err)
}
conf.service = "()"
conf.tag = "("
err = conf.validate()
if err == nil {
t.Fatalf("err: %v", err)
}
conf.service = ""
conf.tag = "foo"
err = conf.validate()
if err == nil {
t.Fatalf("err: %v", err)
}
}
func TestExecCommand_Sessions(t *testing.T) {
a1 := testAgent(t)
defer a1.Shutdown()
waitForLeader(t, a1.httpAddr)
client, err := HTTPClient(a1.httpAddr)
if err != nil {
t.Fatalf("err: %v", err)
}
ui := new(cli.MockUi)
c := &ExecCommand{
Ui: ui,
client: client,
}
id, err := c.createSession()
if err != nil {
t.Fatalf("err: %v", err)
}
se, _, err := client.Session().Info(id, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if se == nil || se.Name != "Remote Exec" {
t.Fatalf("bad: %v", se)
}
c.sessionID = id
err = c.destroySession()
if err != nil {
t.Fatalf("err: %v", err)
}
se, _, err = client.Session().Info(id, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if se != nil {
t.Fatalf("bad: %v", se)
}
}
func TestExecCommand_UploadDestroy(t *testing.T) {
a1 := testAgent(t)
defer a1.Shutdown()
waitForLeader(t, a1.httpAddr)
client, err := HTTPClient(a1.httpAddr)
if err != nil {
t.Fatalf("err: %v", err)
}
ui := new(cli.MockUi)
c := &ExecCommand{
Ui: ui,
client: client,
}
id, err := c.createSession()
if err != nil {
t.Fatalf("err: %v", err)
}
c.sessionID = id
c.conf.prefix = "_rexec"
c.conf.cmd = "uptime"
c.conf.wait = time.Second
buf, err := c.makeRExecSpec()
if err != nil {
t.Fatalf("err: %v", err)
}
err = c.uploadPayload(buf)
if err != nil {
t.Fatalf("err: %v", err)
}
pair, _, err := client.KV().Get("_rexec/"+id+"/job", nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if pair == nil || len(pair.Value) == 0 {
t.Fatalf("missing job spec")
}
err = c.destroyData()
if err != nil {
t.Fatalf("err: %v", err)
}
pair, _, err = client.KV().Get("_rexec/"+id+"/job", nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if pair != nil {
t.Fatalf("should be destroyed")
}
}
func TestExecCommand_StreamResults(t *testing.T) {
a1 := testAgent(t)
defer a1.Shutdown()
waitForLeader(t, a1.httpAddr)
client, err := HTTPClient(a1.httpAddr)
if err != nil {
t.Fatalf("err: %v", err)
}
ui := new(cli.MockUi)
c := &ExecCommand{
Ui: ui,
client: client,
}
c.conf.prefix = "_rexec"
id, err := c.createSession()
if err != nil {
t.Fatalf("err: %v", err)
}
c.sessionID = id
ackCh := make(chan rExecAck, 128)
heartCh := make(chan rExecHeart, 128)
outputCh := make(chan rExecOutput, 128)
exitCh := make(chan rExecExit, 128)
doneCh := make(chan struct{})
errCh := make(chan struct{}, 1)
defer close(doneCh)
go c.streamResults(doneCh, ackCh, heartCh, outputCh, exitCh, errCh)
prefix := "_rexec/" + id + "/"
ok, _, err := client.KV().Acquire(&consulapi.KVPair{
Key: prefix + "foo/ack",
Session: id,
}, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if !ok {
t.Fatalf("should be ok bro")
}
select {
case a := <-ackCh:
if a.Node != "foo" {
t.Fatalf("bad: %#v", a)
}
case <-time.After(50 * time.Millisecond):
t.Fatalf("timeout")
}
ok, _, err = client.KV().Acquire(&consulapi.KVPair{
Key: prefix + "foo/exit",
Value: []byte("127"),
Session: id,
}, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if !ok {
t.Fatalf("should be ok bro")
}
select {
case e := <-exitCh:
if e.Node != "foo" || e.Code != 127 {
t.Fatalf("bad: %#v", e)
}
case <-time.After(50 * time.Millisecond):
t.Fatalf("timeout")
}
// Random key, should ignore
ok, _, err = client.KV().Acquire(&consulapi.KVPair{
Key: prefix + "foo/random",
Session: id,
}, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if !ok {
t.Fatalf("should be ok bro")
}
// Output heartbeat
ok, _, err = client.KV().Acquire(&consulapi.KVPair{
Key: prefix + "foo/out/00000",
Session: id,
}, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if !ok {
t.Fatalf("should be ok bro")
}
select {
case h := <-heartCh:
if h.Node != "foo" {
t.Fatalf("bad: %#v", h)
}
case <-time.After(50 * time.Millisecond):
t.Fatalf("timeout")
}
// Output value
ok, _, err = client.KV().Acquire(&consulapi.KVPair{
Key: prefix + "foo/out/00001",
Value: []byte("test"),
Session: id,
}, nil)
if err != nil {
t.Fatalf("err: %v", err)
}
if !ok {
t.Fatalf("should be ok bro")
}
select {
case o := <-outputCh:
if o.Node != "foo" || string(o.Output) != "test" {
t.Fatalf("bad: %#v", o)
}
case <-time.After(50 * time.Millisecond):
t.Fatalf("timeout")
}
}