Show egress/ingress levels in the registry; freeze checks at the verdict
Registry: the "last result" column now also shows, per level (egress,
ingress), how many of the recorded checks of the latest cycle succeeded, split
by check family (tcp-22 and tcp-443 are both "tcp"). One grouped query per
chunk of addresses; new fields last_cycle_id, egress, ingress in
GET /admin/registry; the dashboard renders them under the verdict.
Verdict integrity (migration 0010):
- the prober is handed an address once per site and attempt, not on every
poll, so results are no longer overwritten by later probe rounds;
- UpsertCheckIfOpen refuses writes once the address is aggregating or has its
verdict, or for an older attempt; senders get {"ok":true,"ignored":N} and a
result_dropped event is recorded;
- the checking window counts from checking_started_at, not from assigned_at;
- checks.recorded_at (server clock) and checks.after_verdict (flag for rows
written after the verdict in existing data);
- the verdict rule is a pure function (computeVerdict) and the aggregated
event carries the egress/ingress check counts.
Rebuilt bin/control-api and bin/admin-dashboard to match. Plans and summaries
are in docs/changes; README, API, USAGE, DASHBOARD and DIAGRAMS are updated.
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
db73409e8f
commit
864208238f
34 files changed
+1570
-72
No files matched your search
@@ -0,0 +1,169 @@
|
||||
package db
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCheckLevelAndFamily(t *testing.T) {
|
||||
for source, want := range map[string]string{
|
||||
"egress": LevelEgress, "inbound-site-1": LevelIngress, "inbound-site-12": LevelIngress,
|
||||
"": "", "other": "",
|
||||
} {
|
||||
if got := CheckLevel(source); got != want {
|
||||
t.Errorf("CheckLevel(%q) = %q, want %q", source, got, want)
|
||||
}
|
||||
}
|
||||
for ct, want := range map[string]string{
|
||||
"https": "https", "icmp": "icmp", "ssh": "ssh",
|
||||
"tcp-22": "tcp", "tcp-443": "tcp", "tls-443": "tls", "dns": "dns", "-x": "-x",
|
||||
} {
|
||||
if got := CheckFamily(ct); got != want {
|
||||
t.Errorf("CheckFamily(%q) = %q, want %q", ct, got, want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// addCheck records one check for the address's current queue row.
|
||||
func addCheck(t *testing.T, d *DB, addr, source, checkType, target string, success bool) {
|
||||
t.Helper()
|
||||
ctx := t.Context()
|
||||
ip, err := d.GetIPByAddress(ctx, addr)
|
||||
if err != nil {
|
||||
t.Fatalf("get %s: %v", addr, err)
|
||||
}
|
||||
if err := d.UpsertCheck(ctx, Check{
|
||||
IPID: ip.ID, IPAddress: addr, AttemptNumber: ip.AttemptNumber,
|
||||
Source: source, CheckType: checkType, Target: target,
|
||||
Success: success, CheckedAt: Now(),
|
||||
}); err != nil {
|
||||
t.Fatalf("upsert check: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryLevelsGroupByTypeAndLevel(t *testing.T) {
|
||||
d, ctx := newTestDB(t)
|
||||
if _, err := d.SubmitIPs(ctx, []string{"1.2.3.4"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
a := "1.2.3.4"
|
||||
// Egress: https 2 of 3, icmp 1 of 1.
|
||||
addCheck(t, d, a, SourceEgress, "https", "https://a.test", true)
|
||||
addCheck(t, d, a, SourceEgress, "https", "https://b.test", true)
|
||||
addCheck(t, d, a, SourceEgress, "https", "https://c.test", false)
|
||||
addCheck(t, d, a, SourceEgress, "icmp", "a.test", true)
|
||||
// Ingress from two sites: tcp-22 and tcp-443 are one family; tls, ssh,
|
||||
// icmp and a type unknown today ("dns") are listed on their own.
|
||||
for site := 1; site <= 2; site++ {
|
||||
src := InboundSource(site)
|
||||
addCheck(t, d, a, src, "tcp-22", a, true)
|
||||
addCheck(t, d, a, src, "tcp-443", a, site == 1)
|
||||
addCheck(t, d, a, src, "tls-443", a, true)
|
||||
addCheck(t, d, a, src, "ssh", a, false)
|
||||
addCheck(t, d, a, src, "icmp", a, true)
|
||||
addCheck(t, d, a, src, "dns", a, true)
|
||||
}
|
||||
// A source that is neither egress nor an inbound site is not counted.
|
||||
addCheck(t, d, a, "manual", "https", "x", true)
|
||||
|
||||
s, err := d.GetRegistryByAddress(ctx, a)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
wantEgress := LevelResult{Total: 4, OK: 3, ByType: []TypeStat{
|
||||
{"https", 3, 2}, {"icmp", 1, 1},
|
||||
}}
|
||||
wantIngress := LevelResult{Total: 12, OK: 9, ByType: []TypeStat{
|
||||
{"dns", 2, 2}, {"icmp", 2, 2}, {"ssh", 2, 0}, {"tcp", 4, 3}, {"tls", 2, 2},
|
||||
}}
|
||||
if !reflect.DeepEqual(s.Egress, wantEgress) {
|
||||
t.Errorf("egress = %+v, want %+v", s.Egress, wantEgress)
|
||||
}
|
||||
if !reflect.DeepEqual(s.Ingress, wantIngress) {
|
||||
t.Errorf("ingress = %+v, want %+v", s.Ingress, wantIngress)
|
||||
}
|
||||
if s.LastCycleID != 1 {
|
||||
t.Errorf("LastCycleID = %d, want 1", s.LastCycleID)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRegistryLevelsNoChecksAreZero(t *testing.T) {
|
||||
d, ctx := newTestDB(t)
|
||||
if _, err := d.SubmitIPs(ctx, []string{"1.2.3.4"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s, err := d.GetRegistryByAddress(ctx, "1.2.3.4")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s.LastCycleID != 0 || s.Egress.Total != 0 || s.Ingress.Total != 0 || len(s.Egress.ByType) != 0 {
|
||||
t.Fatalf("expected empty levels, got %+v", s)
|
||||
}
|
||||
}
|
||||
|
||||
// The counts follow the newest cycle, also after the queue row is deleted,
|
||||
// and one grouped query serves several addresses without mixing them up.
|
||||
func TestRegistryLevelsLatestCycleAndManyAddresses(t *testing.T) {
|
||||
d, ctx := newTestDB(t)
|
||||
if _, err := d.SubmitIPs(ctx, []string{"1.1.1.1", "2.2.2.2"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
addCheck(t, d, "1.1.1.1", SourceEgress, "https", "t1", false)
|
||||
addCheck(t, d, "2.2.2.2", SourceEgress, "https", "t1", true)
|
||||
addCheck(t, d, "2.2.2.2", InboundSource(1), "tcp-22", "2.2.2.2", true)
|
||||
|
||||
// New cycle for 1.1.1.1: delete and submit again.
|
||||
ip, err := d.GetIPByAddress(ctx, "1.1.1.1")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if err := d.DeleteIP(ctx, ip.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if _, err := d.SubmitIPs(ctx, []string{"1.1.1.1"}); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
addCheck(t, d, "1.1.1.1", SourceEgress, "icmp", "t2", true)
|
||||
addCheck(t, d, "1.1.1.1", SourceEgress, "https", "t2", true)
|
||||
|
||||
all, err := d.ListRegistry(ctx)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
got := map[string]RegistrySummary{}
|
||||
for _, s := range all {
|
||||
got[s.IPAddress] = s
|
||||
}
|
||||
a := got["1.1.1.1"]
|
||||
if a.LastCycleID != 2 || a.Egress.Total != 2 || a.Egress.OK != 2 || a.Ingress.Total != 0 {
|
||||
t.Errorf("1.1.1.1: %+v", a)
|
||||
}
|
||||
b := got["2.2.2.2"]
|
||||
if b.LastCycleID != 1 || b.Egress.Total != 1 || b.Ingress.Total != 1 || b.Ingress.OK != 1 {
|
||||
t.Errorf("2.2.2.2: %+v", b)
|
||||
}
|
||||
|
||||
// Page and single lookups agree with the full list.
|
||||
page, _, err := d.ListRegistryPage(ctx, RegistryFilter{}, 10, 0)
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
for _, s := range page {
|
||||
if !reflect.DeepEqual(s.Egress, got[s.IPAddress].Egress) || !reflect.DeepEqual(s.Ingress, got[s.IPAddress].Ingress) {
|
||||
t.Errorf("page differs from list for %s", s.IPAddress)
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the queue row of 2.2.2.2: the counts stay (history outlives it).
|
||||
ip2, _ := d.GetIPByAddress(ctx, "2.2.2.2")
|
||||
if err := d.DeleteIP(ctx, ip2.ID); err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
s, err := d.GetRegistryByAddress(ctx, "2.2.2.2")
|
||||
if err != nil {
|
||||
t.Fatal(err)
|
||||
}
|
||||
if s.Egress.Total != 1 || s.Ingress.Total != 1 {
|
||||
t.Errorf("after delete: %+v", s)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user