Quotas: save their usage after every change
The server hooks the engine's OnChange to save what the quotas used, so a reboot soon after a change that counts a quota differently doesn't bring back its usage counted the old way (it was saved every five minutes and on stop only). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
8fa28d01c2
commit
aaf0ac5206
5 files changed
+33
-5
No files matched your search
@@ -96,6 +96,9 @@ All notable changes to Housecarl are documented here. The format follows
|
|||||||
the old entries behind. Now a total starts at the addresses' sum,
|
the old entries behind. Now a total starts at the addresses' sum,
|
||||||
wider groups add up their addresses, and what can't be carried starts
|
wider groups add up their addresses, and what can't be carried starts
|
||||||
again with a warning (`quota_restarts`) on the change.
|
again with a warning (`quota_restarts`) on the change.
|
||||||
|
- What the quotas used is saved after every change, not only every five
|
||||||
|
minutes: a reboot soon after a change that counts a quota differently
|
||||||
|
could bring back its usage counted the old way.
|
||||||
- Statistics' Quotas card linked every entry to the Rules page, not to
|
- Statistics' Quotas card linked every entry to the Rules page, not to
|
||||||
its rule; each now opens its rule, named. Per-address quotas were
|
its rule; each now opens its rule, named. Per-address quotas were
|
||||||
missing from it (and from the `quota:rN` series): they count their
|
missing from it (and from the `quota:rN` series): they count their
|
||||||
|
|||||||
+3
-3
@@ -164,9 +164,9 @@ start again. A disabled rule keeps its count for when it is enabled.
|
|||||||
resets; `hc rule reset-quota ID` starts one again (`--address` one device's,
|
resets; `hc rule reset-quota ID` starts one again (`--address` one device's,
|
||||||
`--give 5gbytes` a top-up until the next reset). housecarld resets
|
`--give 5gbytes` a top-up until the next reset). housecarld resets
|
||||||
quotas at midnight when their period ends (late, if it was stopped then),
|
quotas at midnight when their period ends (late, if it was stopped then),
|
||||||
and saves their usage every five minutes and when it stops; after a
|
and saves their usage every five minutes, after every change and when
|
||||||
reboot it puts it back. A reboot without a clean stop loses at most the
|
it stops; after a reboot it puts it back. A reboot without a clean stop
|
||||||
last five minutes' count.
|
loses at most the last five minutes' count.
|
||||||
|
|
||||||
## The log
|
## The log
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -209,8 +209,8 @@ Statistics**.
|
|||||||
|
|
||||||
nftables counts a quota but never resets it, and forgets it when its
|
nftables counts a quota but never resets it, and forgets it when its
|
||||||
table is replaced: Housecarl carries what each quota used across every
|
table is replaced: Housecarl carries what each quota used across every
|
||||||
change and restart, saves it every five minutes (and when housecarld
|
change and restart, saves it every five minutes (and after each change,
|
||||||
stops) for after a reboot, and resets quotas when their period ends.
|
and when housecarld stops) for after a reboot, and resets quotas when their period ends.
|
||||||
A per-address quota counts an address: a device that gets a new one
|
A per-address quota counts an address: a device that gets a new one
|
||||||
starts again. With Steward's reservations IPv4 addresses stay; IPv6 is
|
starts again. With Steward's reservations IPv4 addresses stay; IPv6 is
|
||||||
grouped by /64 (below).
|
grouped by /64 (below).
|
||||||
|
|||||||
@@ -141,6 +141,16 @@ func New(cfg Config, eng *engine.Engine, fw Firewall, ifaces Interfaces, log *sl
|
|||||||
s := &Server{cfg: cfg, eng: eng, fw: fw, ifaces: ifaces, store: eng.Store(), log: log, mux: http.NewServeMux(), limiter: newRateLimiter(),
|
s := &Server{cfg: cfg, eng: eng, fw: fw, ifaces: ifaces, store: eng.Store(), log: log, mux: http.NewServeMux(), limiter: newRateLimiter(),
|
||||||
web: web, started: time.Now(), logs: newLogRing(2000), nowFn: time.Now, unitLinks: nftablesUnitLinks}
|
web: web, started: time.Now(), logs: newLogRing(2000), nowFn: time.Now, unitLinks: nftablesUnitLinks}
|
||||||
s.routes()
|
s.routes()
|
||||||
|
// What the quotas used is saved after every change too, not only every
|
||||||
|
// five minutes: a change that counts a quota differently converts its
|
||||||
|
// usage, and a restart must not bring back the old kind.
|
||||||
|
prev := eng.OnChange
|
||||||
|
eng.OnChange = func(st *model.State) {
|
||||||
|
if prev != nil {
|
||||||
|
prev(st)
|
||||||
|
}
|
||||||
|
s.SaveQuotaUsage(context.Background())
|
||||||
|
}
|
||||||
return s
|
return s
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -792,6 +792,21 @@ func TestQuotaResets(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestQuotaUsageSavedOnChange: a change saves what the quotas used at
|
||||||
|
// once (a restart must not bring back usage counted the old way).
|
||||||
|
func TestQuotaUsageSavedOnChange(t *testing.T) {
|
||||||
|
e := newEnv(t)
|
||||||
|
e.call(e.token, "POST", "/zones", model.Zone{Name: "lan", Interfaces: []string{"eth1"}}, nil)
|
||||||
|
e.fw.mu.Lock()
|
||||||
|
e.fw.quotas = nft.QuotaUsage{Quotas: map[string]int64{"quota_r7": 4242}}
|
||||||
|
e.fw.mu.Unlock()
|
||||||
|
e.call(e.token, "POST", "/zones", model.Zone{Name: "dmz", Interfaces: []string{"eth2"}}, nil)
|
||||||
|
v, err := e.eng.Store().GetMeta(context.Background(), QuotaUsageKey)
|
||||||
|
if err != nil || !strings.Contains(v, `"quota_r7":4242`) {
|
||||||
|
t.Errorf("saved usage %q %v", v, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestAutoConfirm: the client that made a change confirms it by itself:
|
// TestAutoConfirm: the client that made a change confirms it by itself:
|
||||||
// with its token, from the same address, over a new connection. Nobody
|
// with its token, from the same address, over a new connection. Nobody
|
||||||
// else can, an older token can't, and with manual_confirm there is none.
|
// else can, an older token can't, and with manual_confirm there is none.
|
||||||
|
|||||||
Reference in new issue
Block a user