github.com/rohankumardubey/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 }