open-nomad/e2e/nomadexec/exec.go
Mahmood Ali f3f8f15b7b e2e: special case "Unexpected EOF" errors
This is an attempt at deflaking the e2e exec tests, and a way to improve
messages.

e2e occasionally fail with "unexpected EOF" even though the exec output matches
expectations. I suspect there is a race in handling EOF in server/http handling.

Here, we special case this error and ensures we get all failures,
to help debug the case better.
2021-01-26 10:01:14 -05:00

151 lines
3.6 KiB
Go

package nomadexec
import (
"bytes"
"context"
"fmt"
"io"
"reflect"
"regexp"
"strings"
"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]
allocs := e2eutil.RegisterAndWaitForAllocs(f.T(), tc.Nomad(), tc.jobFilePath, tc.jobID, "")
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)
// 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)
}
assert.Equal(t, c.ExitCode, exitCode)
switch s := c.Stdout.(type) {
case string:
assert.Equal(t, s, stdout.String())
case *regexp.Regexp:
assert.Regexp(t, s, stdout.String())
case nil:
assert.Empty(t, stdout.String())
default:
assert.Fail(t, "unexpected stdout type", "found %v (%v), but expected string or regexp", s, reflect.TypeOf(s))
}
switch s := c.Stderr.(type) {
case string:
assert.Equal(t, s, stderr.String())
case *regexp.Regexp:
assert.Regexp(t, s, stderr.String())
case nil:
assert.Empty(t, stderr.String())
default:
assert.Fail(t, "unexpected stderr type", "found %v (%v), but expected string or regexp", s, reflect.TypeOf(s))
}
})
}
}
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
}