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 }