github.com/diptanu/nomad@v0.5.7-0.20170516172507-d72e86cbe3d9/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_AllocModification(t *testing.T) { 137 ui := new(cli.MockUi) 138 mon := newMonitor(ui, nil, fullId) 139 140 // New allocs with a create index lower than the 141 // eval create index are logged as modifications 142 state := &evalState{ 143 index: 2, 144 allocs: map[string]*allocState{ 145 "alloc3": &allocState{ 146 id: "87654321-abcd-bafe-cdef-123456789abc", 147 node: "12345678-abcd-efab-cdef-123456789abc", 148 group: "group2", 149 index: 1, 150 }, 151 }, 152 } 153 mon.update(state) 154 155 // Modification was logged 156 out := ui.OutputWriter.String() 157 if !strings.Contains(out, "87654321-abcd-bafe-cdef-123456789abc") { 158 t.Fatalf("missing alloc\n\n%s", out) 159 } 160 if !strings.Contains(out, "group2") { 161 t.Fatalf("missing group\n\n%s", out) 162 } 163 if !strings.Contains(out, "12345678-abcd-efab-cdef-123456789abc") { 164 t.Fatalf("missing node\n\n%s", out) 165 } 166 if !strings.Contains(out, "modified") { 167 t.Fatalf("missing modification\n\n%s", out) 168 } 169 } 170 171 func TestMonitor_Monitor(t *testing.T) { 172 srv, client, _ := testServer(t, nil) 173 defer srv.Stop() 174 175 // Create the monitor 176 ui := new(cli.MockUi) 177 mon := newMonitor(ui, client, fullId) 178 179 // Submit a job - this creates a new evaluation we can monitor 180 job := testJob("job1") 181 evalID, _, err := client.Jobs().Register(job, nil) 182 if err != nil { 183 t.Fatalf("err: %s", err) 184 } 185 186 // Start monitoring the eval 187 var code int 188 doneCh := make(chan struct{}) 189 go func() { 190 defer close(doneCh) 191 code = mon.monitor(evalID, false) 192 }() 193 194 // Wait for completion 195 select { 196 case <-doneCh: 197 case <-time.After(5 * time.Second): 198 t.Fatalf("eval monitor took too long") 199 } 200 201 // Check the return code. We should get exit code 2 as there 202 // would be a scheduling problem on the test server (no clients). 203 if code != 2 { 204 t.Fatalf("expect exit 2, got: %d", code) 205 } 206 207 // Check the output 208 out := ui.OutputWriter.String() 209 if !strings.Contains(out, evalID) { 210 t.Fatalf("missing eval\n\n%s", out) 211 } 212 if !strings.Contains(out, "finished with status") { 213 t.Fatalf("missing final status\n\n%s", out) 214 } 215 } 216 217 func TestMonitor_MonitorWithPrefix(t *testing.T) { 218 srv, client, _ := testServer(t, nil) 219 defer srv.Stop() 220 221 // Create the monitor 222 ui := new(cli.MockUi) 223 mon := newMonitor(ui, client, shortId) 224 225 // Submit a job - this creates a new evaluation we can monitor 226 job := testJob("job1") 227 evalID, _, err := client.Jobs().Register(job, nil) 228 if err != nil { 229 t.Fatalf("err: %s", err) 230 } 231 232 // Start monitoring the eval 233 var code int 234 doneCh := make(chan struct{}) 235 go func() { 236 defer close(doneCh) 237 code = mon.monitor(evalID[:8], true) 238 }() 239 240 // Wait for completion 241 select { 242 case <-doneCh: 243 case <-time.After(5 * time.Second): 244 t.Fatalf("eval monitor took too long") 245 } 246 247 // Check the return code. We should get exit code 2 as there 248 // would be a scheduling problem on the test server (no clients). 249 if code != 2 { 250 t.Fatalf("expect exit 2, got: %d", code) 251 } 252 253 // Check the output 254 out := ui.OutputWriter.String() 255 if !strings.Contains(out, evalID[:8]) { 256 t.Fatalf("missing eval\n\n%s", out) 257 } 258 if strings.Contains(out, evalID) { 259 t.Fatalf("expected truncated eval id, got: %s", out) 260 } 261 if !strings.Contains(out, "finished with status") { 262 t.Fatalf("missing final status\n\n%s", out) 263 } 264 265 // Fail on identifier with too few characters 266 code = mon.monitor(evalID[:1], true) 267 if code != 1 { 268 t.Fatalf("expect exit 1, got: %d", code) 269 } 270 if out := ui.ErrorWriter.String(); !strings.Contains(out, "must contain at least two characters.") { 271 t.Fatalf("expected too few characters error, got: %s", out) 272 } 273 ui.ErrorWriter.Reset() 274 275 code = mon.monitor(evalID[:3], true) 276 if code != 2 { 277 t.Fatalf("expect exit 2, got: %d", code) 278 } 279 if out := ui.OutputWriter.String(); !strings.Contains(out, "Monitoring evaluation") { 280 t.Fatalf("expected evaluation monitoring output, got: %s", out) 281 } 282 283 } 284 285 func TestMonitor_DumpAllocStatus(t *testing.T) { 286 ui := new(cli.MockUi) 287 288 // Create an allocation and dump its status to the UI 289 alloc := &api.Allocation{ 290 ID: "87654321-abcd-efab-cdef-123456789abc", 291 TaskGroup: "group1", 292 ClientStatus: structs.AllocClientStatusRunning, 293 Metrics: &api.AllocationMetric{ 294 NodesEvaluated: 10, 295 NodesFiltered: 5, 296 NodesExhausted: 1, 297 DimensionExhausted: map[string]int{ 298 "cpu": 1, 299 }, 300 ConstraintFiltered: map[string]int{ 301 "$attr.kernel.name = linux": 1, 302 }, 303 ClassExhausted: map[string]int{ 304 "web-large": 1, 305 }, 306 }, 307 } 308 dumpAllocStatus(ui, alloc, fullId) 309 310 // Check the output 311 out := ui.OutputWriter.String() 312 if !strings.Contains(out, "87654321-abcd-efab-cdef-123456789abc") { 313 t.Fatalf("missing alloc\n\n%s", out) 314 } 315 if !strings.Contains(out, structs.AllocClientStatusRunning) { 316 t.Fatalf("missing status\n\n%s", out) 317 } 318 if !strings.Contains(out, "5/10") { 319 t.Fatalf("missing filter stats\n\n%s", out) 320 } 321 if !strings.Contains( 322 out, `Constraint "$attr.kernel.name = linux" filtered 1 nodes`) { 323 t.Fatalf("missing constraint\n\n%s", out) 324 } 325 if !strings.Contains(out, "Resources exhausted on 1 nodes") { 326 t.Fatalf("missing resource exhaustion\n\n%s", out) 327 } 328 if !strings.Contains(out, `Class "web-large" exhausted on 1 nodes`) { 329 t.Fatalf("missing class exhaustion\n\n%s", out) 330 } 331 if !strings.Contains(out, `Dimension "cpu" exhausted on 1 nodes`) { 332 t.Fatalf("missing dimension exhaustion\n\n%s", out) 333 } 334 ui.OutputWriter.Reset() 335 336 // Dumping alloc status with no eligible nodes adds a warning 337 alloc.Metrics.NodesEvaluated = 0 338 dumpAllocStatus(ui, alloc, shortId) 339 340 // Check the output 341 out = ui.OutputWriter.String() 342 if !strings.Contains(out, "No nodes were eligible") { 343 t.Fatalf("missing eligibility warning\n\n%s", out) 344 } 345 if strings.Contains(out, "87654321-abcd-efab-cdef-123456789abc") { 346 t.Fatalf("expected truncated id, got %s", out) 347 } 348 if !strings.Contains(out, "87654321") { 349 t.Fatalf("expected alloc id, got %s", out) 350 } 351 }