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