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  }