github.com/blixtra/nomad@v0.7.2-0.20171221000451-da9a1d7bb050/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 }