Merge pull request #5174 from hashicorp/dani/windows

Some Windows fixes and CI
This commit is contained in:
Danielle Tomlinson
2019-01-18 11:21:53 +01:00
committed by GitHub
27 changed files with 1406 additions and 1258 deletions

View File

@@ -12,7 +12,7 @@ GO_TEST_CMD = $(if $(shell which gotestsum),gotestsum --,go test)
default: help
ifeq (,$(findstring $(THIS_OS),Darwin Linux FreeBSD))
ifeq (,$(findstring $(THIS_OS),Darwin Linux FreeBSD Windows))
$(error Building Nomad is currently only supported on Darwin and Linux.)
endif

View File

@@ -1,6 +1,7 @@
package api
import (
"path/filepath"
"reflect"
"testing"
"time"
@@ -364,7 +365,7 @@ func TestTask_Artifact(t *testing.T) {
if *a.GetterMode != "file" {
t.Errorf("expected file but found %q", *a.GetterMode)
}
if *a.RelativeDest != "local/foo.txt" {
if filepath.ToSlash(*a.RelativeDest) != "local/foo.txt" {
t.Errorf("expected local/foo.txt but found %q", *a.RelativeDest)
}
}

View File

@@ -1,15 +1,12 @@
version: "build-{branch}-{build}"
image: Visual Studio 2017
clone_folder: c:\gopath\src\github.com\hashicorp\nomad
environment:
GOPATH: c:\gopath
GOBIN: c:\gopath\bin
matrix:
- RUN_UI_TESTS: 1
SKIP_NOMAD_TESTS: 1
- {}
GOMAXPROCS: 1
install:
- cmd: set PATH=%GOBIN%;c:\go\bin;%PATH%
@@ -17,11 +14,29 @@ install:
- cmd: go version
- cmd: go env
- ps: mkdir C:\gopath\bin
- ps: appveyor DownloadFile "https://releases.hashicorp.com/vault/0.7.0/vault_0.7.0_windows_amd64.zip" -FileName "C:\\gopath\\bin\\vault.zip"
- ps: appveyor DownloadFile "https://releases.hashicorp.com/vault/0.10.2/vault_0.10.2_windows_amd64.zip" -FileName "C:\\gopath\\bin\\vault.zip"
- ps: Expand-Archive C:\gopath\bin\vault.zip -DestinationPath C:\gopath\bin
- ps: appveyor DownloadFile "https://releases.hashicorp.com/consul/0.7.0/consul_0.7.0_windows_amd64.zip" -FileName "C:\\gopath\\bin\\consul.zip"
- ps: Expand-Archive C:\gopath\bin\consul.zip -DestinationPath C:\gopath\bin
#- cmd: go install
# - ps: appveyor DownloadFile "https://releases.hashicorp.com/consul/1.0.0/consul_1.0.0_windows_amd64.zip" -FileName "C:\\gopath\\bin\\consul.zip"
# - ps: Expand-Archive C:\gopath\bin\consul.zip -DestinationPath C:\gopath\bin
- ps: choco install make
- ps: |
go get -u github.com/kardianos/govendor
go get -u github.com/ugorji/go/codec/codecgen
go get -u github.com/hashicorp/go-bindata/go-bindata
go get -u github.com/elazarl/go-bindata-assetfs/go-bindata-assetfs
go get -u github.com/a8m/tree/cmd/tree
go get -u github.com/magiconair/vendorfmt/cmd/vendorfmt
go get -u github.com/golang/protobuf/protoc-gen-go
go get -u gotest.tools/gotestsum
build_script:
#- cmd: go test ./...
- cmd: go install
- cmd: |
set PATH=%GOPATH%/bin;%PATH%
mkdir -p $GOPATH\bin
go build -o $GOPATH\bin\nomad
# test_script:
# - cmd: gotestsum -f short-verbose --junitfile results.xml
# on_finish:
# - ps: |
# Push-AppveyorArtifact (Resolve-Path .\results.xml)
# $wc = New-Object 'System.Net.WebClient'
# $wc.UploadFile("https://ci.appveyor.com/api/testresults/junit/$($env:APPVEYOR_JOB_ID)", (Resolve-Path .\results.xml))

View File

@@ -5,19 +5,16 @@ import (
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"strings"
"syscall"
"testing"
"time"
hclog "github.com/hashicorp/go-hclog"
"github.com/hashicorp/nomad/client/allocdir"
cstructs "github.com/hashicorp/nomad/client/structs"
ctestutil "github.com/hashicorp/nomad/client/testutil"
"github.com/hashicorp/nomad/helper/testlog"
"github.com/hashicorp/nomad/nomad/mock"
"github.com/hashicorp/nomad/nomad/structs"
@@ -201,148 +198,6 @@ func TestPrevAlloc_LocalPrevAlloc_Terminated(t *testing.T) {
require.NoError(t, waiter.Wait(ctx))
}
// TestPrevAlloc_StreamAllocDir_Ok asserts that streaming a tar to an alloc dir
// works.
func TestPrevAlloc_StreamAllocDir_Ok(t *testing.T) {
ctestutil.RequireRoot(t)
t.Parallel()
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("err: %v", err)
}
defer os.RemoveAll(dir)
// Create foo/
fooDir := filepath.Join(dir, "foo")
if err := os.Mkdir(fooDir, 0777); err != nil {
t.Fatalf("err: %v", err)
}
// Change ownership of foo/ to test #3702 (any non-root user is fine)
const uid, gid = 1, 1
if err := os.Chown(fooDir, uid, gid); err != nil {
t.Fatalf("err : %v", err)
}
dirInfo, err := os.Stat(fooDir)
if err != nil {
t.Fatalf("err: %v", err)
}
// Create foo/bar
f, err := os.Create(filepath.Join(fooDir, "bar"))
if err != nil {
t.Fatalf("err: %v", err)
}
if _, err := f.WriteString("123"); err != nil {
t.Fatalf("err: %v", err)
}
if err := f.Chmod(0644); err != nil {
t.Fatalf("err: %v", err)
}
fInfo, err := f.Stat()
if err != nil {
t.Fatalf("err: %v", err)
}
f.Close()
// Create foo/baz -> bar symlink
if err := os.Symlink("bar", filepath.Join(dir, "foo", "baz")); err != nil {
t.Fatalf("err: %v", err)
}
linkInfo, err := os.Lstat(filepath.Join(dir, "foo", "baz"))
if err != nil {
t.Fatalf("err: %v", err)
}
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
walkFn := func(path string, fileInfo os.FileInfo, err error) error {
// Include the path of the file name relative to the alloc dir
// so that we can put the files in the right directories
link := ""
if fileInfo.Mode()&os.ModeSymlink != 0 {
target, err := os.Readlink(path)
if err != nil {
return fmt.Errorf("error reading symlink: %v", err)
}
link = target
}
hdr, err := tar.FileInfoHeader(fileInfo, link)
if err != nil {
return fmt.Errorf("error creating file header: %v", err)
}
hdr.Name = fileInfo.Name()
tw.WriteHeader(hdr)
// If it's a directory or symlink we just write the header into the tar
if fileInfo.IsDir() || (fileInfo.Mode()&os.ModeSymlink != 0) {
return nil
}
// Write the file into the archive
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
if _, err := io.Copy(tw, file); err != nil {
return err
}
return nil
}
if err := filepath.Walk(dir, walkFn); err != nil {
t.Fatalf("err: %v", err)
}
tw.Close()
dir1, err := ioutil.TempDir("", "nomadtest-")
if err != nil {
t.Fatalf("err: %v", err)
}
defer os.RemoveAll(dir1)
rc := ioutil.NopCloser(buf)
prevAlloc := &remotePrevAlloc{logger: testlog.HCLogger(t)}
if err := prevAlloc.streamAllocDir(context.Background(), rc, dir1); err != nil {
t.Fatalf("err: %v", err)
}
// Ensure foo is present
fi, err := os.Stat(filepath.Join(dir1, "foo"))
if err != nil {
t.Fatalf("err: %v", err)
}
if fi.Mode() != dirInfo.Mode() {
t.Fatalf("mode: %v", fi.Mode())
}
stat := fi.Sys().(*syscall.Stat_t)
if stat.Uid != uid || stat.Gid != gid {
t.Fatalf("foo/ has incorrect ownership: expected %d:%d found %d:%d",
uid, gid, stat.Uid, stat.Gid)
}
fi1, err := os.Stat(filepath.Join(dir1, "bar"))
if err != nil {
t.Fatalf("err: %v", err)
}
if fi1.Mode() != fInfo.Mode() {
t.Fatalf("mode: %v", fi1.Mode())
}
fi2, err := os.Lstat(filepath.Join(dir1, "baz"))
if err != nil {
t.Fatalf("err: %v", err)
}
if fi2.Mode() != linkInfo.Mode() {
t.Fatalf("mode: %v", fi2.Mode())
}
}
// TestPrevAlloc_StreamAllocDir_Error asserts that errors encountered while
// streaming a tar cause the migration to be cancelled and no files are written
// (migrations are atomic).

View File

@@ -0,0 +1,161 @@
// +build !windows
package allocwatcher
import (
"archive/tar"
"bytes"
"context"
"fmt"
"io"
"io/ioutil"
"os"
"path/filepath"
"syscall"
"testing"
ctestutil "github.com/hashicorp/nomad/client/testutil"
"github.com/hashicorp/nomad/helper/testlog"
)
// TestPrevAlloc_StreamAllocDir_Ok asserts that streaming a tar to an alloc dir
// works.
func TestPrevAlloc_StreamAllocDir_Ok(t *testing.T) {
ctestutil.RequireRoot(t)
t.Parallel()
dir, err := ioutil.TempDir("", "")
if err != nil {
t.Fatalf("err: %v", err)
}
defer os.RemoveAll(dir)
// Create foo/
fooDir := filepath.Join(dir, "foo")
if err := os.Mkdir(fooDir, 0777); err != nil {
t.Fatalf("err: %v", err)
}
// Change ownership of foo/ to test #3702 (any non-root user is fine)
const uid, gid = 1, 1
if err := os.Chown(fooDir, uid, gid); err != nil {
t.Fatalf("err : %v", err)
}
dirInfo, err := os.Stat(fooDir)
if err != nil {
t.Fatalf("err: %v", err)
}
// Create foo/bar
f, err := os.Create(filepath.Join(fooDir, "bar"))
if err != nil {
t.Fatalf("err: %v", err)
}
if _, err := f.WriteString("123"); err != nil {
t.Fatalf("err: %v", err)
}
if err := f.Chmod(0644); err != nil {
t.Fatalf("err: %v", err)
}
fInfo, err := f.Stat()
if err != nil {
t.Fatalf("err: %v", err)
}
f.Close()
// Create foo/baz -> bar symlink
if err := os.Symlink("bar", filepath.Join(dir, "foo", "baz")); err != nil {
t.Fatalf("err: %v", err)
}
linkInfo, err := os.Lstat(filepath.Join(dir, "foo", "baz"))
if err != nil {
t.Fatalf("err: %v", err)
}
buf := new(bytes.Buffer)
tw := tar.NewWriter(buf)
walkFn := func(path string, fileInfo os.FileInfo, err error) error {
// Include the path of the file name relative to the alloc dir
// so that we can put the files in the right directories
link := ""
if fileInfo.Mode()&os.ModeSymlink != 0 {
target, err := os.Readlink(path)
if err != nil {
return fmt.Errorf("error reading symlink: %v", err)
}
link = target
}
hdr, err := tar.FileInfoHeader(fileInfo, link)
if err != nil {
return fmt.Errorf("error creating file header: %v", err)
}
hdr.Name = fileInfo.Name()
tw.WriteHeader(hdr)
// If it's a directory or symlink we just write the header into the tar
if fileInfo.IsDir() || (fileInfo.Mode()&os.ModeSymlink != 0) {
return nil
}
// Write the file into the archive
file, err := os.Open(path)
if err != nil {
return err
}
defer file.Close()
if _, err := io.Copy(tw, file); err != nil {
return err
}
return nil
}
if err := filepath.Walk(dir, walkFn); err != nil {
t.Fatalf("err: %v", err)
}
tw.Close()
dir1, err := ioutil.TempDir("", "nomadtest-")
if err != nil {
t.Fatalf("err: %v", err)
}
defer os.RemoveAll(dir1)
rc := ioutil.NopCloser(buf)
prevAlloc := &remotePrevAlloc{logger: testlog.HCLogger(t)}
if err := prevAlloc.streamAllocDir(context.Background(), rc, dir1); err != nil {
t.Fatalf("err: %v", err)
}
// Ensure foo is present
fi, err := os.Stat(filepath.Join(dir1, "foo"))
if err != nil {
t.Fatalf("err: %v", err)
}
if fi.Mode() != dirInfo.Mode() {
t.Fatalf("mode: %v", fi.Mode())
}
stat := fi.Sys().(*syscall.Stat_t)
if stat.Uid != uid || stat.Gid != gid {
t.Fatalf("foo/ has incorrect ownership: expected %d:%d found %d:%d",
uid, gid, stat.Uid, stat.Gid)
}
fi1, err := os.Stat(filepath.Join(dir1, "bar"))
if err != nil {
t.Fatalf("err: %v", err)
}
if fi1.Mode() != fInfo.Mode() {
t.Fatalf("mode: %v", fi1.Mode())
}
fi2, err := os.Lstat(filepath.Join(dir1, "baz"))
if err != nil {
t.Fatalf("err: %v", err)
}
if fi2.Mode() != linkInfo.Mode() {
t.Fatalf("mode: %v", fi2.Mode())
}
}

View File

@@ -7,7 +7,7 @@ import (
)
func TestNetworkFingerPrint_linkspeed_parse(t *testing.T) {
f := &NetworkFingerprint{logger: testlog.Logger(t), interfaceDetector: &DefaultNetworkInterfaceDetector{}}
f := &NetworkFingerprint{logger: testlog.HCLogger(t), interfaceDetector: &DefaultNetworkInterfaceDetector{}}
var outputTests = []struct {
in string

View File

@@ -10,6 +10,7 @@ import (
"os"
"path/filepath"
"reflect"
"runtime"
"strings"
"testing"
"time"
@@ -1523,7 +1524,11 @@ func TestFS_streamFile_NoFile(t *testing.T) {
err := c.endpoints.FileSystem.streamFile(
context.Background(), 0, "foo", 0, ad, framer, nil)
require.NotNil(err)
require.Contains(err.Error(), "no such file")
if runtime.GOOS == "windows" {
require.Contains(err.Error(), "cannot find the file")
} else {
require.Contains(err.Error(), "no such file")
}
}
func TestFS_streamFile_Modify(t *testing.T) {
@@ -1701,6 +1706,9 @@ func TestFS_streamFile_Truncate(t *testing.T) {
}
func TestFS_streamImpl_Delete(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Windows does not allow us to delete a file while it is open")
}
t.Parallel()
c, cleanup := TestClient(t, nil)
@@ -1725,7 +1733,11 @@ func TestFS_streamImpl_Delete(t *testing.T) {
frames := make(chan *sframer.StreamFrame, 4)
go func() {
for {
frame := <-frames
frame, ok := <-frames
if !ok {
return
}
if frame.IsHeartbeat() {
continue
}

View File

@@ -9,10 +9,12 @@ import (
"github.com/hashicorp/nomad/helper/testlog"
"github.com/hashicorp/nomad/testutil"
vaultapi "github.com/hashicorp/vault/api"
"github.com/stretchr/testify/require"
)
func TestVaultClient_TokenRenewals(t *testing.T) {
t.Parallel()
require := require.New(t)
v := testutil.NewTestVault(t)
defer v.Stop()
@@ -67,9 +69,7 @@ func TestVaultClient_TokenRenewals(t *testing.T) {
for {
select {
case err := <-errCh:
if err != nil {
t.Fatalf("error while renewing the token: %v", err)
}
require.NoError(err, "unexpected error while renewing vault token")
}
}
}(errCh)
@@ -83,7 +83,7 @@ func TestVaultClient_TokenRenewals(t *testing.T) {
for i := 0; i < num; i++ {
if err := c.StopRenewToken(tokens[i]); err != nil {
t.Fatal(err)
require.NoError(err)
}
}
@@ -275,7 +275,8 @@ func TestVaultClient_RenewNonexistentLease(t *testing.T) {
_, err = c.RenewToken(c.client.Token(), 10)
if err == nil {
t.Fatalf("expected error, got nil")
} else if !strings.Contains(err.Error(), "lease not found") {
t.Fatalf("expected \"%s\" in error message, got \"%v\"", "lease not found", err)
// The Vault error message changed between 0.10.2 and 1.0.1
} else if !strings.Contains(err.Error(), "lease not found") && !strings.Contains(err.Error(), "lease is not renewable") {
t.Fatalf("expected \"%s\" or \"%s\" in error message, got \"%v\"", "lease not found", "lease is not renewable", err.Error())
}
}

View File

@@ -3,6 +3,7 @@ package docklog
import (
"bytes"
"fmt"
"runtime"
"testing"
docker "github.com/fsouza/go-dockerclient"
@@ -13,22 +14,34 @@ import (
"golang.org/x/net/context"
)
func testContainerDetails() (image string, imageName string, imageTag string) {
if runtime.GOOS == "windows" {
return "dantoml/busybox-windows:08012019",
"dantoml/busybox-windows",
"08012019"
}
return "busybox:1", "busybox", "1"
}
func TestDockerLogger(t *testing.T) {
ctu.DockerCompatible(t)
t.Parallel()
require := require.New(t)
containerImage, containerImageName, containerImageTag := testContainerDetails()
client, err := docker.NewClientFromEnv()
if err != nil {
t.Skip("docker unavailable:", err)
}
if img, err := client.InspectImage("busybox:1"); err != nil || img == nil {
if img, err := client.InspectImage(containerImage); err != nil || img == nil {
t.Log("image not found locally, downloading...")
err = client.PullImage(docker.PullImageOptions{
Repository: "busybox",
Tag: "1",
Repository: containerImageName,
Tag: containerImageTag,
}, docker.AuthConfiguration{})
if err != nil {
t.Fatalf("failed to pull image: %v", err)
@@ -38,9 +51,9 @@ func TestDockerLogger(t *testing.T) {
containerConf := docker.CreateContainerOptions{
Config: &docker.Config{
Cmd: []string{
"/bin/sh", "-c", "touch /tmp/docklog; tail -f /tmp/docklog",
"sh", "-c", "touch ~/docklog; tail -f ~/docklog",
},
Image: "busybox:1",
Image: containerImage,
},
Context: context.Background(),
}
@@ -98,8 +111,8 @@ func echoToContainer(t *testing.T, client *docker.Client, id string, line string
op := docker.CreateExecOptions{
Container: id,
Cmd: []string{
"/bin/ash", "-c",
fmt.Sprintf("echo %s >>/tmp/docklog", line),
"ash", "-c",
fmt.Sprintf("echo %s >>~/docklog", line),
},
}

File diff suppressed because it is too large Load Diff

View File

@@ -1,98 +1,735 @@
// +build !windows
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package docker
import (
"context"
"fmt"
"io/ioutil"
"io"
"os"
"path/filepath"
"sort"
"strconv"
"strings"
"testing"
"time"
docker "github.com/fsouza/go-dockerclient"
"github.com/hashicorp/nomad/client/allocdir"
"github.com/hashicorp/nomad/client/testutil"
"github.com/hashicorp/nomad/helper/uuid"
"github.com/hashicorp/nomad/plugins/drivers"
tu "github.com/hashicorp/nomad/testutil"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
func TestDockerDriver_Signal(t *testing.T) {
func TestDockerDriver_User(t *testing.T) {
if !tu.IsTravis() {
t.Parallel()
}
if !testutil.DockerIsConnected(t) {
t.Skip("Docker not connected")
}
testutil.DockerCompatible(t)
task, cfg, _ := dockerTask(t)
cfg.Command = "/bin/sh"
cfg.Args = []string{"local/test.sh"}
task.User = "alice"
cfg.Command = "/bin/sleep"
cfg.Args = []string{"10000"}
require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
driver := dockerDriverHarness(t, nil)
cleanup := driver.MkAllocDir(task, true)
d := dockerDriverHarness(t, nil)
cleanup := d.MkAllocDir(task, true)
defer cleanup()
// Copy the image into the task's directory
copyImage(t, task.TaskDir(), "busybox.tar")
testFile := filepath.Join(task.TaskDir().LocalDir, "test.sh")
testData := []byte(`
at_term() {
echo 'Terminated.' > $NOMAD_TASK_DIR/output
exit 3
_, _, err := d.StartTask(task)
if err == nil {
d.DestroyTask(task.ID, true)
t.Fatalf("Should've failed")
}
if !strings.Contains(err.Error(), "alice") {
t.Fatalf("Expected failure string not found, found %q instead", err.Error())
}
}
trap at_term INT
while true; do
echo 'sleeping'
sleep 0.2
done
`)
require.NoError(t, ioutil.WriteFile(testFile, testData, 0777))
_, _, err := driver.StartTask(task)
func TestDockerDriver_NetworkAliases_Bridge(t *testing.T) {
if !tu.IsTravis() {
t.Parallel()
}
testutil.DockerCompatible(t)
require := require.New(t)
// Because go-dockerclient doesn't provide api for query network aliases, just check that
// a container can be created with a 'network_aliases' property
// Create network, network-scoped alias is supported only for containers in user defined networks
client := newTestDockerClient(t)
networkOpts := docker.CreateNetworkOptions{Name: "foobar", Driver: "bridge"}
network, err := client.CreateNetwork(networkOpts)
require.NoError(err)
defer client.RemoveNetwork(network.ID)
expected := []string{"foobar"}
taskCfg := newTaskConfig("", busyboxLongRunningCmd)
taskCfg.NetworkMode = network.Name
taskCfg.NetworkAliases = expected
task := &drivers.TaskConfig{
ID: uuid.Generate(),
Name: "busybox",
Resources: basicResources,
}
require.NoError(task.EncodeConcreteDriverConfig(&taskCfg))
d := dockerDriverHarness(t, nil)
cleanup := d.MkAllocDir(task, true)
defer cleanup()
copyImage(t, task.TaskDir(), "busybox.tar")
_, _, err = d.StartTask(task)
require.NoError(err)
require.NoError(d.WaitUntilStarted(task.ID, 5*time.Second))
defer d.DestroyTask(task.ID, true)
dockerDriver, ok := d.Impl().(*Driver)
require.True(ok)
handle, ok := dockerDriver.tasks.Get(task.ID)
require.True(ok)
_, err = client.InspectContainer(handle.containerID)
require.NoError(err)
}
func TestDockerDriver_NetworkMode_Host(t *testing.T) {
if !tu.IsTravis() {
t.Parallel()
}
testutil.DockerCompatible(t)
expected := "host"
taskCfg := newTaskConfig("", busyboxLongRunningCmd)
taskCfg.NetworkMode = expected
task := &drivers.TaskConfig{
ID: uuid.Generate(),
Name: "busybox-demo",
Resources: basicResources,
}
require.NoError(t, task.EncodeConcreteDriverConfig(&taskCfg))
d := dockerDriverHarness(t, nil)
cleanup := d.MkAllocDir(task, true)
defer cleanup()
copyImage(t, task.TaskDir(), "busybox.tar")
_, _, err := d.StartTask(task)
require.NoError(t, err)
defer driver.DestroyTask(task.ID, true)
require.NoError(t, driver.WaitUntilStarted(task.ID, time.Duration(tu.TestMultiplier()*5)*time.Second))
handle, ok := driver.Impl().(*Driver).tasks.Get(task.ID)
require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
defer d.DestroyTask(task.ID, true)
dockerDriver, ok := d.Impl().(*Driver)
require.True(t, ok)
waitForExist(t, newTestDockerClient(t), handle.containerID)
require.NoError(t, handle.Kill(time.Duration(tu.TestMultiplier()*5)*time.Second, os.Interrupt))
handle, ok := dockerDriver.tasks.Get(task.ID)
require.True(t, ok)
waitCh, err := driver.WaitTask(context.Background(), task.ID)
require.NoError(t, err)
select {
case res := <-waitCh:
if res.Successful() {
require.Fail(t, "should err: %v", res)
}
case <-time.After(time.Duration(tu.TestMultiplier()*5) * time.Second):
require.Fail(t, "timeout")
}
// Check the log file to see it exited because of the signal
outputFile := filepath.Join(task.TaskDir().LocalDir, "output")
act, err := ioutil.ReadFile(outputFile)
container, err := client.InspectContainer(handle.containerID)
if err != nil {
t.Fatalf("Couldn't read expected output: %v", err)
t.Fatalf("err: %v", err)
}
exp := "Terminated."
if strings.TrimSpace(string(act)) != exp {
t.Fatalf("Command outputted %v; want %v", act, exp)
}
actual := container.HostConfig.NetworkMode
require.Equal(t, expected, actual)
}
func TestDockerDriver_containerBinds(t *testing.T) {
func TestDockerDriver_CPUCFSPeriod(t *testing.T) {
if !tu.IsTravis() {
t.Parallel()
}
testutil.DockerCompatible(t)
task, cfg, _ := dockerTask(t)
driver := dockerDriverHarness(t, nil)
cleanup := driver.MkAllocDir(task, false)
cfg.CPUHardLimit = true
cfg.CPUCFSPeriod = 1000000
require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
client, _, handle, cleanup := dockerSetup(t, task)
defer cleanup()
binds, err := driver.Impl().(*Driver).containerBinds(task, cfg)
waitForExist(t, client, handle.containerID)
container, err := client.InspectContainer(handle.containerID)
require.NoError(t, err)
require.Contains(t, binds, fmt.Sprintf("%s:/alloc", task.TaskDir().SharedAllocDir))
require.Contains(t, binds, fmt.Sprintf("%s:/local", task.TaskDir().LocalDir))
require.Contains(t, binds, fmt.Sprintf("%s:/secrets", task.TaskDir().SecretsDir))
require.Equal(t, cfg.CPUCFSPeriod, container.HostConfig.CPUPeriod)
}
func TestDockerDriver_Sysctl_Ulimit(t *testing.T) {
testutil.DockerCompatible(t)
task, cfg, _ := dockerTask(t)
expectedUlimits := map[string]string{
"nproc": "4242",
"nofile": "2048:4096",
}
cfg.Sysctl = map[string]string{
"net.core.somaxconn": "16384",
}
cfg.Ulimit = expectedUlimits
require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
client, d, handle, cleanup := dockerSetup(t, task)
defer cleanup()
require.NoError(t, d.WaitUntilStarted(task.ID, 5*time.Second))
container, err := client.InspectContainer(handle.containerID)
assert.Nil(t, err, "unexpected error: %v", err)
want := "16384"
got := container.HostConfig.Sysctls["net.core.somaxconn"]
assert.Equal(t, want, got, "Wrong net.core.somaxconn config for docker job. Expect: %s, got: %s", want, got)
expectedUlimitLen := 2
actualUlimitLen := len(container.HostConfig.Ulimits)
assert.Equal(t, want, got, "Wrong number of ulimit configs for docker job. Expect: %d, got: %d", expectedUlimitLen, actualUlimitLen)
for _, got := range container.HostConfig.Ulimits {
if expectedStr, ok := expectedUlimits[got.Name]; !ok {
t.Errorf("%s config unexpected for docker job.", got.Name)
} else {
if !strings.Contains(expectedStr, ":") {
expectedStr = expectedStr + ":" + expectedStr
}
splitted := strings.SplitN(expectedStr, ":", 2)
soft, _ := strconv.Atoi(splitted[0])
hard, _ := strconv.Atoi(splitted[1])
assert.Equal(t, int64(soft), got.Soft, "Wrong soft %s ulimit for docker job. Expect: %d, got: %d", got.Name, soft, got.Soft)
assert.Equal(t, int64(hard), got.Hard, "Wrong hard %s ulimit for docker job. Expect: %d, got: %d", got.Name, hard, got.Hard)
}
}
}
func TestDockerDriver_Sysctl_Ulimit_Errors(t *testing.T) {
testutil.DockerCompatible(t)
brokenConfigs := []map[string]string{
{
"nofile": "",
},
{
"nofile": "abc:1234",
},
{
"nofile": "1234:abc",
},
}
testCases := []struct {
ulimitConfig map[string]string
err error
}{
{brokenConfigs[0], fmt.Errorf("Malformed ulimit specification nofile: \"\", cannot be empty")},
{brokenConfigs[1], fmt.Errorf("Malformed soft ulimit nofile: abc:1234")},
{brokenConfigs[2], fmt.Errorf("Malformed hard ulimit nofile: 1234:abc")},
}
for _, tc := range testCases {
task, cfg, _ := dockerTask(t)
cfg.Ulimit = tc.ulimitConfig
require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
d := dockerDriverHarness(t, nil)
cleanup := d.MkAllocDir(task, true)
defer cleanup()
copyImage(t, task.TaskDir(), "busybox.tar")
_, _, err := d.StartTask(task)
require.NotNil(t, err, "Expected non nil error")
require.Contains(t, err.Error(), tc.err.Error())
}
}
// This test does not run on Windows due to stricter path validation in the
// negative case for non existent mount paths. We should write a similar test
// for windows.
func TestDockerDriver_BindMountsHonorVolumesEnabledFlag(t *testing.T) {
t.Parallel()
testutil.DockerCompatible(t)
allocDir := "/tmp/nomad/alloc-dir"
cases := []struct {
name string
requiresVolumes bool
volumeDriver string
volumes []string
expectedVolumes []string
}{
{
name: "basic plugin",
requiresVolumes: true,
volumeDriver: "nfs",
volumes: []string{"test-path:/tmp/taskpath"},
expectedVolumes: []string{"test-path:/tmp/taskpath"},
},
{
name: "absolute default driver",
requiresVolumes: true,
volumeDriver: "",
volumes: []string{"/abs/test-path:/tmp/taskpath"},
expectedVolumes: []string{"/abs/test-path:/tmp/taskpath"},
},
{
name: "absolute local driver",
requiresVolumes: true,
volumeDriver: "local",
volumes: []string{"/abs/test-path:/tmp/taskpath"},
expectedVolumes: []string{"/abs/test-path:/tmp/taskpath"},
},
{
name: "relative default driver",
requiresVolumes: false,
volumeDriver: "",
volumes: []string{"test-path:/tmp/taskpath"},
expectedVolumes: []string{"/tmp/nomad/alloc-dir/demo/test-path:/tmp/taskpath"},
},
{
name: "relative local driver",
requiresVolumes: false,
volumeDriver: "local",
volumes: []string{"test-path:/tmp/taskpath"},
expectedVolumes: []string{"/tmp/nomad/alloc-dir/demo/test-path:/tmp/taskpath"},
},
{
name: "relative outside task-dir default driver",
requiresVolumes: false,
volumeDriver: "",
volumes: []string{"../test-path:/tmp/taskpath"},
expectedVolumes: []string{"/tmp/nomad/alloc-dir/test-path:/tmp/taskpath"},
},
{
name: "relative outside task-dir local driver",
requiresVolumes: false,
volumeDriver: "local",
volumes: []string{"../test-path:/tmp/taskpath"},
expectedVolumes: []string{"/tmp/nomad/alloc-dir/test-path:/tmp/taskpath"},
},
{
name: "relative outside alloc-dir default driver",
requiresVolumes: true,
volumeDriver: "",
volumes: []string{"../../test-path:/tmp/taskpath"},
expectedVolumes: []string{"/tmp/nomad/test-path:/tmp/taskpath"},
},
{
name: "relative outside task-dir local driver",
requiresVolumes: true,
volumeDriver: "local",
volumes: []string{"../../test-path:/tmp/taskpath"},
expectedVolumes: []string{"/tmp/nomad/test-path:/tmp/taskpath"},
},
}
t.Run("with volumes enabled", func(t *testing.T) {
dh := dockerDriverHarness(t, nil)
driver := dh.Impl().(*Driver)
driver.config.Volumes.Enabled = true
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
task, cfg, _ := dockerTask(t)
cfg.VolumeDriver = c.volumeDriver
cfg.Volumes = c.volumes
task.AllocDir = allocDir
task.Name = "demo"
require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
cc, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
require.NoError(t, err)
for _, v := range c.expectedVolumes {
require.Contains(t, cc.HostConfig.Binds, v)
}
})
}
})
t.Run("with volumes disabled", func(t *testing.T) {
dh := dockerDriverHarness(t, nil)
driver := dh.Impl().(*Driver)
driver.config.Volumes.Enabled = false
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
task, cfg, _ := dockerTask(t)
cfg.VolumeDriver = c.volumeDriver
cfg.Volumes = c.volumes
task.AllocDir = allocDir
task.Name = "demo"
require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
cc, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
if c.requiresVolumes {
require.Error(t, err, "volumes are not enabled")
} else {
require.NoError(t, err)
for _, v := range c.expectedVolumes {
require.Contains(t, cc.HostConfig.Binds, v)
}
}
})
}
})
}
// This test does not run on windows due to differences in the definition of
// an absolute path, changing path expansion behaviour. A similar test should
// be written for windows.
func TestDockerDriver_MountsSerialization(t *testing.T) {
t.Parallel()
testutil.DockerCompatible(t)
allocDir := "/tmp/nomad/alloc-dir"
cases := []struct {
name string
requiresVolumes bool
passedMounts []DockerMount
expectedMounts []docker.HostMount
}{
{
name: "basic volume",
passedMounts: []DockerMount{
{
Target: "/nomad",
ReadOnly: true,
Source: "test",
},
},
expectedMounts: []docker.HostMount{
{
Type: "volume",
Target: "/nomad",
Source: "test",
ReadOnly: true,
VolumeOptions: &docker.VolumeOptions{},
},
},
},
{
name: "basic bind",
passedMounts: []DockerMount{
{
Type: "bind",
Target: "/nomad",
Source: "test",
},
},
expectedMounts: []docker.HostMount{
{
Type: "bind",
Target: "/nomad",
Source: "/tmp/nomad/alloc-dir/demo/test",
BindOptions: &docker.BindOptions{},
},
},
},
{
name: "basic absolute bind",
requiresVolumes: true,
passedMounts: []DockerMount{
{
Type: "bind",
Target: "/nomad",
Source: "/tmp/test",
},
},
expectedMounts: []docker.HostMount{
{
Type: "bind",
Target: "/nomad",
Source: "/tmp/test",
BindOptions: &docker.BindOptions{},
},
},
},
{
name: "bind relative outside",
requiresVolumes: true,
passedMounts: []DockerMount{
{
Type: "bind",
Target: "/nomad",
Source: "../../test",
},
},
expectedMounts: []docker.HostMount{
{
Type: "bind",
Target: "/nomad",
Source: "/tmp/nomad/test",
BindOptions: &docker.BindOptions{},
},
},
},
{
name: "basic tmpfs",
requiresVolumes: false,
passedMounts: []DockerMount{
{
Type: "tmpfs",
Target: "/nomad",
TmpfsOptions: DockerTmpfsOptions{
SizeBytes: 321,
Mode: 0666,
},
},
},
expectedMounts: []docker.HostMount{
{
Type: "tmpfs",
Target: "/nomad",
TempfsOptions: &docker.TempfsOptions{
SizeBytes: 321,
Mode: 0666,
},
},
},
},
}
t.Run("with volumes enabled", func(t *testing.T) {
dh := dockerDriverHarness(t, nil)
driver := dh.Impl().(*Driver)
driver.config.Volumes.Enabled = true
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
task, cfg, _ := dockerTask(t)
cfg.Mounts = c.passedMounts
task.AllocDir = allocDir
task.Name = "demo"
require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
cc, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
require.NoError(t, err)
require.EqualValues(t, c.expectedMounts, cc.HostConfig.Mounts)
})
}
})
t.Run("with volumes disabled", func(t *testing.T) {
dh := dockerDriverHarness(t, nil)
driver := dh.Impl().(*Driver)
driver.config.Volumes.Enabled = false
for _, c := range cases {
t.Run(c.name, func(t *testing.T) {
task, cfg, _ := dockerTask(t)
cfg.Mounts = c.passedMounts
task.AllocDir = allocDir
task.Name = "demo"
require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
cc, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
if c.requiresVolumes {
require.Error(t, err, "volumes are not enabled")
} else {
require.NoError(t, err)
require.EqualValues(t, c.expectedMounts, cc.HostConfig.Mounts)
}
})
}
})
}
// TestDockerDriver_CreateContainerConfig_MountsCombined asserts that
// devices and mounts set by device managers/plugins are honored
// and present in docker.CreateContainerOptions, and that it is appended
// to any devices/mounts a user sets in the task config.
func TestDockerDriver_CreateContainerConfig_MountsCombined(t *testing.T) {
t.Parallel()
testutil.DockerCompatible(t)
task, cfg, _ := dockerTask(t)
task.Devices = []*drivers.DeviceConfig{
{
HostPath: "/dev/fuse",
TaskPath: "/container/dev/task-fuse",
Permissions: "rw",
},
}
task.Mounts = []*drivers.MountConfig{
{
HostPath: "/tmp/task-mount",
TaskPath: "/container/tmp/task-mount",
Readonly: true,
},
}
cfg.Devices = []DockerDevice{
{
HostPath: "/dev/stdout",
ContainerPath: "/container/dev/cfg-stdout",
CgroupPermissions: "rwm",
},
}
cfg.Mounts = []DockerMount{
{
Type: "bind",
Source: "/tmp/cfg-mount",
Target: "/container/tmp/cfg-mount",
ReadOnly: false,
},
}
require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
dh := dockerDriverHarness(t, nil)
driver := dh.Impl().(*Driver)
c, err := driver.createContainerConfig(task, cfg, "org/repo:0.1")
require.NoError(t, err)
expectedMounts := []docker.HostMount{
{
Type: "bind",
Source: "/tmp/cfg-mount",
Target: "/container/tmp/cfg-mount",
ReadOnly: false,
BindOptions: &docker.BindOptions{},
},
{
Type: "bind",
Source: "/tmp/task-mount",
Target: "/container/tmp/task-mount",
ReadOnly: true,
},
}
foundMounts := c.HostConfig.Mounts
sort.Slice(foundMounts, func(i, j int) bool {
return foundMounts[i].Target < foundMounts[j].Target
})
require.EqualValues(t, expectedMounts, foundMounts)
expectedDevices := []docker.Device{
{
PathOnHost: "/dev/stdout",
PathInContainer: "/container/dev/cfg-stdout",
CgroupPermissions: "rwm",
},
{
PathOnHost: "/dev/fuse",
PathInContainer: "/container/dev/task-fuse",
CgroupPermissions: "rw",
},
}
foundDevices := c.HostConfig.Devices
sort.Slice(foundDevices, func(i, j int) bool {
return foundDevices[i].PathInContainer < foundDevices[j].PathInContainer
})
require.EqualValues(t, expectedDevices, foundDevices)
}
// TestDockerDriver_Cleanup ensures Cleanup removes only downloaded images.
// Doesn't run on windows because it requires an image variant
func TestDockerDriver_Cleanup(t *testing.T) {
testutil.DockerCompatible(t)
// using a small image and an specific point release to avoid accidental conflicts with other tasks
cfg := newTaskConfig("", []string{"sleep", "100"})
cfg.Image = "busybox:1.29.2"
cfg.LoadImage = ""
task := &drivers.TaskConfig{
ID: uuid.Generate(),
Name: "cleanup_test",
Resources: basicResources,
}
require.NoError(t, task.EncodeConcreteDriverConfig(cfg))
client, driver, handle, cleanup := dockerSetup(t, task)
defer cleanup()
require.NoError(t, driver.WaitUntilStarted(task.ID, 5*time.Second))
// Cleanup
require.NoError(t, driver.DestroyTask(task.ID, true))
// Ensure image was removed
tu.WaitForResult(func() (bool, error) {
if _, err := client.InspectImage(cfg.Image); err == nil {
return false, fmt.Errorf("image exists but should have been removed. Does another %v container exist?", cfg.Image)
}
return true, nil
}, func(err error) {
require.NoError(t, err)
})
// The image doesn't exist which shouldn't be an error when calling
// Cleanup, so call it again to make sure.
require.NoError(t, driver.Impl().(*Driver).cleanupImage(handle))
}
func newTaskConfig(variant string, command []string) TaskConfig {
// busyboxImageID is the ID stored in busybox.tar
busyboxImageID := "busybox:1.29.3"
image := busyboxImageID
loadImage := "busybox.tar"
if variant != "" {
image = fmt.Sprintf("%s-%s", busyboxImageID, variant)
loadImage = fmt.Sprintf("busybox_%s.tar", variant)
}
return TaskConfig{
Image: image,
LoadImage: loadImage,
Command: command[0],
Args: command[1:],
}
}
func copyImage(t *testing.T, taskDir *allocdir.TaskDir, image string) {
dst := filepath.Join(taskDir.LocalDir, image)
copyFile(filepath.Join("./test-resources/docker", image), dst, t)
}
// copyFile moves an existing file to the destination
func copyFile(src, dst string, t *testing.T) {
in, err := os.Open(src)
if err != nil {
t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
}
defer in.Close()
out, err := os.Create(dst)
if err != nil {
t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
}
defer func() {
if err := out.Close(); err != nil {
t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
}
}()
if _, err = io.Copy(out, in); err != nil {
t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
}
if err := out.Sync(); err != nil {
t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
}
}

View File

@@ -0,0 +1,26 @@
// +build windows
package docker
import (
"testing"
"github.com/hashicorp/nomad/client/allocdir"
)
func newTaskConfig(variant string, command []string) TaskConfig {
// busyboxImageID is an id of an image containing nanoserver windows and
// a busybox exe.
// See https://github.com/dantoml/windows/blob/81cff1ed77729d1fa36721abd6cb6efebff2f8ef/docker/busybox/Dockerfile
busyboxImageID := "dantoml/busybox-windows:08012019"
return TaskConfig{
Image: busyboxImageID,
Command: command[0],
Args: command[1:],
}
}
// No-op on windows because we don't load images.
func copyImage(t *testing.T, taskDir *allocdir.TaskDir, image string) {
}

View File

@@ -6,60 +6,6 @@ import (
"github.com/stretchr/testify/require"
)
func TestValidateCgroupPermission(t *testing.T) {
positiveCases := []string{
"r",
"rw",
"rwm",
"mr",
"mrw",
"",
}
for _, c := range positiveCases {
t.Run("positive case: "+c, func(t *testing.T) {
require.True(t, validateCgroupPermission(c))
})
}
negativeCases := []string{
"q",
"asdf",
"rq",
}
for _, c := range negativeCases {
t.Run("negative case: "+c, func(t *testing.T) {
require.False(t, validateCgroupPermission(c))
})
}
}
func TestExpandPath(t *testing.T) {
cases := []struct {
base string
target string
expected string
}{
{"/tmp/alloc/task", "/home/user", "/home/user"},
{"/tmp/alloc/task", "/home/user/..", "/home"},
{"/tmp/alloc/task", ".", "/tmp/alloc/task"},
{"/tmp/alloc/task", "..", "/tmp/alloc"},
{"/tmp/alloc/task", "d1/d2", "/tmp/alloc/task/d1/d2"},
{"/tmp/alloc/task", "../d1/d2", "/tmp/alloc/d1/d2"},
{"/tmp/alloc/task", "../../d1/d2", "/tmp/d1/d2"},
}
for _, c := range cases {
t.Run(c.expected, func(t *testing.T) {
require.Equal(t, c.expected, expandPath(c.base, c.target))
})
}
}
func TestIsParentPath(t *testing.T) {
require.True(t, isParentPath("/a/b/c", "/a/b/c"))
require.True(t, isParentPath("/a/b/c", "/a/b/c/d"))

View File

@@ -0,0 +1,63 @@
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package docker
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
func TestValidateCgroupPermission(t *testing.T) {
positiveCases := []string{
"r",
"rw",
"rwm",
"mr",
"mrw",
"",
}
for _, c := range positiveCases {
t.Run("positive case: "+c, func(t *testing.T) {
require.True(t, validateCgroupPermission(c))
})
}
negativeCases := []string{
"q",
"asdf",
"rq",
}
for _, c := range negativeCases {
t.Run("negative case: "+c, func(t *testing.T) {
require.False(t, validateCgroupPermission(c))
})
}
}
func TestExpandPath(t *testing.T) {
cases := []struct {
base string
target string
expected string
}{
{"/tmp/alloc/task", ".", "/tmp/alloc/task"},
{"/tmp/alloc/task", "..", "/tmp/alloc"},
{"/tmp/alloc/task", "d1/d2", "/tmp/alloc/task/d1/d2"},
{"/tmp/alloc/task", "../d1/d2", "/tmp/alloc/d1/d2"},
{"/tmp/alloc/task", "../../d1/d2", "/tmp/d1/d2"},
{"/tmp/alloc/task", "/home/user", "/home/user"},
{"/tmp/alloc/task", "/home/user/..", "/home"},
}
for _, c := range cases {
t.Run(c.expected, func(t *testing.T) {
require.Equal(t, c.expected, filepath.ToSlash(expandPath(c.base, c.target)))
})
}
}

View File

@@ -0,0 +1,34 @@
// +build windows
package docker
import (
"path/filepath"
"testing"
"github.com/stretchr/testify/require"
)
func TestExpandPath(t *testing.T) {
cases := []struct {
base string
target string
expected string
}{
{"/tmp/alloc/task", ".", "/tmp/alloc/task"},
{"/tmp/alloc/task", "..", "/tmp/alloc"},
{"/tmp/alloc/task", "d1/d2", "/tmp/alloc/task/d1/d2"},
{"/tmp/alloc/task", "../d1/d2", "/tmp/alloc/d1/d2"},
{"/tmp/alloc/task", "../../d1/d2", "/tmp/d1/d2"},
{"/tmp/alloc/task", "c:/home/user", "c:/home/user"},
{"/tmp/alloc/task", "c:/home/user/..", "c:/home"},
}
for _, c := range cases {
t.Run(c.expected, func(t *testing.T) {
require.Equal(t, c.expected, filepath.ToSlash(expandPath(c.base, c.target)))
})
}
}

View File

@@ -25,7 +25,6 @@ import (
"github.com/hashicorp/nomad/plugins/shared/hclutils"
"github.com/hashicorp/nomad/testutil"
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
)
func TestMain(m *testing.M) {
@@ -123,66 +122,6 @@ func TestExecDriver_StartWait(t *testing.T) {
require.NoError(harness.DestroyTask(task.ID, true))
}
func TestExecDriver_StartWaitStop(t *testing.T) {
t.Parallel()
require := require.New(t)
ctestutils.ExecCompatible(t)
d := NewExecDriver(testlog.HCLogger(t))
harness := dtestutil.NewDriverHarness(t, d)
task := &drivers.TaskConfig{
ID: uuid.Generate(),
Name: "test",
Resources: testResources,
}
taskConfig := map[string]interface{}{
"command": "/bin/sleep",
"args": []string{"600"},
}
encodeDriverHelper(require, task, taskConfig)
cleanup := harness.MkAllocDir(task, false)
defer cleanup()
handle, _, err := harness.StartTask(task)
require.NoError(err)
ch, err := harness.WaitTask(context.Background(), handle.Config.ID)
require.NoError(err)
require.NoError(harness.WaitUntilStarted(task.ID, 1*time.Second))
go func() {
harness.StopTask(task.ID, 2*time.Second, "SIGINT")
}()
select {
case result := <-ch:
require.Equal(int(unix.SIGINT), result.Signal)
case <-time.After(10 * time.Second):
require.Fail("timeout waiting for task to shutdown")
}
// Ensure that the task is marked as dead, but account
// for WaitTask() closing channel before internal state is updated
testutil.WaitForResult(func() (bool, error) {
status, err := harness.InspectTask(task.ID)
if err != nil {
return false, fmt.Errorf("inspecting task failed: %v", err)
}
if status.State != drivers.TaskStateExited {
return false, fmt.Errorf("task hasn't exited yet; status: %v", status.State)
}
return true, nil
}, func(err error) {
require.NoError(err)
})
require.NoError(harness.DestroyTask(task.ID, true))
}
func TestExecDriver_StartWaitStopKill(t *testing.T) {
t.Parallel()
require := require.New(t)

View File

@@ -0,0 +1,79 @@
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package exec
import (
"context"
"fmt"
"testing"
"time"
ctestutils "github.com/hashicorp/nomad/client/testutil"
"github.com/hashicorp/nomad/helper/testlog"
"github.com/hashicorp/nomad/helper/uuid"
"github.com/hashicorp/nomad/plugins/drivers"
dtestutil "github.com/hashicorp/nomad/plugins/drivers/testutils"
"github.com/hashicorp/nomad/testutil"
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
)
func TestExecDriver_StartWaitStop(t *testing.T) {
t.Parallel()
require := require.New(t)
ctestutils.ExecCompatible(t)
d := NewExecDriver(testlog.HCLogger(t))
harness := dtestutil.NewDriverHarness(t, d)
task := &drivers.TaskConfig{
ID: uuid.Generate(),
Name: "test",
Resources: testResources,
}
taskConfig := map[string]interface{}{
"command": "/bin/sleep",
"args": []string{"600"},
}
encodeDriverHelper(require, task, taskConfig)
cleanup := harness.MkAllocDir(task, false)
defer cleanup()
handle, _, err := harness.StartTask(task)
require.NoError(err)
ch, err := harness.WaitTask(context.Background(), handle.Config.ID)
require.NoError(err)
require.NoError(harness.WaitUntilStarted(task.ID, 1*time.Second))
go func() {
harness.StopTask(task.ID, 2*time.Second, "SIGINT")
}()
select {
case result := <-ch:
require.Equal(int(unix.SIGINT), result.Signal)
case <-time.After(10 * time.Second):
require.Fail("timeout waiting for task to shutdown")
}
// Ensure that the task is marked as dead, but account
// for WaitTask() closing channel before internal state is updated
testutil.WaitForResult(func() (bool, error) {
status, err := harness.InspectTask(task.ID)
if err != nil {
return false, fmt.Errorf("inspecting task failed: %v", err)
}
if status.State != drivers.TaskStateExited {
return false, fmt.Errorf("task hasn't exited yet; status: %v", status.State)
}
return true, nil
}, func(err error) {
require.NoError(err)
})
require.NoError(harness.DestroyTask(task.ID, true))
}

View File

@@ -6,6 +6,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"strconv"
"sync"
"syscall"
@@ -25,7 +26,6 @@ import (
pstructs "github.com/hashicorp/nomad/plugins/shared/structs"
"github.com/hashicorp/nomad/testutil"
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
)
func TestMain(m *testing.M) {
@@ -164,73 +164,6 @@ func TestRawExecDriver_StartWait(t *testing.T) {
require.NoError(harness.DestroyTask(task.ID, true))
}
func TestRawExecDriver_StartWaitStop(t *testing.T) {
t.Parallel()
require := require.New(t)
d := NewRawExecDriver(testlog.HCLogger(t))
harness := dtestutil.NewDriverHarness(t, d)
defer harness.Kill()
// Disable cgroups so test works without root
config := &Config{NoCgroups: true}
var data []byte
require.NoError(basePlug.MsgPackEncode(&data, config))
bconfig := &basePlug.Config{PluginConfig: data}
require.NoError(harness.SetConfig(bconfig))
task := &drivers.TaskConfig{
ID: uuid.Generate(),
Name: "test",
}
taskConfig := map[string]interface{}{}
taskConfig["command"] = testtask.Path()
taskConfig["args"] = []string{"sleep", "100s"}
encodeDriverHelper(require, task, taskConfig)
cleanup := harness.MkAllocDir(task, false)
defer cleanup()
handle, _, err := harness.StartTask(task)
require.NoError(err)
ch, err := harness.WaitTask(context.Background(), handle.Config.ID)
require.NoError(err)
require.NoError(harness.WaitUntilStarted(task.ID, 1*time.Second))
go func() {
harness.StopTask(task.ID, 2*time.Second, "SIGINT")
}()
select {
case result := <-ch:
require.Equal(int(unix.SIGINT), result.Signal)
case <-time.After(10 * time.Second):
require.Fail("timeout waiting for task to shutdown")
}
// Ensure that the task is marked as dead, but account
// for WaitTask() closing channel before internal state is updated
testutil.WaitForResult(func() (bool, error) {
status, err := harness.InspectTask(task.ID)
if err != nil {
return false, fmt.Errorf("inspecting task failed: %v", err)
}
if status.State != drivers.TaskStateExited {
return false, fmt.Errorf("task hasn't exited yet; status: %v", status.State)
}
return true, nil
}, func(err error) {
require.NoError(err)
})
require.NoError(harness.DestroyTask(task.ID, true))
}
func TestRawExecDriver_StartWaitRecoverWaitStop(t *testing.T) {
t.Parallel()
require := require.New(t)
@@ -312,7 +245,6 @@ func TestRawExecDriver_StartWaitRecoverWaitStop(t *testing.T) {
wg.Wait()
require.NoError(d.DestroyTask(task.ID, false))
require.True(waitDone)
}
func TestRawExecDriver_Start_Wait_AllocDir(t *testing.T) {
@@ -483,17 +415,31 @@ func TestRawExecDriver_Exec(t *testing.T) {
_, _, err := harness.StartTask(task)
require.NoError(err)
// Exec a command that should work
res, err := harness.ExecTask(task.ID, []string{"/usr/bin/stat", "/tmp"}, 1*time.Second)
require.NoError(err)
require.True(res.ExitResult.Successful())
require.True(len(res.Stdout) > 100)
if runtime.GOOS == "windows" {
// Exec a command that should work
res, err := harness.ExecTask(task.ID, []string{"cmd.exe", "/c", "echo", "hello"}, 1*time.Second)
require.NoError(err)
require.True(res.ExitResult.Successful())
require.Equal(string(res.Stdout), "hello\r\n")
// Exec a command that should fail
res, err = harness.ExecTask(task.ID, []string{"/usr/bin/stat", "notarealfile123abc"}, 1*time.Second)
require.NoError(err)
require.False(res.ExitResult.Successful())
require.Contains(string(res.Stdout), "No such file or directory")
// Exec a command that should fail
res, err = harness.ExecTask(task.ID, []string{"cmd.exe", "/c", "stat", "notarealfile123abc"}, 1*time.Second)
require.NoError(err)
require.False(res.ExitResult.Successful())
require.Contains(string(res.Stdout), "not recognized")
} else {
// Exec a command that should work
res, err := harness.ExecTask(task.ID, []string{"/usr/bin/stat", "/tmp"}, 1*time.Second)
require.NoError(err)
require.True(res.ExitResult.Successful())
require.True(len(res.Stdout) > 100)
// Exec a command that should fail
res, err = harness.ExecTask(task.ID, []string{"/usr/bin/stat", "notarealfile123abc"}, 1*time.Second)
require.NoError(err)
require.False(res.ExitResult.Successful())
require.Contains(string(res.Stdout), "No such file or directory")
}
require.NoError(harness.DestroyTask(task.ID, true))
}

View File

@@ -16,10 +16,12 @@ import (
"github.com/hashicorp/nomad/helper/testlog"
"github.com/hashicorp/nomad/helper/testtask"
"github.com/hashicorp/nomad/helper/uuid"
basePlug "github.com/hashicorp/nomad/plugins/base"
"github.com/hashicorp/nomad/plugins/drivers"
dtestutil "github.com/hashicorp/nomad/plugins/drivers/testutils"
"github.com/hashicorp/nomad/testutil"
"github.com/stretchr/testify/require"
"golang.org/x/sys/unix"
)
func TestRawExecDriver_User(t *testing.T) {
@@ -128,3 +130,70 @@ done
return true, nil
}, func(err error) { require.NoError(err) })
}
func TestRawExecDriver_StartWaitStop(t *testing.T) {
t.Parallel()
require := require.New(t)
d := NewRawExecDriver(testlog.HCLogger(t))
harness := dtestutil.NewDriverHarness(t, d)
defer harness.Kill()
// Disable cgroups so test works without root
config := &Config{NoCgroups: true}
var data []byte
require.NoError(basePlug.MsgPackEncode(&data, config))
bconfig := &basePlug.Config{PluginConfig: data}
require.NoError(harness.SetConfig(bconfig))
task := &drivers.TaskConfig{
ID: uuid.Generate(),
Name: "test",
}
taskConfig := map[string]interface{}{}
taskConfig["command"] = testtask.Path()
taskConfig["args"] = []string{"sleep", "100s"}
encodeDriverHelper(require, task, taskConfig)
cleanup := harness.MkAllocDir(task, false)
defer cleanup()
handle, _, err := harness.StartTask(task)
require.NoError(err)
ch, err := harness.WaitTask(context.Background(), handle.Config.ID)
require.NoError(err)
require.NoError(harness.WaitUntilStarted(task.ID, 1*time.Second))
go func() {
harness.StopTask(task.ID, 2*time.Second, "SIGINT")
}()
select {
case result := <-ch:
require.Equal(int(unix.SIGINT), result.Signal)
case <-time.After(10 * time.Second):
require.Fail("timeout waiting for task to shutdown")
}
// Ensure that the task is marked as dead, but account
// for WaitTask() closing channel before internal state is updated
testutil.WaitForResult(func() (bool, error) {
status, err := harness.InspectTask(task.ID)
if err != nil {
return false, fmt.Errorf("inspecting task failed: %v", err)
}
if status.State != drivers.TaskStateExited {
return false, fmt.Errorf("task hasn't exited yet; status: %v", status.State)
}
return true, nil
}, func(err error) {
require.NoError(err)
})
require.NoError(harness.DestroyTask(task.ID, true))
}

View File

@@ -7,8 +7,6 @@ import (
"io/ioutil"
"os"
"os/exec"
"strconv"
"syscall"
"time"
"github.com/hashicorp/nomad/nomad/structs"
@@ -115,21 +113,11 @@ func execute() {
ioutil.WriteFile(file, []byte(msg), 0666)
case "pgrp":
// pgrp <group_int> puts the pid in a new process group
if len(args) < 1 {
fmt.Fprintln(os.Stderr, "expected process group number for pgrp")
os.Exit(1)
}
num := popArg()
grp, err := strconv.Atoi(num)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to convert process group number %q: %v\n", num, err)
os.Exit(1)
}
if err := syscall.Setpgid(0, grp); err != nil {
fmt.Fprintf(os.Stderr, "failed to set process group: %v\n", err)
os.Exit(1)
}
executeProcessGroup(popArg())
case "fork/exec":
// fork/exec <pid_file> <args> forks execs the helper process

View File

@@ -0,0 +1,23 @@
// +build darwin dragonfly freebsd linux netbsd openbsd solaris
package testtask
import (
"fmt"
"os"
"strconv"
"syscall"
)
func executeProcessGroup(gid string) {
// pgrp <group_int> puts the pid in a new process group
grp, err := strconv.Atoi(gid)
if err != nil {
fmt.Fprintf(os.Stderr, "failed to convert process group number %q: %v\n", gid, err)
os.Exit(1)
}
if err := syscall.Setpgid(0, grp); err != nil {
fmt.Fprintf(os.Stderr, "failed to set process group: %v\n", err)
os.Exit(1)
}
}

View File

@@ -0,0 +1,14 @@
// +build windows
package testtask
import (
"fmt"
"os"
)
func executeProcessGroup(gid string) {
fmt.Fprintf(os.Stderr, "TODO: implement process groups are on windows\n")
fmt.Fprintf(os.Stderr, "TODO: see https://github.com/hashicorp/nomad/blob/109c5ef650206fc62334d202002cda92ceb67399/drivers/shared/executor/executor_windows.go#L9-L17\n")
os.Exit(1)
}

View File

@@ -0,0 +1,10 @@
// +build !windows
package loader
import "os"
// executable Checks to see if the file is executable by anyone.
func executable(path string, f os.FileInfo) bool {
return f.Mode().Perm()&0111 != 0
}

View File

@@ -0,0 +1,15 @@
// +build windows
package loader
import (
"os"
"path/filepath"
)
// On windows, an executable can be any file with any extension. To avoid
// introspecting the file, here we skip executability checks on windows systems
// and simply check for the convention of an `exe` extension.
func executable(path string, s os.FileInfo) bool {
return filepath.Ext(path) == "exe"
}

View File

@@ -250,8 +250,7 @@ func (l *PluginLoader) scan() ([]os.FileInfo, error) {
continue
}
// Check if it is executable by anyone
if s.Mode().Perm()&0111 == 0 {
if !executable(f, s) {
l.logger.Debug("skipping un-executable file in plugin folder", "file", f)
continue
}

View File

@@ -5,6 +5,7 @@ import (
"io/ioutil"
"os"
"path/filepath"
"runtime"
"sort"
"strings"
"testing"
@@ -55,8 +56,12 @@ func newHarness(t *testing.T, plugins []string) *harness {
t.Fatalf("failed to get self executable path: %v", err)
}
exeSuffix := ""
if runtime.GOOS == "windows" {
exeSuffix = ".exe"
}
for _, p := range plugins {
dest := filepath.Join(h.tmpDir, p)
dest := filepath.Join(h.tmpDir, p) + exeSuffix
if err := copyFile(selfExe, dest); err != nil {
t.Fatalf("failed to copy file: %v", err)
}
@@ -1217,6 +1222,9 @@ func TestPluginLoader_Bad_Executable(t *testing.T) {
// Test that we skip directories, non-executables and follow symlinks
func TestPluginLoader_External_SkipBadFiles(t *testing.T) {
if runtime.GOOS == "windows" {
t.Skip("Windows currently does not skip non exe files")
}
t.Parallel()
require := require.New(t)

View File

@@ -12,7 +12,8 @@ import (
"github.com/hashicorp/nomad/helper/uuid"
"github.com/hashicorp/nomad/nomad/structs/config"
vapi "github.com/hashicorp/vault/api"
"github.com/mitchellh/go-testing-interface"
testing "github.com/mitchellh/go-testing-interface"
"github.com/stretchr/testify/require"
)
// TestVault is a test helper. It uses a fork/exec model to create a test Vault
@@ -167,13 +168,16 @@ func NewTestVaultDelayed(t testing.T) *TestVault {
// Start starts the test Vault server and waits for it to respond to its HTTP
// API
func (tv *TestVault) Start() error {
if err := tv.cmd.Start(); err != nil {
tv.t.Fatalf("failed to start vault: %v", err)
}
// Start the waiter
tv.waitCh = make(chan error, 1)
go func() {
// Must call Start and Wait in the same goroutine on Windows #5174
if err := tv.cmd.Start(); err != nil {
tv.waitCh <- err
return
}
err := tv.cmd.Wait()
tv.waitCh <- err
}()
@@ -198,7 +202,12 @@ func (tv *TestVault) Stop() {
tv.t.Errorf("err: %s", err)
}
if tv.waitCh != nil {
<-tv.waitCh
select {
case <-tv.waitCh:
return
case <-time.After(1 * time.Second):
require.Fail(tv.t, "Timed out waiting for vault to terminate")
}
}
}