github.com/ryanslade/nomad@v0.2.4-0.20160128061903-fc95782f2089/command/monitor_test.go (about) 1 package command 2 3 import ( 4 "strings" 5 "testing" 6 "time" 7 8 "github.com/hashicorp/nomad/api" 9 "github.com/hashicorp/nomad/nomad/structs" 10 "github.com/mitchellh/cli" 11 ) 12 13 func TestMonitor_Update_Eval(t *testing.T) { 14 ui := new(cli.MockUi) 15 mon := newMonitor(ui, nil, fullId) 16 17 // Evals triggered by jobs log 18 state := &evalState{ 19 status: structs.EvalStatusPending, 20 job: "job1", 21 } 22 mon.update(state) 23 24 out := ui.OutputWriter.String() 25 if !strings.Contains(out, "job1") { 26 t.Fatalf("missing job\n\n%s", out) 27 } 28 ui.OutputWriter.Reset() 29 30 // Evals trigerred by nodes log 31 state = &evalState{ 32 status: structs.EvalStatusPending, 33 node: "12345678-abcd-efab-cdef-123456789abc", 34 } 35 mon.update(state) 36 37 out = ui.OutputWriter.String() 38 if !strings.Contains(out, "12345678-abcd-efab-cdef-123456789abc") { 39 t.Fatalf("missing node\n\n%s", out) 40 } 41 42 // Transition to pending should not be logged 43 if strings.Contains(out, structs.EvalStatusPending) { 44 t.Fatalf("should skip status\n\n%s", out) 45 } 46 ui.OutputWriter.Reset() 47 48 // No logs sent if no update 49 mon.update(state) 50 if out := ui.OutputWriter.String(); out != "" { 51 t.Fatalf("expected no output\n\n%s", out) 52 } 53 54 // Status change sends more logs 55 state = &evalState{ 56 status: structs.EvalStatusComplete, 57 node: "12345678-abcd-efab-cdef-123456789abc", 58 } 59 mon.update(state) 60 out = ui.OutputWriter.String() 61 if !strings.Contains(out, structs.EvalStatusComplete) { 62 t.Fatalf("missing status\n\n%s", out) 63 } 64 } 65 66 func TestMonitor_Update_Allocs(t *testing.T) { 67 ui := new(cli.MockUi) 68 mon := newMonitor(ui, nil, fullId) 69 70 // New allocations write new logs 71 state := &evalState{ 72 allocs: map[string]*allocState{ 73 "alloc1": &allocState{ 74 id: "87654321-abcd-efab-cdef-123456789abc", 75 group: "group1", 76 node: "12345678-abcd-efab-cdef-123456789abc", 77 desired: structs.AllocDesiredStatusRun, 78 client: structs.AllocClientStatusPending, 79 index: 1, 80 }, 81 }, 82 } 83 mon.update(state) 84 85 // Logs were output 86 out := ui.OutputWriter.String() 87 if !strings.Contains(out, "87654321-abcd-efab-cdef-123456789abc") { 88 t.Fatalf("missing alloc\n\n%s", out) 89 } 90 if !strings.Contains(out, "group1") { 91 t.Fatalf("missing group\n\n%s", out) 92 } 93 if !strings.Contains(out, "12345678-abcd-efab-cdef-123456789abc") { 94 t.Fatalf("missing node\n\n%s", out) 95 } 96 if !strings.Contains(out, "created") { 97 t.Fatalf("missing created\n\n%s", out) 98 } 99 ui.OutputWriter.Reset() 100 101 // No change yields no logs 102 mon.update(state) 103 if out := ui.OutputWriter.String(); out != "" { 104 t.Fatalf("expected no output\n\n%s", out) 105 } 106 ui.OutputWriter.Reset() 107 108 // Alloc updates cause more log lines 109 state = &evalState{ 110 allocs: map[string]*allocState{ 111 "alloc1": &allocState{ 112 id: "87654321-abcd-efab-cdef-123456789abc", 113 group: "group1", 114 node: "12345678-abcd-efab-cdef-123456789abc", 115 desired: structs.AllocDesiredStatusRun, 116 client: structs.AllocClientStatusRunning, 117 index: 2, 118 }, 119 }, 120 } 121 mon.update(state) 122 123 // Updates were logged 124 out = ui.OutputWriter.String() 125 if !strings.Contains(out, "87654321-abcd-efab-cdef-123456789abc") { 126 t.Fatalf("missing alloc\n\n%s", out) 127 } 128 if !strings.Contains(out, "pending") { 129 t.Fatalf("missing old status\n\n%s", out) 130 } 131 if !strings.Contains(out, "running") { 132 t.Fatalf("missing new status\n\n%s", out) 133 } 134 } 135 136 func TestMonitor_Update_SchedulingFailure(t *testing.T) { 137 ui := new(cli.MockUi) 138 mon := newMonitor(ui, nil, shortId) 139 140 // New allocs with desired status failed warns 141 state := &evalState{ 142 allocs: map[string]*allocState{ 143 "alloc2": &allocState{ 144 id: "87654321-dcba-efab-cdef-123456789abc", 145 group: "group2", 146 desired: structs.AllocDesiredStatusFailed, 147 desiredDesc: "something failed", 148 client: structs.AllocClientStatusFailed, 149 clientDesc: "client failed", 150 index: 1, 151 152 // Attach the full failed allocation 153 full: &api.Allocation{ 154 ID: "87654321-dcba-efab-cdef-123456789abc", 155 TaskGroup: "group2", 156 ClientStatus: structs.AllocClientStatusFailed, 157 DesiredStatus: structs.AllocDesiredStatusFailed, 158 Metrics: &api.AllocationMetric{ 159 NodesEvaluated: 3, 160 NodesFiltered: 3, 161 ConstraintFiltered: map[string]int{ 162 "$attr.kernel.name = linux": 3, 163 }, 164 }, 165 }, 166 }, 167 }, 168 } 169 mon.update(state) 170 171 // Scheduling failure was logged 172 out := ui.OutputWriter.String() 173 if !strings.Contains(out, "group2") { 174 t.Fatalf("missing group\n\n%s", out) 175 } 176 if !strings.Contains(out, "Scheduling error") { 177 t.Fatalf("missing failure\n\n%s", out) 178 } 179 if !strings.Contains(out, "something failed") { 180 t.Fatalf("missing desired desc\n\n%s", out) 181 } 182 if !strings.Contains(out, "client failed") { 183 t.Fatalf("missing client desc\n\n%s", out) 184 } 185 186 // Check that the allocation details were dumped 187 if !strings.Contains(out, "3/3") { 188 t.Fatalf("missing filter stats\n\n%s", out) 189 } 190 if !strings.Contains(out, structs.AllocDesiredStatusFailed) { 191 t.Fatalf("missing alloc status\n\n%s", out) 192 } 193 if !strings.Contains(out, "$attr.kernel.name = linux") { 194 t.Fatalf("missing constraint\n\n%s", out) 195 } 196 } 197 198 func TestMonitor_Update_AllocModification(t *testing.T) { 199 ui := new(cli.MockUi) 200 mon := newMonitor(ui, nil, fullId) 201 202 // New allocs with a create index lower than the 203 // eval create index are logged as modifications 204 state := &evalState{ 205 index: 2, 206 allocs: map[string]*allocState{ 207 "alloc3": &allocState{ 208 id: "87654321-abcd-bafe-cdef-123456789abc", 209 node: "12345678-abcd-efab-cdef-123456789abc", 210 group: "group2", 211 index: 1, 212 }, 213 }, 214 } 215 mon.update(state) 216 217 // Modification was logged 218 out := ui.OutputWriter.String() 219 if !strings.Contains(out, "87654321-abcd-bafe-cdef-123456789abc") { 220 t.Fatalf("missing alloc\n\n%s", out) 221 } 222 if !strings.Contains(out, "group2") { 223 t.Fatalf("missing group\n\n%s", out) 224 } 225 if !strings.Contains(out, "12345678-abcd-efab-cdef-123456789abc") { 226 t.Fatalf("missing node\n\n%s", out) 227 } 228 if !strings.Contains(out, "modified") { 229 t.Fatalf("missing modification\n\n%s", out) 230 } 231 } 232 233 func TestMonitor_Monitor(t *testing.T) { 234 srv, client, _ := testServer(t, nil) 235 defer srv.Stop() 236 237 // Create the monitor 238 ui := new(cli.MockUi) 239 mon := newMonitor(ui, client, fullId) 240 241 // Submit a job - this creates a new evaluation we can monitor 242 job := testJob("job1") 243 evalID, _, err := client.Jobs().Register(job, nil) 244 if err != nil { 245 t.Fatalf("err: %s", err) 246 } 247 248 // Start monitoring the eval 249 var code int 250 doneCh := make(chan struct{}) 251 go func() { 252 defer close(doneCh) 253 code = mon.monitor(evalID, false) 254 }() 255 256 // Wait for completion 257 select { 258 case <-doneCh: 259 case <-time.After(5 * time.Second): 260 t.Fatalf("eval monitor took too long") 261 } 262 263 // Check the return code. We should get exit code 2 as there 264 // would be a scheduling problem on the test server (no clients). 265 if code != 2 { 266 t.Fatalf("expect exit 2, got: %d", code) 267 } 268 269 // Check the output 270 out := ui.OutputWriter.String() 271 if !strings.Contains(out, evalID) { 272 t.Fatalf("missing eval\n\n%s", out) 273 } 274 if !strings.Contains(out, "finished with status") { 275 t.Fatalf("missing final status\n\n%s", out) 276 } 277 } 278 279 func TestMonitor_MonitorWithPrefix(t *testing.T) { 280 srv, client, _ := testServer(t, nil) 281 defer srv.Stop() 282 283 // Create the monitor 284 ui := new(cli.MockUi) 285 mon := newMonitor(ui, client, shortId) 286 287 // Submit a job - this creates a new evaluation we can monitor 288 job := testJob("job1") 289 evalID, _, err := client.Jobs().Register(job, nil) 290 if err != nil { 291 t.Fatalf("err: %s", err) 292 } 293 294 // Start monitoring the eval 295 var code int 296 doneCh := make(chan struct{}) 297 go func() { 298 defer close(doneCh) 299 code = mon.monitor(evalID[:8], true) 300 }() 301 302 // Wait for completion 303 select { 304 case <-doneCh: 305 case <-time.After(5 * time.Second): 306 t.Fatalf("eval monitor took too long") 307 } 308 309 // Check the return code. We should get exit code 2 as there 310 // would be a scheduling problem on the test server (no clients). 311 if code != 2 { 312 t.Fatalf("expect exit 2, got: %d", code) 313 } 314 315 // Check the output 316 out := ui.OutputWriter.String() 317 if !strings.Contains(out, evalID[:8]) { 318 t.Fatalf("missing eval\n\n%s", out) 319 } 320 if strings.Contains(out, evalID) { 321 t.Fatalf("expected truncated eval id, got: %s", out) 322 } 323 if !strings.Contains(out, "finished with status") { 324 t.Fatalf("missing final status\n\n%s", out) 325 } 326 327 // Fail on identifier with too few characters 328 code = mon.monitor(evalID[:1], true) 329 if code != 1 { 330 t.Fatalf("expect exit 1, got: %d", code) 331 } 332 if out := ui.ErrorWriter.String(); !strings.Contains(out, "must contain at least two characters.") { 333 t.Fatalf("expected too few characters error, got: %s", out) 334 } 335 ui.ErrorWriter.Reset() 336 337 code = mon.monitor(evalID[:3], true) 338 if code != 2 { 339 t.Fatalf("expect exit 2, got: %d", code) 340 } 341 if out := ui.OutputWriter.String(); !strings.Contains(out, "Monitoring evaluation") { 342 t.Fatalf("expected evaluation monitoring output, got: %s", out) 343 } 344 345 } 346 347 func TestMonitor_DumpAllocStatus(t *testing.T) { 348 ui := new(cli.MockUi) 349 350 // Create an allocation and dump its status to the UI 351 alloc := &api.Allocation{ 352 ID: "87654321-abcd-efab-cdef-123456789abc", 353 TaskGroup: "group1", 354 ClientStatus: structs.AllocClientStatusRunning, 355 Metrics: &api.AllocationMetric{ 356 NodesEvaluated: 10, 357 NodesFiltered: 5, 358 NodesExhausted: 1, 359 DimensionExhausted: map[string]int{ 360 "cpu": 1, 361 }, 362 ConstraintFiltered: map[string]int{ 363 "$attr.kernel.name = linux": 1, 364 }, 365 ClassExhausted: map[string]int{ 366 "web-large": 1, 367 }, 368 }, 369 } 370 dumpAllocStatus(ui, alloc, fullId) 371 372 // Check the output 373 out := ui.OutputWriter.String() 374 if !strings.Contains(out, "87654321-abcd-efab-cdef-123456789abc") { 375 t.Fatalf("missing alloc\n\n%s", out) 376 } 377 if !strings.Contains(out, structs.AllocClientStatusRunning) { 378 t.Fatalf("missing status\n\n%s", out) 379 } 380 if !strings.Contains(out, "5/10") { 381 t.Fatalf("missing filter stats\n\n%s", out) 382 } 383 if !strings.Contains( 384 out, `Constraint "$attr.kernel.name = linux" filtered 1 nodes`) { 385 t.Fatalf("missing constraint\n\n%s", out) 386 } 387 if !strings.Contains(out, "Resources exhausted on 1 nodes") { 388 t.Fatalf("missing resource exhaustion\n\n%s", out) 389 } 390 if !strings.Contains(out, `Class "web-large" exhausted on 1 nodes`) { 391 t.Fatalf("missing class exhaustion\n\n%s", out) 392 } 393 if !strings.Contains(out, `Dimension "cpu" exhausted on 1 nodes`) { 394 t.Fatalf("missing dimension exhaustion\n\n%s", out) 395 } 396 ui.OutputWriter.Reset() 397 398 // Dumping alloc status with no eligible nodes adds a warning 399 alloc.Metrics.NodesEvaluated = 0 400 dumpAllocStatus(ui, alloc, shortId) 401 402 // Check the output 403 out = ui.OutputWriter.String() 404 if !strings.Contains(out, "No nodes were eligible") { 405 t.Fatalf("missing eligibility warning\n\n%s", out) 406 } 407 if strings.Contains(out, "87654321-abcd-efab-cdef-123456789abc") { 408 t.Fatalf("expected truncated id, got %s", out) 409 } 410 if !strings.Contains(out, "87654321") { 411 t.Fatalf("expected alloc id, got %s", out) 412 } 413 }