github.com/jrxfive/nomad@v0.6.1-0.20170802162750-1fef470e89bf/command/eval_status.go (about) 1 package command 2 3 import ( 4 "fmt" 5 "sort" 6 "strings" 7 8 "github.com/hashicorp/nomad/api" 9 ) 10 11 type EvalStatusCommand struct { 12 Meta 13 } 14 15 func (c *EvalStatusCommand) Help() string { 16 helpText := ` 17 Usage: nomad eval-status [options] <evaluation> 18 19 Display information about evaluations. This command can be used to inspect the 20 current status of an evaluation as well as determine the reason an evaluation 21 did not place all allocations. 22 23 General Options: 24 25 ` + generalOptionsUsage() + ` 26 27 Eval Status Options: 28 29 -monitor 30 Monitor an outstanding evaluation 31 32 -verbose 33 Show full information. 34 35 -json 36 Output the evaluation in its JSON format. 37 38 -t 39 Format and display evaluation using a Go template. 40 ` 41 42 return strings.TrimSpace(helpText) 43 } 44 45 func (c *EvalStatusCommand) Synopsis() string { 46 return "Display evaluation status and placement failure reasons" 47 } 48 49 func (c *EvalStatusCommand) Run(args []string) int { 50 var monitor, verbose, json bool 51 var tmpl string 52 53 flags := c.Meta.FlagSet("eval-status", FlagSetClient) 54 flags.Usage = func() { c.Ui.Output(c.Help()) } 55 flags.BoolVar(&monitor, "monitor", false, "") 56 flags.BoolVar(&verbose, "verbose", false, "") 57 flags.BoolVar(&json, "json", false, "") 58 flags.StringVar(&tmpl, "t", "", "") 59 60 if err := flags.Parse(args); err != nil { 61 return 1 62 } 63 64 // Check that we got exactly one evaluation ID 65 args = flags.Args() 66 67 // Get the HTTP client 68 client, err := c.Meta.Client() 69 if err != nil { 70 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 71 return 1 72 } 73 74 // If args not specified but output format is specified, format and output the evaluations data list 75 if len(args) == 0 && json || len(tmpl) > 0 { 76 evals, _, err := client.Evaluations().List(nil) 77 if err != nil { 78 c.Ui.Error(fmt.Sprintf("Error querying evaluations: %v", err)) 79 return 1 80 } 81 82 out, err := Format(json, tmpl, evals) 83 if err != nil { 84 c.Ui.Error(err.Error()) 85 return 1 86 } 87 88 c.Ui.Output(out) 89 return 0 90 } 91 92 if len(args) != 1 { 93 c.Ui.Error(c.Help()) 94 return 1 95 } 96 97 evalID := args[0] 98 99 // Truncate the id unless full length is requested 100 length := shortId 101 if verbose { 102 length = fullId 103 } 104 105 // Query the allocation info 106 if len(evalID) == 1 { 107 c.Ui.Error(fmt.Sprintf("Identifier must contain at least two characters.")) 108 return 1 109 } 110 if len(evalID)%2 == 1 { 111 // Identifiers must be of even length, so we strip off the last byte 112 // to provide a consistent user experience. 113 evalID = evalID[:len(evalID)-1] 114 } 115 116 evals, _, err := client.Evaluations().PrefixList(evalID) 117 if err != nil { 118 c.Ui.Error(fmt.Sprintf("Error querying evaluation: %v", err)) 119 return 1 120 } 121 if len(evals) == 0 { 122 c.Ui.Error(fmt.Sprintf("No evaluation(s) with prefix or id %q found", evalID)) 123 return 1 124 } 125 126 if len(evals) > 1 { 127 // Format the evals 128 out := make([]string, len(evals)+1) 129 out[0] = "ID|Priority|Triggered By|Status|Placement Failures" 130 for i, eval := range evals { 131 failures, _ := evalFailureStatus(eval) 132 out[i+1] = fmt.Sprintf("%s|%d|%s|%s|%s", 133 limit(eval.ID, length), 134 eval.Priority, 135 eval.TriggeredBy, 136 eval.Status, 137 failures, 138 ) 139 } 140 c.Ui.Error(fmt.Sprintf("Prefix matched multiple evaluations\n\n%s", formatList(out))) 141 return 1 142 } 143 144 // If we are in monitor mode, monitor and exit 145 if monitor { 146 mon := newMonitor(c.Ui, client, length) 147 return mon.monitor(evals[0].ID, true) 148 } 149 150 // Prefix lookup matched a single evaluation 151 eval, _, err := client.Evaluations().Info(evals[0].ID, nil) 152 if err != nil { 153 c.Ui.Error(fmt.Sprintf("Error querying evaluation: %s", err)) 154 return 1 155 } 156 157 // If output format is specified, format and output the data 158 if json || len(tmpl) > 0 { 159 out, err := Format(json, tmpl, eval) 160 if err != nil { 161 c.Ui.Error(err.Error()) 162 return 1 163 } 164 165 c.Ui.Output(out) 166 return 0 167 } 168 169 failureString, failures := evalFailureStatus(eval) 170 triggerNoun, triggerSubj := getTriggerDetails(eval) 171 statusDesc := eval.StatusDescription 172 if statusDesc == "" { 173 statusDesc = eval.Status 174 } 175 176 // Format the evaluation data 177 basic := []string{ 178 fmt.Sprintf("ID|%s", limit(eval.ID, length)), 179 fmt.Sprintf("Status|%s", eval.Status), 180 fmt.Sprintf("Status Description|%s", statusDesc), 181 fmt.Sprintf("Type|%s", eval.Type), 182 fmt.Sprintf("TriggeredBy|%s", eval.TriggeredBy), 183 fmt.Sprintf("%s|%s", triggerNoun, triggerSubj), 184 fmt.Sprintf("Priority|%d", eval.Priority), 185 fmt.Sprintf("Placement Failures|%s", failureString), 186 } 187 188 if verbose { 189 // NextEval, PreviousEval, BlockedEval 190 basic = append(basic, 191 fmt.Sprintf("Previous Eval|%s", eval.PreviousEval), 192 fmt.Sprintf("Next Eval|%s", eval.NextEval), 193 fmt.Sprintf("Blocked Eval|%s", eval.BlockedEval)) 194 } 195 c.Ui.Output(formatKV(basic)) 196 197 if failures { 198 c.Ui.Output(c.Colorize().Color("\n[bold]Failed Placements[reset]")) 199 sorted := sortedTaskGroupFromMetrics(eval.FailedTGAllocs) 200 for _, tg := range sorted { 201 metrics := eval.FailedTGAllocs[tg] 202 203 noun := "allocation" 204 if metrics.CoalescedFailures > 0 { 205 noun += "s" 206 } 207 c.Ui.Output(fmt.Sprintf("Task Group %q (failed to place %d %s):", tg, metrics.CoalescedFailures+1, noun)) 208 c.Ui.Output(formatAllocMetrics(metrics, false, " ")) 209 c.Ui.Output("") 210 } 211 212 if eval.BlockedEval != "" { 213 c.Ui.Output(fmt.Sprintf("Evaluation %q waiting for additional capacity to place remainder", 214 limit(eval.BlockedEval, length))) 215 } 216 } 217 218 return 0 219 } 220 221 func sortedTaskGroupFromMetrics(groups map[string]*api.AllocationMetric) []string { 222 tgs := make([]string, 0, len(groups)) 223 for tg, _ := range groups { 224 tgs = append(tgs, tg) 225 } 226 sort.Strings(tgs) 227 return tgs 228 } 229 230 func getTriggerDetails(eval *api.Evaluation) (noun, subject string) { 231 switch eval.TriggeredBy { 232 case "job-register", "job-deregister", "periodic-job", "rolling-update", "deployment-watcher": 233 return "Job ID", eval.JobID 234 case "node-update": 235 return "Node ID", eval.NodeID 236 case "max-plan-attempts": 237 return "Previous Eval", eval.PreviousEval 238 default: 239 return "", "" 240 } 241 }