github.com/dkerwin/nomad@v0.3.3-0.20160525181927-74554135514b/command/alloc_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/client" 11 ) 12 13 type AllocStatusCommand struct { 14 Meta 15 } 16 17 func (c *AllocStatusCommand) Help() string { 18 helpText := ` 19 Usage: nomad alloc-status [options] <allocation> 20 21 Display information about existing allocations and its tasks. This command can 22 be used to inspect the current status of all allocation, including its running 23 status, metadata, and verbose failure messages reported by internal 24 subsystems. 25 26 General Options: 27 28 ` + generalOptionsUsage() + ` 29 30 31 -short 32 Display short output. Shows only the most recent task event. 33 34 -verbose 35 Show full information. 36 ` 37 38 return strings.TrimSpace(helpText) 39 } 40 41 func (c *AllocStatusCommand) Synopsis() string { 42 return "Display allocation status information and metadata" 43 } 44 45 func (c *AllocStatusCommand) Run(args []string) int { 46 var short, verbose bool 47 48 flags := c.Meta.FlagSet("alloc-status", FlagSetClient) 49 flags.Usage = func() { c.Ui.Output(c.Help()) } 50 flags.BoolVar(&short, "short", false, "") 51 flags.BoolVar(&verbose, "verbose", false, "") 52 53 if err := flags.Parse(args); err != nil { 54 return 1 55 } 56 57 // Check that we got exactly one allocation ID 58 args = flags.Args() 59 if len(args) != 1 { 60 c.Ui.Error(c.Help()) 61 return 1 62 } 63 allocID := args[0] 64 65 // Get the HTTP client 66 client, err := c.Meta.Client() 67 if err != nil { 68 c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err)) 69 return 1 70 } 71 72 // Truncate the id unless full length is requested 73 length := shortId 74 if verbose { 75 length = fullId 76 } 77 78 // Query the allocation info 79 if len(allocID) == 1 { 80 c.Ui.Error(fmt.Sprintf("Identifier must contain at least two characters.")) 81 return 1 82 } 83 if len(allocID)%2 == 1 { 84 // Identifiers must be of even length, so we strip off the last byte 85 // to provide a consistent user experience. 86 allocID = allocID[:len(allocID)-1] 87 } 88 89 allocs, _, err := client.Allocations().PrefixList(allocID) 90 if err != nil { 91 c.Ui.Error(fmt.Sprintf("Error querying allocation: %v", err)) 92 return 1 93 } 94 if len(allocs) == 0 { 95 c.Ui.Error(fmt.Sprintf("No allocation(s) with prefix or id %q found", allocID)) 96 return 1 97 } 98 if len(allocs) > 1 { 99 // Format the allocs 100 out := make([]string, len(allocs)+1) 101 out[0] = "ID|Eval ID|Job ID|Task Group|Desired Status|Client Status" 102 for i, alloc := range allocs { 103 out[i+1] = fmt.Sprintf("%s|%s|%s|%s|%s|%s", 104 limit(alloc.ID, length), 105 limit(alloc.EvalID, length), 106 alloc.JobID, 107 alloc.TaskGroup, 108 alloc.DesiredStatus, 109 alloc.ClientStatus, 110 ) 111 } 112 c.Ui.Output(fmt.Sprintf("Prefix matched multiple allocations\n\n%s", formatList(out))) 113 return 0 114 } 115 // Prefix lookup matched a single allocation 116 alloc, _, err := client.Allocations().Info(allocs[0].ID, nil) 117 if err != nil { 118 c.Ui.Error(fmt.Sprintf("Error querying allocation: %s", err)) 119 return 1 120 } 121 122 // Format the allocation data 123 basic := []string{ 124 fmt.Sprintf("ID|%s", limit(alloc.ID, length)), 125 fmt.Sprintf("Eval ID|%s", limit(alloc.EvalID, length)), 126 fmt.Sprintf("Name|%s", alloc.Name), 127 fmt.Sprintf("Node ID|%s", limit(alloc.NodeID, length)), 128 fmt.Sprintf("Job ID|%s", alloc.JobID), 129 fmt.Sprintf("Client Status|%s", alloc.ClientStatus), 130 } 131 132 if verbose { 133 basic = append(basic, 134 fmt.Sprintf("Evaluated Nodes|%d", alloc.Metrics.NodesEvaluated), 135 fmt.Sprintf("Filtered Nodes|%d", alloc.Metrics.NodesFiltered), 136 fmt.Sprintf("Exhausted Nodes|%d", alloc.Metrics.NodesExhausted), 137 fmt.Sprintf("Allocation Time|%s", alloc.Metrics.AllocationTime), 138 fmt.Sprintf("Failures|%d", alloc.Metrics.CoalescedFailures)) 139 } 140 c.Ui.Output(formatKV(basic)) 141 142 if !short { 143 c.taskResources(alloc) 144 } 145 146 // Print the state of each task. 147 if short { 148 c.shortTaskStatus(alloc) 149 } else { 150 c.taskStatus(alloc) 151 } 152 153 // Format the detailed status 154 if verbose || alloc.DesiredStatus == "failed" { 155 c.Ui.Output("\n==> Status") 156 dumpAllocStatus(c.Ui, alloc, length) 157 } 158 159 return 0 160 } 161 162 // shortTaskStatus prints out the current state of each task. 163 func (c *AllocStatusCommand) shortTaskStatus(alloc *api.Allocation) { 164 tasks := make([]string, 0, len(alloc.TaskStates)+1) 165 tasks = append(tasks, "Name|State|Last Event|Time") 166 for task := range c.sortedTaskStateIterator(alloc.TaskStates) { 167 state := alloc.TaskStates[task] 168 lastState := state.State 169 var lastEvent, lastTime string 170 171 l := len(state.Events) 172 if l != 0 { 173 last := state.Events[l-1] 174 lastEvent = last.Type 175 lastTime = c.formatUnixNanoTime(last.Time) 176 } 177 178 tasks = append(tasks, fmt.Sprintf("%s|%s|%s|%s", 179 task, lastState, lastEvent, lastTime)) 180 } 181 182 c.Ui.Output("\n==> Tasks") 183 c.Ui.Output(formatList(tasks)) 184 } 185 186 // taskStatus prints out the most recent events for each task. 187 func (c *AllocStatusCommand) taskStatus(alloc *api.Allocation) { 188 for task := range c.sortedTaskStateIterator(alloc.TaskStates) { 189 state := alloc.TaskStates[task] 190 events := make([]string, len(state.Events)+1) 191 events[0] = "Time|Type|Description" 192 193 size := len(state.Events) 194 for i, event := range state.Events { 195 formatedTime := c.formatUnixNanoTime(event.Time) 196 197 // Build up the description based on the event type. 198 var desc string 199 switch event.Type { 200 case api.TaskStarted: 201 desc = "Task started by client" 202 case api.TaskReceived: 203 desc = "Task received by client" 204 case api.TaskFailedValidation: 205 if event.ValidationError != "" { 206 desc = event.ValidationError 207 } else { 208 desc = "Validation of task failed" 209 } 210 case api.TaskDriverFailure: 211 if event.DriverError != "" { 212 desc = event.DriverError 213 } else { 214 desc = "Failed to start task" 215 } 216 case api.TaskDownloadingArtifacts: 217 desc = "Client is downloading artifacts" 218 case api.TaskArtifactDownloadFailed: 219 if event.DownloadError != "" { 220 desc = event.DownloadError 221 } else { 222 desc = "Failed to download artifacts" 223 } 224 case api.TaskKilled: 225 if event.KillError != "" { 226 desc = event.KillError 227 } else { 228 desc = "Task successfully killed" 229 } 230 case api.TaskTerminated: 231 var parts []string 232 parts = append(parts, fmt.Sprintf("Exit Code: %d", event.ExitCode)) 233 234 if event.Signal != 0 { 235 parts = append(parts, fmt.Sprintf("Signal: %d", event.Signal)) 236 } 237 238 if event.Message != "" { 239 parts = append(parts, fmt.Sprintf("Exit Message: %q", event.Message)) 240 } 241 desc = strings.Join(parts, ", ") 242 case api.TaskRestarting: 243 in := fmt.Sprintf("Task restarting in %v", time.Duration(event.StartDelay)) 244 if event.RestartReason != "" && event.RestartReason != client.ReasonWithinPolicy { 245 desc = fmt.Sprintf("%s - %s", event.RestartReason, in) 246 } else { 247 desc = in 248 } 249 case api.TaskNotRestarting: 250 if event.RestartReason != "" { 251 desc = event.RestartReason 252 } else { 253 desc = "Task exceeded restart policy" 254 } 255 } 256 257 // Reverse order so we are sorted by time 258 events[size-i] = fmt.Sprintf("%s|%s|%s", formatedTime, event.Type, desc) 259 } 260 261 c.Ui.Output(fmt.Sprintf("\n==> Task %q is %q\nRecent Events:", task, state.State)) 262 c.Ui.Output(formatList(events)) 263 } 264 } 265 266 // formatUnixNanoTime is a helper for formating time for output. 267 func (c *AllocStatusCommand) formatUnixNanoTime(nano int64) string { 268 t := time.Unix(0, nano) 269 return formatTime(t) 270 } 271 272 // sortedTaskStateIterator is a helper that takes the task state map and returns a 273 // channel that returns the keys in a sorted order. 274 func (c *AllocStatusCommand) sortedTaskStateIterator(m map[string]*api.TaskState) <-chan string { 275 output := make(chan string, len(m)) 276 keys := make([]string, len(m)) 277 i := 0 278 for k := range m { 279 keys[i] = k 280 i++ 281 } 282 sort.Strings(keys) 283 284 for _, key := range keys { 285 output <- key 286 } 287 288 close(output) 289 return output 290 } 291 292 // allocResources prints out the allocation current resource usage 293 func (c *AllocStatusCommand) allocResources(alloc *api.Allocation) { 294 resources := make([]string, 2) 295 resources[0] = "CPU|Memory MB|Disk MB|IOPS" 296 resources[1] = fmt.Sprintf("%v|%v|%v|%v", 297 alloc.Resources.CPU, 298 alloc.Resources.MemoryMB, 299 alloc.Resources.DiskMB, 300 alloc.Resources.IOPS) 301 c.Ui.Output(formatList(resources)) 302 } 303 304 // taskResources prints out the tasks current resource usage 305 func (c *AllocStatusCommand) taskResources(alloc *api.Allocation) { 306 if len(alloc.TaskResources) == 0 { 307 return 308 } 309 310 // Sort the tasks. 311 tasks := make([]string, 0, len(alloc.TaskResources)) 312 for task := range alloc.TaskResources { 313 tasks = append(tasks, task) 314 } 315 sort.Strings(tasks) 316 317 c.Ui.Output("\n==> Task Resources") 318 firstLine := true 319 for _, task := range tasks { 320 resource := alloc.TaskResources[task] 321 322 header := fmt.Sprintf("\nTask: %q", task) 323 if firstLine { 324 header = fmt.Sprintf("Task: %q", task) 325 firstLine = false 326 } 327 c.Ui.Output(header) 328 var addr []string 329 for _, nw := range resource.Networks { 330 ports := append(nw.DynamicPorts, nw.ReservedPorts...) 331 for _, port := range ports { 332 addr = append(addr, fmt.Sprintf("%v: %v:%v\n", port.Label, nw.IP, port.Value)) 333 } 334 } 335 var resourcesOutput []string 336 resourcesOutput = append(resourcesOutput, "CPU|Memory MB|Disk MB|IOPS|Addresses") 337 firstAddr := "" 338 if len(addr) > 0 { 339 firstAddr = addr[0] 340 } 341 resourcesOutput = append(resourcesOutput, fmt.Sprintf("%v|%v|%v|%v|%v", 342 resource.CPU, 343 resource.MemoryMB, 344 resource.DiskMB, 345 resource.IOPS, 346 firstAddr)) 347 for i := 1; i < len(addr); i++ { 348 resourcesOutput = append(resourcesOutput, fmt.Sprintf("||||%v", addr[i])) 349 } 350 c.Ui.Output(formatListWithSpaces(resourcesOutput)) 351 } 352 }