github.com/djenriquez/nomad-1@v0.8.1/command/quota_status.go (about)

     1  package command
     2  
     3  import (
     4  	"encoding/base64"
     5  	"fmt"
     6  	"sort"
     7  	"strconv"
     8  	"strings"
     9  
    10  	"github.com/hashicorp/nomad/api"
    11  	"github.com/posener/complete"
    12  )
    13  
    14  type QuotaStatusCommand struct {
    15  	Meta
    16  }
    17  
    18  func (c *QuotaStatusCommand) Help() string {
    19  	helpText := `
    20  Usage: nomad quota status [options] <quota>
    21  
    22    Status is used to view the status of a particular quota specification.
    23  
    24  General Options:
    25  
    26    ` + generalOptionsUsage()
    27  
    28  	return strings.TrimSpace(helpText)
    29  }
    30  
    31  func (c *QuotaStatusCommand) AutocompleteFlags() complete.Flags {
    32  	return c.Meta.AutocompleteFlags(FlagSetClient)
    33  }
    34  
    35  func (c *QuotaStatusCommand) AutocompleteArgs() complete.Predictor {
    36  	return QuotaPredictor(c.Meta.Client)
    37  }
    38  
    39  func (c *QuotaStatusCommand) Synopsis() string {
    40  	return "Display a quota's status and current usage"
    41  }
    42  
    43  func (c *QuotaStatusCommand) Run(args []string) int {
    44  	flags := c.Meta.FlagSet("quota status", FlagSetClient)
    45  	flags.Usage = func() { c.Ui.Output(c.Help()) }
    46  
    47  	if err := flags.Parse(args); err != nil {
    48  		return 1
    49  	}
    50  
    51  	// Check that we got one arguments
    52  	args = flags.Args()
    53  	if l := len(args); l != 1 {
    54  		c.Ui.Error(c.Help())
    55  		return 1
    56  	}
    57  
    58  	name := args[0]
    59  
    60  	// Get the HTTP client
    61  	client, err := c.Meta.Client()
    62  	if err != nil {
    63  		c.Ui.Error(fmt.Sprintf("Error initializing client: %s", err))
    64  		return 1
    65  	}
    66  
    67  	// Do a prefix lookup
    68  	quotas := client.Quotas()
    69  	spec, possible, err := getQuota(quotas, name)
    70  	if err != nil {
    71  		c.Ui.Error(fmt.Sprintf("Error retrieving quota: %s", err))
    72  		return 1
    73  	}
    74  
    75  	if len(possible) != 0 {
    76  		c.Ui.Error(fmt.Sprintf("Prefix matched multiple quotas\n\n%s", formatQuotaSpecs(possible)))
    77  		return 1
    78  	}
    79  
    80  	// Format the basics
    81  	c.Ui.Output(formatQuotaSpecBasics(spec))
    82  
    83  	// Get the quota usages
    84  	usages, failures := quotaUsages(spec, quotas)
    85  
    86  	// Format the limits
    87  	c.Ui.Output(c.Colorize().Color("\n[bold]Quota Limits[reset]"))
    88  	c.Ui.Output(formatQuotaLimits(spec, usages))
    89  
    90  	// Display any failures
    91  	if len(failures) != 0 {
    92  		c.Ui.Error(c.Colorize().Color("\n[bold][red]Lookup Failures[reset]"))
    93  		for region, failure := range failures {
    94  			c.Ui.Error(fmt.Sprintf("  * Failed to retrieve quota usage for region %q: %v", region, failure))
    95  			return 1
    96  		}
    97  	}
    98  
    99  	return 0
   100  }
   101  
   102  // quotaUsages returns the quota usages for the limits described by the spec. It
   103  // will make a request to each referenced Nomad region. If the region couldn't
   104  // be contacted, the error will be stored in the failures map
   105  func quotaUsages(spec *api.QuotaSpec, client *api.Quotas) (usages map[string]*api.QuotaUsage, failures map[string]error) {
   106  	// Determine the regions we have limits for
   107  	regions := make(map[string]struct{})
   108  	for _, limit := range spec.Limits {
   109  		regions[limit.Region] = struct{}{}
   110  	}
   111  
   112  	usages = make(map[string]*api.QuotaUsage, len(regions))
   113  	failures = make(map[string]error)
   114  	q := api.QueryOptions{}
   115  
   116  	// Retrieve the usage per region
   117  	for region := range regions {
   118  		q.Region = region
   119  		usage, _, err := client.Usage(spec.Name, &q)
   120  		if err != nil {
   121  			failures[region] = err
   122  			continue
   123  		}
   124  
   125  		usages[region] = usage
   126  	}
   127  
   128  	return usages, failures
   129  }
   130  
   131  // formatQuotaSpecBasics formats the basic information of the quota
   132  // specification.
   133  func formatQuotaSpecBasics(spec *api.QuotaSpec) string {
   134  	basic := []string{
   135  		fmt.Sprintf("Name|%s", spec.Name),
   136  		fmt.Sprintf("Description|%s", spec.Description),
   137  		fmt.Sprintf("Limits|%d", len(spec.Limits)),
   138  	}
   139  
   140  	return formatKV(basic)
   141  }
   142  
   143  // formatQuotaLimits formats the limits to display the quota usage versus the
   144  // limit per quota limit. It takes as input the specification as well as quota
   145  // usage by region. The formatter handles missing usages.
   146  func formatQuotaLimits(spec *api.QuotaSpec, usages map[string]*api.QuotaUsage) string {
   147  	if len(spec.Limits) == 0 {
   148  		return "No quota limits defined"
   149  	}
   150  
   151  	// Sort the limits
   152  	sort.Sort(api.QuotaLimitSort(spec.Limits))
   153  
   154  	limits := make([]string, len(spec.Limits)+1)
   155  	limits[0] = "Region|CPU Usage|Memory Usage"
   156  	i := 0
   157  	for _, specLimit := range spec.Limits {
   158  		i++
   159  
   160  		// lookupUsage returns the regions quota usage for the limit
   161  		lookupUsage := func() (*api.QuotaLimit, bool) {
   162  			usage, ok := usages[specLimit.Region]
   163  			if !ok {
   164  				return nil, false
   165  			}
   166  
   167  			used, ok := usage.Used[base64.StdEncoding.EncodeToString(specLimit.Hash)]
   168  			return used, ok
   169  		}
   170  
   171  		used, ok := lookupUsage()
   172  		if !ok {
   173  			cpu := fmt.Sprintf("- / %s", formatQuotaLimitInt(specLimit.RegionLimit.CPU))
   174  			memory := fmt.Sprintf("- / %s", formatQuotaLimitInt(specLimit.RegionLimit.MemoryMB))
   175  			limits[i] = fmt.Sprintf("%s|%s|%s", specLimit.Region, cpu, memory)
   176  			continue
   177  		}
   178  
   179  		cpu := fmt.Sprintf("%d / %s", *used.RegionLimit.CPU, formatQuotaLimitInt(specLimit.RegionLimit.CPU))
   180  		memory := fmt.Sprintf("%d / %s", *used.RegionLimit.MemoryMB, formatQuotaLimitInt(specLimit.RegionLimit.MemoryMB))
   181  		limits[i] = fmt.Sprintf("%s|%s|%s", specLimit.Region, cpu, memory)
   182  	}
   183  
   184  	return formatList(limits)
   185  }
   186  
   187  // formatQuotaLimitInt takes a integer resource value and returns the
   188  // appropriate string for output.
   189  func formatQuotaLimitInt(value *int) string {
   190  	if value == nil {
   191  		return "-"
   192  	}
   193  
   194  	v := *value
   195  	if v < 0 {
   196  		return "0"
   197  	} else if v == 0 {
   198  		return "inf"
   199  	}
   200  
   201  	return strconv.Itoa(v)
   202  }
   203  
   204  func getQuota(client *api.Quotas, quota string) (match *api.QuotaSpec, possible []*api.QuotaSpec, err error) {
   205  	// Do a prefix lookup
   206  	quotas, _, err := client.PrefixList(quota, nil)
   207  	if err != nil {
   208  		return nil, nil, err
   209  	}
   210  
   211  	l := len(quotas)
   212  	switch {
   213  	case l == 0:
   214  		return nil, nil, fmt.Errorf("Quota %q matched no quotas", quota)
   215  	case l == 1:
   216  		return quotas[0], nil, nil
   217  	default:
   218  		return nil, quotas, nil
   219  	}
   220  }