github.com/ranjib/nomad@v0.1.1-0.20160225204057-97751b02f70b/client/task_runner_test.go (about) 1 package client 2 3 import ( 4 "fmt" 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" 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 state string 26 events []*structs.TaskEvent 27 } 28 29 func (m *MockTaskStateUpdater) Update(name, state string, event *structs.TaskEvent) { 30 m.state = state 31 m.events = append(m.events, event) 32 } 33 34 func testTaskRunner(restarts bool) (*MockTaskStateUpdater, *TaskRunner) { 35 logger := testLogger() 36 conf := DefaultConfig() 37 conf.StateDir = os.TempDir() 38 conf.AllocDir = os.TempDir() 39 upd := &MockTaskStateUpdater{} 40 alloc := mock.Alloc() 41 task := alloc.Job.TaskGroups[0].Tasks[0] 42 consulClient, _ := NewConsulService(&consulServiceConfig{logger, "127.0.0.1:8500", "", "", false, false, &structs.Node{}}) 43 // Initialize the port listing. This should be done by the offer process but 44 // we have a mock so that doesn't happen. 45 task.Resources.Networks[0].ReservedPorts = []structs.Port{{"", 80}} 46 47 allocDir := allocdir.NewAllocDir(filepath.Join(conf.AllocDir, alloc.ID)) 48 allocDir.Build([]*structs.Task{task}) 49 50 ctx := driver.NewExecContext(allocDir, alloc.ID) 51 tr := NewTaskRunner(logger, conf, upd.Update, ctx, mock.Alloc(), task, consulClient) 52 if !restarts { 53 tr.restartTracker = noRestartsTracker() 54 } 55 return upd, tr 56 } 57 58 func TestTaskRunner_SimpleRun(t *testing.T) { 59 ctestutil.ExecCompatible(t) 60 upd, tr := testTaskRunner(false) 61 go tr.Run() 62 defer tr.Destroy() 63 defer tr.ctx.AllocDir.Destroy() 64 65 select { 66 case <-tr.WaitCh(): 67 case <-time.After(time.Duration(testutil.TestMultiplier()*15) * time.Second): 68 t.Fatalf("timeout") 69 } 70 71 if len(upd.events) != 3 { 72 t.Fatalf("should have 3 updates: %#v", upd.events) 73 } 74 75 if upd.state != structs.TaskStateDead { 76 t.Fatalf("TaskState %v; want %v", upd.state, structs.TaskStateDead) 77 } 78 79 if upd.events[0].Type != structs.TaskReceived { 80 t.Fatalf("First Event was %v; want %v", upd.events[0].Type, structs.TaskReceived) 81 } 82 83 if upd.events[1].Type != structs.TaskStarted { 84 t.Fatalf("Second Event was %v; want %v", upd.events[1].Type, structs.TaskStarted) 85 } 86 87 if upd.events[2].Type != structs.TaskTerminated { 88 t.Fatalf("Third Event was %v; want %v", upd.events[2].Type, structs.TaskTerminated) 89 } 90 } 91 92 func TestTaskRunner_Destroy(t *testing.T) { 93 ctestutil.ExecCompatible(t) 94 upd, tr := testTaskRunner(true) 95 defer tr.ctx.AllocDir.Destroy() 96 97 // Change command to ensure we run for a bit 98 tr.task.Config["command"] = "/bin/sleep" 99 tr.task.Config["args"] = []string{"10"} 100 go tr.Run() 101 102 // Begin the tear down 103 go func() { 104 time.Sleep(100 * time.Millisecond) 105 tr.Destroy() 106 }() 107 108 select { 109 case <-tr.WaitCh(): 110 case <-time.After(time.Duration(testutil.TestMultiplier()*15) * time.Second): 111 t.Fatalf("timeout") 112 } 113 114 if len(upd.events) != 3 { 115 t.Fatalf("should have 3 updates: %#v", upd.events) 116 } 117 118 if upd.state != structs.TaskStateDead { 119 t.Fatalf("TaskState %v; want %v", upd.state, structs.TaskStateDead) 120 } 121 122 if upd.events[0].Type != structs.TaskReceived { 123 t.Fatalf("First Event was %v; want %v", upd.events[0].Type, structs.TaskReceived) 124 } 125 126 if upd.events[1].Type != structs.TaskStarted { 127 t.Fatalf("Second Event was %v; want %v", upd.events[1].Type, structs.TaskStarted) 128 } 129 130 if upd.events[2].Type != structs.TaskKilled { 131 t.Fatalf("Third Event was %v; want %v", upd.events[2].Type, structs.TaskKilled) 132 } 133 134 } 135 136 func TestTaskRunner_Update(t *testing.T) { 137 ctestutil.ExecCompatible(t) 138 _, tr := testTaskRunner(false) 139 140 // Change command to ensure we run for a bit 141 tr.task.Config["command"] = "/bin/sleep" 142 tr.task.Config["args"] = []string{"100"} 143 go tr.Run() 144 defer tr.Destroy() 145 defer tr.ctx.AllocDir.Destroy() 146 147 // Update the task definition 148 updateAlloc := tr.alloc.Copy() 149 150 // Update the restart policy 151 newTG := updateAlloc.Job.TaskGroups[0] 152 newMode := "foo" 153 newTG.RestartPolicy.Mode = newMode 154 155 newTask := updateAlloc.Job.TaskGroups[0].Tasks[0] 156 newTask.Driver = "foobar" 157 158 // Update the kill timeout 159 testutil.WaitForResult(func() (bool, error) { 160 if tr.handle == nil { 161 return false, fmt.Errorf("task not started") 162 } 163 return true, nil 164 }, func(err error) { 165 t.Fatalf("err: %v", err) 166 }) 167 168 oldHandle := tr.handle.ID() 169 newTask.KillTimeout = time.Hour 170 171 tr.Update(updateAlloc) 172 173 // Wait for update to take place 174 testutil.WaitForResult(func() (bool, error) { 175 if tr.task != newTask { 176 return false, fmt.Errorf("task not updated") 177 } 178 if tr.restartTracker.policy.Mode != newMode { 179 return false, fmt.Errorf("restart policy not updated") 180 } 181 if tr.handle.ID() == oldHandle { 182 return false, fmt.Errorf("handle not updated") 183 } 184 return true, nil 185 }, func(err error) { 186 t.Fatalf("err: %v", err) 187 }) 188 } 189 190 func TestTaskRunner_SaveRestoreState(t *testing.T) { 191 ctestutil.ExecCompatible(t) 192 upd, tr := testTaskRunner(false) 193 194 // Change command to ensure we run for a bit 195 tr.task.Config["command"] = "/bin/sleep" 196 tr.task.Config["args"] = []string{"10"} 197 go tr.Run() 198 defer tr.Destroy() 199 200 // Snapshot state 201 time.Sleep(2 * time.Second) 202 if err := tr.SaveState(); err != nil { 203 t.Fatalf("err: %v", err) 204 } 205 206 // Create a new task runner 207 consulClient, _ := NewConsulService(&consulServiceConfig{tr.logger, "127.0.0.1:8500", "", "", false, false, &structs.Node{}}) 208 tr2 := NewTaskRunner(tr.logger, tr.config, upd.Update, 209 tr.ctx, tr.alloc, &structs.Task{Name: tr.task.Name}, consulClient) 210 if err := tr2.RestoreState(); err != nil { 211 t.Fatalf("err: %v", err) 212 } 213 go tr2.Run() 214 defer tr2.Destroy() 215 216 // Destroy and wait 217 testutil.WaitForResult(func() (bool, error) { 218 return tr2.handle != nil, fmt.Errorf("RestoreState() didn't open handle") 219 }, func(err error) { 220 t.Fatalf("err: %v", err) 221 }) 222 }