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 }