github.com/zhizhiboom/nomad@v0.8.5-0.20180907175415-f28fd3a1a056/command/job_status_test.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "strings" 6 "testing" 7 "time" 8 9 "github.com/hashicorp/nomad/api" 10 "github.com/hashicorp/nomad/command/agent" 11 "github.com/hashicorp/nomad/nomad/mock" 12 "github.com/hashicorp/nomad/nomad/structs" 13 "github.com/hashicorp/nomad/testutil" 14 "github.com/mitchellh/cli" 15 "github.com/posener/complete" 16 "github.com/stretchr/testify/assert" 17 "github.com/stretchr/testify/require" 18 ) 19 20 func TestJobStatusCommand_Implements(t *testing.T) { 21 t.Parallel() 22 var _ cli.Command = &JobStatusCommand{} 23 } 24 25 func TestJobStatusCommand_Run(t *testing.T) { 26 t.Parallel() 27 srv, client, url := testServer(t, true, nil) 28 defer srv.Shutdown() 29 30 ui := new(cli.MockUi) 31 cmd := &JobStatusCommand{Meta: Meta{Ui: ui}} 32 33 // Should return blank for no jobs 34 if code := cmd.Run([]string{"-address=" + url}); code != 0 { 35 t.Fatalf("expected exit 0, got: %d", code) 36 } 37 38 // Check for this awkward nil string, since a nil bytes.Buffer 39 // returns this purposely, and mitchellh/cli has a nil pointer 40 // if nothing was ever output. 41 exp := "No running jobs" 42 if out := strings.TrimSpace(ui.OutputWriter.String()); out != exp { 43 t.Fatalf("expected %q; got: %q", exp, out) 44 } 45 46 // Register two jobs 47 job1 := testJob("job1_sfx") 48 resp, _, err := client.Jobs().Register(job1, nil) 49 if err != nil { 50 t.Fatalf("err: %s", err) 51 } 52 if code := waitForSuccess(ui, client, fullId, t, resp.EvalID); code != 0 { 53 t.Fatalf("status code non zero saw %d", code) 54 } 55 56 job2 := testJob("job2_sfx") 57 resp2, _, err := client.Jobs().Register(job2, nil) 58 if err != nil { 59 t.Fatalf("err: %s", err) 60 } 61 if code := waitForSuccess(ui, client, fullId, t, resp2.EvalID); code != 0 { 62 t.Fatalf("status code non zero saw %d", code) 63 } 64 65 // Query again and check the result 66 if code := cmd.Run([]string{"-address=" + url}); code != 0 { 67 t.Fatalf("expected exit 0, got: %d", code) 68 } 69 out := ui.OutputWriter.String() 70 if !strings.Contains(out, "job1_sfx") || !strings.Contains(out, "job2_sfx") { 71 t.Fatalf("expected job1_sfx and job2_sfx, got: %s", out) 72 } 73 ui.OutputWriter.Reset() 74 75 // Query a single job 76 if code := cmd.Run([]string{"-address=" + url, "job2_sfx"}); code != 0 { 77 t.Fatalf("expected exit 0, got: %d", code) 78 } 79 out = ui.OutputWriter.String() 80 if strings.Contains(out, "job1_sfx") || !strings.Contains(out, "job2_sfx") { 81 t.Fatalf("expected only job2_sfx, got: %s", out) 82 } 83 if !strings.Contains(out, "Allocations") { 84 t.Fatalf("should dump allocations") 85 } 86 if !strings.Contains(out, "Summary") { 87 t.Fatalf("should dump summary") 88 } 89 ui.OutputWriter.Reset() 90 91 // Query a single job showing evals 92 if code := cmd.Run([]string{"-address=" + url, "-evals", "job2_sfx"}); code != 0 { 93 t.Fatalf("expected exit 0, got: %d", code) 94 } 95 out = ui.OutputWriter.String() 96 if strings.Contains(out, "job1_sfx") || !strings.Contains(out, "job2_sfx") { 97 t.Fatalf("expected only job2_sfx, got: %s", out) 98 } 99 if !strings.Contains(out, "Evaluations") { 100 t.Fatalf("should dump evaluations") 101 } 102 if !strings.Contains(out, "Allocations") { 103 t.Fatalf("should dump allocations") 104 } 105 ui.OutputWriter.Reset() 106 107 // Query a single job in verbose mode 108 if code := cmd.Run([]string{"-address=" + url, "-verbose", "job2_sfx"}); code != 0 { 109 t.Fatalf("expected exit 0, got: %d", code) 110 } 111 out = ui.OutputWriter.String() 112 if strings.Contains(out, "job1_sfx") || !strings.Contains(out, "job2_sfx") { 113 t.Fatalf("expected only job2_sfx, got: %s", out) 114 } 115 if !strings.Contains(out, "Evaluations") { 116 t.Fatalf("should dump evaluations") 117 } 118 if !strings.Contains(out, "Allocations") { 119 t.Fatalf("should dump allocations") 120 } 121 if !strings.Contains(out, "Created") { 122 t.Fatal("should have created header") 123 } 124 if !strings.Contains(out, "Modified") { 125 t.Fatal("should have modified header") 126 } 127 ui.ErrorWriter.Reset() 128 ui.OutputWriter.Reset() 129 130 // Query jobs with prefix match 131 if code := cmd.Run([]string{"-address=" + url, "job"}); code != 1 { 132 t.Fatalf("expected exit 1, got: %d", code) 133 } 134 out = ui.ErrorWriter.String() 135 if !strings.Contains(out, "job1_sfx") || !strings.Contains(out, "job2_sfx") { 136 t.Fatalf("expected job1_sfx and job2_sfx, got: %s", out) 137 } 138 ui.ErrorWriter.Reset() 139 ui.OutputWriter.Reset() 140 141 // Query a single job with prefix match 142 if code := cmd.Run([]string{"-address=" + url, "job1"}); code != 0 { 143 t.Fatalf("expected exit 0, got: %d", code) 144 } 145 out = ui.OutputWriter.String() 146 if !strings.Contains(out, "job1_sfx") || strings.Contains(out, "job2_sfx") { 147 t.Fatalf("expected only job1_sfx, got: %s", out) 148 } 149 150 if !strings.Contains(out, "Created") { 151 t.Fatal("should have created header") 152 } 153 154 if !strings.Contains(out, "Modified") { 155 t.Fatal("should have modified header") 156 } 157 ui.OutputWriter.Reset() 158 159 // Query in short view mode 160 if code := cmd.Run([]string{"-address=" + url, "-short", "job2"}); code != 0 { 161 t.Fatalf("expected exit 0, got: %d", code) 162 } 163 out = ui.OutputWriter.String() 164 if !strings.Contains(out, "job2") { 165 t.Fatalf("expected job2, got: %s", out) 166 } 167 if strings.Contains(out, "Evaluations") { 168 t.Fatalf("should not dump evaluations") 169 } 170 if strings.Contains(out, "Allocations") { 171 t.Fatalf("should not dump allocations") 172 } 173 if strings.Contains(out, resp.EvalID) { 174 t.Fatalf("should not contain full identifiers, got %s", out) 175 } 176 ui.OutputWriter.Reset() 177 178 // Request full identifiers 179 if code := cmd.Run([]string{"-address=" + url, "-verbose", "job1"}); code != 0 { 180 t.Fatalf("expected exit 0, got: %d", code) 181 } 182 out = ui.OutputWriter.String() 183 if !strings.Contains(out, resp.EvalID) { 184 t.Fatalf("should contain full identifiers, got %s", out) 185 } 186 } 187 188 func TestJobStatusCommand_Fails(t *testing.T) { 189 t.Parallel() 190 ui := new(cli.MockUi) 191 cmd := &JobStatusCommand{Meta: Meta{Ui: ui}} 192 193 // Fails on misuse 194 if code := cmd.Run([]string{"some", "bad", "args"}); code != 1 { 195 t.Fatalf("expected exit code 1, got: %d", code) 196 } 197 if out := ui.ErrorWriter.String(); !strings.Contains(out, commandErrorText(cmd)) { 198 t.Fatalf("expected help output, got: %s", out) 199 } 200 ui.ErrorWriter.Reset() 201 202 // Fails on connection failure 203 if code := cmd.Run([]string{"-address=nope"}); code != 1 { 204 t.Fatalf("expected exit code 1, got: %d", code) 205 } 206 if out := ui.ErrorWriter.String(); !strings.Contains(out, "Error querying jobs") { 207 t.Fatalf("expected failed query error, got: %s", out) 208 } 209 } 210 211 func TestJobStatusCommand_AutocompleteArgs(t *testing.T) { 212 assert := assert.New(t) 213 t.Parallel() 214 215 srv, _, url := testServer(t, true, nil) 216 defer srv.Shutdown() 217 218 ui := new(cli.MockUi) 219 cmd := &JobStatusCommand{Meta: Meta{Ui: ui, flagAddress: url}} 220 221 // Create a fake job 222 state := srv.Agent.Server().State() 223 j := mock.Job() 224 assert.Nil(state.UpsertJob(1000, j)) 225 226 prefix := j.ID[:len(j.ID)-5] 227 args := complete.Args{Last: prefix} 228 predictor := cmd.AutocompleteArgs() 229 230 res := predictor.Predict(args) 231 assert.Equal(1, len(res)) 232 assert.Equal(j.ID, res[0]) 233 } 234 235 func TestJobStatusCommand_WithAccessPolicy(t *testing.T) { 236 assert := assert.New(t) 237 t.Parallel() 238 239 config := func(c *agent.Config) { 240 c.ACL.Enabled = true 241 } 242 243 srv, client, url := testServer(t, true, config) 244 defer srv.Shutdown() 245 246 // Bootstrap an initial ACL token 247 token := srv.RootToken 248 assert.NotNil(token, "failed to bootstrap ACL token") 249 250 // Register a job 251 j := testJob("job1_sfx") 252 253 invalidToken := mock.ACLToken() 254 255 ui := new(cli.MockUi) 256 cmd := &JobStatusCommand{Meta: Meta{Ui: ui, flagAddress: url}} 257 258 // registering a job without a token fails 259 client.SetSecretID(invalidToken.SecretID) 260 resp, _, err := client.Jobs().Register(j, nil) 261 assert.NotNil(err) 262 263 // registering a job with a valid token succeeds 264 client.SetSecretID(token.SecretID) 265 resp, _, err = client.Jobs().Register(j, nil) 266 assert.Nil(err) 267 code := waitForSuccess(ui, client, fullId, t, resp.EvalID) 268 assert.Equal(0, code) 269 270 // Request Job List without providing a valid token 271 code = cmd.Run([]string{"-address=" + url, "-token=" + invalidToken.SecretID, "-short"}) 272 assert.Equal(1, code) 273 274 // Request Job List with a valid token 275 code = cmd.Run([]string{"-address=" + url, "-token=" + token.SecretID, "-short"}) 276 assert.Equal(0, code) 277 278 out := ui.OutputWriter.String() 279 if !strings.Contains(out, *j.ID) { 280 t.Fatalf("should contain full identifiers, got %s", out) 281 } 282 } 283 284 func TestJobStatusCommand_RescheduleEvals(t *testing.T) { 285 t.Parallel() 286 srv, client, url := testServer(t, true, nil) 287 defer srv.Shutdown() 288 289 // Wait for a node to be ready 290 testutil.WaitForResult(func() (bool, error) { 291 nodes, _, err := client.Nodes().List(nil) 292 if err != nil { 293 return false, err 294 } 295 for _, node := range nodes { 296 if node.Status == structs.NodeStatusReady { 297 return true, nil 298 } 299 } 300 return false, fmt.Errorf("no ready nodes") 301 }, func(err error) { 302 t.Fatalf("err: %v", err) 303 }) 304 305 ui := new(cli.MockUi) 306 cmd := &JobStatusCommand{Meta: Meta{Ui: ui, flagAddress: url}} 307 308 require := require.New(t) 309 state := srv.Agent.Server().State() 310 311 // Create state store objects for job, alloc and followup eval with a future WaitUntil value 312 j := mock.Job() 313 require.Nil(state.UpsertJob(900, j)) 314 315 e := mock.Eval() 316 e.WaitUntil = time.Now().Add(1 * time.Hour) 317 require.Nil(state.UpsertEvals(902, []*structs.Evaluation{e})) 318 a := mock.Alloc() 319 a.Job = j 320 a.JobID = j.ID 321 a.TaskGroup = j.TaskGroups[0].Name 322 a.FollowupEvalID = e.ID 323 a.Metrics = &structs.AllocMetric{} 324 a.DesiredStatus = structs.AllocDesiredStatusRun 325 a.ClientStatus = structs.AllocClientStatusRunning 326 require.Nil(state.UpsertAllocs(1000, []*structs.Allocation{a})) 327 328 // Query jobs with prefix match 329 if code := cmd.Run([]string{"-address=" + url, j.ID}); code != 0 { 330 t.Fatalf("expected exit 0, got: %d", code) 331 } 332 out := ui.OutputWriter.String() 333 require.Contains(out, "Future Rescheduling Attempts") 334 require.Contains(out, e.ID[:8]) 335 } 336 337 func waitForSuccess(ui cli.Ui, client *api.Client, length int, t *testing.T, evalId string) int { 338 mon := newMonitor(ui, client, length) 339 monErr := mon.monitor(evalId, false) 340 return monErr 341 }