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