github.com/djenriquez/nomad-1@v0.8.1/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 = sanitizeUUIDPrefix(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 } 211 212 if triggerNoun != "" && triggerSubj != "" { 213 basic = append(basic, fmt.Sprintf("%s|%s", triggerNoun, triggerSubj)) 214 } 215 216 basic = append(basic, 217 fmt.Sprintf("Priority|%d", eval.Priority), 218 fmt.Sprintf("Placement Failures|%s", failureString)) 219 220 if !eval.WaitUntil.IsZero() { 221 basic = append(basic, 222 fmt.Sprintf("Wait Until|%s", formatTime(eval.WaitUntil))) 223 } 224 225 if verbose { 226 // NextEval, PreviousEval, BlockedEval 227 basic = append(basic, 228 fmt.Sprintf("Previous Eval|%s", eval.PreviousEval), 229 fmt.Sprintf("Next Eval|%s", eval.NextEval), 230 fmt.Sprintf("Blocked Eval|%s", eval.BlockedEval)) 231 } 232 c.Ui.Output(formatKV(basic)) 233 234 if failures { 235 c.Ui.Output(c.Colorize().Color("\n[bold]Failed Placements[reset]")) 236 sorted := sortedTaskGroupFromMetrics(eval.FailedTGAllocs) 237 for _, tg := range sorted { 238 metrics := eval.FailedTGAllocs[tg] 239 240 noun := "allocation" 241 if metrics.CoalescedFailures > 0 { 242 noun += "s" 243 } 244 c.Ui.Output(fmt.Sprintf("Task Group %q (failed to place %d %s):", tg, metrics.CoalescedFailures+1, noun)) 245 c.Ui.Output(formatAllocMetrics(metrics, false, " ")) 246 c.Ui.Output("") 247 } 248 249 if eval.BlockedEval != "" { 250 c.Ui.Output(fmt.Sprintf("Evaluation %q waiting for additional capacity to place remainder", 251 limit(eval.BlockedEval, length))) 252 } 253 } 254 255 return 0 256 } 257 258 func sortedTaskGroupFromMetrics(groups map[string]*api.AllocationMetric) []string { 259 tgs := make([]string, 0, len(groups)) 260 for tg := range groups { 261 tgs = append(tgs, tg) 262 } 263 sort.Strings(tgs) 264 return tgs 265 } 266 267 func getTriggerDetails(eval *api.Evaluation) (noun, subject string) { 268 switch eval.TriggeredBy { 269 case "job-register", "job-deregister", "periodic-job", "rolling-update", "deployment-watcher": 270 return "Job ID", eval.JobID 271 case "node-update": 272 return "Node ID", eval.NodeID 273 case "max-plan-attempts": 274 return "Previous Eval", eval.PreviousEval 275 default: 276 return "", "" 277 } 278 }