progress: Show task error in place of progress bar

If a task encounters an error, the interactive "service create" and
"service update" commands should show that error instead of showing a
stuck progress bar.

To validate:

docker service create --detach=false --name broken --restart-condition=none --replicas 3 busybox asdf
and
docker service create --detach=false --name broken --mode global --restart-condition none busybox asdf

Signed-off-by: Aaron Lehmann <aaron.lehmann@docker.com>
This commit is contained in:
Aaron Lehmann 2017-06-27 14:57:47 -07:00
parent 2eac0bb7b7
commit 1ef585f65d
1 changed files with 61 additions and 10 deletions

View File

@ -6,6 +6,7 @@ import (
"io" "io"
"os" "os"
"os/signal" "os/signal"
"strings"
"time" "time"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
@ -29,6 +30,13 @@ var (
swarm.TaskStateReady: 7, swarm.TaskStateReady: 7,
swarm.TaskStateStarting: 8, swarm.TaskStateStarting: 8,
swarm.TaskStateRunning: 9, swarm.TaskStateRunning: 9,
// The following states are not actually shown in progress
// output, but are used internally for ordering.
swarm.TaskStateComplete: 10,
swarm.TaskStateShutdown: 11,
swarm.TaskStateFailed: 12,
swarm.TaskStateRejected: 13,
} }
longestState int longestState int
@ -45,17 +53,21 @@ type progressUpdater interface {
func init() { func init() {
for state := range numberedStates { for state := range numberedStates {
if len(state) > longestState { if !terminalState(state) && len(state) > longestState {
longestState = len(state) longestState = len(state)
} }
} }
} }
func terminalState(state swarm.TaskState) bool {
return numberedStates[state] > numberedStates[swarm.TaskStateRunning]
}
func stateToProgress(state swarm.TaskState, rollback bool) int64 { func stateToProgress(state swarm.TaskState, rollback bool) int64 {
if !rollback { if !rollback {
return numberedStates[state] return numberedStates[state]
} }
return int64(len(numberedStates)) - numberedStates[state] return numberedStates[swarm.TaskStateRunning] - numberedStates[state]
} }
// ServiceProgress outputs progress information for convergence of a service. // ServiceProgress outputs progress information for convergence of a service.
@ -230,6 +242,18 @@ func writeOverallProgress(progressOut progress.Output, numerator, denominator in
}) })
} }
func truncError(errMsg string) string {
// Remove newlines from the error, which corrupt the output.
errMsg = strings.Replace(errMsg, "\n", " ", -1)
// Limit the length to 75 characters, so that even on narrow terminals
// this will not overflow to the next line.
if len(errMsg) > 75 {
errMsg = errMsg[:74] + "…"
}
return errMsg
}
type replicatedProgressUpdater struct { type replicatedProgressUpdater struct {
progressOut progress.Output progressOut progress.Output
@ -303,7 +327,23 @@ func (u *replicatedProgressUpdater) update(service swarm.Service, tasks []swarm.
u.slotMap[task.Slot] = mappedSlot u.slotMap[task.Slot] = mappedSlot
} }
if !u.done && replicas <= maxProgressBars && uint64(mappedSlot) <= replicas { if !terminalState(task.DesiredState) && task.Status.State == swarm.TaskStateRunning {
running++
}
if u.done || replicas > maxProgressBars || uint64(mappedSlot) > replicas {
continue
}
if task.Status.Err != "" {
u.progressOut.WriteProgress(progress.Progress{
ID: fmt.Sprintf("%d/%d", mappedSlot, replicas),
Action: truncError(task.Status.Err),
})
continue
}
if !terminalState(task.DesiredState) && !terminalState(task.Status.State) {
u.progressOut.WriteProgress(progress.Progress{ u.progressOut.WriteProgress(progress.Progress{
ID: fmt.Sprintf("%d/%d", mappedSlot, replicas), ID: fmt.Sprintf("%d/%d", mappedSlot, replicas),
Action: fmt.Sprintf("%-[1]*s", longestState, task.Status.State), Action: fmt.Sprintf("%-[1]*s", longestState, task.Status.State),
@ -312,9 +352,6 @@ func (u *replicatedProgressUpdater) update(service swarm.Service, tasks []swarm.
HideCounts: true, HideCounts: true,
}) })
} }
if task.Status.State == swarm.TaskStateRunning {
running++
}
} }
if !u.done { if !u.done {
@ -335,6 +372,7 @@ type globalProgressUpdater struct {
done bool done bool
} }
// nolint: gocyclo
func (u *globalProgressUpdater) update(service swarm.Service, tasks []swarm.Task, activeNodes map[string]swarm.Node, rollback bool) (bool, error) { func (u *globalProgressUpdater) update(service swarm.Service, tasks []swarm.Task, activeNodes map[string]swarm.Node, rollback bool) (bool, error) {
// If there are multiple tasks with the same node ID, favor the one // If there are multiple tasks with the same node ID, favor the one
// with the *lowest* desired state. This can happen in restart // with the *lowest* desired state. This can happen in restart
@ -388,7 +426,23 @@ func (u *globalProgressUpdater) update(service swarm.Service, tasks []swarm.Task
for _, task := range tasksByNode { for _, task := range tasksByNode {
if node, nodeActive := activeNodes[task.NodeID]; nodeActive { if node, nodeActive := activeNodes[task.NodeID]; nodeActive {
if !u.done && nodeCount <= maxProgressBars { if !terminalState(task.DesiredState) && task.Status.State == swarm.TaskStateRunning {
running++
}
if u.done || nodeCount > maxProgressBars {
continue
}
if task.Status.Err != "" {
u.progressOut.WriteProgress(progress.Progress{
ID: stringid.TruncateID(node.ID),
Action: truncError(task.Status.Err),
})
continue
}
if !terminalState(task.DesiredState) && !terminalState(task.Status.State) {
u.progressOut.WriteProgress(progress.Progress{ u.progressOut.WriteProgress(progress.Progress{
ID: stringid.TruncateID(node.ID), ID: stringid.TruncateID(node.ID),
Action: fmt.Sprintf("%-[1]*s", longestState, task.Status.State), Action: fmt.Sprintf("%-[1]*s", longestState, task.Status.State),
@ -397,9 +451,6 @@ func (u *globalProgressUpdater) update(service swarm.Service, tasks []swarm.Task
HideCounts: true, HideCounts: true,
}) })
} }
if task.Status.State == swarm.TaskStateRunning {
running++
}
} }
} }