github.com/hhrutter/nomad@v0.6.0-rc2.0.20170723054333-80c4b03f0705/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/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/testtask" 17 "github.com/hashicorp/nomad/nomad/mock" 18 "github.com/hashicorp/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 conf.Region = "global" 78 conf.Node = mock.Node() 79 return conf 80 } 81 82 type testContext struct { 83 AllocDir *allocdir.AllocDir 84 DriverCtx *DriverContext 85 ExecCtx *ExecContext 86 EnvBuilder *env.Builder 87 } 88 89 // testDriverContext sets up an alloc dir, task dir, DriverContext, and ExecContext. 90 // 91 // It is up to the caller to call AllocDir.Destroy to cleanup. 92 func testDriverContexts(t *testing.T, task *structs.Task) *testContext { 93 cfg := testConfig() 94 cfg.Node = mock.Node() 95 allocDir := allocdir.NewAllocDir(testLogger(), filepath.Join(cfg.AllocDir, structs.GenerateUUID())) 96 if err := allocDir.Build(); err != nil { 97 t.Fatalf("AllocDir.Build() failed: %v", err) 98 } 99 alloc := mock.Alloc() 100 101 // Build a temp driver so we can call FSIsolation and build the task dir 102 tmpdrv, err := NewDriver(task.Driver, NewEmptyDriverContext()) 103 if err != nil { 104 allocDir.Destroy() 105 t.Fatalf("NewDriver(%q, nil) failed: %v", task.Driver, err) 106 return nil 107 } 108 109 // Build the task dir 110 td := allocDir.NewTaskDir(task.Name) 111 if err := td.Build(false, config.DefaultChrootEnv, tmpdrv.FSIsolation()); err != nil { 112 allocDir.Destroy() 113 t.Fatalf("TaskDir.Build(%#v, %q) failed: %v", config.DefaultChrootEnv, tmpdrv.FSIsolation(), err) 114 return nil 115 } 116 eb := env.NewBuilder(cfg.Node, alloc, task, cfg.Region) 117 SetEnvvars(eb, tmpdrv.FSIsolation(), td, cfg) 118 execCtx := NewExecContext(td, eb.Build()) 119 120 logger := testLogger() 121 emitter := func(m string, args ...interface{}) { 122 logger.Printf("[EVENT] "+m, args...) 123 } 124 driverCtx := NewDriverContext(task.Name, alloc.ID, cfg, cfg.Node, logger, emitter) 125 126 return &testContext{allocDir, driverCtx, execCtx, eb} 127 } 128 129 // setupTaskEnv creates a test env for GetTaskEnv testing. Returns task dir, 130 // expected env, and actual env. 131 func setupTaskEnv(t *testing.T, driver string) (*allocdir.TaskDir, map[string]string, map[string]string) { 132 task := &structs.Task{ 133 Name: "Foo", 134 Driver: driver, 135 Env: map[string]string{ 136 "HELLO": "world", 137 "lorem": "ipsum", 138 }, 139 Resources: &structs.Resources{ 140 CPU: 1000, 141 MemoryMB: 500, 142 Networks: []*structs.NetworkResource{ 143 &structs.NetworkResource{ 144 IP: "1.2.3.4", 145 ReservedPorts: []structs.Port{{Label: "one", Value: 80}, {Label: "two", Value: 443}}, 146 DynamicPorts: []structs.Port{{Label: "admin", Value: 8081}, {Label: "web", Value: 8086}}, 147 }, 148 }, 149 }, 150 Meta: map[string]string{ 151 "chocolate": "cake", 152 "strawberry": "icecream", 153 }, 154 } 155 156 alloc := mock.Alloc() 157 alloc.Job.TaskGroups[0].Tasks[0] = task 158 alloc.Name = "Bar" 159 alloc.TaskResources["web"].Networks[0].DynamicPorts[0].Value = 2000 160 conf := testConfig() 161 allocDir := allocdir.NewAllocDir(testLogger(), filepath.Join(conf.AllocDir, alloc.ID)) 162 taskDir := allocDir.NewTaskDir(task.Name) 163 eb := env.NewBuilder(conf.Node, alloc, task, conf.Region) 164 tmpDriver, err := NewDriver(driver, NewEmptyDriverContext()) 165 if err != nil { 166 t.Fatalf("unable to create driver %q: %v", driver, err) 167 } 168 SetEnvvars(eb, tmpDriver.FSIsolation(), taskDir, conf) 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_INDEX": "0", 210 "NOMAD_ALLOC_NAME": alloc.Name, 211 "NOMAD_TASK_NAME": task.Name, 212 "NOMAD_GROUP_NAME": alloc.TaskGroup, 213 "NOMAD_JOB_NAME": alloc.Job.Name, 214 "NOMAD_DC": "dc1", 215 "NOMAD_REGION": "global", 216 } 217 218 act := eb.Build().Map() 219 return taskDir, exp, act 220 } 221 222 func TestDriver_GetTaskEnv_None(t *testing.T) { 223 t.Parallel() 224 taskDir, exp, act := setupTaskEnv(t, "raw_exec") 225 226 // raw_exec should use host alloc dir path 227 exp[env.AllocDir] = taskDir.SharedAllocDir 228 exp[env.TaskLocalDir] = taskDir.LocalDir 229 exp[env.SecretsDir] = taskDir.SecretsDir 230 231 // Since host env vars are included only ensure expected env vars are present 232 for expk, expv := range exp { 233 v, ok := act[expk] 234 if !ok { 235 t.Errorf("%q not found in task env", expk) 236 continue 237 } 238 if v != expv { 239 t.Errorf("Expected %s=%q but found %q", expk, expv, v) 240 } 241 } 242 243 // Make sure common host env vars are included. 244 for _, envvar := range [...]string{"PATH", "HOME", "USER"} { 245 if exp := os.Getenv(envvar); act[envvar] != exp { 246 t.Errorf("Expected envvar %s=%q != %q", envvar, exp, act[envvar]) 247 } 248 } 249 } 250 251 func TestDriver_GetTaskEnv_Chroot(t *testing.T) { 252 t.Parallel() 253 _, exp, act := setupTaskEnv(t, "exec") 254 255 exp[env.AllocDir] = allocdir.SharedAllocContainerPath 256 exp[env.TaskLocalDir] = allocdir.TaskLocalContainerPath 257 exp[env.SecretsDir] = allocdir.TaskSecretsContainerPath 258 259 // Since host env vars are included only ensure expected env vars are present 260 for expk, expv := range exp { 261 v, ok := act[expk] 262 if !ok { 263 t.Errorf("%q not found in task env", expk) 264 continue 265 } 266 if v != expv { 267 t.Errorf("Expected %s=%q but found %q", expk, expv, v) 268 } 269 } 270 271 // Make sure common host env vars are included. 272 for _, envvar := range [...]string{"PATH", "HOME", "USER"} { 273 if exp := os.Getenv(envvar); act[envvar] != exp { 274 t.Errorf("Expected envvar %s=%q != %q", envvar, exp, act[envvar]) 275 } 276 } 277 } 278 279 // TestDriver_TaskEnv_Image ensures host environment variables are not set 280 // for image based drivers. See #2211 281 func TestDriver_TaskEnv_Image(t *testing.T) { 282 t.Parallel() 283 _, exp, act := setupTaskEnv(t, "docker") 284 285 exp[env.AllocDir] = allocdir.SharedAllocContainerPath 286 exp[env.TaskLocalDir] = allocdir.TaskLocalContainerPath 287 exp[env.SecretsDir] = allocdir.TaskSecretsContainerPath 288 289 // Since host env vars are excluded expected and actual maps should be equal 290 for expk, expv := range exp { 291 v, ok := act[expk] 292 delete(act, expk) 293 if !ok { 294 t.Errorf("Env var %s missing. Expected %s=%q", expk, expk, expv) 295 continue 296 } 297 if v != expv { 298 t.Errorf("Env var %s=%q -- Expected %q", expk, v, expk) 299 } 300 } 301 // Any remaining env vars are unexpected 302 for actk, actv := range act { 303 t.Errorf("Env var %s=%q is unexpected", actk, actv) 304 } 305 } 306 307 func TestMapMergeStrInt(t *testing.T) { 308 t.Parallel() 309 a := map[string]int{ 310 "cakes": 5, 311 "cookies": 3, 312 } 313 314 b := map[string]int{ 315 "cakes": 3, 316 "pies": 2, 317 } 318 319 c := mapMergeStrInt(a, b) 320 321 d := map[string]int{ 322 "cakes": 3, 323 "cookies": 3, 324 "pies": 2, 325 } 326 327 if !reflect.DeepEqual(c, d) { 328 t.Errorf("\nExpected\n%+v\nGot\n%+v\n", d, c) 329 } 330 } 331 332 func TestMapMergeStrStr(t *testing.T) { 333 t.Parallel() 334 a := map[string]string{ 335 "cake": "chocolate", 336 "cookie": "caramel", 337 } 338 339 b := map[string]string{ 340 "cake": "strawberry", 341 "pie": "apple", 342 } 343 344 c := mapMergeStrStr(a, b) 345 346 d := map[string]string{ 347 "cake": "strawberry", 348 "cookie": "caramel", 349 "pie": "apple", 350 } 351 352 if !reflect.DeepEqual(c, d) { 353 t.Errorf("\nExpected\n%+v\nGot\n%+v\n", d, c) 354 } 355 } 356 357 func TestCreatedResources_AddMerge(t *testing.T) { 358 t.Parallel() 359 res1 := NewCreatedResources() 360 res1.Add("k1", "v1") 361 res1.Add("k1", "v2") 362 res1.Add("k1", "v1") 363 res1.Add("k2", "v1") 364 365 expected := map[string][]string{ 366 "k1": {"v1", "v2"}, 367 "k2": {"v1"}, 368 } 369 if !reflect.DeepEqual(expected, res1.Resources) { 370 t.Fatalf("1. %#v != expected %#v", res1.Resources, expected) 371 } 372 373 // Make sure merging nil works 374 var res2 *CreatedResources 375 res1.Merge(res2) 376 if !reflect.DeepEqual(expected, res1.Resources) { 377 t.Fatalf("2. %#v != expected %#v", res1.Resources, expected) 378 } 379 380 // Make sure a normal merge works 381 res2 = NewCreatedResources() 382 res2.Add("k1", "v3") 383 res2.Add("k2", "v1") 384 res2.Add("k3", "v3") 385 res1.Merge(res2) 386 387 expected = map[string][]string{ 388 "k1": {"v1", "v2", "v3"}, 389 "k2": {"v1"}, 390 "k3": {"v3"}, 391 } 392 if !reflect.DeepEqual(expected, res1.Resources) { 393 t.Fatalf("3. %#v != expected %#v", res1.Resources, expected) 394 } 395 } 396 397 func TestCreatedResources_CopyRemove(t *testing.T) { 398 t.Parallel() 399 res1 := NewCreatedResources() 400 res1.Add("k1", "v1") 401 res1.Add("k1", "v2") 402 res1.Add("k1", "v3") 403 res1.Add("k2", "v1") 404 405 // Assert Copy creates a deep copy 406 res2 := res1.Copy() 407 408 if !reflect.DeepEqual(res1, res2) { 409 t.Fatalf("%#v != %#v", res1, res2) 410 } 411 412 // Assert removing v1 from k1 returns true and updates Resources slice 413 if removed := res2.Remove("k1", "v1"); !removed { 414 t.Fatalf("expected v1 to be removed: %#v", res2) 415 } 416 417 if expected := []string{"v2", "v3"}; !reflect.DeepEqual(expected, res2.Resources["k1"]) { 418 t.Fatalf("unpexpected list for k1: %#v", res2.Resources["k1"]) 419 } 420 421 // Assert removing the only value from a key removes the key 422 if removed := res2.Remove("k2", "v1"); !removed { 423 t.Fatalf("expected v1 to be removed from k2: %#v", res2.Resources) 424 } 425 426 if _, found := res2.Resources["k2"]; found { 427 t.Fatalf("k2 should have been removed from Resources: %#v", res2.Resources) 428 } 429 430 // Make sure res1 wasn't updated 431 if reflect.DeepEqual(res1, res2) { 432 t.Fatalf("res1 should not equal res2: #%v", res1) 433 } 434 }