Skip to content

Commit

Permalink
many: add frontend for journal quotas
Browse files Browse the repository at this point in the history
  • Loading branch information
Meulengracht committed Jun 27, 2022
1 parent ee1a45b commit c569177
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 17 deletions.
16 changes: 12 additions & 4 deletions client/quota.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"bytes"
"encoding/json"
"fmt"
"time"

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

type QuotaJournalValues struct {
Size quantity.Size `json:"size,omitempty"`
RateCount int `json:"rate-count,omitempty"`
RatePeriod time.Duration `json:"rate-period,omitempty"`
}

type QuotaValues struct {
Memory quantity.Size `json:"memory,omitempty"`
CPU *QuotaCPUValues `json:"cpu,omitempty"`
CPUSet *QuotaCPUSetValues `json:"cpu-set,omitempty"`
Threads int `json:"threads,omitempty"`
Memory quantity.Size `json:"memory,omitempty"`
CPU *QuotaCPUValues `json:"cpu,omitempty"`
CPUSet *QuotaCPUSetValues `json:"cpu-set,omitempty"`
Threads int `json:"threads,omitempty"`
Journal *QuotaJournalValues `json:"journal,omitempty"`
}

// EnsureQuota creates a quota group or updates an existing group.
Expand Down
11 changes: 11 additions & 0 deletions client/quota_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"bytes"
"encoding/json"
"io/ioutil"
"time"

"gopkg.in/check.v1"

Expand Down Expand Up @@ -54,6 +55,11 @@ func (cs *clientSuite) TestEnsureQuotaGroup(c *check.C) {
CPUs: []int{0},
},
Threads: 32,
Journal: &client.QuotaJournalValues{
Size: quantity.SizeMiB,
RateCount: 150,
RatePeriod: time.Minute,
},
}

chgID, err := cs.cli.EnsureQuota("foo", "bar", []string{"snap-a", "snap-b"}, quotaValues)
Expand Down Expand Up @@ -81,6 +87,11 @@ func (cs *clientSuite) TestEnsureQuotaGroup(c *check.C) {
"cpus": []interface{}{json.Number("0")},
},
"threads": json.Number("32"),
"journal": map[string]interface{}{
"size": json.Number("1048576"),
"rate-count": json.Number("150"),
"rate-period": json.Number("60000000000"),
},
},
})
}
Expand Down
67 changes: 60 additions & 7 deletions cmd/snap/cmd_quota.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ import (
"sort"
"strconv"
"strings"
"time"

"github.com/jessevdk/go-flags"

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

MemoryMax string `long:"memory" optional:"true"`
CPUMax string `long:"cpu" optional:"true"`
CPUSet string `long:"cpu-set" optional:"true"`
ThreadsMax string `long:"threads" optional:"true"`
Parent string `long:"parent" optional:"true"`
Positional struct {
MemoryMax string `long:"memory" optional:"true"`
CPUMax string `long:"cpu" optional:"true"`
CPUSet string `long:"cpu-set" optional:"true"`
ThreadsMax string `long:"threads" optional:"true"`
JournalSizeMax string `long:"journal-size" optional:"true"`
JournalRateLimit string `long:"journal-rate-limit" optional:"true"`
Parent string `long:"parent" optional:"true"`
Positional struct {
GroupName string `positional-arg-name:"<group-name>" required:"true"`
Snaps []installedSnapName `positional-arg-name:"<snap>" optional:"true"`
} `positional-args:"yes"`
Expand Down Expand Up @@ -165,6 +172,31 @@ func parseCpuQuota(cpuMax string) (count int, percentage int, err error) {
return count, percentage, nil
}

func parseJournalRateQuota(journalRateLimit string) (count int, period time.Duration, err error) {
// the rate limit is a string of the form N/P, where N is the number of
// messages and P is the period as a time string (e.g 5s)
parseError := func(input string) error {
return fmt.Errorf("cannot parse journal rate limit string %q", input)
}

parts := strings.Split(journalRateLimit, "/")
if len(parts) != 2 {
return 0, 0, parseError(journalRateLimit)
}

count, err = strconv.Atoi(parts[0])
if err != nil || count == 0 {
return 0, 0, parseError(journalRateLimit)
}

period, err = time.ParseDuration(parts[1])
if err != nil || period == 0 {
return 0, 0, parseError(journalRateLimit)
}

return count, period, nil
}

func (x *cmdSetQuota) parseQuotas() (*client.QuotaValues, error) {
var quotaValues client.QuotaValues

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

if x.JournalSizeMax != "" || x.JournalRateLimit != "" {
quotaValues.Journal = &client.QuotaJournalValues{}
if x.JournalSizeMax != "" {
value, err := strutil.ParseByteSize(x.JournalSizeMax)
if err != nil {
return nil, err
}
quotaValues.Journal.Size = quantity.Size(value)
}

if x.JournalRateLimit != "" {
count, period, err := parseJournalRateQuota(x.JournalRateLimit)
if err != nil {
return nil, err
}
quotaValues.Journal.RateCount = count
quotaValues.Journal.RatePeriod = period
}
}

return &quotaValues, nil
}

func (x *cmdSetQuota) hasQuotaSet() bool {
return x.MemoryMax != "" || x.CPUMax != "" || x.CPUSet != "" || x.ThreadsMax != ""
return x.MemoryMax != "" || x.CPUMax != "" || x.CPUSet != "" ||
x.ThreadsMax != "" || x.JournalSizeMax != "" || x.JournalRateLimit != ""
}

func (x *cmdSetQuota) Execute(args []string) (err error) {
Expand Down
21 changes: 16 additions & 5 deletions cmd/snap/cmd_quota_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -203,10 +203,12 @@ func makeChangesHandler(c *check.C) func(w http.ResponseWriter, r *http.Request)

func (s *quotaSuite) TestParseQuotas(c *check.C) {
for _, testData := range []struct {
maxMemory string
cpuMax string
cpuSet string
threadsMax string
maxMemory string
cpuMax string
cpuSet string
threadsMax string
journalSizeMax string
journalRateLimit string

// Use the JSON representation of the quota, as it's easier to handle in the test data
quotas string
Expand All @@ -217,6 +219,11 @@ func (s *quotaSuite) TestParseQuotas(c *check.C) {
{cpuMax: "40%", quotas: `{"cpu":{"percentage":40}}`},
{cpuSet: "1,3", quotas: `{"cpu-set":{"cpus":[1,3]}}`},
{threadsMax: "2", quotas: `{"threads":2}`},
{journalSizeMax: "16MB", quotas: `{"journal":{"size":16000000}}`},
{journalRateLimit: "10/15s", quotas: `{"journal":{"rate-count":10,"rate-period":15000000000}}`},
{journalRateLimit: "1500/15ms", quotas: `{"journal":{"rate-count":1500,"rate-period":15000000}}`},
{journalRateLimit: "1/15us", quotas: `{"journal":{"rate-count":1,"rate-period":15000}}`},

// Error cases
{cpuMax: "ASD", err: `cannot parse cpu quota string "ASD"`},
{cpuMax: "0x100%", err: `cannot parse cpu quota string "0x100%"`},
Expand All @@ -229,8 +236,12 @@ func (s *quotaSuite) TestParseQuotas(c *check.C) {
{cpuSet: "0,-2", err: `cannot parse CPU set value "-2"`},
{threadsMax: "xxx", err: `cannot use threads value "xxx"`},
{threadsMax: "-3", err: `cannot use threads value "-3"`},
{journalRateLimit: "0", err: `cannot parse journal rate limit string "0"`},
{journalRateLimit: "x/5m", err: `cannot parse journal rate limit string "x\/5m"`},
{journalRateLimit: "1/wow", err: `cannot parse journal rate limit string "1\/wow"`},
} {
quotas, err := main.ParseQuotaValues(testData.maxMemory, testData.cpuMax, testData.cpuSet, testData.threadsMax)
quotas, err := main.ParseQuotaValues(testData.maxMemory, testData.cpuMax,
testData.cpuSet, testData.threadsMax, testData.journalSizeMax, testData.journalRateLimit)
testLabel := check.Commentf("%v", testData)
if testData.err == "" {
c.Check(err, check.IsNil, testLabel)
Expand Down
4 changes: 3 additions & 1 deletion cmd/snap/export_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -458,13 +458,15 @@ func MockAutostartSessionApps(f func(string) error) func() {
}
}

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

quotas.MemoryMax = maxMemory
quotas.CPUMax = cpuMax
quotas.CPUSet = cpuSet
quotas.ThreadsMax = threadsMax
quotas.JournalSizeMax = journalSizeMax
quotas.JournalRateLimit = journalRateLimit

return quotas.parseQuotas()
}
9 changes: 9 additions & 0 deletions daemon/api_quotas.go
Original file line number Diff line number Diff line change
Expand Up @@ -196,6 +196,15 @@ func quotaValuesToResources(values client.QuotaValues) quota.Resources {
if values.Threads != 0 {
resourcesBuilder.WithThreadLimit(values.Threads)
}
if values.Journal != nil {
resourcesBuilder.WithJournalNamespace()
if values.Journal.Size != 0 {
resourcesBuilder.WithJournalSize(values.Journal.Size)
}
if values.Journal.RateCount != 0 && values.Journal.RatePeriod != 0 {
resourcesBuilder.WithJournalRate(values.Journal.RateCount, values.Journal.RatePeriod)
}
}
return resourcesBuilder.Build()
}

Expand Down

0 comments on commit c569177

Please sign in to comment.