github.com/anuvu/nomad@v0.8.7-atom1/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) Name() string { return "eval status" } 81 82 func (c *EvalStatusCommand) Run(args []string) int { 83 var monitor, verbose, json bool 84 var tmpl string 85 86 flags := c.Meta.FlagSet(c.Name(), FlagSetClient) 87 flags.Usage = func() { c.Ui.Output(c.Help()) } 88 flags.BoolVar(&monitor, "monitor", false, "") 89 flags.BoolVar(&verbose, "verbose", false, "") 90 flags.BoolVar(&json, "json", false, "") 91 flags.StringVar(&tmpl, "t", "", "") 92 93 if err := flags.Parse(args); err != nil { 94 return 1 95 } 96 97 // Check that we got exactly one evaluation ID 98 args = flags.Args() 99 100 // Get the HTTP client 101 client, err := c.Meta.Client() 102 if err != nil { 103 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 104 return 1 105 } 106 107 // If args not specified but output format is specified, format and output the evaluations data list 108 if len(args) == 0 && json || len(tmpl) > 0 { 109 evals, _, err := client.Evaluations().List(nil) 110 if err != nil { 111 c.Ui.Error(fmt.Sprintf("Error querying evaluations: %v", err)) 112 return 1 113 } 114 115 out, err := Format(json, tmpl, evals) 116 if err != nil { 117 c.Ui.Error(err.Error()) 118 return 1 119 } 120 121 c.Ui.Output(out) 122 return 0 123 } 124 125 if len(args) != 1 { 126 c.Ui.Error("This command takes one argument") 127 c.Ui.Error(commandErrorText(c)) 128 return 1 129 } 130 131 evalID := args[0] 132 133 // Truncate the id unless full length is requested 134 length := shortId 135 if verbose { 136 length = fullId 137 } 138 139 // Query the allocation info 140 if len(evalID) == 1 { 141 c.Ui.Error(fmt.Sprintf("Identifier must contain at least two characters.")) 142 return 1 143 } 144 145 evalID = sanitizeUUIDPrefix(evalID) 146 evals, _, err := client.Evaluations().PrefixList(evalID) 147 if err != nil { 148 c.Ui.Error(fmt.Sprintf("Error querying evaluation: %v", err)) 149 return 1 150 } 151 if len(evals) == 0 { 152 c.Ui.Error(fmt.Sprintf("No evaluation(s) with prefix or id %q found", evalID)) 153 return 1 154 } 155 156 if len(evals) > 1 { 157 // Format the evals 158 out := make([]string, len(evals)+1) 159 out[0] = "ID|Priority|Triggered By|Status|Placement Failures" 160 for i, eval := range evals { 161 failures, _ := evalFailureStatus(eval) 162 out[i+1] = fmt.Sprintf("%s|%d|%s|%s|%s", 163 limit(eval.ID, length), 164 eval.Priority, 165 eval.TriggeredBy, 166 eval.Status, 167 failures, 168 ) 169 } 170 c.Ui.Error(fmt.Sprintf("Prefix matched multiple evaluations\n\n%s", formatList(out))) 171 return 1 172 } 173 174 // If we are in monitor mode, monitor and exit 175 if monitor { 176 mon := newMonitor(c.Ui, client, length) 177 return mon.monitor(evals[0].ID, true) 178 } 179 180 // Prefix lookup matched a single evaluation 181 eval, _, err := client.Evaluations().Info(evals[0].ID, nil) 182 if err != nil { 183 c.Ui.Error(fmt.Sprintf("Error querying evaluation: %s", err)) 184 return 1 185 } 186 187 // If output format is specified, format and output the data 188 if json || len(tmpl) > 0 { 189 out, err := Format(json, tmpl, eval) 190 if err != nil { 191 c.Ui.Error(err.Error()) 192 return 1 193 } 194 195 c.Ui.Output(out) 196 return 0 197 } 198 199 failureString, failures := evalFailureStatus(eval) 200 triggerNoun, triggerSubj := getTriggerDetails(eval) 201 statusDesc := eval.StatusDescription 202 if statusDesc == "" { 203 statusDesc = eval.Status 204 } 205 206 // Format the evaluation data 207 basic := []string{ 208 fmt.Sprintf("ID|%s", limit(eval.ID, length)), 209 fmt.Sprintf("Status|%s", eval.Status), 210 fmt.Sprintf("Status Description|%s", statusDesc), 211 fmt.Sprintf("Type|%s", eval.Type), 212 fmt.Sprintf("TriggeredBy|%s", eval.TriggeredBy), 213 } 214 215 if triggerNoun != "" && triggerSubj != "" { 216 basic = append(basic, fmt.Sprintf("%s|%s", triggerNoun, triggerSubj)) 217 } 218 219 basic = append(basic, 220 fmt.Sprintf("Priority|%d", eval.Priority), 221 fmt.Sprintf("Placement Failures|%s", failureString)) 222 223 if !eval.WaitUntil.IsZero() { 224 basic = append(basic, 225 fmt.Sprintf("Wait Until|%s", formatTime(eval.WaitUntil))) 226 } 227 228 if verbose { 229 // NextEval, PreviousEval, BlockedEval 230 basic = append(basic, 231 fmt.Sprintf("Previous Eval|%s", eval.PreviousEval), 232 fmt.Sprintf("Next Eval|%s", eval.NextEval), 233 fmt.Sprintf("Blocked Eval|%s", eval.BlockedEval)) 234 } 235 c.Ui.Output(formatKV(basic)) 236 237 if failures { 238 c.Ui.Output(c.Colorize().Color("\n[bold]Failed Placements[reset]")) 239 sorted := sortedTaskGroupFromMetrics(eval.FailedTGAllocs) 240 for _, tg := range sorted { 241 metrics := eval.FailedTGAllocs[tg] 242 243 noun := "allocation" 244 if metrics.CoalescedFailures > 0 { 245 noun += "s" 246 } 247 c.Ui.Output(fmt.Sprintf("Task Group %q (failed to place %d %s):", tg, metrics.CoalescedFailures+1, noun)) 248 c.Ui.Output(formatAllocMetrics(metrics, false, " ")) 249 c.Ui.Output("") 250 } 251 252 if eval.BlockedEval != "" { 253 c.Ui.Output(fmt.Sprintf("Evaluation %q waiting for additional capacity to place remainder", 254 limit(eval.BlockedEval, length))) 255 } 256 } 257 258 return 0 259 } 260 261 func sortedTaskGroupFromMetrics(groups map[string]*api.AllocationMetric) []string { 262 tgs := make([]string, 0, len(groups)) 263 for tg := range groups { 264 tgs = append(tgs, tg) 265 } 266 sort.Strings(tgs) 267 return tgs 268 } 269 270 func getTriggerDetails(eval *api.Evaluation) (noun, subject string) { 271 switch eval.TriggeredBy { 272 case "job-register", "job-deregister", "periodic-job", "rolling-update", "deployment-watcher": 273 return "Job ID", eval.JobID 274 case "node-update": 275 return "Node ID", eval.NodeID 276 case "max-plan-attempts": 277 return "Previous Eval", eval.PreviousEval 278 default: 279 return "", "" 280 } 281 }