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