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