github.com/ncodes/nomad@v0.5.7-0.20170403112158-97adf4a74fb3/client/driver/driver_test.go (about)

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