github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/client/driver/driver_test.go (about)

     1  package driver
     2  
     3  import (
     4  	"io"
     5  	"io/ioutil"
     6  	"log"
     7  	"math/rand"
     8  	"os"
     9  	"path/filepath"
    10  	"reflect"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/hashicorp/nomad/client/allocdir"
    15  	"github.com/hashicorp/nomad/client/config"
    16  	"github.com/hashicorp/nomad/client/driver/env"
    17  	"github.com/hashicorp/nomad/helper/testtask"
    18  	"github.com/hashicorp/nomad/helper/uuid"
    19  	"github.com/hashicorp/nomad/nomad/mock"
    20  	"github.com/hashicorp/nomad/nomad/structs"
    21  )
    22  
    23  var basicResources = &structs.Resources{
    24  	CPU:      250,
    25  	MemoryMB: 256,
    26  	DiskMB:   20,
    27  	Networks: []*structs.NetworkResource{
    28  		{
    29  			IP:            "0.0.0.0",
    30  			ReservedPorts: []structs.Port{{Label: "main", Value: 12345}},
    31  			DynamicPorts:  []structs.Port{{Label: "HTTP", Value: 43330}},
    32  		},
    33  	},
    34  }
    35  
    36  func init() {
    37  	rand.Seed(49875)
    38  }
    39  
    40  func TestMain(m *testing.M) {
    41  	if !testtask.Run() {
    42  		os.Exit(m.Run())
    43  	}
    44  }
    45  
    46  // copyFile moves an existing file to the destination
    47  func copyFile(src, dst string, t *testing.T) {
    48  	in, err := os.Open(src)
    49  	if err != nil {
    50  		t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
    51  	}
    52  	defer in.Close()
    53  	out, err := os.Create(dst)
    54  	if err != nil {
    55  		t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
    56  	}
    57  	defer func() {
    58  		if err := out.Close(); err != nil {
    59  			t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
    60  		}
    61  	}()
    62  	if _, err = io.Copy(out, in); err != nil {
    63  		t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
    64  	}
    65  	if err := out.Sync(); err != nil {
    66  		t.Fatalf("copying %v -> %v failed: %v", src, dst, err)
    67  	}
    68  }
    69  
    70  func testLogger() *log.Logger {
    71  	return log.New(os.Stderr, "", log.LstdFlags)
    72  }
    73  
    74  func testConfig(t *testing.T) *config.Config {
    75  	conf := config.DefaultConfig()
    76  
    77  	// Evaluate the symlinks so that the temp directory resolves correctly on
    78  	// Mac OS.
    79  	d1, err := ioutil.TempDir("", "TestStateDir")
    80  	if err != nil {
    81  		t.Fatal(err)
    82  	}
    83  	d2, err := ioutil.TempDir("", "TestAllocDir")
    84  	if err != nil {
    85  		t.Fatal(err)
    86  	}
    87  
    88  	p1, err := filepath.EvalSymlinks(d1)
    89  	if err != nil {
    90  		t.Fatal(err)
    91  	}
    92  	p2, err := filepath.EvalSymlinks(d2)
    93  	if err != nil {
    94  		t.Fatal(err)
    95  	}
    96  
    97  	// Give the directories access to everyone
    98  	if err := os.Chmod(p1, 0777); err != nil {
    99  		t.Fatal(err)
   100  	}
   101  	if err := os.Chmod(p2, 0777); err != nil {
   102  		t.Fatal(err)
   103  	}
   104  
   105  	conf.StateDir = p1
   106  	conf.AllocDir = p2
   107  	conf.MaxKillTimeout = 10 * time.Second
   108  	conf.Region = "global"
   109  	conf.Node = mock.Node()
   110  	return conf
   111  }
   112  
   113  type testContext struct {
   114  	AllocDir   *allocdir.AllocDir
   115  	DriverCtx  *DriverContext
   116  	ExecCtx    *ExecContext
   117  	EnvBuilder *env.Builder
   118  }
   119  
   120  // testDriverContext sets up an alloc dir, task dir, DriverContext, and ExecContext.
   121  //
   122  // It is up to the caller to call AllocDir.Destroy to cleanup.
   123  func testDriverContexts(t *testing.T, task *structs.Task) *testContext {
   124  	cfg := testConfig(t)
   125  	cfg.Node = mock.Node()
   126  	allocDir := allocdir.NewAllocDir(testLogger(), filepath.Join(cfg.AllocDir, uuid.Generate()))
   127  	if err := allocDir.Build(); err != nil {
   128  		t.Fatalf("AllocDir.Build() failed: %v", err)
   129  	}
   130  	alloc := mock.Alloc()
   131  
   132  	// Build a temp driver so we can call FSIsolation and build the task dir
   133  	tmpdrv, err := NewDriver(task.Driver, NewEmptyDriverContext())
   134  	if err != nil {
   135  		allocDir.Destroy()
   136  		t.Fatalf("NewDriver(%q, nil) failed: %v", task.Driver, err)
   137  		return nil
   138  	}
   139  
   140  	// Build the task dir
   141  	td := allocDir.NewTaskDir(task.Name)
   142  	if err := td.Build(false, config.DefaultChrootEnv, tmpdrv.FSIsolation()); err != nil {
   143  		allocDir.Destroy()
   144  		t.Fatalf("TaskDir.Build(%#v, %q) failed: %v", config.DefaultChrootEnv, tmpdrv.FSIsolation(), err)
   145  		return nil
   146  	}
   147  	eb := env.NewBuilder(cfg.Node, alloc, task, cfg.Region)
   148  	SetEnvvars(eb, tmpdrv.FSIsolation(), td, cfg)
   149  	execCtx := NewExecContext(td, eb.Build())
   150  
   151  	logger := testLogger()
   152  	emitter := func(m string, args ...interface{}) {
   153  		logger.Printf("[EVENT] "+m, args...)
   154  	}
   155  	driverCtx := NewDriverContext(task.Name, alloc.ID, cfg, cfg.Node, logger, emitter)
   156  
   157  	return &testContext{allocDir, driverCtx, execCtx, eb}
   158  }
   159  
   160  // setupTaskEnv creates a test env for GetTaskEnv testing. Returns task dir,
   161  // expected env, and actual env.
   162  func setupTaskEnv(t *testing.T, driver string) (*allocdir.TaskDir, map[string]string, map[string]string) {
   163  	task := &structs.Task{
   164  		Name:   "Foo",
   165  		Driver: driver,
   166  		Env: map[string]string{
   167  			"HELLO": "world",
   168  			"lorem": "ipsum",
   169  		},
   170  		Resources: &structs.Resources{
   171  			CPU:      1000,
   172  			MemoryMB: 500,
   173  			Networks: []*structs.NetworkResource{
   174  				{
   175  					IP:            "1.2.3.4",
   176  					ReservedPorts: []structs.Port{{Label: "one", Value: 80}, {Label: "two", Value: 443}},
   177  					DynamicPorts:  []structs.Port{{Label: "admin", Value: 8081}, {Label: "web", Value: 8086}},
   178  				},
   179  			},
   180  		},
   181  		Meta: map[string]string{
   182  			"chocolate":  "cake",
   183  			"strawberry": "icecream",
   184  		},
   185  	}
   186  
   187  	alloc := mock.Alloc()
   188  	alloc.Job.TaskGroups[0].Tasks[0] = task
   189  	alloc.Name = "Bar"
   190  	alloc.TaskResources["web"].Networks[0].DynamicPorts[0].Value = 2000
   191  	conf := testConfig(t)
   192  	allocDir := allocdir.NewAllocDir(testLogger(), filepath.Join(conf.AllocDir, alloc.ID))
   193  	taskDir := allocDir.NewTaskDir(task.Name)
   194  	eb := env.NewBuilder(conf.Node, alloc, task, conf.Region)
   195  	tmpDriver, err := NewDriver(driver, NewEmptyDriverContext())
   196  	if err != nil {
   197  		t.Fatalf("unable to create driver %q: %v", driver, err)
   198  	}
   199  	SetEnvvars(eb, tmpDriver.FSIsolation(), taskDir, conf)
   200  	exp := map[string]string{
   201  		"NOMAD_CPU_LIMIT":               "1000",
   202  		"NOMAD_MEMORY_LIMIT":            "500",
   203  		"NOMAD_ADDR_one":                "1.2.3.4:80",
   204  		"NOMAD_IP_one":                  "1.2.3.4",
   205  		"NOMAD_PORT_one":                "80",
   206  		"NOMAD_HOST_PORT_one":           "80",
   207  		"NOMAD_ADDR_two":                "1.2.3.4:443",
   208  		"NOMAD_IP_two":                  "1.2.3.4",
   209  		"NOMAD_PORT_two":                "443",
   210  		"NOMAD_HOST_PORT_two":           "443",
   211  		"NOMAD_ADDR_admin":              "1.2.3.4:8081",
   212  		"NOMAD_ADDR_web_admin":          "192.168.0.100:5000",
   213  		"NOMAD_ADDR_web_http":           "192.168.0.100:2000",
   214  		"NOMAD_IP_web_admin":            "192.168.0.100",
   215  		"NOMAD_IP_web_http":             "192.168.0.100",
   216  		"NOMAD_PORT_web_http":           "2000",
   217  		"NOMAD_PORT_web_admin":          "5000",
   218  		"NOMAD_IP_admin":                "1.2.3.4",
   219  		"NOMAD_PORT_admin":              "8081",
   220  		"NOMAD_HOST_PORT_admin":         "8081",
   221  		"NOMAD_ADDR_web":                "1.2.3.4:8086",
   222  		"NOMAD_IP_web":                  "1.2.3.4",
   223  		"NOMAD_PORT_web":                "8086",
   224  		"NOMAD_HOST_PORT_web":           "8086",
   225  		"NOMAD_META_CHOCOLATE":          "cake",
   226  		"NOMAD_META_STRAWBERRY":         "icecream",
   227  		"NOMAD_META_ELB_CHECK_INTERVAL": "30s",
   228  		"NOMAD_META_ELB_CHECK_TYPE":     "http",
   229  		"NOMAD_META_ELB_CHECK_MIN":      "3",
   230  		"NOMAD_META_OWNER":              "armon",
   231  		"NOMAD_META_chocolate":          "cake",
   232  		"NOMAD_META_strawberry":         "icecream",
   233  		"NOMAD_META_elb_check_interval": "30s",
   234  		"NOMAD_META_elb_check_type":     "http",
   235  		"NOMAD_META_elb_check_min":      "3",
   236  		"NOMAD_META_owner":              "armon",
   237  		"HELLO":                         "world",
   238  		"lorem":                         "ipsum",
   239  		"NOMAD_ALLOC_ID":                alloc.ID,
   240  		"NOMAD_ALLOC_INDEX":             "0",
   241  		"NOMAD_ALLOC_NAME":              alloc.Name,
   242  		"NOMAD_TASK_NAME":               task.Name,
   243  		"NOMAD_GROUP_NAME":              alloc.TaskGroup,
   244  		"NOMAD_JOB_NAME":                alloc.Job.Name,
   245  		"NOMAD_DC":                      "dc1",
   246  		"NOMAD_REGION":                  "global",
   247  	}
   248  
   249  	act := eb.Build().Map()
   250  	return taskDir, exp, act
   251  }
   252  
   253  func TestDriver_GetTaskEnv_None(t *testing.T) {
   254  	t.Parallel()
   255  	taskDir, exp, act := setupTaskEnv(t, "raw_exec")
   256  
   257  	// raw_exec should use host alloc dir path
   258  	exp[env.AllocDir] = taskDir.SharedAllocDir
   259  	exp[env.TaskLocalDir] = taskDir.LocalDir
   260  	exp[env.SecretsDir] = taskDir.SecretsDir
   261  
   262  	// Since host env vars are included only ensure expected env vars are present
   263  	for expk, expv := range exp {
   264  		v, ok := act[expk]
   265  		if !ok {
   266  			t.Errorf("%q not found in task env", expk)
   267  			continue
   268  		}
   269  		if v != expv {
   270  			t.Errorf("Expected %s=%q but found %q", expk, expv, v)
   271  		}
   272  	}
   273  
   274  	// Make sure common host env vars are included.
   275  	for _, envvar := range [...]string{"PATH", "HOME", "USER"} {
   276  		if exp := os.Getenv(envvar); act[envvar] != exp {
   277  			t.Errorf("Expected envvar %s=%q  !=  %q", envvar, exp, act[envvar])
   278  		}
   279  	}
   280  }
   281  
   282  func TestDriver_GetTaskEnv_Chroot(t *testing.T) {
   283  	t.Parallel()
   284  	_, exp, act := setupTaskEnv(t, "exec")
   285  
   286  	exp[env.AllocDir] = allocdir.SharedAllocContainerPath
   287  	exp[env.TaskLocalDir] = allocdir.TaskLocalContainerPath
   288  	exp[env.SecretsDir] = allocdir.TaskSecretsContainerPath
   289  
   290  	// Since host env vars are included only ensure expected env vars are present
   291  	for expk, expv := range exp {
   292  		v, ok := act[expk]
   293  		if !ok {
   294  			t.Errorf("%q not found in task env", expk)
   295  			continue
   296  		}
   297  		if v != expv {
   298  			t.Errorf("Expected %s=%q but found %q", expk, expv, v)
   299  		}
   300  	}
   301  
   302  	// Make sure common host env vars are included.
   303  	for _, envvar := range [...]string{"PATH", "HOME", "USER"} {
   304  		if exp := os.Getenv(envvar); act[envvar] != exp {
   305  			t.Errorf("Expected envvar %s=%q  !=  %q", envvar, exp, act[envvar])
   306  		}
   307  	}
   308  }
   309  
   310  // TestDriver_TaskEnv_Image ensures host environment variables are not set
   311  // for image based drivers. See #2211
   312  func TestDriver_TaskEnv_Image(t *testing.T) {
   313  	t.Parallel()
   314  	_, exp, act := setupTaskEnv(t, "docker")
   315  
   316  	exp[env.AllocDir] = allocdir.SharedAllocContainerPath
   317  	exp[env.TaskLocalDir] = allocdir.TaskLocalContainerPath
   318  	exp[env.SecretsDir] = allocdir.TaskSecretsContainerPath
   319  
   320  	// Since host env vars are excluded expected and actual maps should be equal
   321  	for expk, expv := range exp {
   322  		v, ok := act[expk]
   323  		delete(act, expk)
   324  		if !ok {
   325  			t.Errorf("Env var %s missing. Expected %s=%q", expk, expk, expv)
   326  			continue
   327  		}
   328  		if v != expv {
   329  			t.Errorf("Env var %s=%q -- Expected %q", expk, v, expk)
   330  		}
   331  	}
   332  	// Any remaining env vars are unexpected
   333  	for actk, actv := range act {
   334  		t.Errorf("Env var %s=%q is unexpected", actk, actv)
   335  	}
   336  }
   337  
   338  func TestMapMergeStrStr(t *testing.T) {
   339  	t.Parallel()
   340  	a := map[string]string{
   341  		"cake":   "chocolate",
   342  		"cookie": "caramel",
   343  	}
   344  
   345  	b := map[string]string{
   346  		"cake": "strawberry",
   347  		"pie":  "apple",
   348  	}
   349  
   350  	c := mapMergeStrStr(a, b)
   351  
   352  	d := map[string]string{
   353  		"cake":   "strawberry",
   354  		"cookie": "caramel",
   355  		"pie":    "apple",
   356  	}
   357  
   358  	if !reflect.DeepEqual(c, d) {
   359  		t.Errorf("\nExpected\n%+v\nGot\n%+v\n", d, c)
   360  	}
   361  }
   362  
   363  func TestCreatedResources_AddMerge(t *testing.T) {
   364  	t.Parallel()
   365  	res1 := NewCreatedResources()
   366  	res1.Add("k1", "v1")
   367  	res1.Add("k1", "v2")
   368  	res1.Add("k1", "v1")
   369  	res1.Add("k2", "v1")
   370  
   371  	expected := map[string][]string{
   372  		"k1": {"v1", "v2"},
   373  		"k2": {"v1"},
   374  	}
   375  	if !reflect.DeepEqual(expected, res1.Resources) {
   376  		t.Fatalf("1.  %#v != expected %#v", res1.Resources, expected)
   377  	}
   378  
   379  	// Make sure merging nil works
   380  	var res2 *CreatedResources
   381  	res1.Merge(res2)
   382  	if !reflect.DeepEqual(expected, res1.Resources) {
   383  		t.Fatalf("2.  %#v != expected %#v", res1.Resources, expected)
   384  	}
   385  
   386  	// Make sure a normal merge works
   387  	res2 = NewCreatedResources()
   388  	res2.Add("k1", "v3")
   389  	res2.Add("k2", "v1")
   390  	res2.Add("k3", "v3")
   391  	res1.Merge(res2)
   392  
   393  	expected = map[string][]string{
   394  		"k1": {"v1", "v2", "v3"},
   395  		"k2": {"v1"},
   396  		"k3": {"v3"},
   397  	}
   398  	if !reflect.DeepEqual(expected, res1.Resources) {
   399  		t.Fatalf("3.  %#v != expected %#v", res1.Resources, expected)
   400  	}
   401  }
   402  
   403  func TestCreatedResources_CopyRemove(t *testing.T) {
   404  	t.Parallel()
   405  	res1 := NewCreatedResources()
   406  	res1.Add("k1", "v1")
   407  	res1.Add("k1", "v2")
   408  	res1.Add("k1", "v3")
   409  	res1.Add("k2", "v1")
   410  
   411  	// Assert Copy creates a deep copy
   412  	res2 := res1.Copy()
   413  
   414  	if !reflect.DeepEqual(res1, res2) {
   415  		t.Fatalf("%#v != %#v", res1, res2)
   416  	}
   417  
   418  	// Assert removing v1 from k1 returns true and updates Resources slice
   419  	if removed := res2.Remove("k1", "v1"); !removed {
   420  		t.Fatalf("expected v1 to be removed: %#v", res2)
   421  	}
   422  
   423  	if expected := []string{"v2", "v3"}; !reflect.DeepEqual(expected, res2.Resources["k1"]) {
   424  		t.Fatalf("unpexpected list for k1: %#v", res2.Resources["k1"])
   425  	}
   426  
   427  	// Assert removing the only value from a key removes the key
   428  	if removed := res2.Remove("k2", "v1"); !removed {
   429  		t.Fatalf("expected v1 to be removed from k2: %#v", res2.Resources)
   430  	}
   431  
   432  	if _, found := res2.Resources["k2"]; found {
   433  		t.Fatalf("k2 should have been removed from Resources: %#v", res2.Resources)
   434  	}
   435  
   436  	// Make sure res1 wasn't updated
   437  	if reflect.DeepEqual(res1, res2) {
   438  		t.Fatalf("res1 should not equal res2: #%v", res1)
   439  	}
   440  }