cli: show lifecycle info in alloc status
Display task lifecycle info in `nomad alloc status <alloc_id>` output. I chose to embed it in the Task header and only add it for tasks with lifecycle info. Also, I chose to order the tasks in the following order: 1. prestart non-sidecar tasks 2. prestart sidecar tasks 3. main tasks The tasks are sorted lexicographically within each tier. Sample output: ``` $ nomad alloc status 6ec0eb52 ID = 6ec0eb52-e6c8-665c-169c-113d6081309b Eval ID = fb0caa98 Name = lifecycle.cache[0] [...] Task "init" (prestart) is "dead" Task Resources CPU Memory Disk Addresses 0/500 MHz 0 B/256 MiB 300 MiB [...] Task "some-sidecar" (prestart sidecar) is "running" Task Resources CPU Memory Disk Addresses 0/500 MHz 68 KiB/256 MiB 300 MiB [...] Task "redis" is "running" Task Resources CPU Memory Disk Addresses 10/500 MHz 984 KiB/256 MiB 300 MiB [...] ```
This commit is contained in:
parent
c7d07aa20c
commit
61c42034d5
|
@ -364,9 +364,20 @@ func futureEvalTimePretty(evalID string, client *api.Client) string {
|
|||
// outputTaskDetails prints task details for each task in the allocation,
|
||||
// optionally printing verbose statistics if displayStats is set
|
||||
func (c *AllocStatusCommand) outputTaskDetails(alloc *api.Allocation, stats *api.AllocResourceUsage, displayStats bool, verbose bool) {
|
||||
for task := range c.sortedTaskStateIterator(alloc.TaskStates) {
|
||||
taskLifecycles := map[string]*api.TaskLifecycle{}
|
||||
for _, t := range alloc.Job.LookupTaskGroup(alloc.TaskGroup).Tasks {
|
||||
taskLifecycles[t.Name] = t.Lifecycle
|
||||
}
|
||||
|
||||
for _, task := range c.sortedTaskStateIterator(alloc.TaskStates, taskLifecycles) {
|
||||
state := alloc.TaskStates[task]
|
||||
c.Ui.Output(c.Colorize().Color(fmt.Sprintf("\n[bold]Task %q is %q[reset]", task, state.State)))
|
||||
|
||||
lcIndicator := ""
|
||||
if lc := taskLifecycles[task]; !lc.Empty() {
|
||||
lcIndicator = " (" + lifecycleDisplayName(lc) + ")"
|
||||
}
|
||||
|
||||
c.Ui.Output(c.Colorize().Color(fmt.Sprintf("\n[bold]Task %q%v is %q[reset]", task, lcIndicator, state.State)))
|
||||
c.outputTaskResources(alloc, task, stats, displayStats)
|
||||
c.Ui.Output("")
|
||||
c.outputTaskVolumes(alloc, task, verbose)
|
||||
|
@ -671,20 +682,12 @@ func (c *AllocStatusCommand) shortTaskStatus(alloc *api.Allocation) {
|
|||
tasks := make([]string, 0, len(alloc.TaskStates)+1)
|
||||
tasks = append(tasks, "Name|State|Last Event|Time|Lifecycle")
|
||||
|
||||
taskLifecycles := map[string]string{}
|
||||
taskLifecycles := map[string]*api.TaskLifecycle{}
|
||||
for _, t := range alloc.Job.LookupTaskGroup(alloc.TaskGroup).Tasks {
|
||||
lc := "main"
|
||||
if t.Lifecycle != nil {
|
||||
sidecar := ""
|
||||
if t.Lifecycle.Sidecar {
|
||||
sidecar = "sidecar"
|
||||
}
|
||||
lc = fmt.Sprintf("%s %s", t.Lifecycle.Hook, sidecar)
|
||||
}
|
||||
taskLifecycles[t.Name] = lc
|
||||
taskLifecycles[t.Name] = t.Lifecycle
|
||||
}
|
||||
|
||||
for task := range c.sortedTaskStateIterator(alloc.TaskStates) {
|
||||
for _, task := range c.sortedTaskStateIterator(alloc.TaskStates, taskLifecycles) {
|
||||
state := alloc.TaskStates[task]
|
||||
lastState := state.State
|
||||
var lastEvent, lastTime string
|
||||
|
@ -697,7 +700,7 @@ func (c *AllocStatusCommand) shortTaskStatus(alloc *api.Allocation) {
|
|||
}
|
||||
|
||||
tasks = append(tasks, fmt.Sprintf("%s|%s|%s|%s|%s",
|
||||
task, lastState, lastEvent, lastTime, taskLifecycles[task]))
|
||||
task, lastState, lastEvent, lastTime, lifecycleDisplayName(taskLifecycles[task])))
|
||||
}
|
||||
|
||||
c.Ui.Output(c.Colorize().Color("\n[bold]Tasks[reset]"))
|
||||
|
@ -706,8 +709,7 @@ func (c *AllocStatusCommand) shortTaskStatus(alloc *api.Allocation) {
|
|||
|
||||
// sortedTaskStateIterator is a helper that takes the task state map and returns a
|
||||
// channel that returns the keys in a sorted order.
|
||||
func (c *AllocStatusCommand) sortedTaskStateIterator(m map[string]*api.TaskState) <-chan string {
|
||||
output := make(chan string, len(m))
|
||||
func (c *AllocStatusCommand) sortedTaskStateIterator(m map[string]*api.TaskState, lifecycles map[string]*api.TaskLifecycle) []string {
|
||||
keys := make([]string, len(m))
|
||||
i := 0
|
||||
for k := range m {
|
||||
|
@ -716,12 +718,36 @@ func (c *AllocStatusCommand) sortedTaskStateIterator(m map[string]*api.TaskState
|
|||
}
|
||||
sort.Strings(keys)
|
||||
|
||||
for _, key := range keys {
|
||||
output <- key
|
||||
// display prestart then prestart sidecar then main
|
||||
sort.SliceStable(keys, func(i, j int) bool {
|
||||
lci := lifecycles[keys[i]]
|
||||
lcj := lifecycles[keys[j]]
|
||||
|
||||
switch {
|
||||
case lci == nil:
|
||||
return false
|
||||
case lcj == nil:
|
||||
return true
|
||||
case !lci.Sidecar && lcj.Sidecar:
|
||||
return true
|
||||
default:
|
||||
return false
|
||||
}
|
||||
})
|
||||
|
||||
return keys
|
||||
}
|
||||
|
||||
func lifecycleDisplayName(l *api.TaskLifecycle) string {
|
||||
if l.Empty() {
|
||||
return "main"
|
||||
}
|
||||
|
||||
close(output)
|
||||
return output
|
||||
sidecar := ""
|
||||
if l.Sidecar {
|
||||
sidecar = " sidecar"
|
||||
}
|
||||
return l.Hook + sidecar
|
||||
}
|
||||
|
||||
func (c *AllocStatusCommand) outputTaskVolumes(alloc *api.Allocation, taskName string, verbose bool) {
|
||||
|
|
|
@ -87,6 +87,69 @@ func TestAllocStatusCommand_Fails(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func TestAllocStatusCommand_LifecycleInfo(t *testing.T) {
|
||||
t.Parallel()
|
||||
srv, client, url := testServer(t, true, nil)
|
||||
defer srv.Shutdown()
|
||||
|
||||
// Wait for a node to be ready
|
||||
testutil.WaitForResult(func() (bool, error) {
|
||||
nodes, _, err := client.Nodes().List(nil)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
for _, node := range nodes {
|
||||
if node.Status == structs.NodeStatusReady {
|
||||
return true, nil
|
||||
}
|
||||
}
|
||||
return false, fmt.Errorf("no ready nodes")
|
||||
}, func(err error) {
|
||||
require.NoError(t, err)
|
||||
})
|
||||
|
||||
ui := new(cli.MockUi)
|
||||
cmd := &AllocStatusCommand{Meta: Meta{Ui: ui}}
|
||||
state := srv.Agent.Server().State()
|
||||
|
||||
a := mock.Alloc()
|
||||
a.Metrics = &structs.AllocMetric{}
|
||||
tg := a.Job.LookupTaskGroup(a.TaskGroup)
|
||||
|
||||
initTask := tg.Tasks[0].Copy()
|
||||
initTask.Name = "init_task"
|
||||
initTask.Lifecycle = &structs.TaskLifecycleConfig{
|
||||
Hook: "prestart",
|
||||
}
|
||||
|
||||
prestartSidecarTask := tg.Tasks[0].Copy()
|
||||
prestartSidecarTask.Name = "prestart_sidecar"
|
||||
prestartSidecarTask.Lifecycle = &structs.TaskLifecycleConfig{
|
||||
Hook: "prestart",
|
||||
Sidecar: true,
|
||||
}
|
||||
|
||||
tg.Tasks = append(tg.Tasks, initTask, prestartSidecarTask)
|
||||
a.TaskResources["init_task"] = a.TaskResources["web"]
|
||||
a.TaskResources["prestart_sidecar"] = a.TaskResources["web"]
|
||||
a.TaskStates = map[string]*structs.TaskState{
|
||||
"web": &structs.TaskState{State: "pending"},
|
||||
"init_task": &structs.TaskState{State: "running"},
|
||||
"prestart_sidecar": &structs.TaskState{State: "running"},
|
||||
}
|
||||
|
||||
require.Nil(t, state.UpsertAllocs(1000, []*structs.Allocation{a}))
|
||||
|
||||
if code := cmd.Run([]string{"-address=" + url, a.ID}); code != 0 {
|
||||
t.Fatalf("expected exit 0, got: %d", code)
|
||||
}
|
||||
out := ui.OutputWriter.String()
|
||||
|
||||
require.Contains(t, out, `Task "init_task" (prestart) is "running"`)
|
||||
require.Contains(t, out, `Task "prestart_sidecar" (prestart sidecar) is "running"`)
|
||||
require.Contains(t, out, `Task "web" is "pending"`)
|
||||
}
|
||||
|
||||
func TestAllocStatusCommand_Run(t *testing.T) {
|
||||
t.Parallel()
|
||||
srv, client, url := testServer(t, true, nil)
|
||||
|
|
Loading…
Reference in New Issue