github.com/mattyr/nomad@v0.3.3-0.20160919021406-3485a065154a/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-id> 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 { 76 var format string 77 if json && len(tmpl) > 0 { 78 c.Ui.Error("Both -json and -t are not allowed") 79 return 1 80 } else if json { 81 format = "json" 82 } else if len(tmpl) > 0 { 83 format = "template" 84 } 85 if len(format) > 0 { 86 evals, _, err := client.Evaluations().List(nil) 87 if err != nil { 88 c.Ui.Error(fmt.Sprintf("Error querying evaluations: %v", err)) 89 return 1 90 } 91 // Return nothing if no evaluations found 92 if len(evals) == 0 { 93 return 0 94 } 95 96 f, err := DataFormat(format, tmpl) 97 if err != nil { 98 c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err)) 99 return 1 100 } 101 102 out, err := f.TransformData(evals) 103 if err != nil { 104 c.Ui.Error(fmt.Sprintf("Error formatting the data: %s", err)) 105 return 1 106 } 107 c.Ui.Output(out) 108 return 0 109 } 110 } 111 112 if len(args) != 1 { 113 c.Ui.Error(c.Help()) 114 return 1 115 } 116 117 evalID := args[0] 118 119 // Truncate the id unless full length is requested 120 length := shortId 121 if verbose { 122 length = fullId 123 } 124 125 // Query the allocation info 126 if len(evalID) == 1 { 127 c.Ui.Error(fmt.Sprintf("Identifier must contain at least two characters.")) 128 return 1 129 } 130 if len(evalID)%2 == 1 { 131 // Identifiers must be of even length, so we strip off the last byte 132 // to provide a consistent user experience. 133 evalID = evalID[:len(evalID)-1] 134 } 135 136 evals, _, err := client.Evaluations().PrefixList(evalID) 137 if err != nil { 138 c.Ui.Error(fmt.Sprintf("Error querying evaluation: %v", err)) 139 return 1 140 } 141 if len(evals) == 0 { 142 c.Ui.Error(fmt.Sprintf("No evaluation(s) with prefix or id %q found", evalID)) 143 return 1 144 } 145 146 if len(evals) > 1 { 147 // Format the evals 148 out := make([]string, len(evals)+1) 149 out[0] = "ID|Priority|Triggered By|Status|Placement Failures" 150 for i, eval := range evals { 151 failures, _ := evalFailureStatus(eval) 152 out[i+1] = fmt.Sprintf("%s|%d|%s|%s|%s", 153 limit(eval.ID, length), 154 eval.Priority, 155 eval.TriggeredBy, 156 eval.Status, 157 failures, 158 ) 159 } 160 c.Ui.Output(fmt.Sprintf("Prefix matched multiple evaluations\n\n%s", formatList(out))) 161 return 0 162 } 163 164 // If we are in monitor mode, monitor and exit 165 if monitor { 166 mon := newMonitor(c.Ui, client, length) 167 return mon.monitor(evals[0].ID, true) 168 } 169 170 // Prefix lookup matched a single evaluation 171 eval, _, err := client.Evaluations().Info(evals[0].ID, nil) 172 if err != nil { 173 c.Ui.Error(fmt.Sprintf("Error querying evaluation: %s", err)) 174 return 1 175 } 176 177 // If output format is specified, format and output the data 178 var format string 179 if json { 180 format = "json" 181 } else if len(tmpl) > 0 { 182 format = "template" 183 } 184 if len(format) > 0 { 185 f, err := DataFormat(format, tmpl) 186 if err != nil { 187 c.Ui.Error(fmt.Sprintf("Error getting formatter: %s", err)) 188 return 1 189 } 190 191 out, err := f.TransformData(eval) 192 if err != nil { 193 c.Ui.Error(fmt.Sprintf("Error formatting the data: %s", err)) 194 return 1 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 the evaluation data 208 basic := []string{ 209 fmt.Sprintf("ID|%s", limit(eval.ID, length)), 210 fmt.Sprintf("Status|%s", eval.Status), 211 fmt.Sprintf("Status Description|%s", statusDesc), 212 fmt.Sprintf("Type|%s", eval.Type), 213 fmt.Sprintf("TriggeredBy|%s", eval.TriggeredBy), 214 fmt.Sprintf("%s|%s", triggerNoun, triggerSubj), 215 fmt.Sprintf("Priority|%d", eval.Priority), 216 fmt.Sprintf("Placement Failures|%s", failureString), 217 } 218 219 if verbose { 220 // NextEval, PreviousEval, BlockedEval 221 basic = append(basic, 222 fmt.Sprintf("Previous Eval|%s", eval.PreviousEval), 223 fmt.Sprintf("Next Eval|%s", eval.NextEval), 224 fmt.Sprintf("Blocked Eval|%s", eval.BlockedEval)) 225 } 226 c.Ui.Output(formatKV(basic)) 227 228 if failures { 229 c.Ui.Output(c.Colorize().Color("\n[bold]Failed Placements[reset]")) 230 sorted := sortedTaskGroupFromMetrics(eval.FailedTGAllocs) 231 for _, tg := range sorted { 232 metrics := eval.FailedTGAllocs[tg] 233 234 noun := "allocation" 235 if metrics.CoalescedFailures > 0 { 236 noun += "s" 237 } 238 c.Ui.Output(fmt.Sprintf("Task Group %q (failed to place %d %s):", tg, metrics.CoalescedFailures+1, noun)) 239 c.Ui.Output(formatAllocMetrics(metrics, false, " ")) 240 c.Ui.Output("") 241 } 242 243 if eval.BlockedEval != "" { 244 c.Ui.Output(fmt.Sprintf("Evaluation %q waiting for additional capacity to place remainder", 245 limit(eval.BlockedEval, length))) 246 } 247 } 248 249 return 0 250 } 251 252 func sortedTaskGroupFromMetrics(groups map[string]*api.AllocationMetric) []string { 253 tgs := make([]string, 0, len(groups)) 254 for tg, _ := range groups { 255 tgs = append(tgs, tg) 256 } 257 sort.Strings(tgs) 258 return tgs 259 } 260 261 func getTriggerDetails(eval *api.Evaluation) (noun, subject string) { 262 switch eval.TriggeredBy { 263 case "job-register", "job-deregister", "periodic-job", "rolling-update": 264 return "Job ID", eval.JobID 265 case "node-update": 266 return "Node ID", eval.NodeID 267 case "max-plan-attempts": 268 return "Previous Eval", eval.PreviousEval 269 default: 270 return "", "" 271 } 272 }