Show preemption output in plan CLI

This commit is contained in:
Preetha Appan 2018-10-30 18:26:23 -05:00
parent fcb06c9982
commit 5f0a9d2cfd
No known key found for this signature in database
GPG Key ID: 9F7C19990A50EAFC
4 changed files with 157 additions and 0 deletions

View File

@ -133,8 +133,10 @@ type AllocationListStub struct {
ID string
EvalID string
Name string
Namespace string
NodeID string
JobID string
JobType string
JobVersion uint64
TaskGroup string
DesiredStatus string

View File

@ -20,6 +20,7 @@ When running the job with the check-index flag, the job will only be run if the
server side version matches the job modify index returned. If the index has
changed, another user has modified the job and the plan's results are
potentially invalid.`
preemptionShowByJobIdThreshold = 10
)
type JobPlanCommand struct {
@ -173,11 +174,74 @@ func (c *JobPlanCommand) Run(args []string) int {
c.Colorize().Color(fmt.Sprintf("[bold][yellow]Job Warnings:\n%s[reset]\n", resp.Warnings)))
}
// Print preemptions if there are any
if resp.Annotations != nil && len(resp.Annotations.PreemptedAllocs) > 0 {
c.addPreemptions(resp)
}
// Print the job index info
c.Ui.Output(c.Colorize().Color(formatJobModifyIndex(resp.JobModifyIndex, path)))
return getExitCode(resp)
}
// addPreemptions shows details about preempted allocations
func (c *JobPlanCommand) addPreemptions(resp *api.JobPlanResponse) {
c.Ui.Output(c.Colorize().Color("[bold][yellow]Preemptions:\n[reset]"))
if len(resp.Annotations.PreemptedAllocs) < preemptionShowByJobIdThreshold {
var allocs []string
allocs = append(allocs, fmt.Sprintf("Alloc ID|Job ID|Task Group"))
for _, alloc := range resp.Annotations.PreemptedAllocs {
allocs = append(allocs, fmt.Sprintf("%s|%s|%s", alloc.ID, alloc.JobID, alloc.TaskGroup))
}
c.Ui.Output(formatList(allocs))
} else {
// Display in a summary format if the list is too large
// Group by job type and job ids
allocDetails := make(map[string]map[namespaceIdPair]int)
numJobs := 0
for _, alloc := range resp.Annotations.PreemptedAllocs {
id := namespaceIdPair{alloc.JobID, alloc.Namespace}
countMap := allocDetails[alloc.JobType]
if countMap == nil {
countMap = make(map[namespaceIdPair]int)
}
cnt, ok := countMap[id]
if !ok {
// First time we are seeing this job, increment counter
numJobs++
}
countMap[id] = cnt + 1
allocDetails[alloc.JobType] = countMap
}
var output []string
// Show counts grouped by job ID if its less than a threshold
if numJobs < preemptionShowByJobIdThreshold {
output = append(output, fmt.Sprintf("Job ID|Namespace|Job Type|Preemptions"))
for jobType, jobCounts := range allocDetails {
for jobId, count := range jobCounts {
output = append(output, fmt.Sprintf("%s|%s|%s|%d", jobId.id, jobId.namespace, jobType, count))
}
}
} else {
// Show counts grouped by job type
output = append(output, fmt.Sprintf("Job Type|Preemptions"))
for jobType, jobCounts := range allocDetails {
total := 0
for _, count := range jobCounts {
total += count
}
output = append(output, fmt.Sprintf("%s|%d", jobType, total))
}
}
c.Ui.Output(formatList(output))
}
}
type namespaceIdPair struct {
id string
namespace string
}
// getExitCode returns 0:
// * 0: No allocations created or destroyed.
// * 1: Allocations created or destroyed.

View File

@ -7,8 +7,12 @@ import (
"strings"
"testing"
"strconv"
"github.com/hashicorp/nomad/api"
"github.com/hashicorp/nomad/testutil"
"github.com/mitchellh/cli"
require2 "github.com/stretchr/testify/require"
)
func TestPlanCommand_Implements(t *testing.T) {
@ -169,3 +173,86 @@ func TestPlanCommand_From_URL(t *testing.T) {
t.Fatalf("expected error getting jobfile, got: %s", out)
}
}
func TestPlanCommad_Preemptions(t *testing.T) {
t.Parallel()
ui := new(cli.MockUi)
cmd := &JobPlanCommand{Meta: Meta{Ui: ui}}
require := require2.New(t)
// Only one preempted alloc
resp1 := &api.JobPlanResponse{
Annotations: &api.PlanAnnotations{
PreemptedAllocs: []*api.AllocationListStub{
{
ID: "alloc1",
JobID: "jobID1",
TaskGroup: "meta",
JobType: "batch",
Namespace: "test",
},
},
},
}
cmd.addPreemptions(resp1)
out := ui.OutputWriter.String()
require.Contains(out, "Alloc ID")
require.Contains(out, "alloc1")
// Less than 10 unique job ids
var preemptedAllocs []*api.AllocationListStub
for i := 0; i < 12; i++ {
job_id := "job" + strconv.Itoa(i%4)
alloc := &api.AllocationListStub{
ID: "alloc",
JobID: job_id,
TaskGroup: "meta",
JobType: "batch",
Namespace: "test",
}
preemptedAllocs = append(preemptedAllocs, alloc)
}
resp2 := &api.JobPlanResponse{
Annotations: &api.PlanAnnotations{
PreemptedAllocs: preemptedAllocs,
},
}
ui.OutputWriter.Reset()
cmd.addPreemptions(resp2)
out = ui.OutputWriter.String()
require.Contains(out, "Job ID")
require.Contains(out, "Namespace")
// More than 10 unique job IDs
preemptedAllocs = make([]*api.AllocationListStub, 0)
job_type := "batch"
for i := 0; i < 20; i++ {
job_id := "job" + strconv.Itoa(i)
if i%2 == 0 {
job_type = "service"
} else {
job_type = "batch"
}
alloc := &api.AllocationListStub{
ID: "alloc",
JobID: job_id,
TaskGroup: "meta",
JobType: job_type,
Namespace: "test",
}
preemptedAllocs = append(preemptedAllocs, alloc)
}
resp3 := &api.JobPlanResponse{
Annotations: &api.PlanAnnotations{
PreemptedAllocs: preemptedAllocs,
},
}
ui.OutputWriter.Reset()
cmd.addPreemptions(resp3)
out = ui.OutputWriter.String()
require.Contains(out, "Job Type")
require.Contains(out, "batch")
require.Contains(out, "service")
}

View File

@ -7538,8 +7538,10 @@ func (a *Allocation) Stub() *AllocListStub {
ID: a.ID,
EvalID: a.EvalID,
Name: a.Name,
Namespace: a.Namespace,
NodeID: a.NodeID,
JobID: a.JobID,
JobType: a.Job.Type,
JobVersion: a.Job.Version,
TaskGroup: a.TaskGroup,
DesiredStatus: a.DesiredStatus,
@ -7563,8 +7565,10 @@ type AllocListStub struct {
ID string
EvalID string
Name string
Namespace string
NodeID string
JobID string
JobType string
JobVersion uint64
TaskGroup string
DesiredStatus string