Merge pull request #7417 from hashicorp/f-allocstatus-lifecycle
cli: show lifecycle info in alloc status
This commit is contained in:
commit
ec1ad8d457
|
@ -364,9 +364,20 @@ func futureEvalTimePretty(evalID string, client *api.Client) string {
|
||||||
// outputTaskDetails prints task details for each task in the allocation,
|
// outputTaskDetails prints task details for each task in the allocation,
|
||||||
// optionally printing verbose statistics if displayStats is set
|
// optionally printing verbose statistics if displayStats is set
|
||||||
func (c *AllocStatusCommand) outputTaskDetails(alloc *api.Allocation, stats *api.AllocResourceUsage, displayStats bool, verbose bool) {
|
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]
|
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.outputTaskResources(alloc, task, stats, displayStats)
|
||||||
c.Ui.Output("")
|
c.Ui.Output("")
|
||||||
c.outputTaskVolumes(alloc, task, verbose)
|
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 := make([]string, 0, len(alloc.TaskStates)+1)
|
||||||
tasks = append(tasks, "Name|State|Last Event|Time|Lifecycle")
|
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 {
|
for _, t := range alloc.Job.LookupTaskGroup(alloc.TaskGroup).Tasks {
|
||||||
lc := "main"
|
taskLifecycles[t.Name] = t.Lifecycle
|
||||||
if t.Lifecycle != nil {
|
|
||||||
sidecar := ""
|
|
||||||
if t.Lifecycle.Sidecar {
|
|
||||||
sidecar = "sidecar"
|
|
||||||
}
|
|
||||||
lc = fmt.Sprintf("%s %s", t.Lifecycle.Hook, sidecar)
|
|
||||||
}
|
|
||||||
taskLifecycles[t.Name] = lc
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for task := range c.sortedTaskStateIterator(alloc.TaskStates) {
|
for _, task := range c.sortedTaskStateIterator(alloc.TaskStates, taskLifecycles) {
|
||||||
state := alloc.TaskStates[task]
|
state := alloc.TaskStates[task]
|
||||||
lastState := state.State
|
lastState := state.State
|
||||||
var lastEvent, lastTime string
|
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",
|
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]"))
|
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
|
// sortedTaskStateIterator is a helper that takes the task state map and returns a
|
||||||
// channel that returns the keys in a sorted order.
|
// channel that returns the keys in a sorted order.
|
||||||
func (c *AllocStatusCommand) sortedTaskStateIterator(m map[string]*api.TaskState) <-chan string {
|
func (c *AllocStatusCommand) sortedTaskStateIterator(m map[string]*api.TaskState, lifecycles map[string]*api.TaskLifecycle) []string {
|
||||||
output := make(chan string, len(m))
|
|
||||||
keys := make([]string, len(m))
|
keys := make([]string, len(m))
|
||||||
i := 0
|
i := 0
|
||||||
for k := range m {
|
for k := range m {
|
||||||
|
@ -716,12 +718,36 @@ func (c *AllocStatusCommand) sortedTaskStateIterator(m map[string]*api.TaskState
|
||||||
}
|
}
|
||||||
sort.Strings(keys)
|
sort.Strings(keys)
|
||||||
|
|
||||||
for _, key := range keys {
|
// display prestart then prestart sidecar then main
|
||||||
output <- key
|
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)
|
sidecar := ""
|
||||||
return output
|
if l.Sidecar {
|
||||||
|
sidecar = " sidecar"
|
||||||
|
}
|
||||||
|
return l.Hook + sidecar
|
||||||
}
|
}
|
||||||
|
|
||||||
func (c *AllocStatusCommand) outputTaskVolumes(alloc *api.Allocation, taskName string, verbose bool) {
|
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) {
|
func TestAllocStatusCommand_Run(t *testing.T) {
|
||||||
t.Parallel()
|
t.Parallel()
|
||||||
srv, client, url := testServer(t, true, nil)
|
srv, client, url := testServer(t, true, nil)
|
||||||
|
|
Loading…
Reference in New Issue