Skip to content

Cell Congestion Optimization rApp

The TOSSI Open RAN O1 Stack is a complete, standards-based environment for building and testing O1-driven, closed-loop applications on a live 5G network. It brings together a 5G RAN, an O1 adapter, and an O1 NETCONF server, and integrates with a Non-RT RIC application to close a control loop over the O-RAN O1 interface.

The stack is designed to interoperate with the platforms the community already runs, the O-RAN Software Community Non-RT RIC and an ONAP-based SMO, so that an independent rApp can observe the network, decide, and act, entirely through standard interfaces. The reference application is the open-source Cell Congestion Optimization rApp published by coRAN LABS, which detects Physical Resource Block congestion from O1 performance data and relieves it with signal-aware inter-gNB handovers.

Cell Congestion Optimization rApp architecture across three planes. The SMO / Non-RT RIC management plane hosts the rApp on the O-RAN SC Non-RT RIC over R1, an ONAP SMO SDNR OpenDaylight O1 controller, InfluxDB for trends, and a dashboard and policy web UI. The congestion rApp decision pipeline runs ingest, classify, plan, an RSRP gate, and actuate. The O1 plane holds the OCUDU O1 Adaptor and the OCUDU NETCONF server over the managed element, above two OCUDU gNBs on n78 TDD 100 MHz with O-RUs and a 5G core.
The Cell Congestion Optimization rApp across three planes. The rApp is onboarded onto the O-RAN SC Non-RT RIC over R1; it ingests the O1 performance stream, classifies and plans congestion relief with an RSRP gate, and actuates inter-gNB handovers over O1 through the SMO SDNR controller. The O1 plane exposes the RAN as a 3GPP managed element for two OCUDU gNBs. ref. coRAN LABS

Architecture

The stack has four cooperating components.

OCUDU RAN. A monolithic 5G gNB connected to O-RUs over Open Fronthaul 7.2x and to a 5G core over N2 and N3. It exports per-cell and per-user performance, configured neighbour relations, and serving and neighbour radio measurements, and executes inter-gNB handovers over Xn.

OCUDU O1 Adaptor. Consumes the RAN metrics stream and translates it into 3GPP TS 28.554 performance-management envelopes, streaming them to the collector defined by a provisioned PerfMetricJob.

OCUDU NETCONF. Presents the RAN as a 3GPP network resource model over the O1 NETCONF interface, and holds the PerfMetricJob that controls performance streaming. It is compatible with OpenDaylight-based controllers, so an SMO can mount the network element and manage it.

Cell Congestion Optimization rApp. The open-source Non-RT RIC application published by coRAN LABS. It ingests the O1 performance-management stream, detects Physical Resource Block congestion at the cell level, and selects a relief neighbour the user can actually hear by gating the target on reported RSRP. It actuates the resulting inter-gNB handover over O1 through the SMO SDNR controller, exposes an operator dashboard and a runtime policy API, and is onboarded onto the O-RAN SC Non-RT RIC over the R1 interface.

Lab topology for the Cell Congestion Optimization rApp. A GNSS antenna feeds a Fibrolan grandmaster for timing. Two OCUDU gNBs, gNB 1 on Host B and gNB 2 on Host C, connect over Open Fronthaul 7.2x to Radio Unit 1 and Radio Unit 2, both Liteon O-RUs. The gNBs connect over NGAP to a 5G Core running SD-Core on Host A, and over O1 to an SMO plus rApp host running ONAP and the O-RAN SC Non-RT RIC on Host D. The legend marks Front Haul, NGAP, and O1 links.
The two-gNB lab topology. Two OCUDU gNBs serve Liteon O-RUs over Open Fronthaul 7.2x, connect to an SD-Core over NGAP, and are managed over O1 by an SMO plus rApp host running ONAP and the O-RAN SC Non-RT RIC. A Fibrolan grandmaster distributes GNSS timing. ref. coRAN LABS
Detailed internal flow of the Cell Congestion Optimization rApp. Within the SMO framework, the Non-RT RIC hosts the rApp over R1 with an SDNR RESTCONF and NETCONF controller. The congestion rApp runs a six-stage pipeline: ingest (envelope.go), registry (topology.go), classifier (classifier.go), policy (policy.go with a runtime policy API), planner (decision.go, movers are the top-PRB UEs), and actuator (o1/actuator.go, gated on O1_HANDOVER_ENABLED). It exposes a login-protected dashboard, a per-report state machine from NORMAL to RESOURCE_CONGESTION, an eight-check safety chain, an observability API surface, and Helm-values config, writing cell KPIs and handovers to a 30-day InfluxDB. Below, the RAN plane shows gNB A (source, congested) handing a UE over Xn to gNB B (target, relief), driven over O1 by an O1 adapter and a NETCONF server, with a loop timeline of roughly ten seconds end to end.
ref. coRAN LABS

Prerequisites

  • A Linux host for the RAN, with a DPDK-capable fronthaul NIC and access to O-RUs and a 5G core.
  • Python 3 for the O1 adapter.
  • Docker Engine 24.0 or newer, for the NETCONF server and container image builds.
  • For the rApp, an O-RAN SC Non-RT RIC with rApp Manager is the primary deployment target (R1-based onboarding). A Kubernetes cluster and Helm are optional, only for a standalone manual deployment of the rApp that is unrelated to R1.
sudo apt install python3 python3-venv python3-pip

Deployment

Clone the three TOSSI components into a working directory (the adapter, NETCONF, and RAN side by side):

git clone https://github.com/TOSSI-Foundation/OCUDU-RAN.git
git clone https://github.com/TOSSI-Foundation/OCUDU-Netconf.git
git clone https://github.com/TOSSI-Foundation/OCUDU-O1-Adaptor.git

NETCONF server (start first)

cd OCUDU-Netconf
docker build -t ocudu-netconf/ocudu-netconf:latest .
docker run -it --name ocudu-netconf -p 830:830 ocudu-netconf/ocudu-netconf:latest --config gnb
cd ..

The server is ready when it logs that it is listening for connections on port 830.

RAN (gNB)

The Xn inter-gNB handover support and the paired example configurations live on the xnap_handover branch. Check it out, then build (install the build dependencies listed in the OCUDU-RAN README first, including DPDK):

cd OCUDU-RAN
git checkout xnap_handover
mkdir -p build && cd build
sudo cmake -DDU_SPLIT_TYPE=SPLIT_7_2 -DENABLE_DPDK=True -DASSERT_LEVEL=MINIMAL -DENABLE_UHD=OFF ../
make -j "$(nproc)"
cd ../..

Inter-gNB handover requires two gNBs. The repository ships a paired set of example configurations under OCUDU-RAN/configs/, one for each gNB:

  • gnb_ru_liteon_tdd_n78_100mhz_xnap_gnb1.yml for the first gNB
  • gnb_ru_liteon_tdd_n78_100mhz_xnap_gnb2.yml for the second gNB

Edit each configuration for your site (core/AMF address, the fronthaul NIC and O-RU parameters, and the CPU affinities), keeping the cell identity and neighbour relations that pair the two gNBs. Then run each on its host:

# first gNB host
sudo ./OCUDU-RAN/build/apps/gnb/gnb -c OCUDU-RAN/configs/gnb_ru_liteon_tdd_n78_100mhz_xnap_gnb1.yml

# second gNB host
sudo ./OCUDU-RAN/build/apps/gnb/gnb -c OCUDU-RAN/configs/gnb_ru_liteon_tdd_n78_100mhz_xnap_gnb2.yml

O1 adapter

cd OCUDU-O1-Adaptor
python3 -m venv venv
./venv/bin/pip install -r requirements.txt
./venv/bin/python src/o1_adapter.py \
  --netconf_host localhost --netconf_port 830 \
  --netconf_username root --netconf_password root \
  --profile gnb --ws_host localhost --ws_port 8001
cd ..

Provision O1 performance streaming

The NETCONF server re-seeds its factory config on every start, so run this once after it comes up (and again after any restart) to point the gNB O1 performance stream at your collector. Set STREAM_TARGET to your endpoint (for example a Non-RT RIC rApp /pm). It is an idempotent merge, safe to re-run.

STREAM_TARGET="http://<collector-host>:<port>/pm" ./OCUDU-O1-Adaptor/venv/bin/python - <<'PY'
import os
from ncclient import manager
t = os.environ["STREAM_TARGET"]
cfg = f"""<config xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"><ManagedElement xmlns="urn:3gpp:sa5:_3gpp-common-managed-element"><id>ran1</id><GNBCUCPFunction xmlns="urn:3gpp:sa5:_3gpp-nr-nrm-gnbcucpfunction"><id>cucp1</id><PerfMetricJob><id>congestion-rapp</id><attributes><administrativeState>UNLOCKED</administrativeState><performanceMetrics>DRB.MaxActiveUeDl</performanceMetrics><granularityPeriod>5</granularityPeriod><streamTarget>{t}</streamTarget></attributes></PerfMetricJob></GNBCUCPFunction></ManagedElement></config>"""
with manager.connect(host="localhost", port=830, username="root", password="root", hostkey_verify=False, allow_agent=False, look_for_keys=False, timeout=20) as m:
    m.edit_config(target="running", config=cfg, default_operation="merge")
    print("PerfMetricJob:", "PROVISIONED" if "congestion-rapp" in m.get_config(source="running").data_xml else "FAILED")
PY

Prints PerfMetricJob: PROVISIONED on success. The adapter picks up the job over its NETCONF subscription automatically; no restart is required.

Cell Congestion Optimization rApp (coRAN LABS)

git clone https://github.com/coranlabs/cell_congestion_optimization_rapp.git
cd cell_congestion_optimization_rapp

Primary: R1-based deployment (O-RAN SC Non-RT RIC rApp Manager). Prerequisites: build and push the rApp image to a registry your cluster can pull from; bake your configuration and secrets into the Helm chart values (SDNR endpoint and credentials, managed-element id, InfluxDB token, dashboard accounts), because the R1 and ACM path ignores instance values; and upload the charts to the Non-RT RIC chart repository. Then onboard, prime, create an instance, and deploy:

RM=$(kubectl get svc rappmanager -n <namespace> -o jsonpath='{.spec.clusterIP}')

# build the CSAR package
bash scripts/create-csar.sh

# onboard
curl -X POST http://$RM:8080/rapps/congestion-rapp \
  -F file=@rapp-package/dist/congestion-rapp-1.1.0.csar

# prime
curl -X PUT http://$RM:8080/rapps/congestion-rapp \
  -H 'Content-Type: application/json' -d '{"primeOrder":"PRIME"}'

# create instance (ACM + SME + DME), capture the instance id
IID=$(curl -s -X POST http://$RM:8080/rapps/congestion-rapp/instance \
  -H 'Content-Type: application/json' \
  -d '{"acm":{"instance":"congestion-instance"},"sme":{"providerFunction":"congestion-provider","serviceApis":"congestion-api","invokers":"ics-invoker"},"dme":{"infoTypesProducer":["cell-congestion-state"],"infoProducer":"congestion-rapp-producer"}}' \
  | python3 -c 'import json,sys;print(json.load(sys.stdin)["rappInstanceId"])')
echo "instance: $IID"

# deploy
curl -X PUT http://$RM:8080/rapps/congestion-rapp/instance/$IID \
  -H 'Content-Type: application/json' -d '{"deployOrder":"DEPLOY"}'

Optional: manual Helm deployment (standalone, no R1).

docker build -t congestion-rapp:2.1.7 .
helm install congestion-rapp deploy/helm/congestion -n <namespace> \
  --set image.repository=congestion-rapp --set image.tag=2.1.7 \
  --set o1HandoverEnabled=true \
  --set sdnr.endpoint=<sdnr-restconf-url> \
  --set sdnr.username=<user> --set secrets.sdnrPassword=<password> \
  --set nodeId=<managed-element-id>

Point the adapter STREAM_TARGET (Provision O1 performance streaming) at the rApp PM ingest endpoint. For trends and handover history, deploy the companion InfluxDB chart under deploy/helm/.

References