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