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