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  }