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