github.com/Prakhar-Agarwal-byte/moby@v0.0.0-20231027092010-a14e3e8ab87e/integration/internal/swarm/states.go (about) 1 package swarm 2 3 import ( 4 "context" 5 "fmt" 6 7 "github.com/Prakhar-Agarwal-byte/moby/api/types" 8 "github.com/Prakhar-Agarwal-byte/moby/api/types/filters" 9 swarmtypes "github.com/Prakhar-Agarwal-byte/moby/api/types/swarm" 10 "github.com/Prakhar-Agarwal-byte/moby/client" 11 "gotest.tools/v3/poll" 12 ) 13 14 // NoTasksForService verifies that there are no more tasks for the given service 15 func NoTasksForService(ctx context.Context, client client.ServiceAPIClient, serviceID string) func(log poll.LogT) poll.Result { 16 return func(log poll.LogT) poll.Result { 17 tasks, err := client.TaskList(ctx, types.TaskListOptions{ 18 Filters: filters.NewArgs( 19 filters.Arg("service", serviceID), 20 ), 21 }) 22 if err == nil { 23 if len(tasks) == 0 { 24 return poll.Success() 25 } 26 if len(tasks) > 0 { 27 return poll.Continue("task count for service %s at %d waiting for 0", serviceID, len(tasks)) 28 } 29 return poll.Continue("waiting for tasks for service %s to be deleted", serviceID) 30 } 31 // TODO we should not use an error as indication that the tasks are gone. There may be other reasons for an error to occur. 32 return poll.Success() 33 } 34 } 35 36 // NoTasks verifies that all tasks are gone 37 func NoTasks(ctx context.Context, client client.ServiceAPIClient) func(log poll.LogT) poll.Result { 38 return func(log poll.LogT) poll.Result { 39 tasks, err := client.TaskList(ctx, types.TaskListOptions{}) 40 switch { 41 case err != nil: 42 return poll.Error(err) 43 case len(tasks) == 0: 44 return poll.Success() 45 default: 46 return poll.Continue("waiting for all tasks to be removed: task count at %d", len(tasks)) 47 } 48 } 49 } 50 51 // RunningTasksCount verifies there are `instances` tasks running for `serviceID` 52 func RunningTasksCount(ctx context.Context, client client.ServiceAPIClient, serviceID string, instances uint64) func(log poll.LogT) poll.Result { 53 return func(log poll.LogT) poll.Result { 54 filter := filters.NewArgs() 55 filter.Add("service", serviceID) 56 tasks, err := client.TaskList(ctx, types.TaskListOptions{ 57 Filters: filter, 58 }) 59 var running int 60 var taskError string 61 for _, task := range tasks { 62 switch task.Status.State { 63 case swarmtypes.TaskStateRunning: 64 running++ 65 case swarmtypes.TaskStateFailed: 66 if task.Status.Err != "" { 67 taskError = task.Status.Err 68 } 69 } 70 } 71 72 switch { 73 case err != nil: 74 return poll.Error(err) 75 case running > int(instances): 76 return poll.Continue("waiting for tasks to terminate") 77 case running < int(instances) && taskError != "": 78 return poll.Continue("waiting for tasks to enter run state. task failed with error: %s", taskError) 79 case running == int(instances): 80 return poll.Success() 81 default: 82 return poll.Continue("running task count at %d waiting for %d (total tasks: %d)", running, instances, len(tasks)) 83 } 84 } 85 } 86 87 // JobComplete is a poll function for determining that a ReplicatedJob is 88 // completed additionally, while polling, it verifies that the job never 89 // exceeds MaxConcurrent running tasks 90 func JobComplete(ctx context.Context, client client.CommonAPIClient, service swarmtypes.Service) func(log poll.LogT) poll.Result { 91 filter := filters.NewArgs(filters.Arg("service", service.ID)) 92 93 var jobIteration swarmtypes.Version 94 if service.JobStatus != nil { 95 jobIteration = service.JobStatus.JobIteration 96 } 97 98 maxConcurrent := int(*service.Spec.Mode.ReplicatedJob.MaxConcurrent) 99 totalCompletions := int(*service.Spec.Mode.ReplicatedJob.TotalCompletions) 100 previousResult := "" 101 102 return func(log poll.LogT) poll.Result { 103 tasks, err := client.TaskList(ctx, types.TaskListOptions{ 104 Filters: filter, 105 }) 106 if err != nil { 107 poll.Error(err) 108 } 109 110 var running int 111 var completed int 112 113 var runningSlot []int 114 var runningID []string 115 116 for _, task := range tasks { 117 // make sure the task has the same job iteration 118 if task.JobIteration == nil || task.JobIteration.Index != jobIteration.Index { 119 continue 120 } 121 switch task.Status.State { 122 case swarmtypes.TaskStateRunning: 123 running++ 124 runningSlot = append(runningSlot, task.Slot) 125 runningID = append(runningID, task.ID) 126 case swarmtypes.TaskStateComplete: 127 completed++ 128 } 129 } 130 131 switch { 132 case running > maxConcurrent: 133 return poll.Error(fmt.Errorf( 134 "number of running tasks (%v) exceeds max (%v)", running, maxConcurrent, 135 )) 136 case (completed + running) > totalCompletions: 137 return poll.Error(fmt.Errorf( 138 "number of tasks exceeds total (%v), %v running and %v completed", 139 totalCompletions, running, completed, 140 )) 141 case completed == totalCompletions && running == 0: 142 return poll.Success() 143 default: 144 newRes := fmt.Sprintf( 145 "Completed: %2d Running: %v\n\t%v", 146 completed, runningSlot, runningID, 147 ) 148 if newRes == previousResult { 149 } else { 150 previousResult = newRes 151 } 152 153 return poll.Continue( 154 "Job not yet finished, %v completed and %v running out of %v total", 155 completed, running, totalCompletions, 156 ) 157 } 158 } 159 }