Skip to content

Commit 37e7842

Browse files
committed
client, cmd/snap, daemon: refactor REST API for quotas to match CLI org
The CLI has organization where the current and constraint keys are maps of the unit of measure for the quota group to the value, so update the REST API to match this. Signed-off-by: Ian Johnson <ian.johnson@canonical.com>
1 parent d659fa8 commit 37e7842

File tree

7 files changed

+203
-115
lines changed

7 files changed

+203
-115
lines changed

client/quota.go

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -24,29 +24,30 @@ import (
2424
"encoding/json"
2525
"fmt"
2626

27+
"github.com/snapcore/snapd/gadget/quantity"
2728
"golang.org/x/xerrors"
2829
)
2930

3031
type postQuotaData struct {
31-
Action string `json:"action"`
32-
GroupName string `json:"group-name"`
33-
Parent string `json:"parent,omitempty"`
34-
Snaps []string `json:"snaps,omitempty"`
35-
MaxMemory uint64 `json:"max-memory,omitempty"`
32+
Action string `json:"action"`
33+
GroupName string `json:"group-name"`
34+
Parent string `json:"parent,omitempty"`
35+
Snaps []string `json:"snaps,omitempty"`
36+
Constraints map[string]string `json:"constraints,omitempty"`
3637
}
3738

3839
type QuotaGroupResult struct {
39-
GroupName string `json:"group-name"`
40-
Parent string `json:"parent,omitempty"`
41-
Subgroups []string `json:"subgroups,omitempty"`
42-
Snaps []string `json:"snaps,omitempty"`
43-
MaxMemory uint64 `json:"max-memory"`
44-
CurrentMemory uint64 `json:"current-memory"`
40+
GroupName string `json:"group-name"`
41+
Parent string `json:"parent,omitempty"`
42+
Subgroups []string `json:"subgroups,omitempty"`
43+
Snaps []string `json:"snaps,omitempty"`
44+
Constraints map[string]string `json:"constraints,omitempty"`
45+
Current map[string]string `json:"current,omitempty"`
4546
}
4647

4748
// EnsureQuota creates a quota group or updates an existing group.
4849
// The list of snaps can be empty.
49-
func (client *Client) EnsureQuota(groupName string, parent string, snaps []string, maxMemory uint64) (changeID string, err error) {
50+
func (client *Client) EnsureQuota(groupName string, parent string, snaps []string, maxMemory quantity.Size) (changeID string, err error) {
5051
if groupName == "" {
5152
return "", xerrors.Errorf("cannot create or update quota group without a name")
5253
}
@@ -57,7 +58,9 @@ func (client *Client) EnsureQuota(groupName string, parent string, snaps []strin
5758
GroupName: groupName,
5859
Parent: parent,
5960
Snaps: snaps,
60-
MaxMemory: maxMemory,
61+
Constraints: map[string]string{
62+
"memory": maxMemory.String(),
63+
},
6164
}
6265

6366
var body bytes.Buffer

client/quota_test.go

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,9 @@ func (cs *clientSuite) TestEnsureQuotaGroup(c *check.C) {
5656
"group-name": "foo",
5757
"parent": "bar",
5858
"snaps": []interface{}{"snap-a", "snap-b"},
59-
"max-memory": float64(1001),
59+
"constraints": map[string]interface{}{
60+
"memory": "1001",
61+
},
6062
})
6163
}
6264

@@ -76,20 +78,27 @@ func (cs *clientSuite) TestGetQuotaGroup(c *check.C) {
7678
cs.rsp = `{
7779
"type": "sync",
7880
"status-code": 200,
79-
"result": {"group-name":"foo", "parent":"bar", "subgroups":["foo-subgrp"], "snaps":["snap-a"], "max-memory":999, "current-memory":450}
81+
"result": {
82+
"group-name":"foo",
83+
"parent":"bar",
84+
"subgroups":["foo-subgrp"],
85+
"snaps":["snap-a"],
86+
"constraints": { "memory": "999" },
87+
"current": { "memory": "450" }
88+
}
8089
}`
8190

8291
grp, err := cs.cli.GetQuotaGroup("foo")
8392
c.Assert(err, check.IsNil)
8493
c.Check(cs.req.Method, check.Equals, "GET")
8594
c.Check(cs.req.URL.Path, check.Equals, "/v2/quotas/foo")
8695
c.Check(grp, check.DeepEquals, &client.QuotaGroupResult{
87-
GroupName: "foo",
88-
Parent: "bar",
89-
Subgroups: []string{"foo-subgrp"},
90-
MaxMemory: 999,
91-
CurrentMemory: 450,
92-
Snaps: []string{"snap-a"},
96+
GroupName: "foo",
97+
Parent: "bar",
98+
Subgroups: []string{"foo-subgrp"},
99+
Constraints: map[string]string{"memory": "999"},
100+
Current: map[string]string{"memory": "450"},
101+
Snaps: []string{"snap-a"},
93102
})
94103
}
95104

cmd/snap/cmd_quota.go

Lines changed: 67 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@ package main
2222
import (
2323
"fmt"
2424
"sort"
25+
"strconv"
2526
"strings"
2627

2728
"github.com/jessevdk/go-flags"
2829

2930
"github.com/snapcore/snapd/client"
31+
"github.com/snapcore/snapd/gadget/quantity"
3032
"github.com/snapcore/snapd/i18n"
3133
"github.com/snapcore/snapd/strutil"
3234
)
@@ -162,7 +164,7 @@ func (x *cmdSetQuota) Execute(args []string) (err error) {
162164
// orphan a sub-group to no longer have a parent, but currently it just
163165
// means leave the group with whatever parent it has, or if it doesn't
164166
// currently exist, create the group without a parent group
165-
chgID, err = x.client.EnsureQuota(x.Positional.GroupName, x.Parent, names, uint64(mem))
167+
chgID, err = x.client.EnsureQuota(x.Positional.GroupName, x.Parent, names, quantity.Size(mem))
166168
if err != nil {
167169
return err
168170
}
@@ -220,10 +222,35 @@ func (x *cmdQuota) Execute(args []string) (err error) {
220222
if group.Parent != "" {
221223
fmt.Fprintf(w, "parent:\t%s\n", group.Parent)
222224
}
225+
223226
fmt.Fprintf(w, "constraints:\n")
224-
fmt.Fprintf(w, " memory:\t%s\n", strings.TrimSpace(fmtSize(int64(group.MaxMemory))))
227+
for constraintKey, val := range group.Constraints {
228+
if constraintKey == "memory" {
229+
// parse the memory value into a quantity.Size and then format it
230+
// with appropriate units
231+
var err error
232+
val, err = quantitySizeStrToUnitFormat(val, "memory constraint")
233+
if err != nil {
234+
return err
235+
}
236+
}
237+
fmt.Fprintf(w, " %s:\t%s\n", constraintKey, val)
238+
}
239+
225240
fmt.Fprintf(w, "current:\n")
226-
fmt.Fprintf(w, " memory:\t%s\n", strings.TrimSpace(fmtSize(int64(group.CurrentMemory))))
241+
for currentKey, val := range group.Current {
242+
if currentKey == "memory" {
243+
// parse the memory value into a quantity.Size and then format it
244+
// with appropriate units
245+
var err error
246+
val, err = quantitySizeStrToUnitFormat(val, "memory current usage")
247+
if err != nil {
248+
return err
249+
}
250+
}
251+
fmt.Fprintf(w, " %s:\t%s\n", currentKey, val)
252+
}
253+
227254
if len(group.Subgroups) > 0 {
228255
fmt.Fprint(w, "subgroups:\n")
229256
for _, name := range group.Subgroups {
@@ -240,6 +267,14 @@ func (x *cmdQuota) Execute(args []string) (err error) {
240267
return nil
241268
}
242269

270+
func quantitySizeStrToUnitFormat(s string, errMsg string) (string, error) {
271+
valSize, err := strconv.ParseUint(s, 10, 64)
272+
if err != nil {
273+
return "", fmt.Errorf("unable to parse %s (%q) as uint: %v", errMsg, s, err)
274+
}
275+
return strings.TrimSpace(fmtSize(int64(valSize))), nil
276+
}
277+
243278
type cmdRemoveQuota struct {
244279
waitMixin
245280

@@ -281,17 +316,39 @@ func (x *cmdQuotas) Execute(args []string) (err error) {
281316
w := tabWriter()
282317
fmt.Fprintf(w, "Quota\tParent\tConstraints\tCurrent\n")
283318
err = processQuotaGroupsTree(res, func(q *client.QuotaGroupResult) {
284-
constraintMem := ""
285-
if q.MaxMemory != 0 {
286-
constraintMem = "memory=" + strings.TrimSpace(fmtSize(int64(q.MaxMemory)))
319+
constraintVals := []string{}
320+
for constraintKey, val := range q.Constraints {
321+
if constraintKey == "memory" {
322+
out, err := quantitySizeStrToUnitFormat(val, "memory constraint")
323+
if err != nil {
324+
fmt.Fprintln(Stderr, err)
325+
}
326+
val = out
327+
}
328+
329+
constraintVals = append(constraintVals, fmt.Sprintf("%s=%s", constraintKey, val))
287330
}
331+
constraints := strings.Join(constraintVals, ",")
332+
333+
currentVals := []string{}
334+
for currentKey, val := range q.Current {
335+
if currentKey == "memory" {
336+
// skip reporting memory if it is exactly zero
337+
if val == "" || val == "0" {
338+
continue
339+
}
340+
out, err := quantitySizeStrToUnitFormat(val, "current memory usage")
341+
if err != nil {
342+
fmt.Fprintln(Stderr, err)
343+
}
344+
val = out
345+
}
288346

289-
currentMem := ""
290-
if q.CurrentMemory != 0 {
291-
currentMem = "memory=" + strings.TrimSpace(fmtSize(int64(q.CurrentMemory)))
347+
currentVals = append(currentVals, fmt.Sprintf("%s=%s", currentKey, val))
292348
}
349+
currents := strings.Join(currentVals, ",")
293350

294-
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", q.GroupName, q.Parent, constraintMem, currentMem)
351+
fmt.Fprintf(w, "%s\t%s\t%s\t%s\n", q.GroupName, q.Parent, constraints, currents)
295352
})
296353
if err != nil {
297354
return err

cmd/snap/cmd_quota_test.go

Lines changed: 31 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -92,21 +92,20 @@ func dispatchFakeHandlers(c *check.C, routes map[string]http.HandlerFunc) func(w
9292
}
9393

9494
type fakeQuotaGroupPostHandlerOpts struct {
95-
action string
96-
body string
97-
groupName string
98-
parentName string
99-
snaps []string
100-
maxMemory int64
101-
currentMemory int64
95+
action string
96+
body string
97+
groupName string
98+
parentName string
99+
snaps []string
100+
maxMemory int64
102101
}
103102

104103
type quotasEnsureBody struct {
105-
Action string `json:"action"`
106-
GroupName string `json:"group-name,omitempty"`
107-
ParentName string `json:"parent,omitempty"`
108-
Snaps []string `json:"snaps,omitempty"`
109-
MaxMemory int64 `json:"max-memory,omitempty"`
104+
Action string `json:"action"`
105+
GroupName string `json:"group-name,omitempty"`
106+
ParentName string `json:"parent,omitempty"`
107+
Snaps []string `json:"snaps,omitempty"`
108+
Constraints map[string]string `json:"constraints,omitempty"`
110109
}
111110

112111
func makeFakeQuotaPostHandler(c *check.C, opts fakeQuotaGroupPostHandlerOpts) func(w http.ResponseWriter, r *http.Request) {
@@ -127,11 +126,11 @@ func makeFakeQuotaPostHandler(c *check.C, opts fakeQuotaGroupPostHandlerOpts) fu
127126
c.Check(string(buf), check.Equals, fmt.Sprintf(`{"action":"remove","group-name":%q}`+"\n", opts.groupName))
128127
case "ensure":
129128
exp := quotasEnsureBody{
130-
Action: "ensure",
131-
GroupName: opts.groupName,
132-
ParentName: opts.parentName,
133-
Snaps: opts.snaps,
134-
MaxMemory: opts.maxMemory,
129+
Action: "ensure",
130+
GroupName: opts.groupName,
131+
ParentName: opts.parentName,
132+
Snaps: opts.snaps,
133+
Constraints: map[string]string{"memory": fmt.Sprintf("%d", opts.maxMemory)},
135134
}
136135

137136
postJSON := quotasEnsureBody{}
@@ -197,8 +196,8 @@ func (s *quotaSuite) TestGetQuotaGroup(c *check.C) {
197196
"parent":"bar",
198197
"subgroups":["subgrp1"],
199198
"snaps":["snap-a","snap-b"],
200-
"max-memory":1000,
201-
"current-memory":900
199+
"constraints": { "memory": "1000" },
200+
"current": { "memory": "900" }
202201
}
203202
}`
204203

@@ -232,8 +231,8 @@ func (s *quotaSuite) TestGetQuotaGroupSimple(c *check.C) {
232231
"status-code": 200,
233232
"result": {
234233
"group-name": "foo",
235-
"max-memory": 1000,
236-
"current-memory": %d
234+
"constraints": {"memory": "1000"},
235+
"current": {"memory": "%d"}
237236
}
238237
}`
239238

@@ -354,8 +353,8 @@ func (s *quotaSuite) TestSetQuotaGroupUpdateExisting(c *check.C) {
354353
"status-code": 200,
355354
"result": {
356355
"group-name":"foo",
357-
"max-memory": %d,
358-
"current-memory":500
356+
"constraints": { "memory": "%d" },
357+
"current": { "memory": "500" }
359358
}
360359
}`
361360

@@ -437,13 +436,14 @@ func (s *quotaSuite) TestGetAllQuotaGroups(c *check.C) {
437436

438437
s.RedirectClientToTestServer(makeFakeGetQuotaGroupsHandler(c,
439438
`{"type": "sync", "status-code": 200, "result": [
440-
{"group-name":"aaa","subgroups":["ccc","ddd"],"parent":"zzz","max-memory":1000},
441-
{"group-name":"ddd","parent":"aaa","max-memory":400},
442-
{"group-name":"bbb","parent":"zzz","max-memory":1000,"current-memory":400},
443-
{"group-name":"yyyyyyy","max-memory":1000},
444-
{"group-name":"zzz","subgroups":["bbb","aaa"],"max-memory":5000},
445-
{"group-name":"ccc","parent":"aaa","max-memory":400},
446-
{"group-name":"xxx","max-memory":9900,"current-memory":9999}
439+
{"group-name":"aaa","subgroups":["ccc","ddd","fff"],"parent":"zzz","constraints":{"memory":"1000"}},
440+
{"group-name":"ddd","parent":"aaa","constraints":{"memory":"400"}},
441+
{"group-name":"bbb","parent":"zzz","constraints":{"memory":"1000"},"current":{"memory":"400"}},
442+
{"group-name":"yyyyyyy","constraints":{"memory":"1000"}},
443+
{"group-name":"zzz","subgroups":["bbb","aaa"],"constraints":{"memory":"5000"}},
444+
{"group-name":"ccc","parent":"aaa","constraints":{"memory":"400"}},
445+
{"group-name":"fff","parent":"aaa","constraints":{"memory":"1000"},"current":{"memory":"0"}},
446+
{"group-name":"xxx","constraints":{"memory":"9900"},"current":{"memory":"10000"}}
447447
]}`))
448448

449449
rest, err := main.Parser(main.Client()).ParseArgs([]string{"quotas"})
@@ -458,6 +458,7 @@ zzz memory=5000B
458458
aaa zzz memory=1000B
459459
ccc aaa memory=400B
460460
ddd aaa memory=400B
461+
fff aaa memory=1000B
461462
bbb zzz memory=1000B memory=400B
462463
`[1:])
463464
}

0 commit comments

Comments
 (0)