Quotas: carry what was used when a rule counts differently
model.QuotaCarry says what becomes of a quota's use when its rule changes how it counts; the firewall's apply follows it, comparing with the rules in force: per address to a total starts at the addresses' sum, wider groups add up their addresses, and what can't be carried (a total to per address, per source to per destination, finer groups) starts again, with the engine warning quota_restarts on that change. Before, per source to per destination kept the sources' use as destinations', and regrouping left old entries behind. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
46c319eced
commit
9863dd1cdd
10 files changed
+294
-1
No files matched your search
@@ -191,6 +191,7 @@ func (e *Engine) commit(ctx context.Context, p *auth.Principal, description stri
|
||||
// Only report warnings the change brings; old ones are on the status
|
||||
// page.
|
||||
res.Warnings = newProblems(validate.Check(e.state, e.env()), problems)
|
||||
res.Warnings = append(res.Warnings, quotaRestarts(e.state, next)...)
|
||||
if e.Firewall != nil {
|
||||
if err := e.Firewall.Check(ctx, next); err != nil {
|
||||
return res, &Error{Kind: KindFirewall, Message: "nftables refused the ruleset: " + err.Error()}
|
||||
@@ -230,6 +231,23 @@ func (e *Engine) commit(ctx context.Context, p *auth.Principal, description stri
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// quotaRestarts warns of the rules whose quota starts again because they
|
||||
// now count it in a way what they used can't be carried to.
|
||||
func quotaRestarts(before, after *model.State) []validate.Problem {
|
||||
was := map[int64]*model.Rule{}
|
||||
for _, r := range before.Rules {
|
||||
was[r.ID] = r
|
||||
}
|
||||
var out []validate.Problem
|
||||
for _, r := range after.Rules {
|
||||
if carry, why := model.QuotaCarry(was[r.ID], r); carry == model.QuotaRestarts {
|
||||
out = append(out, validate.Problem{Severity: validate.Warning, Code: "quota_restarts", Object: r.Label(),
|
||||
Message: "its quota starts again: " + why})
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
func newProblems(before, after []validate.Problem) []validate.Problem {
|
||||
seen := map[string]bool{}
|
||||
for _, p := range before {
|
||||
|
||||
@@ -173,3 +173,49 @@ func TestPositionsStayStableUntilCrowded(t *testing.T) {
|
||||
t.Errorf("order %v", order)
|
||||
}
|
||||
}
|
||||
|
||||
// TestQuotaRestartsWarns: a change that counts a quota so what it used
|
||||
// can't carry over warns; one that can (to a total, wider groups) doesn't.
|
||||
func TestQuotaRestartsWarns(t *testing.T) {
|
||||
e := newEngine(t)
|
||||
ctx := context.Background()
|
||||
var id int64
|
||||
if _, err := e.Apply(ctx, admin, "", Options{}, func(st *model.State, newID func() int64) error {
|
||||
id = newID()
|
||||
st.Rules = append(st.Rules, &model.Rule{ID: id, Chain: "forward", Action: "drop", Quota: &model.Quota{Bytes: 1 << 30, Over: true, PerSource: true}})
|
||||
return nil
|
||||
}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
change := func(f func(r *model.Rule)) []string {
|
||||
res, err := e.Apply(ctx, admin, "", Options{DryRun: true}, func(st *model.State, _ func() int64) error {
|
||||
f(st.ByID(model.KindRule, id).(*model.Rule))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
var codes []string
|
||||
for _, w := range res.Warnings {
|
||||
if w.Code == "quota_restarts" {
|
||||
codes = append(codes, w.Message)
|
||||
}
|
||||
}
|
||||
return codes
|
||||
}
|
||||
if w := change(func(r *model.Rule) { r.Quota.PerSource, r.Quota.PerDestination = false, true }); len(w) != 1 || !strings.Contains(w[0], "per source can't be counted per destination") {
|
||||
t.Errorf("per source to per destination: %v", w)
|
||||
}
|
||||
if w := change(func(r *model.Rule) { r.Quota.PerSource = false }); len(w) != 0 {
|
||||
t.Errorf("to a total (summed): %v", w)
|
||||
}
|
||||
if w := change(func(r *model.Rule) { r.GroupV6 = 56 }); len(w) != 0 {
|
||||
t.Errorf("wider groups: %v", w)
|
||||
}
|
||||
if w := change(func(r *model.Rule) { r.GroupV6 = 128 }); len(w) != 1 {
|
||||
t.Errorf("finer groups: %v", w)
|
||||
}
|
||||
if w := change(func(r *model.Rule) { r.Quota.Bytes = 2 << 30 }); len(w) != 0 {
|
||||
t.Errorf("another amount: %v", w)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user