open-nomad/client/driver/driver_test.go

479 lines
13 KiB
Go
Raw Normal View History

2015-09-01 21:56:42 +00:00
package driver
import (
"fmt"
2016-03-15 20:28:25 +00:00
"io"
2017-10-18 20:43:26 +00:00
"io/ioutil"
"math/rand"
2015-09-01 21:56:42 +00:00
"os"
2015-09-25 23:49:14 +00:00
"path/filepath"
2015-09-26 22:37:48 +00:00
"reflect"
"runtime"
2015-09-24 07:17:33 +00:00
"testing"
"time"
plugin "github.com/hashicorp/go-plugin"
2015-09-25 23:49:14 +00:00
"github.com/hashicorp/nomad/client/allocdir"
"github.com/hashicorp/nomad/client/config"
"github.com/hashicorp/nomad/client/driver/env"
"github.com/hashicorp/nomad/client/logmon"
2018-06-13 22:33:25 +00:00
"github.com/hashicorp/nomad/helper/testlog"
"github.com/hashicorp/nomad/helper/testtask"
"github.com/hashicorp/nomad/helper/uuid"
2016-03-02 00:22:33 +00:00
"github.com/hashicorp/nomad/nomad/mock"
2015-09-24 07:17:33 +00:00
"github.com/hashicorp/nomad/nomad/structs"
2015-09-01 21:56:42 +00:00
)
var basicResources = &structs.Resources{
CPU: 250,
MemoryMB: 256,
2016-08-23 21:51:09 +00:00
DiskMB: 20,
}
func init() {
rand.Seed(49875)
}
func TestMain(m *testing.M) {
if !testtask.Run() {
os.Exit(m.Run())
}
}
2016-03-15 20:28:25 +00:00
// 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)
}
}
2017-10-18 20:43:26 +00:00
func testConfig(t *testing.T) *config.Config {
2016-06-17 21:24:49 +00:00
conf := config.DefaultConfig()
2017-10-18 20:43:26 +00:00
// Evaluate the symlinks so that the temp directory resolves correctly on
// Mac OS.
d1, err := ioutil.TempDir("", "TestStateDir")
if err != nil {
t.Fatal(err)
}
d2, err := ioutil.TempDir("", "TestAllocDir")
if err != nil {
t.Fatal(err)
}
p1, err := filepath.EvalSymlinks(d1)
if err != nil {
t.Fatal(err)
}
p2, err := filepath.EvalSymlinks(d2)
if err != nil {
t.Fatal(err)
}
2017-10-18 21:01:45 +00:00
// Give the directories access to everyone
if err := os.Chmod(p1, 0777); err != nil {
t.Fatal(err)
}
if err := os.Chmod(p2, 0777); err != nil {
t.Fatal(err)
}
2017-10-18 20:43:26 +00:00
conf.StateDir = p1
conf.AllocDir = p2
2016-02-09 03:31:57 +00:00
conf.MaxKillTimeout = 10 * time.Second
2017-05-03 22:14:19 +00:00
conf.Region = "global"
conf.Node = mock.Node()
conf.LogLevel = "DEBUG"
conf.LogOutput = testlog.NewWriter(t)
2015-09-25 23:49:14 +00:00
return conf
}
type testContext struct {
AllocDir *allocdir.AllocDir
DriverCtx *DriverContext
ExecCtx *ExecContext
EnvBuilder *env.Builder
logmon logmon.LogMon
logmonPlugin *plugin.Client
}
func (ctx *testContext) Destroy() {
ctx.AllocDir.Destroy()
ctx.logmon.Stop()
ctx.logmonPlugin.Kill()
}
// testDriverContext sets up an alloc dir, task dir, DriverContext, and ExecContext.
//
// It is up to the caller to call Destroy to cleanup.
func testDriverContexts(t *testing.T, task *structs.Task) *testContext {
2017-10-18 20:43:26 +00:00
cfg := testConfig(t)
2017-04-13 21:32:52 +00:00
cfg.Node = mock.Node()
alloc := mock.Alloc()
alloc.NodeID = cfg.Node.ID
allocDir := allocdir.NewAllocDir(testlog.HCLogger(t), filepath.Join(cfg.AllocDir, alloc.ID))
if err := allocDir.Build(); err != nil {
t.Fatalf("AllocDir.Build() failed: %v", err)
}
2016-01-06 02:02:11 +00:00
// Build a temp driver so we can call FSIsolation and build the task dir
tmpdrv, err := NewDriver(task.Driver, NewEmptyDriverContext())
if err != nil {
allocDir.Destroy()
t.Fatalf("NewDriver(%q, nil) failed: %v", task.Driver, err)
return nil
}
// Build the task dir
td := allocDir.NewTaskDir(task.Name)
2017-03-02 23:50:18 +00:00
if err := td.Build(false, config.DefaultChrootEnv, tmpdrv.FSIsolation()); err != nil {
allocDir.Destroy()
t.Fatalf("TaskDir.Build(%#v, %q) failed: %v", config.DefaultChrootEnv, tmpdrv.FSIsolation(), err)
return nil
}
eb := env.NewBuilder(cfg.Node, alloc, task, cfg.Region)
SetEnvvars(eb, tmpdrv.FSIsolation(), td, cfg)
execCtx := NewExecContext(td, eb.Build())
2016-01-06 02:02:11 +00:00
2018-06-13 22:33:25 +00:00
logger := testlog.Logger(t)
hcLogger := testlog.HCLogger(t)
emitter := func(m string, args ...interface{}) {
hcLogger.Info(fmt.Sprintf("[EVENT] "+m, args...))
}
driverCtx := NewDriverContext(alloc.Job.Name, alloc.TaskGroup, task.Name, alloc.ID, cfg, cfg.Node, logger, emitter)
l, c, err := logmon.LaunchLogMon(hcLogger)
if err != nil {
allocDir.Destroy()
t.Fatalf("LaunchLogMon() failed: %v", err)
}
var stdoutFifo, stderrFifo string
if runtime.GOOS == "windows" {
id := uuid.Generate()[:8]
stdoutFifo = fmt.Sprintf("//./pipe/%s.stdout.%s", id, task.Name)
stderrFifo = fmt.Sprintf("//./pipe/%s.stderr.%s", id, task.Name)
} else {
stdoutFifo = filepath.Join(td.LogDir, fmt.Sprintf("%s.stdout", task.Name))
stderrFifo = filepath.Join(td.LogDir, fmt.Sprintf("%s.stderr", task.Name))
}
err = l.Start(&logmon.LogConfig{
LogDir: td.LogDir,
StdoutLogFile: fmt.Sprintf("%s.stdout", task.Name),
StderrLogFile: fmt.Sprintf("%s.stderr", task.Name),
StdoutFifo: stdoutFifo,
StderrFifo: stderrFifo,
MaxFiles: 10,
MaxFileSizeMB: 10,
})
if err != nil {
allocDir.Destroy()
t.Fatalf("LogMon.Start() failed: %v", err)
}
execCtx.StdoutFifo = stdoutFifo
execCtx.StderrFifo = stderrFifo
return &testContext{allocDir, driverCtx, execCtx, eb, l, c}
}
2015-09-24 07:17:33 +00:00
// setupTaskEnv creates a test env for GetTaskEnv testing. Returns task dir,
// expected env, and actual env.
func setupTaskEnv(t *testing.T, driver string) (*allocdir.TaskDir, map[string]string, map[string]string) {
2015-09-24 07:17:33 +00:00
task := &structs.Task{
Name: "Foo",
Driver: driver,
Env: map[string]string{
"HELLO": "world",
"lorem": "ipsum",
},
2015-09-24 07:17:33 +00:00
Resources: &structs.Resources{
CPU: 1000,
MemoryMB: 500,
Networks: []*structs.NetworkResource{
2017-09-26 22:26:33 +00:00
{
2015-09-24 07:17:33 +00:00
IP: "1.2.3.4",
ReservedPorts: []structs.Port{{Label: "one", Value: 80}, {Label: "two", Value: 443}},
DynamicPorts: []structs.Port{{Label: "admin", Value: 8081}, {Label: "web", Value: 8086}},
2015-09-24 07:17:33 +00:00
},
},
},
Meta: map[string]string{
"chocolate": "cake",
"strawberry": "icecream",
},
}
2016-03-02 00:22:33 +00:00
alloc := mock.Alloc()
alloc.Job.TaskGroups[0].Tasks[0] = task
2016-03-02 00:22:33 +00:00
alloc.Name = "Bar"
2018-10-03 16:47:18 +00:00
alloc.AllocatedResources.Tasks["web"].Networks[0].DynamicPorts[0].Value = 2000
2017-10-18 20:43:26 +00:00
conf := testConfig(t)
allocDir := allocdir.NewAllocDir(testlog.HCLogger(t), filepath.Join(conf.AllocDir, alloc.ID))
taskDir := allocDir.NewTaskDir(task.Name)
eb := env.NewBuilder(conf.Node, alloc, task, conf.Region)
tmpDriver, err := NewDriver(driver, NewEmptyDriverContext())
2016-01-11 17:58:26 +00:00
if err != nil {
t.Fatalf("unable to create driver %q: %v", driver, err)
2016-01-11 17:58:26 +00:00
}
SetEnvvars(eb, tmpDriver.FSIsolation(), taskDir, conf)
2015-09-26 22:37:48 +00:00
exp := map[string]string{
2016-03-25 17:26:32 +00:00
"NOMAD_CPU_LIMIT": "1000",
"NOMAD_MEMORY_LIMIT": "500",
"NOMAD_ADDR_one": "1.2.3.4:80",
2016-04-15 17:27:51 +00:00
"NOMAD_IP_one": "1.2.3.4",
"NOMAD_PORT_one": "80",
2016-07-09 01:27:51 +00:00
"NOMAD_HOST_PORT_one": "80",
2016-03-25 17:26:32 +00:00
"NOMAD_ADDR_two": "1.2.3.4:443",
2016-04-15 17:27:51 +00:00
"NOMAD_IP_two": "1.2.3.4",
"NOMAD_PORT_two": "443",
2016-07-09 01:27:51 +00:00
"NOMAD_HOST_PORT_two": "443",
2016-03-25 17:26:32 +00:00
"NOMAD_ADDR_admin": "1.2.3.4:8081",
2017-12-09 00:45:25 +00:00
"NOMAD_ADDR_web_admin": "192.168.0.100:5000",
2017-01-24 00:33:35 +00:00
"NOMAD_ADDR_web_http": "192.168.0.100:2000",
2017-12-09 00:45:25 +00:00
"NOMAD_IP_web_admin": "192.168.0.100",
2017-03-15 00:14:40 +00:00
"NOMAD_IP_web_http": "192.168.0.100",
"NOMAD_PORT_web_http": "2000",
2017-12-09 00:45:25 +00:00
"NOMAD_PORT_web_admin": "5000",
2016-04-15 17:27:51 +00:00
"NOMAD_IP_admin": "1.2.3.4",
"NOMAD_PORT_admin": "8081",
2016-07-09 01:27:51 +00:00
"NOMAD_HOST_PORT_admin": "8081",
2016-03-25 17:26:32 +00:00
"NOMAD_ADDR_web": "1.2.3.4:8086",
2016-04-15 17:27:51 +00:00
"NOMAD_IP_web": "1.2.3.4",
"NOMAD_PORT_web": "8086",
2016-07-09 01:27:51 +00:00
"NOMAD_HOST_PORT_web": "8086",
2016-03-25 17:26:32 +00:00
"NOMAD_META_CHOCOLATE": "cake",
"NOMAD_META_STRAWBERRY": "icecream",
"NOMAD_META_ELB_CHECK_INTERVAL": "30s",
"NOMAD_META_ELB_CHECK_TYPE": "http",
"NOMAD_META_ELB_CHECK_MIN": "3",
"NOMAD_META_OWNER": "armon",
2017-02-22 22:15:22 +00:00
"NOMAD_META_chocolate": "cake",
"NOMAD_META_strawberry": "icecream",
"NOMAD_META_elb_check_interval": "30s",
"NOMAD_META_elb_check_type": "http",
"NOMAD_META_elb_check_min": "3",
"NOMAD_META_owner": "armon",
2016-03-25 17:26:32 +00:00
"HELLO": "world",
"lorem": "ipsum",
"NOMAD_ALLOC_ID": alloc.ID,
2017-07-07 20:55:39 +00:00
"NOMAD_ALLOC_INDEX": "0",
2016-03-25 17:26:32 +00:00
"NOMAD_ALLOC_NAME": alloc.Name,
"NOMAD_TASK_NAME": task.Name,
"NOMAD_GROUP_NAME": alloc.TaskGroup,
2016-10-11 17:57:12 +00:00
"NOMAD_JOB_NAME": alloc.Job.Name,
2017-05-03 22:14:19 +00:00
"NOMAD_DC": "dc1",
"NOMAD_REGION": "global",
2015-09-24 07:17:33 +00:00
}
act := eb.Build().Map()
return taskDir, exp, act
}
func TestDriver_GetTaskEnv_None(t *testing.T) {
2017-07-21 19:06:39 +00:00
t.Parallel()
taskDir, exp, act := setupTaskEnv(t, "raw_exec")
// raw_exec should use host alloc dir path
exp[env.AllocDir] = taskDir.SharedAllocDir
exp[env.TaskLocalDir] = taskDir.LocalDir
exp[env.SecretsDir] = taskDir.SecretsDir
2016-12-20 22:37:35 +00:00
// Since host env vars are included only ensure expected env vars are present
for expk, expv := range exp {
v, ok := act[expk]
if !ok {
t.Errorf("%q not found in task env", expk)
continue
}
if v != expv {
t.Errorf("Expected %s=%q but found %q", expk, expv, v)
}
}
// Make sure common host env vars are included.
for _, envvar := range [...]string{"PATH", "HOME", "USER"} {
if exp := os.Getenv(envvar); act[envvar] != exp {
t.Errorf("Expected envvar %s=%q != %q", envvar, exp, act[envvar])
}
}
}
func TestDriver_GetTaskEnv_Chroot(t *testing.T) {
2017-07-21 19:06:39 +00:00
t.Parallel()
_, exp, act := setupTaskEnv(t, "exec")
exp[env.AllocDir] = allocdir.SharedAllocContainerPath
exp[env.TaskLocalDir] = allocdir.TaskLocalContainerPath
exp[env.SecretsDir] = allocdir.TaskSecretsContainerPath
// Since host env vars are included only ensure expected env vars are present
for expk, expv := range exp {
v, ok := act[expk]
if !ok {
t.Errorf("%q not found in task env", expk)
continue
}
if v != expv {
t.Errorf("Expected %s=%q but found %q", expk, expv, v)
}
}
// Make sure common host env vars are included.
for _, envvar := range [...]string{"PATH", "HOME", "USER"} {
if exp := os.Getenv(envvar); act[envvar] != exp {
t.Errorf("Expected envvar %s=%q != %q", envvar, exp, act[envvar])
}
}
}
// TestDriver_TaskEnv_Image ensures host environment variables are not set
// for image based drivers. See #2211
func TestDriver_TaskEnv_Image(t *testing.T) {
2017-07-21 19:06:39 +00:00
t.Parallel()
_, exp, act := setupTaskEnv(t, "docker")
exp[env.AllocDir] = allocdir.SharedAllocContainerPath
exp[env.TaskLocalDir] = allocdir.TaskLocalContainerPath
exp[env.SecretsDir] = allocdir.TaskSecretsContainerPath
// Since host env vars are excluded expected and actual maps should be equal
for expk, expv := range exp {
v, ok := act[expk]
delete(act, expk)
if !ok {
t.Errorf("Env var %s missing. Expected %s=%q", expk, expk, expv)
continue
}
if v != expv {
t.Errorf("Env var %s=%q -- Expected %q", expk, v, expk)
}
}
// Any remaining env vars are unexpected
for actk, actv := range act {
t.Errorf("Env var %s=%q is unexpected", actk, actv)
}
2015-09-24 07:17:33 +00:00
}
func TestMapMergeStrStr(t *testing.T) {
2017-07-21 19:06:39 +00:00
t.Parallel()
a := map[string]string{
"cake": "chocolate",
"cookie": "caramel",
}
b := map[string]string{
"cake": "strawberry",
"pie": "apple",
}
c := mapMergeStrStr(a, b)
d := map[string]string{
"cake": "strawberry",
"cookie": "caramel",
"pie": "apple",
}
if !reflect.DeepEqual(c, d) {
t.Errorf("\nExpected\n%+v\nGot\n%+v\n", d, c)
}
}
2017-01-18 00:41:59 +00:00
func TestCreatedResources_AddMerge(t *testing.T) {
2017-07-21 19:06:39 +00:00
t.Parallel()
res1 := NewCreatedResources()
res1.Add("k1", "v1")
res1.Add("k1", "v2")
res1.Add("k1", "v1")
res1.Add("k2", "v1")
expected := map[string][]string{
"k1": {"v1", "v2"},
"k2": {"v1"},
}
if !reflect.DeepEqual(expected, res1.Resources) {
t.Fatalf("1. %#v != expected %#v", res1.Resources, expected)
}
// Make sure merging nil works
var res2 *CreatedResources
res1.Merge(res2)
if !reflect.DeepEqual(expected, res1.Resources) {
t.Fatalf("2. %#v != expected %#v", res1.Resources, expected)
}
// Make sure a normal merge works
res2 = NewCreatedResources()
res2.Add("k1", "v3")
res2.Add("k2", "v1")
res2.Add("k3", "v3")
res1.Merge(res2)
expected = map[string][]string{
"k1": {"v1", "v2", "v3"},
"k2": {"v1"},
"k3": {"v3"},
}
if !reflect.DeepEqual(expected, res1.Resources) {
t.Fatalf("3. %#v != expected %#v", res1.Resources, expected)
}
}
2017-01-18 00:41:59 +00:00
func TestCreatedResources_CopyRemove(t *testing.T) {
2017-07-21 19:06:39 +00:00
t.Parallel()
2017-01-18 00:41:59 +00:00
res1 := NewCreatedResources()
res1.Add("k1", "v1")
res1.Add("k1", "v2")
res1.Add("k1", "v3")
res1.Add("k2", "v1")
// Assert Copy creates a deep copy
res2 := res1.Copy()
if !reflect.DeepEqual(res1, res2) {
t.Fatalf("%#v != %#v", res1, res2)
}
// Assert removing v1 from k1 returns true and updates Resources slice
if removed := res2.Remove("k1", "v1"); !removed {
t.Fatalf("expected v1 to be removed: %#v", res2)
}
if expected := []string{"v2", "v3"}; !reflect.DeepEqual(expected, res2.Resources["k1"]) {
2018-03-11 19:08:07 +00:00
t.Fatalf("unexpected list for k1: %#v", res2.Resources["k1"])
2017-01-18 00:41:59 +00:00
}
// Assert removing the only value from a key removes the key
if removed := res2.Remove("k2", "v1"); !removed {
t.Fatalf("expected v1 to be removed from k2: %#v", res2.Resources)
}
if _, found := res2.Resources["k2"]; found {
t.Fatalf("k2 should have been removed from Resources: %#v", res2.Resources)
}
// Make sure res1 wasn't updated
if reflect.DeepEqual(res1, res2) {
t.Fatalf("res1 should not equal res2: #%v", res1)
}
}