github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/command/status.go (about) 1 package command 2 3 import ( 4 "bytes" 5 "encoding/gob" 6 "fmt" 7 "strings" 8 "time" 9 10 "github.com/hashicorp/nomad/api" 11 "github.com/hashicorp/nomad/nomad/structs" 12 ) 13 14 type StatusCommand struct { 15 Meta 16 length int 17 } 18 19 func (c *StatusCommand) Help() string { 20 helpText := ` 21 Usage: nomad status [options] <job> 22 23 Display status information about jobs. If no job ID is given, 24 a list of all known jobs will be dumped. 25 26 General Options: 27 28 ` + generalOptionsUsage() + ` 29 30 Status Options: 31 32 -short 33 Display short output. Used only when a single job is being 34 queried, and drops verbose information about allocations 35 and evaluations. 36 37 -verbose 38 Display full information. 39 ` 40 return strings.TrimSpace(helpText) 41 } 42 43 func (c *StatusCommand) Synopsis() string { 44 return "Display status information about jobs" 45 } 46 47 func (c *StatusCommand) Run(args []string) int { 48 var short, verbose bool 49 50 flags := c.Meta.FlagSet("status", FlagSetClient) 51 flags.Usage = func() { c.Ui.Output(c.Help()) } 52 flags.BoolVar(&short, "short", false, "") 53 flags.BoolVar(&verbose, "verbose", false, "") 54 55 if err := flags.Parse(args); err != nil { 56 return 1 57 } 58 59 // Check that we either got no jobs or exactly one. 60 args = flags.Args() 61 if len(args) > 1 { 62 c.Ui.Error(c.Help()) 63 return 1 64 } 65 66 // Truncate the id unless full length is requested 67 c.length = shortId 68 if verbose { 69 c.length = fullId 70 } 71 72 // Get the HTTP client 73 client, err := c.Meta.Client() 74 if err != nil { 75 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 76 return 1 77 } 78 79 // Invoke list mode if no job ID. 80 if len(args) == 0 { 81 jobs, _, err := client.Jobs().List(nil) 82 if err != nil { 83 c.Ui.Error(fmt.Sprintf("Error querying jobs: %s", err)) 84 return 1 85 } 86 87 // No output if we have no jobs 88 if len(jobs) == 0 { 89 c.Ui.Output("No running jobs") 90 return 0 91 } 92 93 out := make([]string, len(jobs)+1) 94 out[0] = "ID|Type|Priority|Status" 95 for i, job := range jobs { 96 out[i+1] = fmt.Sprintf("%s|%s|%d|%s", 97 job.ID, 98 job.Type, 99 job.Priority, 100 job.Status) 101 } 102 c.Ui.Output(formatList(out)) 103 return 0 104 } 105 106 // Try querying the job 107 jobID := args[0] 108 jobs, _, err := client.Jobs().PrefixList(jobID) 109 if err != nil { 110 c.Ui.Error(fmt.Sprintf("Error querying job: %s", err)) 111 return 1 112 } 113 if len(jobs) == 0 { 114 c.Ui.Error(fmt.Sprintf("No job(s) with prefix or id %q found", jobID)) 115 return 1 116 } 117 if len(jobs) > 1 && strings.TrimSpace(jobID) != jobs[0].ID { 118 out := make([]string, len(jobs)+1) 119 out[0] = "ID|Type|Priority|Status" 120 for i, job := range jobs { 121 out[i+1] = fmt.Sprintf("%s|%s|%d|%s", 122 job.ID, 123 job.Type, 124 job.Priority, 125 job.Status) 126 } 127 c.Ui.Output(fmt.Sprintf("Prefix matched multiple jobs\n\n%s", formatList(out))) 128 return 0 129 } 130 // Prefix lookup matched a single job 131 job, _, err := client.Jobs().Info(jobs[0].ID, nil) 132 if err != nil { 133 c.Ui.Error(fmt.Sprintf("Error querying job: %s", err)) 134 return 1 135 } 136 137 // Check if it is periodic 138 sJob, err := convertApiJob(job) 139 if err != nil { 140 c.Ui.Error(fmt.Sprintf("Error converting job: %s", err)) 141 return 1 142 } 143 periodic := sJob.IsPeriodic() 144 145 // Format the job info 146 basic := []string{ 147 fmt.Sprintf("ID|%s", job.ID), 148 fmt.Sprintf("Name|%s", job.Name), 149 fmt.Sprintf("Type|%s", job.Type), 150 fmt.Sprintf("Priority|%d", job.Priority), 151 fmt.Sprintf("Datacenters|%s", strings.Join(job.Datacenters, ",")), 152 fmt.Sprintf("Status|%s", job.Status), 153 fmt.Sprintf("Periodic|%v", periodic), 154 } 155 156 if periodic { 157 basic = append(basic, fmt.Sprintf("Next Periodic Launch|%v", 158 sJob.Periodic.Next(time.Now().UTC()))) 159 } 160 161 c.Ui.Output(formatKV(basic)) 162 163 // Exit early 164 if short { 165 return 0 166 } 167 168 // Print periodic job information 169 if periodic { 170 if err := c.outputPeriodicInfo(client, job); err != nil { 171 c.Ui.Error(err.Error()) 172 return 1 173 } 174 175 return 0 176 } 177 178 if err := c.outputJobInfo(client, job); err != nil { 179 c.Ui.Error(err.Error()) 180 return 1 181 } 182 183 return 0 184 } 185 186 // outputPeriodicInfo prints information about the passed periodic job. If a 187 // request fails, an error is returned. 188 func (c *StatusCommand) outputPeriodicInfo(client *api.Client, job *api.Job) error { 189 // Generate the prefix that matches launched jobs from the periodic job. 190 prefix := fmt.Sprintf("%s%s", job.ID, structs.PeriodicLaunchSuffix) 191 children, _, err := client.Jobs().PrefixList(prefix) 192 if err != nil { 193 return fmt.Errorf("Error querying job: %s", err) 194 } 195 196 if len(children) == 0 { 197 c.Ui.Output("\nNo instances of periodic job found") 198 return nil 199 } 200 201 out := make([]string, 1) 202 out[0] = "ID|Status" 203 for _, child := range children { 204 // Ensure that we are only showing jobs whose parent is the requested 205 // job. 206 if child.ParentID != job.ID { 207 continue 208 } 209 210 out = append(out, fmt.Sprintf("%s|%s", 211 child.ID, 212 child.Status)) 213 } 214 215 c.Ui.Output(fmt.Sprintf("\nPreviously launched jobs:\n%s", formatList(out))) 216 return nil 217 } 218 219 // outputJobInfo prints information about the passed non-periodic job. If a 220 // request fails, an error is returned. 221 func (c *StatusCommand) outputJobInfo(client *api.Client, job *api.Job) error { 222 var evals, allocs []string 223 224 // Query the evaluations 225 jobEvals, _, err := client.Jobs().Evaluations(job.ID, nil) 226 if err != nil { 227 return fmt.Errorf("Error querying job evaluations: %s", err) 228 } 229 230 // Query the allocations 231 jobAllocs, _, err := client.Jobs().Allocations(job.ID, nil) 232 if err != nil { 233 return fmt.Errorf("Error querying job allocations: %s", err) 234 } 235 236 // Format the evals 237 evals = make([]string, len(jobEvals)+1) 238 evals[0] = "ID|Priority|Triggered By|Status" 239 for i, eval := range jobEvals { 240 evals[i+1] = fmt.Sprintf("%s|%d|%s|%s", 241 limit(eval.ID, c.length), 242 eval.Priority, 243 eval.TriggeredBy, 244 eval.Status) 245 } 246 247 // Format the allocs 248 allocs = make([]string, len(jobAllocs)+1) 249 allocs[0] = "ID|Eval ID|Node ID|Task Group|Desired|Status" 250 for i, alloc := range jobAllocs { 251 allocs[i+1] = fmt.Sprintf("%s|%s|%s|%s|%s|%s", 252 limit(alloc.ID, c.length), 253 limit(alloc.EvalID, c.length), 254 limit(alloc.NodeID, c.length), 255 alloc.TaskGroup, 256 alloc.DesiredStatus, 257 alloc.ClientStatus) 258 } 259 260 c.Ui.Output("\n==> Evaluations") 261 c.Ui.Output(formatList(evals)) 262 c.Ui.Output("\n==> Allocations") 263 c.Ui.Output(formatList(allocs)) 264 return nil 265 } 266 267 // convertApiJob is used to take a *api.Job and convert it to an *struct.Job. 268 // This function is just a hammer and probably needs to be revisited. 269 func convertApiJob(in *api.Job) (*structs.Job, error) { 270 gob.Register(map[string]interface{}{}) 271 gob.Register([]interface{}{}) 272 var structJob *structs.Job 273 buf := new(bytes.Buffer) 274 if err := gob.NewEncoder(buf).Encode(in); err != nil { 275 return nil, err 276 } 277 if err := gob.NewDecoder(buf).Decode(&structJob); err != nil { 278 return nil, err 279 } 280 return structJob, nil 281 }