open-nomad/command/alloc_exec_test.go
Drew Bailey 6c788fdccd
Events/msgtype cleanup (#9117)
* use msgtype in upsert node

adds message type to signature for upsert node, update tests, remove placeholder method

* UpsertAllocs msg type test setup

* use upsertallocs with msg type in signature

update test usage of delete node

delete placeholder msgtype method

* add msgtype to upsert evals signature, update test call sites with test setup msg type

handle snapshot upsert eval outside of FSM and ignore eval event

remove placeholder upsertevalsmsgtype

handle job plan rpc and prevent event creation for plan

msgtype cleanup upsertnodeevents

updatenodedrain msgtype

msg type 0 is a node registration event, so set the default  to the ignore type

* fix named import

* fix signature ordering on upsertnode to match
2020-10-19 09:30:15 -04:00

293 lines
7.2 KiB
Go

package command
import (
"bytes"
"fmt"
"strings"
"testing"
"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/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
// static check
var _ cli.Command = &AllocExecCommand{}
func TestAllocExecCommand_Fails(t *testing.T) {
t.Parallel()
srv, client, url := testServer(t, true, nil)
defer srv.Shutdown()
cases := []struct {
name string
args []string
expectedError string
}{
{
"misuse",
[]string{"bad"},
commandErrorText(&AllocExecCommand{}),
},
{
"connection failure",
[]string{"-address=nope", "26470238-5CF2-438F-8772-DC67CFB0705C", "/bin/bash"},
"Error querying allocation",
},
{
"not found alloc",
[]string{"-address=" + url, "26470238-5CF2-438F-8772-DC67CFB0705C", "/bin/bash"},
"No allocation(s) with prefix or id",
},
{
"not found job",
[]string{"-address=" + url, "-job", "example", "/bin/bash"},
`job "example" doesn't exist`,
},
{
"too short allocis",
[]string{"-address=" + url, "2", "/bin/bash"},
"Alloc ID must contain at least two characters",
},
{
"missing command",
[]string{"-address=" + url, "26470238-5CF2-438F-8772-DC67CFB0705C"},
"A command is required",
},
{
"long escaped char",
[]string{"-address=" + url, "-e", "long_escape", "26470238-5CF2-438F-8772-DC67CFB0705C", "/bin/bash"},
"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)
require.Equal(t, 1, code)
require.Contains(t, ui.ErrorWriter.String(), c.expectedError)
ui.ErrorWriter.Reset()
ui.OutputWriter.Reset()
})
}
// Wait for a node to be ready
testutil.WaitForResult(func() (bool, error) {
nodes, _, err := client.Nodes().List(nil)
if err != nil {
return false, err
}
for _, node := range nodes {
if _, ok := node.Drivers["mock_driver"]; ok &&
node.Status == structs.NodeStatusReady {
return true, nil
}
}
return false, fmt.Errorf("no ready nodes")
}, func(err error) {
require.NoError(t, err)
})
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)
require.NoError(t, err)
code := waitForSuccess(ui, client, fullId, t, resp.EvalID)
require.Zero(t, code, "status code not zero")
// get an alloc id
allocId1 := ""
if allocs, _, err := client.Jobs().Allocations(jobID, false, nil); err == nil {
if len(allocs) > 0 {
allocId1 = allocs[0].ID
}
}
require.NotEmpty(t, allocId1, "unable to find allocation")
// by alloc
require.Equal(t, 1, cmd.Run([]string{"-address=" + url, "-task=nonexistenttask1", allocId1, "/bin/bash"}))
require.Contains(t, ui.ErrorWriter.String(), "Could not find task named: nonexistenttask1")
ui.ErrorWriter.Reset()
// by jobID
require.Equal(t, 1, cmd.Run([]string{"-address=" + url, "-task=nonexistenttask2", "-job", jobID, "/bin/bash"}))
require.Contains(t, ui.ErrorWriter.String(), "Could not find task named: nonexistenttask2")
ui.ErrorWriter.Reset()
})
}
func TestAllocExecCommand_AutocompleteArgs(t *testing.T) {
assert := assert.New(t)
t.Parallel()
srv, _, url := testServer(t, true, nil)
defer srv.Shutdown()
ui := cli.NewMockUi()
cmd := &AllocExecCommand{Meta: Meta{Ui: ui, flagAddress: url}}
// Create a fake alloc
state := srv.Agent.Server().State()
a := mock.Alloc()
assert.Nil(state.UpsertAllocs(structs.MsgTypeTestSetup, 1000, []*structs.Allocation{a}))
prefix := a.ID[:5]
args := complete.Args{Last: prefix}
predictor := cmd.AutocompleteArgs()
res := predictor.Predict(args)
assert.Equal(1, len(res))
assert.Equal(a.ID, res[0])
}
func TestAllocExecCommand_Run(t *testing.T) {
t.Parallel()
srv, client, url := testServer(t, true, nil)
defer srv.Shutdown()
// Wait for a node to be ready
testutil.WaitForResult(func() (bool, error) {
nodes, _, err := client.Nodes().List(nil)
if err != nil {
return false, err
}
for _, node := range nodes {
if _, ok := node.Drivers["mock_driver"]; ok &&
node.Status == structs.NodeStatusReady {
return true, nil
}
}
return false, fmt.Errorf("no ready nodes")
}, func(err error) {
require.NoError(t, err)
})
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)
require.NoError(t, err)
evalUi := cli.NewMockUi()
code := waitForSuccess(evalUi, client, fullId, t, resp.EvalID)
require.Equal(t, 0, code, "failed to get status - output: %v", evalUi.ErrorWriter.String())
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) {
require.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})
assert.Equal(t, c.exitCode, code)
assert.Equal(t, c.stdout, strings.TrimSpace(stdout.String()))
assert.Equal(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})
assert.Equal(t, c.exitCode, code)
assert.Equal(t, c.stdout, strings.TrimSpace(stdout.String()))
assert.Equal(t, c.stderr, strings.TrimSpace(stderr.String()))
})
}
}
type bufferCloser struct {
bytes.Buffer
}
func (b *bufferCloser) Close() error {
return nil
}