github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/client/driver/executor/test_harness_test.go (about) 1 package executor 2 3 import ( 4 "io/ioutil" 5 "log" 6 "os" 7 "path/filepath" 8 "testing" 9 "time" 10 11 "github.com/hashicorp/nomad/client/allocdir" 12 "github.com/hashicorp/nomad/client/driver/env" 13 "github.com/hashicorp/nomad/helper/testtask" 14 "github.com/hashicorp/nomad/nomad/mock" 15 "github.com/hashicorp/nomad/nomad/structs" 16 "github.com/hashicorp/nomad/testutil" 17 ) 18 19 func TestMain(m *testing.M) { 20 if !testtask.Run() { 21 os.Exit(m.Run()) 22 } 23 } 24 25 var ( 26 constraint = &structs.Resources{ 27 CPU: 250, 28 MemoryMB: 256, 29 Networks: []*structs.NetworkResource{ 30 &structs.NetworkResource{ 31 MBits: 50, 32 DynamicPorts: []structs.Port{{Label: "http"}}, 33 }, 34 }, 35 } 36 ) 37 38 func mockAllocDir(t *testing.T) (string, *allocdir.AllocDir) { 39 alloc := mock.Alloc() 40 task := alloc.Job.TaskGroups[0].Tasks[0] 41 42 allocDir := allocdir.NewAllocDir(filepath.Join(os.TempDir(), alloc.ID)) 43 if err := allocDir.Build([]*structs.Task{task}); err != nil { 44 log.Panicf("allocDir.Build() failed: %v", err) 45 } 46 47 return task.Name, allocDir 48 } 49 50 func testExecutorContext() *ExecutorContext { 51 taskEnv := env.NewTaskEnvironment(mock.Node()) 52 return &ExecutorContext{taskEnv: taskEnv} 53 } 54 55 func testExecutor(t *testing.T, buildExecutor func(*ExecutorContext) Executor, compatible func(*testing.T)) { 56 if compatible != nil { 57 compatible(t) 58 } 59 60 command := func(name string, args ...string) Executor { 61 ctx := testExecutorContext() 62 e := buildExecutor(ctx) 63 SetCommand(e, name, args) 64 testtask.SetEnv(ctx.taskEnv) 65 return e 66 } 67 68 Executor_Start_Invalid(t, command) 69 Executor_Start_Wait_Failure_Code(t, command) 70 Executor_Start_Wait(t, command) 71 Executor_Start_Kill(t, command) 72 Executor_Open(t, command, buildExecutor) 73 Executor_Open_Invalid(t, command, buildExecutor) 74 } 75 76 type buildExecCommand func(name string, args ...string) Executor 77 78 func Executor_Start_Invalid(t *testing.T, command buildExecCommand) { 79 invalid := "/bin/foobar" 80 e := command(invalid, "1") 81 82 if err := e.Limit(constraint); err != nil { 83 log.Panicf("Limit() failed: %v", err) 84 } 85 86 task, alloc := mockAllocDir(t) 87 defer alloc.Destroy() 88 if err := e.ConfigureTaskDir(task, alloc); err != nil { 89 log.Panicf("ConfigureTaskDir(%v, %v) failed: %v", task, alloc, err) 90 } 91 92 if err := e.Start(); err == nil { 93 log.Panicf("Start(%v) should have failed", invalid) 94 } 95 } 96 97 func Executor_Start_Wait_Failure_Code(t *testing.T, command buildExecCommand) { 98 e := command(testtask.Path(), "fail") 99 100 if err := e.Limit(constraint); err != nil { 101 log.Panicf("Limit() failed: %v", err) 102 } 103 104 task, alloc := mockAllocDir(t) 105 defer alloc.Destroy() 106 if err := e.ConfigureTaskDir(task, alloc); err != nil { 107 log.Panicf("ConfigureTaskDir(%v, %v) failed: %v", task, alloc, err) 108 } 109 110 if err := e.Start(); err != nil { 111 log.Panicf("Start() failed: %v", err) 112 } 113 114 if err := e.Wait(); err == nil { 115 log.Panicf("Wait() should have failed") 116 } 117 } 118 119 func Executor_Start_Wait(t *testing.T, command buildExecCommand) { 120 task, alloc := mockAllocDir(t) 121 defer alloc.Destroy() 122 123 taskDir, ok := alloc.TaskDirs[task] 124 if !ok { 125 log.Panicf("No task directory found for task %v", task) 126 } 127 128 expected := "hello world" 129 file := filepath.Join(allocdir.TaskLocal, "output.txt") 130 absFilePath := filepath.Join(taskDir, file) 131 e := command(testtask.Path(), "sleep", "1s", "write", expected, file) 132 133 if err := e.Limit(constraint); err != nil { 134 log.Panicf("Limit() failed: %v", err) 135 } 136 137 if err := e.ConfigureTaskDir(task, alloc); err != nil { 138 log.Panicf("ConfigureTaskDir(%v, %v) failed: %v", task, alloc, err) 139 } 140 141 if err := e.Start(); err != nil { 142 log.Panicf("Start() failed: %v", err) 143 } 144 145 if res := e.Wait(); !res.Successful() { 146 log.Panicf("Wait() failed: %v", res) 147 } 148 149 output, err := ioutil.ReadFile(absFilePath) 150 if err != nil { 151 log.Panicf("Couldn't read file %v", absFilePath) 152 } 153 154 act := string(output) 155 if act != expected { 156 log.Panicf("Command output incorrectly: want %v; got %v", expected, act) 157 } 158 } 159 160 func Executor_Start_Kill(t *testing.T, command buildExecCommand) { 161 task, alloc := mockAllocDir(t) 162 defer alloc.Destroy() 163 164 taskDir, ok := alloc.TaskDirs[task] 165 if !ok { 166 log.Panicf("No task directory found for task %v", task) 167 } 168 169 filePath := filepath.Join(taskDir, "output") 170 e := command(testtask.Path(), "sleep", "1s", "write", "failure", filePath) 171 172 if err := e.Limit(constraint); err != nil { 173 log.Panicf("Limit() failed: %v", err) 174 } 175 176 if err := e.ConfigureTaskDir(task, alloc); err != nil { 177 log.Panicf("ConfigureTaskDir(%v, %v) failed: %v", task, alloc, err) 178 } 179 180 if err := e.Start(); err != nil { 181 log.Panicf("Start() failed: %v", err) 182 } 183 184 if err := e.Shutdown(); err != nil { 185 log.Panicf("Shutdown() failed: %v", err) 186 } 187 188 time.Sleep(time.Duration(testutil.TestMultiplier()*2) * time.Second) 189 190 // Check that the file doesn't exist. 191 if _, err := os.Stat(filePath); err == nil { 192 log.Panicf("Stat(%v) should have failed: task not killed", filePath) 193 } 194 } 195 196 func Executor_Open(t *testing.T, command buildExecCommand, newExecutor func(*ExecutorContext) Executor) { 197 task, alloc := mockAllocDir(t) 198 defer alloc.Destroy() 199 200 taskDir, ok := alloc.TaskDirs[task] 201 if !ok { 202 log.Panicf("No task directory found for task %v", task) 203 } 204 205 expected := "hello world" 206 file := filepath.Join(allocdir.TaskLocal, "output.txt") 207 absFilePath := filepath.Join(taskDir, file) 208 e := command(testtask.Path(), "sleep", "1s", "write", expected, file) 209 210 if err := e.Limit(constraint); err != nil { 211 log.Panicf("Limit() failed: %v", err) 212 } 213 214 if err := e.ConfigureTaskDir(task, alloc); err != nil { 215 log.Panicf("ConfigureTaskDir(%v, %v) failed: %v", task, alloc, err) 216 } 217 218 if err := e.Start(); err != nil { 219 log.Panicf("Start() failed: %v", err) 220 } 221 222 id, err := e.ID() 223 if err != nil { 224 log.Panicf("ID() failed: %v", err) 225 } 226 227 e2 := newExecutor(testExecutorContext()) 228 if err := e2.Open(id); err != nil { 229 log.Panicf("Open(%v) failed: %v", id, err) 230 } 231 232 if res := e2.Wait(); !res.Successful() { 233 log.Panicf("Wait() failed: %v", res) 234 } 235 236 output, err := ioutil.ReadFile(absFilePath) 237 if err != nil { 238 log.Panicf("Couldn't read file %v", absFilePath) 239 } 240 241 act := string(output) 242 if act != expected { 243 log.Panicf("Command output incorrectly: want %v; got %v", expected, act) 244 } 245 } 246 247 func Executor_Open_Invalid(t *testing.T, command buildExecCommand, newExecutor func(*ExecutorContext) Executor) { 248 task, alloc := mockAllocDir(t) 249 e := command(testtask.Path(), "echo", "foo") 250 251 if err := e.Limit(constraint); err != nil { 252 log.Panicf("Limit() failed: %v", err) 253 } 254 255 if err := e.ConfigureTaskDir(task, alloc); err != nil { 256 log.Panicf("ConfigureTaskDir(%v, %v) failed: %v", task, alloc, err) 257 } 258 259 if err := e.Start(); err != nil { 260 log.Panicf("Start() failed: %v", err) 261 } 262 263 id, err := e.ID() 264 if err != nil { 265 log.Panicf("ID() failed: %v", err) 266 } 267 268 // Kill the task because some OSes (windows) will not let us destroy the 269 // alloc (below) if the task is still running. 270 if err := e.ForceStop(); err != nil { 271 log.Panicf("e.ForceStop() failed: %v", err) 272 } 273 274 // Wait until process is actually gone, we don't care what the result was. 275 e.Wait() 276 277 // Destroy the allocdir which removes the exit code. 278 if err := alloc.Destroy(); err != nil { 279 log.Panicf("alloc.Destroy() failed: %v", err) 280 } 281 282 e2 := newExecutor(testExecutorContext()) 283 if err := e2.Open(id); err == nil { 284 log.Panicf("Open(%v) should have failed", id) 285 } 286 }