github.com/xeptore/docker-cli@v20.10.14+incompatible/cli/command/service/progress/progress_test.go (about) 1 package progress 2 3 import ( 4 "fmt" 5 "strconv" 6 "testing" 7 8 "github.com/docker/docker/api/types/swarm" 9 "github.com/docker/docker/pkg/progress" 10 "gotest.tools/v3/assert" 11 is "gotest.tools/v3/assert/cmp" 12 ) 13 14 type mockProgress struct { 15 p []progress.Progress 16 } 17 18 func (mp *mockProgress) WriteProgress(p progress.Progress) error { 19 mp.p = append(mp.p, p) 20 return nil 21 } 22 23 func (mp *mockProgress) clear() { 24 mp.p = nil 25 } 26 27 type updaterTester struct { 28 t *testing.T 29 updater progressUpdater 30 p *mockProgress 31 service swarm.Service 32 activeNodes map[string]struct{} 33 rollback bool 34 } 35 36 func (u updaterTester) testUpdater(tasks []swarm.Task, expectedConvergence bool, expectedProgress []progress.Progress) { 37 u.p.clear() 38 39 converged, err := u.updater.update(u.service, tasks, u.activeNodes, u.rollback) 40 assert.Check(u.t, err) 41 assert.Check(u.t, is.Equal(expectedConvergence, converged)) 42 assert.Check(u.t, is.DeepEqual(expectedProgress, u.p.p)) 43 } 44 45 func (u updaterTester) testUpdaterNoOrder(tasks []swarm.Task, expectedConvergence bool, expectedProgress []progress.Progress) { 46 u.p.clear() 47 converged, err := u.updater.update(u.service, tasks, u.activeNodes, u.rollback) 48 assert.Check(u.t, err) 49 assert.Check(u.t, is.Equal(expectedConvergence, converged)) 50 51 // instead of checking that expected and actual match exactly, verify that 52 // they are the same length, and every time from actual is in expected. 53 assert.Check(u.t, is.Equal(len(expectedProgress), len(u.p.p))) 54 for _, prog := range expectedProgress { 55 assert.Check(u.t, is.Contains(u.p.p, prog)) 56 } 57 } 58 59 func TestReplicatedProgressUpdaterOneReplica(t *testing.T) { 60 replicas := uint64(1) 61 62 service := swarm.Service{ 63 Spec: swarm.ServiceSpec{ 64 Mode: swarm.ServiceMode{ 65 Replicated: &swarm.ReplicatedService{ 66 Replicas: &replicas, 67 }, 68 }, 69 }, 70 } 71 72 p := &mockProgress{} 73 updaterTester := updaterTester{ 74 t: t, 75 updater: &replicatedProgressUpdater{ 76 progressOut: p, 77 }, 78 p: p, 79 activeNodes: map[string]struct{}{"a": {}, "b": {}}, 80 service: service, 81 } 82 83 tasks := []swarm.Task{} 84 85 updaterTester.testUpdater(tasks, false, 86 []progress.Progress{ 87 {ID: "overall progress", Action: "0 out of 1 tasks"}, 88 {ID: "1/1", Action: " "}, 89 {ID: "overall progress", Action: "0 out of 1 tasks"}, 90 }) 91 92 // Task with DesiredState beyond Running is ignored 93 tasks = append(tasks, 94 swarm.Task{ID: "1", 95 NodeID: "a", 96 DesiredState: swarm.TaskStateShutdown, 97 Status: swarm.TaskStatus{State: swarm.TaskStateNew}, 98 }) 99 updaterTester.testUpdater(tasks, false, 100 []progress.Progress{ 101 {ID: "overall progress", Action: "0 out of 1 tasks"}, 102 }) 103 104 // Task with valid DesiredState and State updates progress bar 105 tasks[0].DesiredState = swarm.TaskStateRunning 106 updaterTester.testUpdater(tasks, false, 107 []progress.Progress{ 108 {ID: "1/1", Action: "new ", Current: 1, Total: 9, HideCounts: true}, 109 {ID: "overall progress", Action: "0 out of 1 tasks"}, 110 }) 111 112 // If the task exposes an error, we should show that instead of the 113 // progress bar. 114 tasks[0].Status.Err = "something is wrong" 115 updaterTester.testUpdater(tasks, false, 116 []progress.Progress{ 117 {ID: "1/1", Action: "something is wrong"}, 118 {ID: "overall progress", Action: "0 out of 1 tasks"}, 119 }) 120 121 // When the task reaches running, update should return true 122 tasks[0].Status.Err = "" 123 tasks[0].Status.State = swarm.TaskStateRunning 124 updaterTester.testUpdater(tasks, true, 125 []progress.Progress{ 126 {ID: "1/1", Action: "running ", Current: 9, Total: 9, HideCounts: true}, 127 {ID: "overall progress", Action: "1 out of 1 tasks"}, 128 }) 129 130 // If the task fails, update should return false again 131 tasks[0].Status.Err = "task failed" 132 tasks[0].Status.State = swarm.TaskStateFailed 133 updaterTester.testUpdater(tasks, false, 134 []progress.Progress{ 135 {ID: "1/1", Action: "task failed"}, 136 {ID: "overall progress", Action: "0 out of 1 tasks"}, 137 }) 138 139 // If the task is restarted, progress output should be shown for the 140 // replacement task, not the old task. 141 tasks[0].DesiredState = swarm.TaskStateShutdown 142 tasks = append(tasks, 143 swarm.Task{ID: "2", 144 NodeID: "b", 145 DesiredState: swarm.TaskStateRunning, 146 Status: swarm.TaskStatus{State: swarm.TaskStateRunning}, 147 }) 148 updaterTester.testUpdater(tasks, true, 149 []progress.Progress{ 150 {ID: "1/1", Action: "running ", Current: 9, Total: 9, HideCounts: true}, 151 {ID: "overall progress", Action: "1 out of 1 tasks"}, 152 }) 153 154 // Add a new task while the current one is still running, to simulate 155 // "start-then-stop" updates. 156 tasks = append(tasks, 157 swarm.Task{ID: "3", 158 NodeID: "b", 159 DesiredState: swarm.TaskStateRunning, 160 Status: swarm.TaskStatus{State: swarm.TaskStatePreparing}, 161 }) 162 updaterTester.testUpdater(tasks, false, 163 []progress.Progress{ 164 {ID: "1/1", Action: "preparing", Current: 6, Total: 9, HideCounts: true}, 165 {ID: "overall progress", Action: "0 out of 1 tasks"}, 166 }) 167 } 168 169 func TestReplicatedProgressUpdaterManyReplicas(t *testing.T) { 170 replicas := uint64(50) 171 172 service := swarm.Service{ 173 Spec: swarm.ServiceSpec{ 174 Mode: swarm.ServiceMode{ 175 Replicated: &swarm.ReplicatedService{ 176 Replicas: &replicas, 177 }, 178 }, 179 }, 180 } 181 182 p := &mockProgress{} 183 updaterTester := updaterTester{ 184 t: t, 185 updater: &replicatedProgressUpdater{ 186 progressOut: p, 187 }, 188 p: p, 189 activeNodes: map[string]struct{}{"a": {}, "b": {}}, 190 service: service, 191 } 192 193 tasks := []swarm.Task{} 194 195 // No per-task progress bars because there are too many replicas 196 updaterTester.testUpdater(tasks, false, 197 []progress.Progress{ 198 {ID: "overall progress", Action: fmt.Sprintf("0 out of %d tasks", replicas)}, 199 {ID: "overall progress", Action: fmt.Sprintf("0 out of %d tasks", replicas)}, 200 }) 201 202 for i := 0; i != int(replicas); i++ { 203 tasks = append(tasks, 204 swarm.Task{ 205 ID: strconv.Itoa(i), 206 Slot: i + 1, 207 NodeID: "a", 208 DesiredState: swarm.TaskStateRunning, 209 Status: swarm.TaskStatus{State: swarm.TaskStateNew}, 210 }) 211 212 if i%2 == 1 { 213 tasks[i].NodeID = "b" 214 } 215 updaterTester.testUpdater(tasks, false, 216 []progress.Progress{ 217 {ID: "overall progress", Action: fmt.Sprintf("%d out of %d tasks", i, replicas)}, 218 }) 219 220 tasks[i].Status.State = swarm.TaskStateRunning 221 updaterTester.testUpdater(tasks, uint64(i) == replicas-1, 222 []progress.Progress{ 223 {ID: "overall progress", Action: fmt.Sprintf("%d out of %d tasks", i+1, replicas)}, 224 }) 225 } 226 } 227 228 func TestGlobalProgressUpdaterOneNode(t *testing.T) { 229 service := swarm.Service{ 230 Spec: swarm.ServiceSpec{ 231 Mode: swarm.ServiceMode{ 232 Global: &swarm.GlobalService{}, 233 }, 234 }, 235 } 236 237 p := &mockProgress{} 238 updaterTester := updaterTester{ 239 t: t, 240 updater: &globalProgressUpdater{ 241 progressOut: p, 242 }, 243 p: p, 244 activeNodes: map[string]struct{}{"a": {}, "b": {}}, 245 service: service, 246 } 247 248 tasks := []swarm.Task{} 249 250 updaterTester.testUpdater(tasks, false, 251 []progress.Progress{ 252 {ID: "overall progress", Action: "waiting for new tasks"}, 253 }) 254 255 // Task with DesiredState beyond Running is ignored 256 tasks = append(tasks, 257 swarm.Task{ID: "1", 258 NodeID: "a", 259 DesiredState: swarm.TaskStateShutdown, 260 Status: swarm.TaskStatus{State: swarm.TaskStateNew}, 261 }) 262 updaterTester.testUpdater(tasks, false, 263 []progress.Progress{ 264 {ID: "overall progress", Action: "0 out of 1 tasks"}, 265 {ID: "overall progress", Action: "0 out of 1 tasks"}, 266 }) 267 268 // Task with valid DesiredState and State updates progress bar 269 tasks[0].DesiredState = swarm.TaskStateRunning 270 updaterTester.testUpdater(tasks, false, 271 []progress.Progress{ 272 {ID: "a", Action: "new ", Current: 1, Total: 9, HideCounts: true}, 273 {ID: "overall progress", Action: "0 out of 1 tasks"}, 274 }) 275 276 // If the task exposes an error, we should show that instead of the 277 // progress bar. 278 tasks[0].Status.Err = "something is wrong" 279 updaterTester.testUpdater(tasks, false, 280 []progress.Progress{ 281 {ID: "a", Action: "something is wrong"}, 282 {ID: "overall progress", Action: "0 out of 1 tasks"}, 283 }) 284 285 // When the task reaches running, update should return true 286 tasks[0].Status.Err = "" 287 tasks[0].Status.State = swarm.TaskStateRunning 288 updaterTester.testUpdater(tasks, true, 289 []progress.Progress{ 290 {ID: "a", Action: "running ", Current: 9, Total: 9, HideCounts: true}, 291 {ID: "overall progress", Action: "1 out of 1 tasks"}, 292 }) 293 294 // If the task fails, update should return false again 295 tasks[0].Status.Err = "task failed" 296 tasks[0].Status.State = swarm.TaskStateFailed 297 updaterTester.testUpdater(tasks, false, 298 []progress.Progress{ 299 {ID: "a", Action: "task failed"}, 300 {ID: "overall progress", Action: "0 out of 1 tasks"}, 301 }) 302 303 // If the task is restarted, progress output should be shown for the 304 // replacement task, not the old task. 305 tasks[0].DesiredState = swarm.TaskStateShutdown 306 tasks = append(tasks, 307 swarm.Task{ID: "2", 308 NodeID: "a", 309 DesiredState: swarm.TaskStateRunning, 310 Status: swarm.TaskStatus{State: swarm.TaskStateRunning}, 311 }) 312 updaterTester.testUpdater(tasks, true, 313 []progress.Progress{ 314 {ID: "a", Action: "running ", Current: 9, Total: 9, HideCounts: true}, 315 {ID: "overall progress", Action: "1 out of 1 tasks"}, 316 }) 317 318 // Add a new task while the current one is still running, to simulate 319 // "start-then-stop" updates. 320 tasks = append(tasks, 321 swarm.Task{ID: "3", 322 NodeID: "a", 323 DesiredState: swarm.TaskStateRunning, 324 Status: swarm.TaskStatus{State: swarm.TaskStatePreparing}, 325 }) 326 updaterTester.testUpdater(tasks, false, 327 []progress.Progress{ 328 {ID: "a", Action: "preparing", Current: 6, Total: 9, HideCounts: true}, 329 {ID: "overall progress", Action: "0 out of 1 tasks"}, 330 }) 331 } 332 333 func TestGlobalProgressUpdaterManyNodes(t *testing.T) { 334 nodes := 50 335 336 service := swarm.Service{ 337 Spec: swarm.ServiceSpec{ 338 Mode: swarm.ServiceMode{ 339 Global: &swarm.GlobalService{}, 340 }, 341 }, 342 } 343 344 p := &mockProgress{} 345 updaterTester := updaterTester{ 346 t: t, 347 updater: &globalProgressUpdater{ 348 progressOut: p, 349 }, 350 p: p, 351 activeNodes: map[string]struct{}{}, 352 service: service, 353 } 354 355 for i := 0; i != nodes; i++ { 356 updaterTester.activeNodes[strconv.Itoa(i)] = struct{}{} 357 } 358 359 tasks := []swarm.Task{} 360 361 updaterTester.testUpdater(tasks, false, 362 []progress.Progress{ 363 {ID: "overall progress", Action: "waiting for new tasks"}, 364 }) 365 366 for i := 0; i != nodes; i++ { 367 tasks = append(tasks, 368 swarm.Task{ 369 ID: "task" + strconv.Itoa(i), 370 NodeID: strconv.Itoa(i), 371 DesiredState: swarm.TaskStateRunning, 372 Status: swarm.TaskStatus{State: swarm.TaskStateNew}, 373 }) 374 } 375 376 updaterTester.testUpdater(tasks, false, 377 []progress.Progress{ 378 {ID: "overall progress", Action: fmt.Sprintf("0 out of %d tasks", nodes)}, 379 {ID: "overall progress", Action: fmt.Sprintf("0 out of %d tasks", nodes)}, 380 }) 381 382 for i := 0; i != nodes; i++ { 383 tasks[i].Status.State = swarm.TaskStateRunning 384 updaterTester.testUpdater(tasks, i == nodes-1, 385 []progress.Progress{ 386 {ID: "overall progress", Action: fmt.Sprintf("%d out of %d tasks", i+1, nodes)}, 387 }) 388 } 389 } 390 391 func TestReplicatedJobProgressUpdaterSmall(t *testing.T) { 392 concurrent := uint64(2) 393 total := uint64(5) 394 395 service := swarm.Service{ 396 Spec: swarm.ServiceSpec{ 397 Mode: swarm.ServiceMode{ 398 ReplicatedJob: &swarm.ReplicatedJob{ 399 MaxConcurrent: &concurrent, 400 TotalCompletions: &total, 401 }, 402 }, 403 }, 404 JobStatus: &swarm.JobStatus{ 405 JobIteration: swarm.Version{Index: 1}, 406 }, 407 } 408 409 p := &mockProgress{} 410 ut := updaterTester{ 411 t: t, 412 updater: newReplicatedJobProgressUpdater(service, p), 413 p: p, 414 activeNodes: map[string]struct{}{"a": {}, "b": {}}, 415 service: service, 416 } 417 418 // create some tasks belonging to a previous iteration 419 tasks := []swarm.Task{ 420 { 421 ID: "oldtask1", 422 Slot: 0, 423 NodeID: "", 424 DesiredState: swarm.TaskStateComplete, 425 Status: swarm.TaskStatus{State: swarm.TaskStateNew}, 426 JobIteration: &swarm.Version{Index: 0}, 427 }, { 428 ID: "oldtask2", 429 Slot: 1, 430 NodeID: "", 431 DesiredState: swarm.TaskStateComplete, 432 Status: swarm.TaskStatus{State: swarm.TaskStateComplete}, 433 JobIteration: &swarm.Version{Index: 0}, 434 }, 435 } 436 437 ut.testUpdater(tasks, false, []progress.Progress{ 438 // on the initial pass, we draw all of the progress bars at once, which 439 // puts them in order for the rest of the operation 440 {ID: "job progress", Action: "0 out of 5 complete", Current: 0, Total: 5, HideCounts: true}, 441 {ID: "active tasks", Action: "0 out of 2 tasks"}, 442 {ID: "1/5", Action: " "}, 443 {ID: "2/5", Action: " "}, 444 {ID: "3/5", Action: " "}, 445 {ID: "4/5", Action: " "}, 446 {ID: "5/5", Action: " "}, 447 // from here on, we draw as normal. as a side effect, we will have a 448 // second update for the job progress and active tasks. This has no 449 // practical effect on the UI, it's just a side effect of the update 450 // logic. 451 {ID: "job progress", Action: "0 out of 5 complete", Current: 0, Total: 5, HideCounts: true}, 452 {ID: "active tasks", Action: "0 out of 2 tasks"}, 453 }) 454 455 // wipe the old tasks out of the list 456 tasks = []swarm.Task{} 457 tasks = append(tasks, 458 swarm.Task{ 459 ID: "task1", 460 Slot: 0, 461 NodeID: "", 462 DesiredState: swarm.TaskStateComplete, 463 Status: swarm.TaskStatus{State: swarm.TaskStateNew}, 464 JobIteration: &swarm.Version{Index: service.JobStatus.JobIteration.Index}, 465 }, 466 swarm.Task{ 467 ID: "task2", 468 Slot: 1, 469 NodeID: "", 470 DesiredState: swarm.TaskStateComplete, 471 Status: swarm.TaskStatus{State: swarm.TaskStateNew}, 472 JobIteration: &swarm.Version{Index: service.JobStatus.JobIteration.Index}, 473 }, 474 ) 475 ut.testUpdater(tasks, false, []progress.Progress{ 476 {ID: "1/5", Action: "new ", Current: 1, Total: 10, HideCounts: true}, 477 {ID: "2/5", Action: "new ", Current: 1, Total: 10, HideCounts: true}, 478 {ID: "job progress", Action: "0 out of 5 complete", Current: 0, Total: 5, HideCounts: true}, 479 {ID: "active tasks", Action: "2 out of 2 tasks"}, 480 }) 481 482 tasks[0].Status.State = swarm.TaskStatePreparing 483 tasks[1].Status.State = swarm.TaskStateAssigned 484 ut.testUpdater(tasks, false, []progress.Progress{ 485 {ID: "1/5", Action: "preparing", Current: 6, Total: 10, HideCounts: true}, 486 {ID: "2/5", Action: "assigned ", Current: 4, Total: 10, HideCounts: true}, 487 {ID: "job progress", Action: "0 out of 5 complete", Current: 0, Total: 5, HideCounts: true}, 488 {ID: "active tasks", Action: "2 out of 2 tasks"}, 489 }) 490 491 tasks[0].Status.State = swarm.TaskStateRunning 492 tasks[1].Status.State = swarm.TaskStatePreparing 493 ut.testUpdater(tasks, false, []progress.Progress{ 494 {ID: "1/5", Action: "running ", Current: 9, Total: 10, HideCounts: true}, 495 {ID: "2/5", Action: "preparing", Current: 6, Total: 10, HideCounts: true}, 496 {ID: "job progress", Action: "0 out of 5 complete", Current: 0, Total: 5, HideCounts: true}, 497 {ID: "active tasks", Action: "2 out of 2 tasks"}, 498 }) 499 500 tasks[0].Status.State = swarm.TaskStateComplete 501 tasks[1].Status.State = swarm.TaskStateComplete 502 ut.testUpdater(tasks, false, []progress.Progress{ 503 {ID: "1/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 504 {ID: "2/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 505 {ID: "job progress", Action: "2 out of 5 complete", Current: 2, Total: 5, HideCounts: true}, 506 {ID: "active tasks", Action: "0 out of 2 tasks"}, 507 }) 508 509 tasks = append(tasks, 510 swarm.Task{ 511 ID: "task3", 512 Slot: 2, 513 NodeID: "", 514 DesiredState: swarm.TaskStateComplete, 515 Status: swarm.TaskStatus{State: swarm.TaskStateNew}, 516 JobIteration: &swarm.Version{Index: service.JobStatus.JobIteration.Index}, 517 }, 518 swarm.Task{ 519 ID: "task4", 520 Slot: 3, 521 NodeID: "", 522 DesiredState: swarm.TaskStateComplete, 523 Status: swarm.TaskStatus{State: swarm.TaskStateNew}, 524 JobIteration: &swarm.Version{Index: service.JobStatus.JobIteration.Index}, 525 }, 526 ) 527 528 ut.testUpdater(tasks, false, []progress.Progress{ 529 {ID: "1/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 530 {ID: "2/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 531 {ID: "3/5", Action: "new ", Current: 1, Total: 10, HideCounts: true}, 532 {ID: "4/5", Action: "new ", Current: 1, Total: 10, HideCounts: true}, 533 {ID: "job progress", Action: "2 out of 5 complete", Current: 2, Total: 5, HideCounts: true}, 534 {ID: "active tasks", Action: "2 out of 2 tasks"}, 535 }) 536 537 tasks[2].Status.State = swarm.TaskStateRunning 538 tasks[3].Status.State = swarm.TaskStateRunning 539 ut.testUpdater(tasks, false, []progress.Progress{ 540 {ID: "1/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 541 {ID: "2/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 542 {ID: "3/5", Action: "running ", Current: 9, Total: 10, HideCounts: true}, 543 {ID: "4/5", Action: "running ", Current: 9, Total: 10, HideCounts: true}, 544 {ID: "job progress", Action: "2 out of 5 complete", Current: 2, Total: 5, HideCounts: true}, 545 {ID: "active tasks", Action: "2 out of 2 tasks"}, 546 }) 547 548 tasks[3].Status.State = swarm.TaskStateComplete 549 tasks = append(tasks, 550 swarm.Task{ 551 ID: "task5", 552 Slot: 4, 553 NodeID: "", 554 DesiredState: swarm.TaskStateComplete, 555 Status: swarm.TaskStatus{State: swarm.TaskStateRunning}, 556 JobIteration: &swarm.Version{Index: service.JobStatus.JobIteration.Index}, 557 }, 558 ) 559 ut.testUpdater(tasks, false, []progress.Progress{ 560 {ID: "1/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 561 {ID: "2/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 562 {ID: "3/5", Action: "running ", Current: 9, Total: 10, HideCounts: true}, 563 {ID: "4/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 564 {ID: "5/5", Action: "running ", Current: 9, Total: 10, HideCounts: true}, 565 {ID: "job progress", Action: "3 out of 5 complete", Current: 3, Total: 5, HideCounts: true}, 566 {ID: "active tasks", Action: "2 out of 2 tasks"}, 567 }) 568 569 tasks[2].Status.State = swarm.TaskStateFailed 570 tasks[2].Status.Err = "the task failed" 571 ut.testUpdater(tasks, false, []progress.Progress{ 572 {ID: "1/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 573 {ID: "2/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 574 {ID: "3/5", Action: "the task failed"}, 575 {ID: "4/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 576 {ID: "5/5", Action: "running ", Current: 9, Total: 10, HideCounts: true}, 577 {ID: "job progress", Action: "3 out of 5 complete", Current: 3, Total: 5, HideCounts: true}, 578 {ID: "active tasks", Action: "1 out of 2 tasks"}, 579 }) 580 581 tasks[4].Status.State = swarm.TaskStateComplete 582 tasks = append(tasks, 583 swarm.Task{ 584 ID: "task6", 585 Slot: 2, 586 NodeID: "", 587 DesiredState: swarm.TaskStateComplete, 588 Status: swarm.TaskStatus{State: swarm.TaskStateRunning}, 589 JobIteration: &swarm.Version{Index: service.JobStatus.JobIteration.Index}, 590 }, 591 ) 592 ut.testUpdater(tasks, false, []progress.Progress{ 593 {ID: "1/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 594 {ID: "2/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 595 {ID: "3/5", Action: "running ", Current: 9, Total: 10, HideCounts: true}, 596 {ID: "4/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 597 {ID: "5/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 598 {ID: "job progress", Action: "4 out of 5 complete", Current: 4, Total: 5, HideCounts: true}, 599 {ID: "active tasks", Action: "1 out of 1 tasks"}, 600 }) 601 602 tasks[5].Status.State = swarm.TaskStateComplete 603 ut.testUpdater(tasks, true, []progress.Progress{ 604 {ID: "1/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 605 {ID: "2/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 606 {ID: "3/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 607 {ID: "4/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 608 {ID: "5/5", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 609 {ID: "job progress", Action: "5 out of 5 complete", Current: 5, Total: 5, HideCounts: true}, 610 {ID: "active tasks", Action: "0 out of 0 tasks"}, 611 }) 612 } 613 614 func TestReplicatedJobProgressUpdaterLarge(t *testing.T) { 615 concurrent := uint64(10) 616 total := uint64(50) 617 618 service := swarm.Service{ 619 Spec: swarm.ServiceSpec{ 620 Mode: swarm.ServiceMode{ 621 ReplicatedJob: &swarm.ReplicatedJob{ 622 MaxConcurrent: &concurrent, 623 TotalCompletions: &total, 624 }, 625 }, 626 }, 627 JobStatus: &swarm.JobStatus{ 628 JobIteration: swarm.Version{Index: 0}, 629 }, 630 } 631 632 p := &mockProgress{} 633 ut := updaterTester{ 634 t: t, 635 updater: newReplicatedJobProgressUpdater(service, p), 636 p: p, 637 activeNodes: map[string]struct{}{"a": {}, "b": {}}, 638 service: service, 639 } 640 641 tasks := []swarm.Task{} 642 643 // see the comments in TestReplicatedJobProgressUpdaterSmall for why 644 // we write this out twice. 645 ut.testUpdater(tasks, false, []progress.Progress{ 646 {ID: "job progress", Action: " 0 out of 50 complete", Current: 0, Total: 50, HideCounts: true}, 647 {ID: "active tasks", Action: " 0 out of 10 tasks"}, 648 // we don't write out individual status bars for a large job, only the 649 // overall progress bar 650 {ID: "job progress", Action: " 0 out of 50 complete", Current: 0, Total: 50, HideCounts: true}, 651 {ID: "active tasks", Action: " 0 out of 10 tasks"}, 652 }) 653 654 // first, create the initial batch of running tasks 655 for i := 0; i < int(concurrent); i++ { 656 tasks = append(tasks, swarm.Task{ 657 ID: strconv.Itoa(i), 658 Slot: i, 659 DesiredState: swarm.TaskStateComplete, 660 Status: swarm.TaskStatus{State: swarm.TaskStateNew}, 661 JobIteration: &swarm.Version{Index: 0}, 662 }) 663 664 ut.testUpdater(tasks, false, []progress.Progress{ 665 {ID: "job progress", Action: " 0 out of 50 complete", Current: 0, Total: 50, HideCounts: true}, 666 {ID: "active tasks", Action: fmt.Sprintf("%2d out of 10 tasks", i+1)}, 667 }) 668 } 669 670 // now, start moving tasks to completed, and starting new tasks after them. 671 // to do this, we'll start at 0, mark a task complete, and then append a 672 // new one. we'll stop before we get to the end, because the end has a 673 // steadily decreasing denominator for the active tasks 674 // 675 // for 10 concurrent 50 total, this means we'll stop at 50 - 10 = 40 tasks 676 // in the completed state, 10 tasks running. the last index in use will be 677 // 39. 678 for i := 0; i < int(total)-int(concurrent); i++ { 679 tasks[i].Status.State = swarm.TaskStateComplete 680 ut.testUpdater(tasks, false, []progress.Progress{ 681 {ID: "job progress", Action: fmt.Sprintf("%2d out of 50 complete", i+1), Current: int64(i + 1), Total: 50, HideCounts: true}, 682 {ID: "active tasks", Action: " 9 out of 10 tasks"}, 683 }) 684 685 last := len(tasks) 686 tasks = append(tasks, swarm.Task{ 687 ID: strconv.Itoa(last), 688 Slot: last, 689 DesiredState: swarm.TaskStateComplete, 690 Status: swarm.TaskStatus{State: swarm.TaskStateNew}, 691 JobIteration: &swarm.Version{Index: 0}, 692 }) 693 694 ut.testUpdater(tasks, false, []progress.Progress{ 695 {ID: "job progress", Action: fmt.Sprintf("%2d out of 50 complete", i+1), Current: int64(i + 1), Total: 50, HideCounts: true}, 696 {ID: "active tasks", Action: "10 out of 10 tasks"}, 697 }) 698 } 699 700 // quick check, to make sure we did the math right when we wrote this code: 701 // we do have 50 tasks in the slice, right? 702 assert.Check(t, is.Equal(len(tasks), int(total))) 703 704 // now, we're down to our last 10 tasks, which are all running. We need to 705 // wind these down 706 for i := int(total) - int(concurrent) - 1; i < int(total); i++ { 707 tasks[i].Status.State = swarm.TaskStateComplete 708 ut.testUpdater(tasks, (i+1 == int(total)), []progress.Progress{ 709 {ID: "job progress", Action: fmt.Sprintf("%2d out of 50 complete", i+1), Current: int64(i + 1), Total: 50, HideCounts: true}, 710 {ID: "active tasks", Action: fmt.Sprintf("%2[1]d out of %2[1]d tasks", int(total)-(i+1))}, 711 }) 712 } 713 } 714 715 func TestGlobalJobProgressUpdaterSmall(t *testing.T) { 716 service := swarm.Service{ 717 Spec: swarm.ServiceSpec{ 718 Mode: swarm.ServiceMode{ 719 GlobalJob: &swarm.GlobalJob{}, 720 }, 721 }, 722 JobStatus: &swarm.JobStatus{ 723 JobIteration: swarm.Version{Index: 1}, 724 }, 725 } 726 727 p := &mockProgress{} 728 ut := updaterTester{ 729 t: t, 730 updater: &globalJobProgressUpdater{ 731 progressOut: p, 732 }, 733 p: p, 734 activeNodes: map[string]struct{}{"a": {}, "b": {}, "c": {}}, 735 service: service, 736 } 737 738 tasks := []swarm.Task{ 739 { 740 ID: "oldtask1", 741 DesiredState: swarm.TaskStateComplete, 742 Status: swarm.TaskStatus{State: swarm.TaskStateComplete}, 743 JobIteration: &swarm.Version{Index: 0}, 744 NodeID: "a", 745 }, { 746 ID: "oldtask2", 747 DesiredState: swarm.TaskStateComplete, 748 Status: swarm.TaskStatus{State: swarm.TaskStateComplete}, 749 JobIteration: &swarm.Version{Index: 0}, 750 NodeID: "b", 751 }, { 752 ID: "task1", 753 DesiredState: swarm.TaskStateComplete, 754 Status: swarm.TaskStatus{State: swarm.TaskStateNew}, 755 JobIteration: &swarm.Version{Index: 1}, 756 NodeID: "a", 757 }, { 758 ID: "task2", 759 DesiredState: swarm.TaskStateComplete, 760 Status: swarm.TaskStatus{State: swarm.TaskStateNew}, 761 JobIteration: &swarm.Version{Index: 1}, 762 NodeID: "b", 763 }, { 764 ID: "task3", 765 DesiredState: swarm.TaskStateComplete, 766 Status: swarm.TaskStatus{State: swarm.TaskStateNew}, 767 JobIteration: &swarm.Version{Index: 1}, 768 NodeID: "c", 769 }, 770 } 771 772 // we don't know how many tasks will be created until we get the initial 773 // task list, so we should not write out any definitive answers yet. 774 ut.testUpdater([]swarm.Task{}, false, []progress.Progress{ 775 {ID: "job progress", Action: "waiting for tasks"}, 776 }) 777 778 ut.testUpdaterNoOrder(tasks, false, []progress.Progress{ 779 {ID: "job progress", Action: "0 out of 3 complete", Current: 0, Total: 3, HideCounts: true}, 780 {ID: "a", Action: "new ", Current: 1, Total: 10, HideCounts: true}, 781 {ID: "b", Action: "new ", Current: 1, Total: 10, HideCounts: true}, 782 {ID: "c", Action: "new ", Current: 1, Total: 10, HideCounts: true}, 783 {ID: "job progress", Action: "0 out of 3 complete", Current: 0, Total: 3, HideCounts: true}, 784 }) 785 786 tasks[2].Status.State = swarm.TaskStatePreparing 787 tasks[3].Status.State = swarm.TaskStateRunning 788 tasks[4].Status.State = swarm.TaskStateAccepted 789 ut.testUpdaterNoOrder(tasks, false, []progress.Progress{ 790 {ID: "a", Action: "preparing", Current: 6, Total: 10, HideCounts: true}, 791 {ID: "b", Action: "running ", Current: 9, Total: 10, HideCounts: true}, 792 {ID: "c", Action: "accepted ", Current: 5, Total: 10, HideCounts: true}, 793 {ID: "job progress", Action: "0 out of 3 complete", Current: 0, Total: 3, HideCounts: true}, 794 }) 795 796 tasks[2].Status.State = swarm.TaskStateRunning 797 tasks[3].Status.State = swarm.TaskStateComplete 798 tasks[4].Status.State = swarm.TaskStateRunning 799 ut.testUpdaterNoOrder(tasks, false, []progress.Progress{ 800 {ID: "a", Action: "running ", Current: 9, Total: 10, HideCounts: true}, 801 {ID: "b", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 802 {ID: "c", Action: "running ", Current: 9, Total: 10, HideCounts: true}, 803 {ID: "job progress", Action: "1 out of 3 complete", Current: 1, Total: 3, HideCounts: true}, 804 }) 805 806 tasks[2].Status.State = swarm.TaskStateFailed 807 tasks[2].Status.Err = "task failed" 808 tasks[4].Status.State = swarm.TaskStateComplete 809 ut.testUpdaterNoOrder(tasks, false, []progress.Progress{ 810 {ID: "a", Action: "task failed"}, 811 {ID: "b", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 812 {ID: "c", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 813 {ID: "job progress", Action: "2 out of 3 complete", Current: 2, Total: 3, HideCounts: true}, 814 }) 815 816 tasks = append(tasks, swarm.Task{ 817 ID: "task4", 818 DesiredState: swarm.TaskStateComplete, 819 Status: swarm.TaskStatus{State: swarm.TaskStatePreparing}, 820 NodeID: tasks[2].NodeID, 821 JobIteration: &swarm.Version{Index: 1}, 822 }) 823 824 ut.testUpdaterNoOrder(tasks, false, []progress.Progress{ 825 {ID: "a", Action: "preparing", Current: 6, Total: 10, HideCounts: true}, 826 {ID: "b", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 827 {ID: "c", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 828 {ID: "job progress", Action: "2 out of 3 complete", Current: 2, Total: 3, HideCounts: true}, 829 }) 830 831 tasks[5].Status.State = swarm.TaskStateComplete 832 ut.testUpdaterNoOrder(tasks, true, []progress.Progress{ 833 {ID: "a", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 834 {ID: "b", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 835 {ID: "c", Action: "complete ", Current: 10, Total: 10, HideCounts: true}, 836 {ID: "job progress", Action: "3 out of 3 complete", Current: 3, Total: 3, HideCounts: true}, 837 }) 838 } 839 840 func TestGlobalJobProgressUpdaterLarge(t *testing.T) { 841 service := swarm.Service{ 842 Spec: swarm.ServiceSpec{ 843 Mode: swarm.ServiceMode{ 844 GlobalJob: &swarm.GlobalJob{}, 845 }, 846 }, 847 JobStatus: &swarm.JobStatus{ 848 JobIteration: swarm.Version{Index: 1}, 849 }, 850 } 851 852 activeNodes := map[string]struct{}{} 853 for i := 0; i < 50; i++ { 854 activeNodes[fmt.Sprintf("node%v", i)] = struct{}{} 855 } 856 857 p := &mockProgress{} 858 ut := updaterTester{ 859 t: t, 860 updater: &globalJobProgressUpdater{ 861 progressOut: p, 862 }, 863 p: p, 864 activeNodes: activeNodes, 865 service: service, 866 } 867 868 tasks := []swarm.Task{} 869 for nodeID := range activeNodes { 870 tasks = append(tasks, swarm.Task{ 871 ID: fmt.Sprintf("task%s", nodeID), 872 NodeID: nodeID, 873 DesiredState: swarm.TaskStateComplete, 874 Status: swarm.TaskStatus{ 875 State: swarm.TaskStateNew, 876 }, 877 JobIteration: &swarm.Version{Index: 1}, 878 }) 879 } 880 881 // no bars, because too many tasks 882 ut.testUpdater(tasks, false, []progress.Progress{ 883 {ID: "job progress", Action: " 0 out of 50 complete", Current: 0, Total: 50, HideCounts: true}, 884 {ID: "job progress", Action: " 0 out of 50 complete", Current: 0, Total: 50, HideCounts: true}, 885 }) 886 887 for i := range tasks { 888 tasks[i].Status.State = swarm.TaskStateComplete 889 ut.testUpdater(tasks, i+1 == len(activeNodes), []progress.Progress{ 890 { 891 ID: "job progress", 892 Action: fmt.Sprintf("%2d out of 50 complete", i+1), 893 Current: int64(i + 1), Total: 50, HideCounts: true, 894 }, 895 }) 896 } 897 }