Skip to content

Commit c569177

Browse files
committed
many: add frontend for journal quotas
1 parent ee1a45b commit c569177

File tree

6 files changed

+111
-17
lines changed

6 files changed

+111
-17
lines changed

client/quota.go

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"bytes"
2424
"encoding/json"
2525
"fmt"
26+
"time"
2627

2728
"github.com/snapcore/snapd/gadget/quantity"
2829
)
@@ -53,11 +54,18 @@ type QuotaCPUSetValues struct {
5354
CPUs []int `json:"cpus,omitempty"`
5455
}
5556

57+
type QuotaJournalValues struct {
58+
Size quantity.Size `json:"size,omitempty"`
59+
RateCount int `json:"rate-count,omitempty"`
60+
RatePeriod time.Duration `json:"rate-period,omitempty"`
61+
}
62+
5663
type QuotaValues struct {
57-
Memory quantity.Size `json:"memory,omitempty"`
58-
CPU *QuotaCPUValues `json:"cpu,omitempty"`
59-
CPUSet *QuotaCPUSetValues `json:"cpu-set,omitempty"`
60-
Threads int `json:"threads,omitempty"`
64+
Memory quantity.Size `json:"memory,omitempty"`
65+
CPU *QuotaCPUValues `json:"cpu,omitempty"`
66+
CPUSet *QuotaCPUSetValues `json:"cpu-set,omitempty"`
67+
Threads int `json:"threads,omitempty"`
68+
Journal *QuotaJournalValues `json:"journal,omitempty"`
6169
}
6270

6371
// EnsureQuota creates a quota group or updates an existing group.

client/quota_test.go

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ import (
2323
"bytes"
2424
"encoding/json"
2525
"io/ioutil"
26+
"time"
2627

2728
"gopkg.in/check.v1"
2829

@@ -54,6 +55,11 @@ func (cs *clientSuite) TestEnsureQuotaGroup(c *check.C) {
5455
CPUs: []int{0},
5556
},
5657
Threads: 32,
58+
Journal: &client.QuotaJournalValues{
59+
Size: quantity.SizeMiB,
60+
RateCount: 150,
61+
RatePeriod: time.Minute,
62+
},
5763
}
5864

5965
chgID, err := cs.cli.EnsureQuota("foo", "bar", []string{"snap-a", "snap-b"}, quotaValues)
@@ -81,6 +87,11 @@ func (cs *clientSuite) TestEnsureQuotaGroup(c *check.C) {
8187
"cpus": []interface{}{json.Number("0")},
8288
},
8389
"threads": json.Number("32"),
90+
"journal": map[string]interface{}{
91+
"size": json.Number("1048576"),
92+
"rate-count": json.Number("150"),
93+
"rate-period": json.Number("60000000000"),
94+
},
8495
},
8596
})
8697
}

cmd/snap/cmd_quota.go

Lines changed: 60 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ import (
2525
"sort"
2626
"strconv"
2727
"strings"
28+
"time"
2829

2930
"github.com/jessevdk/go-flags"
3031

@@ -90,6 +91,10 @@ The threads limit for a quota group can be increased but not decreased. To
9091
decrease the threads limit for a quota group, the entire group must be removed
9192
with the remove-quota command and recreated with a lower limit.
9293
94+
The journal limits can be increased and decreased after being set on a group.
95+
Setting a journal limit will cause the snaps in the group to be put into the same
96+
journal namespace. This will affect the behaviour of the log command.
97+
9398
New quotas can be set on existing quota groups, but existing quotas cannot be removed
9499
from a quota group, without removing and recreating the entire group.
95100
@@ -125,12 +130,14 @@ func init() {
125130
type cmdSetQuota struct {
126131
waitMixin
127132

128-
MemoryMax string `long:"memory" optional:"true"`
129-
CPUMax string `long:"cpu" optional:"true"`
130-
CPUSet string `long:"cpu-set" optional:"true"`
131-
ThreadsMax string `long:"threads" optional:"true"`
132-
Parent string `long:"parent" optional:"true"`
133-
Positional struct {
133+
MemoryMax string `long:"memory" optional:"true"`
134+
CPUMax string `long:"cpu" optional:"true"`
135+
CPUSet string `long:"cpu-set" optional:"true"`
136+
ThreadsMax string `long:"threads" optional:"true"`
137+
JournalSizeMax string `long:"journal-size" optional:"true"`
138+
JournalRateLimit string `long:"journal-rate-limit" optional:"true"`
139+
Parent string `long:"parent" optional:"true"`
140+
Positional struct {
134141
GroupName string `positional-arg-name:"<group-name>" required:"true"`
135142
Snaps []installedSnapName `positional-arg-name:"<snap>" optional:"true"`
136143
} `positional-args:"yes"`
@@ -165,6 +172,31 @@ func parseCpuQuota(cpuMax string) (count int, percentage int, err error) {
165172
return count, percentage, nil
166173
}
167174

175+
func parseJournalRateQuota(journalRateLimit string) (count int, period time.Duration, err error) {
176+
// the rate limit is a string of the form N/P, where N is the number of
177+
// messages and P is the period as a time string (e.g 5s)
178+
parseError := func(input string) error {
179+
return fmt.Errorf("cannot parse journal rate limit string %q", input)
180+
}
181+
182+
parts := strings.Split(journalRateLimit, "/")
183+
if len(parts) != 2 {
184+
return 0, 0, parseError(journalRateLimit)
185+
}
186+
187+
count, err = strconv.Atoi(parts[0])
188+
if err != nil || count == 0 {
189+
return 0, 0, parseError(journalRateLimit)
190+
}
191+
192+
period, err = time.ParseDuration(parts[1])
193+
if err != nil || period == 0 {
194+
return 0, 0, parseError(journalRateLimit)
195+
}
196+
197+
return count, period, nil
198+
}
199+
168200
func (x *cmdSetQuota) parseQuotas() (*client.QuotaValues, error) {
169201
var quotaValues client.QuotaValues
170202

@@ -215,11 +247,32 @@ func (x *cmdSetQuota) parseQuotas() (*client.QuotaValues, error) {
215247
quotaValues.Threads = int(value)
216248
}
217249

250+
if x.JournalSizeMax != "" || x.JournalRateLimit != "" {
251+
quotaValues.Journal = &client.QuotaJournalValues{}
252+
if x.JournalSizeMax != "" {
253+
value, err := strutil.ParseByteSize(x.JournalSizeMax)
254+
if err != nil {
255+
return nil, err
256+
}
257+
quotaValues.Journal.Size = quantity.Size(value)
258+
}
259+
260+
if x.JournalRateLimit != "" {
261+
count, period, err := parseJournalRateQuota(x.JournalRateLimit)
262+
if err != nil {
263+
return nil, err
264+
}
265+
quotaValues.Journal.RateCount = count
266+
quotaValues.Journal.RatePeriod = period
267+
}
268+
}
269+
218270
return &quotaValues, nil
219271
}
220272

221273
func (x *cmdSetQuota) hasQuotaSet() bool {
222-
return x.MemoryMax != "" || x.CPUMax != "" || x.CPUSet != "" || x.ThreadsMax != ""
274+
return x.MemoryMax != "" || x.CPUMax != "" || x.CPUSet != "" ||
275+
x.ThreadsMax != "" || x.JournalSizeMax != "" || x.JournalRateLimit != ""
223276
}
224277

225278
func (x *cmdSetQuota) Execute(args []string) (err error) {

cmd/snap/cmd_quota_test.go

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -203,10 +203,12 @@ func makeChangesHandler(c *check.C) func(w http.ResponseWriter, r *http.Request)
203203

204204
func (s *quotaSuite) TestParseQuotas(c *check.C) {
205205
for _, testData := range []struct {
206-
maxMemory string
207-
cpuMax string
208-
cpuSet string
209-
threadsMax string
206+
maxMemory string
207+
cpuMax string
208+
cpuSet string
209+
threadsMax string
210+
journalSizeMax string
211+
journalRateLimit string
210212

211213
// Use the JSON representation of the quota, as it's easier to handle in the test data
212214
quotas string
@@ -217,6 +219,11 @@ func (s *quotaSuite) TestParseQuotas(c *check.C) {
217219
{cpuMax: "40%", quotas: `{"cpu":{"percentage":40}}`},
218220
{cpuSet: "1,3", quotas: `{"cpu-set":{"cpus":[1,3]}}`},
219221
{threadsMax: "2", quotas: `{"threads":2}`},
222+
{journalSizeMax: "16MB", quotas: `{"journal":{"size":16000000}}`},
223+
{journalRateLimit: "10/15s", quotas: `{"journal":{"rate-count":10,"rate-period":15000000000}}`},
224+
{journalRateLimit: "1500/15ms", quotas: `{"journal":{"rate-count":1500,"rate-period":15000000}}`},
225+
{journalRateLimit: "1/15us", quotas: `{"journal":{"rate-count":1,"rate-period":15000}}`},
226+
220227
// Error cases
221228
{cpuMax: "ASD", err: `cannot parse cpu quota string "ASD"`},
222229
{cpuMax: "0x100%", err: `cannot parse cpu quota string "0x100%"`},
@@ -229,8 +236,12 @@ func (s *quotaSuite) TestParseQuotas(c *check.C) {
229236
{cpuSet: "0,-2", err: `cannot parse CPU set value "-2"`},
230237
{threadsMax: "xxx", err: `cannot use threads value "xxx"`},
231238
{threadsMax: "-3", err: `cannot use threads value "-3"`},
239+
{journalRateLimit: "0", err: `cannot parse journal rate limit string "0"`},
240+
{journalRateLimit: "x/5m", err: `cannot parse journal rate limit string "x\/5m"`},
241+
{journalRateLimit: "1/wow", err: `cannot parse journal rate limit string "1\/wow"`},
232242
} {
233-
quotas, err := main.ParseQuotaValues(testData.maxMemory, testData.cpuMax, testData.cpuSet, testData.threadsMax)
243+
quotas, err := main.ParseQuotaValues(testData.maxMemory, testData.cpuMax,
244+
testData.cpuSet, testData.threadsMax, testData.journalSizeMax, testData.journalRateLimit)
234245
testLabel := check.Commentf("%v", testData)
235246
if testData.err == "" {
236247
c.Check(err, check.IsNil, testLabel)

cmd/snap/export_test.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -458,13 +458,15 @@ func MockAutostartSessionApps(f func(string) error) func() {
458458
}
459459
}
460460

461-
func ParseQuotaValues(maxMemory, cpuMax, cpuSet, threadsMax string) (*client.QuotaValues, error) {
461+
func ParseQuotaValues(maxMemory, cpuMax, cpuSet, threadsMax, journalSizeMax, journalRateLimit string) (*client.QuotaValues, error) {
462462
var quotas cmdSetQuota
463463

464464
quotas.MemoryMax = maxMemory
465465
quotas.CPUMax = cpuMax
466466
quotas.CPUSet = cpuSet
467467
quotas.ThreadsMax = threadsMax
468+
quotas.JournalSizeMax = journalSizeMax
469+
quotas.JournalRateLimit = journalRateLimit
468470

469471
return quotas.parseQuotas()
470472
}

daemon/api_quotas.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -196,6 +196,15 @@ func quotaValuesToResources(values client.QuotaValues) quota.Resources {
196196
if values.Threads != 0 {
197197
resourcesBuilder.WithThreadLimit(values.Threads)
198198
}
199+
if values.Journal != nil {
200+
resourcesBuilder.WithJournalNamespace()
201+
if values.Journal.Size != 0 {
202+
resourcesBuilder.WithJournalSize(values.Journal.Size)
203+
}
204+
if values.Journal.RateCount != 0 && values.Journal.RatePeriod != 0 {
205+
resourcesBuilder.WithJournalRate(values.Journal.RateCount, values.Journal.RatePeriod)
206+
}
207+
}
199208
return resourcesBuilder.Build()
200209
}
201210

0 commit comments

Comments
 (0)