Merge branch 'feature/traffic-analytics'

This commit is contained in:
Veillax committed 2026-10-27 17:09:40 -06:00
commit 5fdeff012c
10 files changed
+649 -6

No files matched your search

+80
View File
@@ -0,0 +1,80 @@
package e2e
import (
"strconv"
"testing"
"time"
"git.veillax.com/Veillax/blacktip"
"git.veillax.com/Veillax/reef/model"
)
// Traffic through an edge shows up in Hammerhead's charts, with the client address named.
func TestTrafficAnalyticsFlow(t *testing.T) {
w := setup(t, blacktip.TraefikConfig{})
target := freeRun(t, 1)
pub := freeRun(t, 1)
if pub == target {
pub = freeRun(t, 2) + 1
}
tcpOn(t, target)
var m model.Mapping
if code, raw := w.admin.do("POST", "/api/orgs/"+w.org+"/mappings", map[string]any{"proto": "tcp", "edgeId": w.edge, "bindIp": "127.0.0.1", "port": pub,
"targets": tgt(w.local, target), "enabled": true}, &m); code != 200 {
t.Fatalf("open: %d %s", code, raw)
}
for i := 0; i < 3; i++ {
if got := tcpAsk(t, "127.0.0.1:"+strconv.Itoa(pub), "hello"); got == "" {
t.Fatal("no answer through the edge")
}
}
type detail struct {
Total struct{ Conns, BytesIn, BytesOut int64 } `json:"total"`
Points []struct{ Conns int64 } `json:"points"`
Sources []struct {
IP string
Conns int64
} `json:"sources"`
}
var d detail
for i := 0; ; i++ { // the edge reports every 20 seconds
d = detail{}
if code, _ := w.admin.do("GET", "/api/orgs/"+w.org+"/traffic/"+m.ID+"?range=1h", nil, &d); code == 200 && d.Total.Conns >= 3 {
break
}
if i >= 450 {
t.Fatal("timed out waiting for the edge to report the traffic")
}
time.Sleep(100 * time.Millisecond)
}
if d.Total.BytesIn < 15 || d.Total.BytesOut == 0 || len(d.Points) != 60 {
t.Errorf("totals: %+v with %d points", d.Total, len(d.Points))
}
if len(d.Sources) != 1 || d.Sources[0].IP != "127.0.0.1" || d.Sources[0].Conns < 3 {
t.Errorf("sources: %+v", d.Sources)
}
var all detail
if code, raw := w.admin.do("GET", "/api/orgs/"+w.org+"/traffic/all?range=24h", nil, &all); code != 200 || all.Total.Conns < 3 || len(all.Points) != 24 {
t.Errorf("organization total: %d %s", code, raw)
}
if code, _ := w.admin.do("GET", "/api/orgs/"+w.org+"/traffic/"+m.ID+"?range=5y", nil, nil); code != 400 {
t.Errorf("bad range: %d", code)
}
if code, _ := w.admin.do("GET", "/api/orgs/"+w.org+"/traffic/nope", nil, nil); code != 404 {
t.Errorf("unknown port: %d", code)
}
alerts := "/api/orgs/" + w.org + "/alerts"
if code, _ := w.admin.do("PUT", alerts+"/traffic", map[string]any{"spikeFactor": 1}, nil); code != 400 {
t.Errorf("a spike of 1x was accepted: %d", code)
}
if code, raw := w.admin.do("PUT", alerts+"/traffic", map[string]any{"spikeFactor": 5, "dailyMb": 2048}, nil); code != 200 {
t.Fatalf("save traffic alerts: %d %s", code, raw)
}
var ov struct{ Settings map[string]int }
if code, _ := w.admin.do("GET", alerts, nil, &ov); code != 200 || ov.Settings["spikeFactor"] != 5 || ov.Settings["dailyMb"] != 2048 || ov.Settings["debounceMin"] != 2 {
t.Errorf("settings after save: %d %+v", code, ov.Settings)
}
}