github.com/ilhicas/nomad@v0.11.8/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|Network 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  		specBits := 0
   175  		if len(specLimit.RegionLimit.Networks) == 1 {
   176  			specBits = *specLimit.RegionLimit.Networks[0].MBits
   177  		}
   178  
   179  		used, ok := lookupUsage()
   180  		if !ok {
   181  			cpu := fmt.Sprintf("- / %s", formatQuotaLimitInt(specLimit.RegionLimit.CPU))
   182  			memory := fmt.Sprintf("- / %s", formatQuotaLimitInt(specLimit.RegionLimit.MemoryMB))
   183  			net := fmt.Sprintf("- / %s", formatQuotaLimitInt(&specBits))
   184  			limits[i] = fmt.Sprintf("%s|%s|%s|%s", specLimit.Region, cpu, memory, net)
   185  			continue
   186  		}
   187  
   188  		cpu := fmt.Sprintf("%d / %s", *used.RegionLimit.CPU, formatQuotaLimitInt(specLimit.RegionLimit.CPU))
   189  		memory := fmt.Sprintf("%d / %s", *used.RegionLimit.MemoryMB, formatQuotaLimitInt(specLimit.RegionLimit.MemoryMB))
   190  		net := fmt.Sprintf("- / %s", formatQuotaLimitInt(&specBits))
   191  		if len(used.RegionLimit.Networks) == 1 {
   192  			net = fmt.Sprintf("%d / %s", *used.RegionLimit.Networks[0].MBits, formatQuotaLimitInt(&specBits))
   193  		}
   194  		limits[i] = fmt.Sprintf("%s|%s|%s|%s", specLimit.Region, cpu, memory, net)
   195  	}
   196  
   197  	return formatList(limits)
   198  }
   199  
   200  // formatQuotaLimitInt takes a integer resource value and returns the
   201  // appropriate string for output.
   202  func formatQuotaLimitInt(value *int) string {
   203  	if value == nil {
   204  		return "-"
   205  	}
   206  
   207  	v := *value
   208  	if v < 0 {
   209  		return "0"
   210  	} else if v == 0 {
   211  		return "inf"
   212  	}
   213  
   214  	return strconv.Itoa(v)
   215  }
   216  
   217  func getQuota(client *api.Quotas, quota string) (match *api.QuotaSpec, possible []*api.QuotaSpec, err error) {
   218  	// Do a prefix lookup
   219  	quotas, _, err := client.PrefixList(quota, nil)
   220  	if err != nil {
   221  		return nil, nil, err
   222  	}
   223  
   224  	l := len(quotas)
   225  	switch {
   226  	case l == 0:
   227  		return nil, nil, fmt.Errorf("Quota %q matched no quotas", quota)
   228  	case l == 1:
   229  		return quotas[0], nil, nil
   230  	default:
   231  		return nil, quotas, nil
   232  	}
   233  }