2019-04-28 21:35:42 +00:00
|
|
|
package nomadexec
|
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"context"
|
|
|
|
"fmt"
|
|
|
|
"io"
|
|
|
|
"reflect"
|
|
|
|
"regexp"
|
2021-01-26 14:18:59 +00:00
|
|
|
"strings"
|
2019-04-28 21:35:42 +00:00
|
|
|
"testing"
|
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/hashicorp/nomad/api"
|
|
|
|
"github.com/hashicorp/nomad/e2e/e2eutil"
|
|
|
|
"github.com/hashicorp/nomad/e2e/framework"
|
|
|
|
"github.com/hashicorp/nomad/helper/uuid"
|
|
|
|
dtestutils "github.com/hashicorp/nomad/plugins/drivers/testutils"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
|
|
)
|
|
|
|
|
|
|
|
type NomadExecE2ETest struct {
|
|
|
|
framework.TC
|
|
|
|
|
|
|
|
name string
|
|
|
|
jobFilePath string
|
|
|
|
|
|
|
|
jobID string
|
|
|
|
alloc api.Allocation
|
|
|
|
}
|
|
|
|
|
|
|
|
func init() {
|
|
|
|
framework.AddSuites(&framework.TestSuite{
|
|
|
|
Component: "Nomad exec",
|
|
|
|
CanRunLocal: true,
|
|
|
|
Cases: []framework.TestCase{
|
|
|
|
newNomadExecE2eTest("docker", "./nomadexec/testdata/docker.nomad"),
|
|
|
|
},
|
|
|
|
})
|
|
|
|
}
|
|
|
|
|
|
|
|
func newNomadExecE2eTest(name, jobFilePath string) *NomadExecE2ETest {
|
|
|
|
return &NomadExecE2ETest{
|
|
|
|
name: name,
|
|
|
|
jobFilePath: jobFilePath,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tc *NomadExecE2ETest) Name() string {
|
|
|
|
return fmt.Sprintf("%v (%v)", tc.TC.Name(), tc.name)
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tc *NomadExecE2ETest) BeforeAll(f *framework.F) {
|
|
|
|
// Ensure cluster has leader before running tests
|
|
|
|
e2eutil.WaitForLeader(f.T(), tc.Nomad())
|
|
|
|
e2eutil.WaitForNodesReady(f.T(), tc.Nomad(), 1)
|
|
|
|
|
|
|
|
// register a job for execing into
|
|
|
|
tc.jobID = "nomad-exec" + uuid.Generate()[:8]
|
2020-01-28 22:33:59 +00:00
|
|
|
allocs := e2eutil.RegisterAndWaitForAllocs(f.T(), tc.Nomad(), tc.jobFilePath, tc.jobID, "")
|
2019-04-28 21:35:42 +00:00
|
|
|
f.Len(allocs, 1)
|
|
|
|
|
|
|
|
e2eutil.WaitForAllocRunning(f.T(), tc.Nomad(), allocs[0].ID)
|
|
|
|
|
|
|
|
tc.alloc = api.Allocation{
|
|
|
|
ID: allocs[0].ID,
|
|
|
|
Namespace: allocs[0].Namespace,
|
|
|
|
NodeID: allocs[0].NodeID,
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tc *NomadExecE2ETest) TestExecBasicResponses(f *framework.F) {
|
|
|
|
for _, c := range dtestutils.ExecTaskStreamingBasicCases {
|
|
|
|
f.T().Run(c.Name, func(t *testing.T) {
|
|
|
|
|
|
|
|
stdin := newTestStdin(c.Tty, c.Stdin)
|
|
|
|
var stdout, stderr bytes.Buffer
|
|
|
|
|
|
|
|
resizeCh := make(chan api.TerminalSize)
|
|
|
|
go func() {
|
|
|
|
resizeCh <- api.TerminalSize{Height: 100, Width: 100}
|
|
|
|
}()
|
|
|
|
|
|
|
|
ctx, cancelFn := context.WithTimeout(context.Background(), 15*time.Second)
|
|
|
|
defer cancelFn()
|
|
|
|
|
|
|
|
exitCode, err := tc.Nomad().Allocations().Exec(ctx,
|
|
|
|
&tc.alloc, "task", c.Tty,
|
|
|
|
[]string{"/bin/sh", "-c", c.Command},
|
|
|
|
stdin, &stdout, &stderr,
|
|
|
|
resizeCh, nil)
|
|
|
|
|
2021-01-26 14:18:59 +00:00
|
|
|
// TODO: Occasionally, we get "Unexpected EOF" error, but with the correct output.
|
|
|
|
// investigate why
|
|
|
|
if err != nil && strings.Contains(err.Error(), io.ErrUnexpectedEOF.Error()) {
|
|
|
|
f.T().Logf("got unexpected EOF error, ignoring: %v", err)
|
|
|
|
} else {
|
|
|
|
assert.NoError(t, err)
|
|
|
|
}
|
2019-04-28 21:35:42 +00:00
|
|
|
|
|
|
|
assert.Equal(t, c.ExitCode, exitCode)
|
|
|
|
|
|
|
|
switch s := c.Stdout.(type) {
|
|
|
|
case string:
|
2021-01-26 14:18:59 +00:00
|
|
|
assert.Equal(t, s, stdout.String())
|
2019-04-28 21:35:42 +00:00
|
|
|
case *regexp.Regexp:
|
2021-01-26 14:18:59 +00:00
|
|
|
assert.Regexp(t, s, stdout.String())
|
2019-04-28 21:35:42 +00:00
|
|
|
case nil:
|
2021-01-26 14:18:59 +00:00
|
|
|
assert.Empty(t, stdout.String())
|
2019-04-28 21:35:42 +00:00
|
|
|
default:
|
2021-01-26 14:18:59 +00:00
|
|
|
assert.Fail(t, "unexpected stdout type", "found %v (%v), but expected string or regexp", s, reflect.TypeOf(s))
|
2019-04-28 21:35:42 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch s := c.Stderr.(type) {
|
|
|
|
case string:
|
2021-01-26 14:18:59 +00:00
|
|
|
assert.Equal(t, s, stderr.String())
|
2019-04-28 21:35:42 +00:00
|
|
|
case *regexp.Regexp:
|
2021-01-26 14:18:59 +00:00
|
|
|
assert.Regexp(t, s, stderr.String())
|
2019-04-28 21:35:42 +00:00
|
|
|
case nil:
|
2021-01-26 14:18:59 +00:00
|
|
|
assert.Empty(t, stderr.String())
|
2019-04-28 21:35:42 +00:00
|
|
|
default:
|
2021-01-26 14:18:59 +00:00
|
|
|
assert.Fail(t, "unexpected stderr type", "found %v (%v), but expected string or regexp", s, reflect.TypeOf(s))
|
2019-04-28 21:35:42 +00:00
|
|
|
}
|
|
|
|
})
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
func (tc *NomadExecE2ETest) AfterAll(f *framework.F) {
|
|
|
|
jobs := tc.Nomad().Jobs()
|
|
|
|
if tc.jobID != "" {
|
|
|
|
jobs.Deregister(tc.jobID, true, nil)
|
|
|
|
}
|
|
|
|
tc.Nomad().System().GarbageCollect()
|
|
|
|
}
|
|
|
|
|
|
|
|
func newTestStdin(tty bool, d string) io.Reader {
|
|
|
|
pr, pw := io.Pipe()
|
|
|
|
go func() {
|
|
|
|
pw.Write([]byte(d))
|
|
|
|
|
|
|
|
// when testing TTY, leave connection open for the entire duration of command
|
|
|
|
// closing stdin may cause TTY session prematurely before command completes
|
|
|
|
if !tty {
|
|
|
|
pw.Close()
|
|
|
|
}
|
|
|
|
|
|
|
|
}()
|
|
|
|
|
|
|
|
return pr
|
|
|
|
}
|