Skip to content

Commit 1c13536

Browse files
committed
feat(ees): embed Nupf-like Event Exposure (demo) with slot-aligned reporter
1 parent e777851 commit 1c13536

16 files changed

Lines changed: 1701 additions & 112 deletions

File tree

README.md

Lines changed: 286 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,286 @@
1-
# go-upf
2-
### The specific version
3-
**Note:** Please make sure to check your UPF version and use a compatible gtp5g version according to the table below.
4-
5-
|free5GC Version| UPF Version | Compatible gtp5g Versions |
6-
|-----------------|-----------------|--------------------------|
7-
|v3.4.4| v1.2.4 | >= 0.9.5 and < 0.10.0 |
8-
|v3.4.3| v1.2.3 | >= 0.8.6 and < 0.9.0 |
9-
|v3.4.2| v1.2.3 | >= 0.8.6 and < 0.9.0 |
10-
|v3.4.1| v1.2.2 | >= 0.8.6 and < 0.9.0 |
11-
|v3.4.0| v1.2.1 | >= 0.8.6 and < 0.9.0 |
12-
|v3.3.0| v1.2.0 | >= 0.8.1 and < 0.9.0 |
13-
|v3.2.1| v1.1.0 | >= 0.7.0 and < 0.7.0 |
14-
|v3.2.0| v1.1.0 | >= 0.7.0 and < 0.7.0 |
1+
# UPF Embedded EES (Nupf_EventExposure) — MVP
2+
3+
This MVP embeds an **Event Exposure**–like service directly inside **go-upf**.
4+
It exposes a simple **subscription API** and periodically delivers `USER_DATA_USAGE_MEASURES` notifications based on **PFCP USAR** measurements reported by the UPF.
5+
6+
* **Provider-clock aligned reporting** (slot boundaries, e.g., every 10s at `...:00, :10, :20, ...`)
7+
* **Period whitelist** with rounding (e.g., allow only `10/30/60s`)
8+
* **On-demand** immediate one-shot notifications
9+
* **No external sidecar**; ingestion happens in-process via an **IndexSink**
10+
11+
---
12+
13+
## What’s inside
14+
15+
### Data path (in-process)
16+
17+
```
18+
PFCP (USAR) → usage.Collector.Emit(seid, samples)
19+
→ usage.IndexSink (observer)
20+
→ ees/index (IntervalPoint ring, per (SEID, URR))
21+
```
22+
23+
### Control/notify path
24+
25+
```
26+
EE Subscription API (HTTP)
27+
→ ees/context.Store (in-mem)
28+
→ ees/service.Reporter (slot scheduler, window aggregation)
29+
→ HTTP notifier (POST JSON to notifUri)
30+
```
31+
32+
### Key components (new/updated)
33+
34+
* `internal/ees/index/` — in-memory window index keyed by `(SEID, URR)`
35+
* `internal/usage/index_sink.go` — converts `UsageReport``IntervalPoint` and stores in the index
36+
* `internal/ees/context/` — subscription store & types (immediate, maxReports, expiry, nextReport)
37+
* `internal/ees/service/reporter.go` — slot-aligned scheduler + aggregation + notify
38+
* `internal/ees/api/``POST/DELETE /nupf-ee/v1/ee-subscriptions`
39+
* `pkg/app/eescfg.go` — loads the `ees:` block from `UPF-EES.yaml`
40+
* `pkg/app/app.go` — wires Collector → IndexSink, starts Reporter & API server
41+
* (UPF path) `internal/pfcp/report.go` — emits USAR samples with `SEID, URRID, URSEQN, startTime, endTime, total/uplink/downlink bytes & packets`
42+
43+
---
44+
45+
## Why provider-clock alignment?
46+
47+
3GPP allows the producer NF to decide reporting behaviors (e.g., periodic vs. one-shot, immediate flag). To keep scheduling predictable and capacity-friendly, we **align reports to a fixed provider clock** (e.g., every 10 seconds), rather than starting each subscriber on their own offsets.
48+
We also **whitelist allowed periods** (e.g., `10/30/60s`) and round requests to the nearest allowed value.
49+
50+
---
51+
52+
## Build & Run (UPF)
53+
54+
```bash
55+
# Build go-upf as usual
56+
make # or your existing build flow
57+
58+
# Run with your UPF-EES.yaml (see EES block below)
59+
sudo -E ./bin/upf -c /config/UPF-EES.yaml -l upf.log &
60+
```
61+
62+
You should see logs similar to:
63+
64+
```
65+
EES configuration enabled=true api=:8088 whitelist=[10 30 60] slot=10 ...
66+
EES IndexSink registered (maxPerKey=4096, ttl=30m)
67+
EES Reporter started (provider-clock aligned)
68+
EES API listening :8088
69+
PFCP UsageCollector connected
70+
```
71+
72+
---
73+
74+
## Configuration (`UPF-EES.yaml`)
75+
76+
Add an `ees:` block to the same YAML used by UPF:
77+
78+
```yaml
79+
ees:
80+
enabled: true
81+
82+
api:
83+
listen: ":8088" # Subscription API server (HTTP)
84+
85+
reporter:
86+
periodWhitelist: [10, 30, 60] # Allowed reporting periods (seconds)
87+
slotAlignSec: 10 # Provider-clock slot alignment (seconds)
88+
tickMs: 1000 # Reporter scan interval (ms)
89+
90+
index:
91+
maxPerKey: 4096 # Max interval points per (SEID, URR)
92+
ttlMinutes: 30 # Retain points this long
93+
94+
notifier:
95+
timeoutMs: 3000 # HTTP POST timeout
96+
retries: 1 # Retries on failure
97+
backoffSec: 1 # Backoff between retries (s)
98+
```
99+
100+
> If `ees.enabled` is `false`, the embedded EES stack (API/Reporter/Index) will not start.
101+
102+
---
103+
104+
## Subscription API
105+
106+
### Create subscription
107+
108+
`POST /nupf-ee/v1/ee-subscriptions`
109+
110+
**Request (MVP fields)**
111+
112+
```json
113+
{
114+
"nfId": "string",
115+
"target": { "seid": 1, "urrId": 2 }, // URRID optional; SEID required in MVP
116+
"events": ["USER_DATA_USAGE_MEASURES"],
117+
"measurementType": "VOLUME_MEASUREMENT",
118+
"eventTrigger": "PERIODIC",
119+
"reportingPeriodSec": 10, // will be rounded to whitelist
120+
"immediate": true, // send one snapshot immediately if true
121+
"maxReports": 1, // optional; 1 = on-demand one-shot
122+
"notifUri": "http://127.0.0.1:8080/callback"
123+
}
124+
```
125+
126+
**Response**
127+
128+
```json
129+
{
130+
"subscriptionId": "uuid",
131+
"effectiveReportingPeriodSec": 10,
132+
"nextReportAt": "2025-10-01T12:34:50Z",
133+
"slotAligned": true,
134+
"onDemand": false,
135+
"target": { "seid": 1, "urrId": 2 }
136+
}
137+
```
138+
139+
* **Immediate + Periodic**: send one snapshot now, then periodic reports aligned to the next slot.
140+
* **On-Demand**: if `immediate=true` and `maxReports=1`, send one snapshot now and **do not** schedule (not stored).
141+
* **Rounding**: if the request period isn’t in the whitelist, it is rounded to the nearest allowed value and returned as `effectiveReportingPeriodSec`.
142+
143+
### Delete subscription
144+
145+
`DELETE /nupf-ee/v1/ee-subscriptions/{subscriptionId}` → `204 No Content`
146+
147+
---
148+
149+
## Notification payload
150+
151+
`POST`ed to `notifUri` as JSON (MVP format):
152+
153+
```json
154+
{
155+
"notifications": [
156+
{
157+
"eventType": "USER_DATA_USAGE_MEASURES",
158+
"correlationId": "subscription-uuid",
159+
"timestamp": "2025-10-01T12:34:40Z",
160+
"userDataUsageMeasurements": {
161+
"sessionRef": { "seid": 1, "urrId": 2 },
162+
"volumeMeasurement": {
163+
"totalBytes": 1984, "ulBytes": 1024, "dlBytes": 960,
164+
"totalPkts": 31, "ulPkts": 16, "dlPkts": 15
165+
},
166+
"throughputMeasurement": {
167+
"ulBps": 800000, "dlBps": 760000
168+
}
169+
}
170+
}
171+
]
172+
}
173+
```
174+
175+
* **Aggregation**: sums all `IntervalPoint`s (from Index) within `[now - period, now]`.
176+
* **Throughput**: computed using the **actual window duration** (first point `Start` → last point `End`).
177+
* **Empty window**: sends a **zero report** (useful for demos and troubleshooting).
178+
179+
---
180+
181+
## Test tools
182+
183+
### Tiny notification sink
184+
185+
Use this Python script to receive and pretty-print notifications:
186+
187+
```bash
188+
python3 notify_sink.py --port 8080 --path /callback --outdir ./ees_payloads
189+
```
190+
191+
### Example subscriptions (curl)
192+
193+
**A) Immediate + periodic (SEID)**
194+
195+
```bash
196+
curl -s -X POST http://127.0.0.1:8088/nupf-ee/v1/ee-subscriptions \
197+
-H 'Content-Type: application/json' \
198+
-d '{
199+
"nfId":"lab-nwdaf-1",
200+
"target":{"seid":1},
201+
"events":["USER_DATA_USAGE_MEASURES"],
202+
"measurementType":"VOLUME_MEASUREMENT",
203+
"eventTrigger":"PERIODIC",
204+
"reportingPeriodSec":10,
205+
"immediate":true,
206+
"notifUri":"http://127.0.0.1:8080/callback"
207+
}' | jq
208+
```
209+
210+
**B) Periodic only (SEID+URR)**
211+
212+
```bash
213+
curl -s -X POST http://127.0.0.1:8088/nupf-ee/v1/ee-subscriptions \
214+
-H 'Content-Type: application/json' \
215+
-d '{
216+
"nfId":"lab-nwdaf-2",
217+
"target":{"seid":1,"urrId":2},
218+
"events":["USER_DATA_USAGE_MEASURES"],
219+
"measurementType":"VOLUME_MEASUREMENT",
220+
"eventTrigger":"PERIODIC",
221+
"reportingPeriodSec":30,
222+
"immediate":false,
223+
"notifUri":"http://127.0.0.1:8080/callback"
224+
}' | jq
225+
```
226+
227+
**C) On-demand one-shot**
228+
229+
```bash
230+
curl -s -X POST http://127.0.0.1:8088/nupf-ee/v1/ee-subscriptions \
231+
-H 'Content-Type: application/json' \
232+
-d '{
233+
"nfId":"lab-nwdaf-once",
234+
"target":{"seid":1},
235+
"events":["USER_DATA_USAGE_MEASURES"],
236+
"measurementType":"VOLUME_MEASUREMENT",
237+
"eventTrigger":"PERIODIC",
238+
"reportingPeriodSec":10,
239+
"immediate":true,
240+
"maxReports":1,
241+
"notifUri":"http://127.0.0.1:8080/callback"
242+
}' | jq
243+
```
244+
245+
**D) Whitelist rounding check** (request 7s → rounded to 10s, e.g.)
246+
247+
```bash
248+
curl -s -X POST http://127.0.0.1:8088/nupf-ee/v1/ee-subscriptions \
249+
-H 'Content-Type: application/json' \
250+
-d '{
251+
"nfId":"lab-nwdaf-rounding",
252+
"target":{"seid":1},
253+
"events":["USER_DATA_USAGE_MEASURES"],
254+
"measurementType":"VOLUME_MEASUREMENT",
255+
"eventTrigger":"PERIODIC",
256+
"reportingPeriodSec":7,
257+
"immediate":true,
258+
"notifUri":"http://127.0.0.1:8080/callback"
259+
}' | jq
260+
```
261+
262+
**E) Delete**
263+
264+
```bash
265+
curl -i -X DELETE http://127.0.0.1:8088/nupf-ee/v1/ee-subscriptions/<subscriptionId>
266+
```
267+
268+
---
269+
270+
## Logging
271+
272+
* **IndexSink**
273+
274+
* `point added` with `(seid, urr, seq)` and ring size
275+
* duplicates/out-of-order sequences are dropped with clear warnings
276+
* **Reporter**
277+
278+
* `create-subscription` with requested vs. effective period, target, immediate/on-demand
279+
* per-report `INFO`: `sub, seid[/urr], period, points, UL/DL bytes, UL/DL bps, winStart, winEnd`
280+
* empty windows: `WARN` (still sends a zero report)
281+
* **Notifier**
282+
283+
* `notify ok` (attempt) or `retry`/`failed` with status
284+
285+
Use UPF logger settings in `UPF-EES.yaml` → `logger` section (`level: debug` recommended during bring-up).
286+

cmd/main.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -57,12 +57,13 @@ func action(cliCtx *cli.Context) error {
5757

5858
logger.MainLog.Infoln("UPF version: ", version.GetVersion())
5959

60-
cfg, err := factory.ReadConfig(cliCtx.String("config"))
60+
cfgPath := cliCtx.String("config")
61+
cfg, err := factory.ReadConfig(cfgPath)
6162
if err != nil {
6263
return err
6364
}
6465

65-
upf, err := upfapp.NewApp(cfg)
66+
upf, err := upfapp.NewApp(cfg, cfgPath) // pass config path for 'ees:' loader
6667
if err != nil {
6768
return err
6869
}

go.mod

Lines changed: 24 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
module github.com/free5gc/go-upf
22

3-
go 1.24
3+
go 1.22
44

55
require (
66
github.com/asaskevich/govalidator v0.0.0-20210307081110-f21760c49a8d
77
github.com/davecgh/go-spew v1.1.1
88
github.com/free5gc/go-gtp5gnl v1.4.7-0.20241008130314-a3088e4cb7fa
9-
github.com/free5gc/util v1.1.1
9+
github.com/free5gc/util v1.0.6
1010
github.com/hashicorp/go-version v1.6.0
1111
github.com/khirono/go-genl v1.0.1
1212
github.com/khirono/go-nl v1.0.5
@@ -15,44 +15,45 @@ require (
1515
github.com/pkg/errors v0.9.1
1616
github.com/sirupsen/logrus v1.9.3
1717
github.com/stretchr/testify v1.9.0
18+
github.com/urfave/cli v1.22.5
1819
github.com/urfave/cli/v2 v2.27.7
1920
github.com/wmnsk/go-pfcp v0.0.23-0.20231009074152-d5a9c1f47114
2021
gopkg.in/yaml.v2 v2.4.0
2122
)
2223

24+
require github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
25+
2326
require (
24-
github.com/bytedance/sonic v1.11.6 // indirect
25-
github.com/bytedance/sonic/loader v0.1.1 // indirect
26-
github.com/cloudwego/base64x v0.1.4 // indirect
27-
github.com/cloudwego/iasm v0.2.0 // indirect
27+
github.com/bytedance/sonic v1.9.1 // indirect
28+
github.com/chenzhuoyu/base64x v0.0.0-20221115062448-fe3a3abad311 // indirect
2829
github.com/cpuguy83/go-md2man/v2 v2.0.7 // indirect
29-
github.com/gabriel-vasile/mimetype v1.4.3 // indirect
30+
github.com/gabriel-vasile/mimetype v1.4.2 // indirect
3031
github.com/gin-contrib/sse v0.1.0 // indirect
31-
github.com/gin-gonic/gin v1.10.0 // indirect
32+
github.com/gin-gonic/gin v1.9.1 // indirect
3233
github.com/go-playground/locales v0.14.1 // indirect
3334
github.com/go-playground/universal-translator v0.18.1 // indirect
34-
github.com/go-playground/validator/v10 v10.20.0 // indirect
35+
github.com/go-playground/validator/v10 v10.14.0 // indirect
3536
github.com/goccy/go-json v0.10.2 // indirect
36-
github.com/golang-jwt/jwt/v5 v5.2.2 // indirect
3737
github.com/google/go-cmp v0.6.0 // indirect
38+
github.com/google/uuid v1.6.0
3839
github.com/json-iterator/go v1.1.12 // indirect
39-
github.com/klauspost/cpuid/v2 v2.2.7 // indirect
40-
github.com/leodido/go-urn v1.4.0 // indirect
41-
github.com/mattn/go-isatty v0.0.20 // indirect
40+
github.com/klauspost/cpuid/v2 v2.2.4 // indirect
41+
github.com/leodido/go-urn v1.2.4 // indirect
42+
github.com/mattn/go-isatty v0.0.19 // indirect
4243
github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect
4344
github.com/modern-go/reflect2 v1.0.2 // indirect
44-
github.com/pelletier/go-toml/v2 v2.2.2 // indirect
45+
github.com/pelletier/go-toml/v2 v2.0.8 // indirect
4546
github.com/pmezard/go-difflib v1.0.0 // indirect
4647
github.com/russross/blackfriday/v2 v2.1.0 // indirect
48+
github.com/shurcooL/sanitized_anchor_name v1.0.0 // indirect
4749
github.com/tim-ywliu/nested-logrus-formatter v1.3.2 // indirect
4850
github.com/twitchyliquid64/golang-asm v0.15.1 // indirect
49-
github.com/ugorji/go/codec v1.2.12 // indirect
50-
github.com/xrash/smetrics v0.0.0-20240521201337-686a1a2994c1 // indirect
51-
golang.org/x/arch v0.8.0 // indirect
52-
golang.org/x/crypto v0.36.0 // indirect
53-
golang.org/x/net v0.38.0 // indirect
54-
golang.org/x/sys v0.31.0 // indirect
55-
golang.org/x/text v0.23.0 // indirect
56-
google.golang.org/protobuf v1.34.1 // indirect
57-
gopkg.in/yaml.v3 v3.0.1 // indirect
51+
github.com/ugorji/go/codec v1.2.11 // indirect
52+
golang.org/x/arch v0.3.0 // indirect
53+
golang.org/x/crypto v0.31.0 // indirect
54+
golang.org/x/net v0.33.0 // indirect
55+
golang.org/x/sys v0.28.0 // indirect
56+
golang.org/x/text v0.21.0 // indirect
57+
google.golang.org/protobuf v1.33.0 // indirect
58+
gopkg.in/yaml.v3 v3.0.1
5859
)

0 commit comments

Comments
 (0)