github.com/jmitchell/nomad@v0.1.3-0.20151007230021-7ab84c2862d8/client/task_runner_test.go (about) 1 package client 2 3 import ( 4 "log" 5 "os" 6 "path/filepath" 7 "strings" 8 "testing" 9 "time" 10 11 "github.com/hashicorp/nomad/client/allocdir" 12 "github.com/hashicorp/nomad/client/driver" 13 "github.com/hashicorp/nomad/nomad/mock" 14 "github.com/hashicorp/nomad/nomad/structs" 15 "github.com/hashicorp/nomad/testutil" 16 17 ctestutil "github.com/hashicorp/nomad/client/testutil" 18 ) 19 20 func testLogger() *log.Logger { 21 return log.New(os.Stderr, "", log.LstdFlags) 22 } 23 24 type MockTaskStateUpdater struct { 25 Count int 26 Name []string 27 Status []string 28 Description []string 29 } 30 31 func (m *MockTaskStateUpdater) Update(name, status, desc string) { 32 m.Count += 1 33 m.Name = append(m.Name, name) 34 m.Status = append(m.Status, status) 35 m.Description = append(m.Description, desc) 36 } 37 38 func testTaskRunner() (*MockTaskStateUpdater, *TaskRunner) { 39 logger := testLogger() 40 conf := DefaultConfig() 41 conf.StateDir = os.TempDir() 42 conf.AllocDir = os.TempDir() 43 upd := &MockTaskStateUpdater{} 44 alloc := mock.Alloc() 45 task := alloc.Job.TaskGroups[0].Tasks[0] 46 47 // Initialize the port listing. This should be done by the offer process but 48 // we have a mock so that doesn't happen. 49 task.Resources.Networks[0].ReservedPorts = []int{80} 50 51 allocDir := allocdir.NewAllocDir(filepath.Join(conf.AllocDir, alloc.ID)) 52 allocDir.Build([]*structs.Task{task}) 53 54 ctx := driver.NewExecContext(allocDir) 55 tr := NewTaskRunner(logger, conf, upd.Update, ctx, alloc.ID, task) 56 return upd, tr 57 } 58 59 func TestTaskRunner_SimpleRun(t *testing.T) { 60 ctestutil.ExecCompatible(t) 61 upd, tr := testTaskRunner() 62 go tr.Run() 63 defer tr.Destroy() 64 defer tr.ctx.AllocDir.Destroy() 65 66 select { 67 case <-tr.WaitCh(): 68 case <-time.After(2 * time.Second): 69 t.Fatalf("timeout") 70 } 71 72 if upd.Count != 2 { 73 t.Fatalf("should have 2 updates: %#v", upd) 74 } 75 if upd.Name[0] != tr.task.Name { 76 t.Fatalf("bad: %#v", upd.Name) 77 } 78 if upd.Status[0] != structs.AllocClientStatusRunning { 79 t.Fatalf("bad: %#v", upd.Status) 80 } 81 if upd.Description[0] != "task started" { 82 t.Fatalf("bad: %#v", upd.Description) 83 } 84 85 if upd.Name[1] != tr.task.Name { 86 t.Fatalf("bad: %#v", upd.Name) 87 } 88 if upd.Status[1] != structs.AllocClientStatusDead { 89 t.Fatalf("bad: %#v", upd.Status) 90 } 91 if upd.Description[1] != "task completed" { 92 t.Fatalf("bad: %#v", upd.Description) 93 } 94 } 95 96 func TestTaskRunner_Destroy(t *testing.T) { 97 ctestutil.ExecCompatible(t) 98 upd, tr := testTaskRunner() 99 defer tr.ctx.AllocDir.Destroy() 100 101 // Change command to ensure we run for a bit 102 tr.task.Config["command"] = "/bin/sleep" 103 tr.task.Config["args"] = "10" 104 go tr.Run() 105 106 // Begin the tear down 107 go func() { 108 time.Sleep(100 * time.Millisecond) 109 tr.Destroy() 110 }() 111 112 select { 113 case <-tr.WaitCh(): 114 case <-time.After(2 * time.Second): 115 t.Fatalf("timeout") 116 } 117 118 if upd.Count != 2 { 119 t.Fatalf("should have 2 updates: %#v", upd) 120 } 121 if upd.Status[0] != structs.AllocClientStatusRunning { 122 t.Fatalf("bad: %#v", upd.Status) 123 } 124 if upd.Status[1] != structs.AllocClientStatusDead { 125 t.Fatalf("bad: %#v", upd.Status) 126 } 127 if !strings.Contains(upd.Description[1], "task failed") { 128 t.Fatalf("bad: %#v", upd.Description) 129 } 130 } 131 132 func TestTaskRunner_Update(t *testing.T) { 133 ctestutil.ExecCompatible(t) 134 _, tr := testTaskRunner() 135 136 // Change command to ensure we run for a bit 137 tr.task.Config["command"] = "/bin/sleep" 138 tr.task.Config["args"] = "10" 139 go tr.Run() 140 defer tr.Destroy() 141 defer tr.ctx.AllocDir.Destroy() 142 143 // Update the task definition 144 newTask := new(structs.Task) 145 *newTask = *tr.task 146 newTask.Driver = "foobar" 147 tr.Update(newTask) 148 149 // Wait for update to take place 150 testutil.WaitForResult(func() (bool, error) { 151 return tr.task == newTask, nil 152 }, func(err error) { 153 t.Fatalf("err: %v", err) 154 }) 155 } 156 157 /* 158 TODO: This test is disabled til a follow-up api changes the restore state interface. 159 The driver/executor interface will be changed from Open to Cleanup, in which 160 clean-up tears down previous allocs. 161 162 func TestTaskRunner_SaveRestoreState(t *testing.T) { 163 upd, tr := testTaskRunner() 164 165 // Change command to ensure we run for a bit 166 tr.task.Config["command"] = "/bin/sleep" 167 tr.task.Config["args"] = "10" 168 go tr.Run() 169 defer tr.Destroy() 170 171 // Snapshot state 172 time.Sleep(200 * time.Millisecond) 173 err := tr.SaveState() 174 if err != nil { 175 t.Fatalf("err: %v", err) 176 } 177 178 // Create a new task runner 179 tr2 := NewTaskRunner(tr.logger, tr.config, upd.Update, 180 tr.ctx, tr.allocID, &structs.Task{Name: tr.task.Name}) 181 err = tr2.RestoreState() 182 if err != nil { 183 t.Fatalf("err: %v", err) 184 } 185 go tr2.Run() 186 defer tr2.Destroy() 187 188 // Destroy and wait 189 tr2.Destroy() 190 191 select { 192 case <-tr.WaitCh(): 193 case <-time.After(2 * time.Second): 194 t.Fatalf("timeout") 195 } 196 } 197 */