c5d36eaa2f
This PR continues the cleanup of the command package, removing linter warnings, refactoring to use helpers, making tests easier to read, etc.
280 lines
6.6 KiB
Go
280 lines
6.6 KiB
Go
package command
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/hashicorp/nomad/ci"
|
|
"github.com/hashicorp/nomad/helper/uuid"
|
|
"github.com/hashicorp/nomad/nomad/mock"
|
|
"github.com/hashicorp/nomad/nomad/structs"
|
|
"github.com/hashicorp/nomad/testutil"
|
|
"github.com/mitchellh/cli"
|
|
"github.com/posener/complete"
|
|
"github.com/shoenig/test/must"
|
|
)
|
|
|
|
// static check
|
|
var _ cli.Command = &AllocExecCommand{}
|
|
|
|
func TestAllocExecCommand_Fails(t *testing.T) {
|
|
ci.Parallel(t)
|
|
srv, client, url := testServer(t, true, nil)
|
|
defer stopTestAgent(srv)
|
|
|
|
cases := []struct {
|
|
name string
|
|
args []string
|
|
expectedError string
|
|
}{
|
|
{
|
|
"alloc id missing",
|
|
[]string{},
|
|
`An allocation ID is required`,
|
|
},
|
|
{
|
|
"alloc id too short",
|
|
[]string{"-address=" + url, "2", "/bin/bash"},
|
|
`Alloc ID must contain at least two characters`,
|
|
},
|
|
{
|
|
"alloc not found",
|
|
[]string{"-address=" + url, "26470238-5CF2-438F-8772-DC67CFB0705C", "/bin/bash"},
|
|
`No allocation(s) with prefix or id "26470238-5CF2-438F-8772-DC67CFB0705C"`,
|
|
},
|
|
{
|
|
"alloc not found with odd-length prefix",
|
|
[]string{"-address=" + url, "26470238-5CF", "/bin/bash"},
|
|
`No allocation(s) with prefix or id "26470238-5CF"`,
|
|
},
|
|
{
|
|
"job id missing",
|
|
[]string{"-job"},
|
|
`A job ID is required`,
|
|
},
|
|
{
|
|
"job not found",
|
|
[]string{"-address=" + url, "-job", "example", "/bin/bash"},
|
|
`job "example" doesn't exist`,
|
|
},
|
|
{
|
|
"command missing",
|
|
[]string{"-address=" + url, "26470238-5CF2-438F-8772-DC67CFB0705C"},
|
|
`A command is required`,
|
|
},
|
|
{
|
|
"connection failure",
|
|
[]string{"-address=nope", "26470238-5CF2-438F-8772-DC67CFB0705C", "/bin/bash"},
|
|
`Error querying allocation`,
|
|
},
|
|
{
|
|
"escape char too long",
|
|
[]string{"-address=" + url, "-e", "es", "26470238-5CF2-438F-8772-DC67CFB0705C", "/bin/bash"},
|
|
`-e requires 'none' or a single character`,
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run(c.name, func(t *testing.T) {
|
|
ui := cli.NewMockUi()
|
|
cmd := &AllocExecCommand{Meta: Meta{Ui: ui}}
|
|
|
|
code := cmd.Run(c.args)
|
|
must.One(t, code)
|
|
|
|
out := ui.ErrorWriter.String()
|
|
must.StrContains(t, out, c.expectedError)
|
|
|
|
ui.ErrorWriter.Reset()
|
|
ui.OutputWriter.Reset()
|
|
})
|
|
}
|
|
|
|
// Wait for a node to be ready
|
|
waitForNodes(t, client)
|
|
|
|
t.Run("non existent task", func(t *testing.T) {
|
|
ui := cli.NewMockUi()
|
|
cmd := &AllocExecCommand{Meta: Meta{Ui: ui}}
|
|
|
|
jobID := "job1_sfx"
|
|
job1 := testJob(jobID)
|
|
|
|
resp, _, err := client.Jobs().Register(job1, nil)
|
|
must.NoError(t, err)
|
|
|
|
code := waitForSuccess(ui, client, fullId, t, resp.EvalID)
|
|
must.Zero(t, code)
|
|
|
|
// get an alloc id
|
|
allocId1 := ""
|
|
if allocs, _, err := client.Jobs().Allocations(jobID, false, nil); err == nil {
|
|
if len(allocs) > 0 {
|
|
allocId1 = allocs[0].ID
|
|
}
|
|
}
|
|
must.NotEq(t, "", allocId1)
|
|
|
|
// by alloc
|
|
code = cmd.Run([]string{"-address=" + url, "-task=nonexistenttask1", allocId1, "/bin/bash"})
|
|
must.One(t, code)
|
|
|
|
out := ui.ErrorWriter.String()
|
|
must.StrContains(t, out, "Could not find task named: nonexistenttask1")
|
|
|
|
ui.ErrorWriter.Reset()
|
|
|
|
// by jobID
|
|
code = cmd.Run([]string{"-address=" + url, "-task=nonexistenttask2", "-job", jobID, "/bin/bash"})
|
|
must.One(t, code)
|
|
|
|
out = ui.ErrorWriter.String()
|
|
must.StrContains(t, out, "Could not find task named: nonexistenttask2")
|
|
|
|
ui.ErrorWriter.Reset()
|
|
})
|
|
|
|
}
|
|
|
|
func TestAllocExecCommand_AutocompleteArgs(t *testing.T) {
|
|
ci.Parallel(t)
|
|
|
|
srv, _, url := testServer(t, true, nil)
|
|
defer stopTestAgent(srv)
|
|
|
|
ui := cli.NewMockUi()
|
|
cmd := &AllocExecCommand{Meta: Meta{Ui: ui, flagAddress: url}}
|
|
|
|
// Create a fake alloc
|
|
state := srv.Agent.Server().State()
|
|
a := mock.Alloc()
|
|
must.NoError(t, state.UpsertAllocs(structs.MsgTypeTestSetup, 1000, []*structs.Allocation{a}))
|
|
|
|
prefix := a.ID[:5]
|
|
args := complete.Args{Last: prefix}
|
|
predictor := cmd.AutocompleteArgs()
|
|
|
|
res := predictor.Predict(args)
|
|
must.Len(t, 1, res)
|
|
must.Eq(t, a.ID, res[0])
|
|
}
|
|
|
|
func TestAllocExecCommand_Run(t *testing.T) {
|
|
ci.Parallel(t)
|
|
srv, client, url := testServer(t, true, nil)
|
|
defer stopTestAgent(srv)
|
|
|
|
// Wait for a node to be ready
|
|
waitForNodes(t, client)
|
|
|
|
jobID := uuid.Generate()
|
|
job := testJob(jobID)
|
|
job.TaskGroups[0].Tasks[0].Config = map[string]interface{}{
|
|
"run_for": "10s",
|
|
"exec_command": map[string]interface{}{
|
|
"run_for": "1ms",
|
|
"exit_code": 21,
|
|
"stdout_string": "sample stdout output\n",
|
|
"stderr_string": "sample stderr output\n",
|
|
},
|
|
}
|
|
resp, _, err := client.Jobs().Register(job, nil)
|
|
must.NoError(t, err)
|
|
|
|
evalUi := cli.NewMockUi()
|
|
code := waitForSuccess(evalUi, client, fullId, t, resp.EvalID)
|
|
must.Zero(t, code)
|
|
|
|
allocId := ""
|
|
|
|
testutil.WaitForResult(func() (bool, error) {
|
|
allocs, _, err := client.Jobs().Allocations(jobID, false, nil)
|
|
if err != nil {
|
|
return false, fmt.Errorf("failed to get allocations: %v", err)
|
|
}
|
|
|
|
if len(allocs) < 0 {
|
|
return false, fmt.Errorf("no allocations yet")
|
|
}
|
|
|
|
alloc := allocs[0]
|
|
if alloc.ClientStatus != "running" {
|
|
return false, fmt.Errorf("alloc is not running yet: %v", alloc.ClientStatus)
|
|
}
|
|
|
|
allocId = alloc.ID
|
|
return true, nil
|
|
}, func(err error) { must.NoError(t, err) })
|
|
|
|
cases := []struct {
|
|
name string
|
|
command string
|
|
stdin string
|
|
|
|
stdout string
|
|
stderr string
|
|
exitCode int
|
|
}{
|
|
{
|
|
name: "basic stdout/err",
|
|
command: "simplecommand",
|
|
stdin: "",
|
|
stdout: "sample stdout output",
|
|
stderr: "sample stderr output",
|
|
exitCode: 21,
|
|
},
|
|
{
|
|
name: "notty: streamining input",
|
|
command: "showinput",
|
|
stdin: "hello from stdin",
|
|
stdout: "TTY: false\nStdin:\nhello from stdin",
|
|
exitCode: 0,
|
|
},
|
|
}
|
|
|
|
for _, c := range cases {
|
|
t.Run("by id: "+c.name, func(t *testing.T) {
|
|
ui := cli.NewMockUi()
|
|
var stdout, stderr bufferCloser
|
|
|
|
cmd := &AllocExecCommand{
|
|
Meta: Meta{Ui: ui},
|
|
Stdin: strings.NewReader(c.stdin),
|
|
Stdout: &stdout,
|
|
Stderr: &stderr,
|
|
}
|
|
|
|
code = cmd.Run([]string{"-address=" + url, allocId, c.command})
|
|
must.Eq(t, c.exitCode, code)
|
|
must.Eq(t, c.stdout, strings.TrimSpace(stdout.String()))
|
|
must.Eq(t, c.stderr, strings.TrimSpace(stderr.String()))
|
|
})
|
|
t.Run("by job: "+c.name, func(t *testing.T) {
|
|
ui := cli.NewMockUi()
|
|
var stdout, stderr bufferCloser
|
|
|
|
cmd := &AllocExecCommand{
|
|
Meta: Meta{Ui: ui},
|
|
Stdin: strings.NewReader(c.stdin),
|
|
Stdout: &stdout,
|
|
Stderr: &stderr,
|
|
}
|
|
|
|
code = cmd.Run([]string{"-address=" + url, "-job", jobID, c.command})
|
|
must.Eq(t, c.exitCode, code)
|
|
must.Eq(t, c.stdout, strings.TrimSpace(stdout.String()))
|
|
must.Eq(t, c.stderr, strings.TrimSpace(stderr.String()))
|
|
})
|
|
}
|
|
}
|
|
|
|
type bufferCloser struct {
|
|
bytes.Buffer
|
|
}
|
|
|
|
func (b *bufferCloser) Close() error {
|
|
return nil
|
|
}
|