Add monolithic N2 handover test pipeline with B210 and Quectel

- Add XML pipeline for N2 handover testing
- Add Docker compose for two monolithic gNBs
- Features:
  * Two monolithic gNBs (gNB1, gNB2) with proper CU+DU parameters
  * Gradual attenuation control using RC4DAT attenuator (5s duration)
  * N2 handover sequence: gNB1 → gNB2 → gNB1
  * Proper channel mapping: ch1-2 for gNB1, ch3-4 for gNB2
  * Telnet server configuration for CI commands
  * Handover-specific neighbor configuration
  * Clean test state management with attenuator reset
This commit is contained in:
Guido Casati
2025-10-22 14:48:34 +02:00
parent 7045140845
commit c3eaef704f
3 changed files with 528 additions and 0 deletions
@@ -0,0 +1,237 @@
Active_gNBs = ( "gNB-OAI");
# Asn1_verbosity, choice in: none, info, annoying
Asn1_verbosity = "none";
gNBs =
(
{
////////// Identification parameters:
gNB_ID = 0xe00;
gNB_name = "gNB-OAI";
// Tracking area code, 0x0000 and 0xfffe are reserved values
tracking_area_code = 1;
plmn_list = ({ mcc = 001; mnc = 03; mnc_length = 2; snssaiList = ({ sst = 1, sd = 0xffffff }) });
// Include neighbour configuration for N2 handover
@include "neighbour-config-ho.conf"
nr_cellid = 12345678L;
////////// Physical parameters:
servingCellConfigCommon = (
{
#spCellConfigCommon
physCellId = 0;
# downlinkConfigCommon
#frequencyInfoDL
# this is 3610.56 MHz
absoluteFrequencySSB = 679104;
dl_frequencyBand = 77;
# this is 3599.94 MHz
dl_absoluteFrequencyPointA = 678388;
#scs-SpecificCarrierList
dl_offstToCarrier = 0;
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
dl_subcarrierSpacing = 1;
dl_carrierBandwidth = 51;
#initialDownlinkBWP
#genericParameters
# this is RBstart=27,L=48 (275*(L-1))+RBstart
initialDLBWPlocationAndBandwidth = 13750;
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
initialDLBWPsubcarrierSpacing = 1;
#pdcch-ConfigCommon
initialDLBWPcontrolResourceSetZero = 12;
initialDLBWPsearchSpaceZero = 0;
#uplinkConfigCommon
#frequencyInfoUL
ul_frequencyBand = 77;
#scs-SpecificCarrierList
ul_offstToCarrier = 0;
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
ul_subcarrierSpacing = 1;
ul_carrierBandwidth = 51;
pMax = 20;
#initialUplinkBWP
#genericParameters
initialULBWPlocationAndBandwidth = 13750;
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
initialULBWPsubcarrierSpacing = 1;
#rach-ConfigCommon
#rach-ConfigGeneric
prach_ConfigurationIndex = 98;
#prach_msg1_FDM
#0 = one, 1=two, 2=four, 3=eight
prach_msg1_FDM = 0;
prach_msg1_FrequencyStart = 0;
zeroCorrelationZoneConfig = 13;
preambleReceivedTargetPower = -96;
#preamblTransMax (0...10) = (3,4,5,6,7,8,10,20,50,100,200)
preambleTransMax = 6;
#powerRampingStep
# 0=dB0,1=dB2,2=dB4,3=dB6
powerRampingStep = 1;
#ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR
#1=oneeighth,2=onefourth,3=half,4=one,5=two,6=four,7=eight,8=sixteen
ssb_perRACH_OccasionAndCB_PreamblesPerSSB_PR = 4;
#one (0..15) 4,8,12,16,...60,64
ssb_perRACH_OccasionAndCB_PreamblesPerSSB = 14;
#ra_ContentionResolutionTimer
#(0..7) 8,16,24,32,40,48,56,64
ra_ContentionResolutionTimer = 7;
rsrp_ThresholdSSB = 19;
#prach-RootSequenceIndex_PR
#1 = 839, 2 = 139
prach_RootSequenceIndex_PR = 2;
prach_RootSequenceIndex = 1;
# SCS for msg1, can only be 15 for 30 kHz < 6 GHz, takes precendence over the one derived from prach-ConfigIndex
#
msg1_SubcarrierSpacing = 1,
# restrictedSetConfig
# 0=unrestricted, 1=restricted type A, 2=restricted type B
restrictedSetConfig = 0,
msg3_DeltaPreamble = 1;
p0_NominalWithGrant =-90;
# pucch-ConfigCommon setup :
# pucchGroupHopping
# 0 = neither, 1= group hopping, 2=sequence hopping
pucchGroupHopping = 0;
hoppingId = 40;
p0_nominal = -90;
ssb_PositionsInBurst_Bitmap = 1;
# ssb_periodicityServingCell
# 0 = ms5, 1=ms10, 2=ms20, 3=ms40, 4=ms80, 5=ms160, 6=spare2, 7=spare1
ssb_periodicityServingCell = 2;
# dmrs_TypeA_position
# 0 = pos2, 1 = pos3
dmrs_TypeA_Position = 0;
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
subcarrierSpacing = 1;
#tdd-UL-DL-ConfigurationCommon
# subcarrierSpacing
# 0=kHz15, 1=kHz30, 2=kHz60, 3=kHz120
referenceSubcarrierSpacing = 1;
# pattern1
# dl_UL_TransmissionPeriodicity
# 0=ms0p5, 1=ms0p625, 2=ms1, 3=ms1p25, 4=ms2, 5=ms2p5, 6=ms5, 7=ms10
dl_UL_TransmissionPeriodicity = 6;
nrofDownlinkSlots = 7;
nrofDownlinkSymbols = 6;
nrofUplinkSlots = 2;
nrofUplinkSymbols = 4;
ssPBCH_BlockPower = -25;
}
);
# ------- SCTP definitions
SCTP :
{
# Number of streams to use in input/output
SCTP_INSTREAMS = 2;
SCTP_OUTSTREAMS = 2;
};
////////// AMF parameters:
amf_ip_address = ({ ipv4 = "172.21.6.109"; });
NETWORK_INTERFACES :
{
GNB_IPV4_ADDRESS_FOR_NG_AMF = "172.21.19.98";
GNB_IPV4_ADDRESS_FOR_NGU = "172.21.19.98";
GNB_PORT_FOR_S1U = 2152; # Spec 2152
};
}
);
MACRLCs = (
{
num_cc = 1;
tr_s_preference = "local_L1";
tr_n_preference = "local_RRC";
pusch_TargetSNRx10 = 200;
pucch_TargetSNRx10 = 200;
ul_prbblack_SNR_threshold = 10;
}
);
L1s = (
{
num_cc = 1;
tr_n_preference = "local_mac";
prach_dtx_threshold = 120;
pucch0_dtx_threshold = 100;
ofdm_offset_divisor = 8; #set this to UINT_MAX for offset 0
}
);
RUs = (
{
local_rf = "yes"
nb_tx = 1
nb_rx = 1
att_tx = 0;
att_rx = 0;
bands = [77];
max_pdschReferenceSignalPower = -27;
max_rxgain = 114;
eNB_instances = [0];
clock_src = "external";
time_src = "external";
}
);
security = {
# preferred ciphering algorithms
# the first one of the list that an UE supports in chosen
# valid values: nea0, nea1, nea2, nea3
ciphering_algorithms = ( "nea0" );
# preferred integrity algorithms
# the first one of the list that an UE supports in chosen
# valid values: nia0, nia1, nia2, nia3
integrity_algorithms = ( "nia2", "nia0" );
# setting 'drb_ciphering' to "no" disables ciphering for DRBs, no matter
# what 'ciphering_algorithms' configures; same thing for 'drb_integrity'
drb_ciphering = "yes";
drb_integrity = "no";
};
log_config :
{
global_log_level ="info";
hw_log_level ="info";
phy_log_level ="info";
mac_log_level ="info";
rlc_log_level ="info";
pdcp_log_level ="info";
rrc_log_level ="info";
ngap_log_level ="debug";
f1ap_log_level ="debug";
};
@@ -0,0 +1,230 @@
<!--
Licensed to the OpenAirInterface (OAI) Software Alliance under one or more
contributor license agreements. See the NOTICE file distributed with
this work for additional information regarding copyright ownership.
The OpenAirInterface Software Alliance licenses this file to You under
the OAI Public License, Version 1.1 (the "License"); you may not use this file
except in compliance with the License.
You may obtain a copy of the License at
http://www.openairinterface.org/?page_id=698
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
For more information about the OpenAirInterface (OAI) Software Alliance:
contact@openairinterface.org
-->
<testCaseList>
<htmlTabRef>TEST-MONO-N2-HO-B210</htmlTabRef>
<htmlTabName>Monolithic N2 Handover 20 MHz TDD SA</htmlTabName>
<htmlTabIcon>tasks</htmlTabIcon>
<TestCaseRequestedList>
000001
000002
000023
000003
000004
000005
000006
000007
000008
000009
000010
000011
000012
000013
000014
000015
000017
000018
000019
000016
000020
000021
000022
</TestCaseRequestedList>
<TestCaseExclusionList></TestCaseExclusionList>
<testCase id="000001">
<class>DeployCoreNetwork</class>
<desc>Initialize 5G Core</desc>
<cn_id>oc-cn5g-00103-ho</cn_id>
</testCase>
<testCase id="000002">
<class>Pull_Local_Registry</class>
<desc>Pull Images from Local Registry</desc>
<images>oai-gnb</images>
<node>groot</node>
</testCase>
<testCase id="000023">
<class>Pull_Local_Registry</class>
<desc>Pull Images from Local Registry</desc>
<images>oai-gnb</images>
<node>rocket</node>
</testCase>
<testCase id="000003">
<class>Create_Workspace</class>
<desc>Create new Workspace for groot</desc>
<node>groot</node>
</testCase>
<testCase id="000005">
<class>Create_Workspace</class>
<desc>Create new Workspace on rocket</desc>
<node>rocket</node>
</testCase>
<testCase id="000004">
<class>Custom_Command</class>
<desc>Set initial attenuation on target gNB</desc>
<node>rocket</node>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -s 1,2 0 -s 3,4 15</command>
</testCase>
<testCase id="000005">
<class>Deploy_Object</class>
<desc>Deploy Monolithic gNB-PCI0 (TDD/Band77/20MHz/B210) in a container</desc>
<yaml_path>ci-scripts/yaml_files/5g_sa_n2_ho_b210</yaml_path>
<node>groot</node>
<services>oai-gnb-pci0</services>
</testCase>
<testCase id="000006">
<class>Deploy_Object</class>
<desc>Deploy Monolithic gNB-PCI1 (TDD/Band77/20MHz/B210) in a container</desc>
<yaml_path>ci-scripts/yaml_files/5g_sa_n2_ho_b210</yaml_path>
<node>rocket</node>
<services>oai-gnb-pci1</services>
</testCase>
<testCase id="000007">
<class>Attach_UE</class>
<desc>Attach Quectel</desc>
<id>raspix</id>
</testCase>
<testCase id="000008">
<class>Ping</class>
<desc>Ping: 100pings in 20sec</desc>
<id>raspix</id>
<svr_id>oc-cn5g-00103-ho</svr_id>
<ping_args>-c 100 -i 0.2</ping_args>
<ping_packetloss_threshold>5</ping_packetloss_threshold>
</testCase>
<testCase id="000009">
<class>IdleSleep</class>
<desc>Sleep</desc>
<idle_sleep_time_in_sec>5</idle_sleep_time_in_sec>
</testCase>
<testCase id="000010">
<class>Custom_Command</class>
<desc>Gradually attenuate gNB-PCI0 (ch1-2=15dB) and gNB-PCI1 (ch3-4=0dB) over 5s to trigger N2 Handover to gNB-PCI1</desc>
<node>rocket</node>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 15 -r 3,4 0 -d 5</command>
</testCase>
<testCase id="000011">
<class>Ping</class>
<desc>Ping: 50pings in 10sec</desc>
<id>raspix</id>
<svr_id>oc-cn5g-00103-ho</svr_id>
<ping_args>-c 50 -i 0.2</ping_args>
<ping_packetloss_threshold>5</ping_packetloss_threshold>
</testCase>
<testCase id="000012">
<class>Custom_Command</class>
<desc>UE connected to gNB-PCI1?</desc>
<node>rocket</node>
<command>echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3585"</command>
</testCase>
<testCase id="000013">
<class>Custom_Command</class>
<desc>Gradually attenuate gNB-PCI0 (ch1-2=0dB) and gNB-PCI1 (ch3-4=15dB) over 5s to trigger N2 Handover back to gNB-PCI0</desc>
<node>rocket</node>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -r 1,2 0 -r 3,4 15 -d 5</command>
</testCase>
<testCase id="000014">
<class>Ping</class>
<desc>Ping: 50pings in 10sec</desc>
<id>raspix</id>
<svr_id>oc-cn5g-00103-ho</svr_id>
<ping_args>-c 50 -i 0.2</ping_args>
<ping_packetloss_threshold>5</ping_packetloss_threshold>
</testCase>
<testCase id="000015">
<class>Custom_Command</class>
<desc>UE connected to gNB-PCI0?</desc>
<node>groot</node>
<command>echo ci fetch_du_by_ue_id 1 | ncat 127.0.0.1 9090 | grep "3584"</command>
</testCase>
<testCase id="000016">
<class>Custom_Command</class>
<always_exec>true</always_exec>
<desc>Reset all attenuators to 0dB</desc>
<node>rocket</node>
<command>python3 %%workspace%%/ci-scripts/attenuatorctl.py -s 1-4 0</command>
</testCase>
<testCase id="000017">
<class>Detach_UE</class>
<always_exec>true</always_exec>
<desc>Detach Quectel</desc>
<id>raspix</id>
</testCase>
<testCase id="000018">
<class>Undeploy_Object</class>
<always_exec>true</always_exec>
<desc>Undeploy Monolithic gNB-PCI0</desc>
<node>groot</node>
<yaml_path>ci-scripts/yaml_files/5g_sa_n2_ho_b210</yaml_path>
</testCase>
<testCase id="000019">
<class>Undeploy_Object</class>
<always_exec>true</always_exec>
<desc>Undeploy Monolithic gNB-PCI1</desc>
<node>rocket</node>
<yaml_path>ci-scripts/yaml_files/5g_sa_n2_ho_b210</yaml_path>
</testCase>
<testCase id="000020">
<class>Clean_Test_Server_Images</class>
<always_exec>true</always_exec>
<desc>Clean Test Images on Test Server</desc>
<node>groot</node>
<images>oai-gnb</images>
</testCase>
<testCase id="000021">
<class>Clean_Test_Server_Images</class>
<always_exec>true</always_exec>
<desc>Clean Test Images on Test Server</desc>
<node>rocket</node>
<images>oai-gnb</images>
</testCase>
<testCase id="000022">
<class>UndeployCoreNetwork</class>
<always_exec>true</always_exec>
<desc>Terminate 5G Core</desc>
<cn_id>oc-cn5g-00103-ho</cn_id>
</testCase>
</testCaseList>
@@ -0,0 +1,61 @@
services:
oai-gnb-pci0:
image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop}
container_name: oai-gnb-pci0
cap_drop:
- ALL
cap_add:
- SYS_NICE
- IPC_LOCK
ulimits:
core: -1 # for core dumps
environment:
USE_B2XX: 'yes'
USE_ADDITIONAL_OPTIONS: --RUs.[0].sdr_addrs serial=30AD30F
--telnetsrv --telnetsrv.shrmod ci
--log_config.global_log_options level,nocolor,utc_time,line_num,function
ASAN_OPTIONS: detect_leaks=0:detect_odr_violation=0
devices:
- /dev/bus/usb/:/dev/bus/usb/
volumes:
- ../../conf_files/neighbour-config-ho.conf:/opt/oai-gnb/etc/neighbour-config-ho.conf
- ../../conf_files/gnb.sa.band77.51prb.usrpb200.n2-ho.conf:/opt/oai-gnb/etc/gnb.conf
network_mode: "host"
healthcheck:
test: /bin/bash -c "ps aux | grep -v grep | grep -c softmodem"
interval: 10s
timeout: 5s
retries: 5
oai-gnb-pci1:
image: ${REGISTRY:-oaisoftwarealliance}/${GNB_IMG:-oai-gnb}:${TAG:-develop}
container_name: oai-gnb-pci1
cap_drop:
- ALL
cap_add:
- SYS_NICE
- IPC_LOCK
ulimits:
core: -1 # for core dumps
environment:
USE_B2XX: 'yes'
USE_ADDITIONAL_OPTIONS: --RUs.[0].sdr_addrs serial=31F8010
--gNBs.[0].nr_cellid 11111111
--gNBs.[0].gNB_ID 0xe01
--gNBs.[0].servingCellConfigCommon.[0].physCellId 1
--gNBs.[0].servingCellConfigCommon.[0].ssb_PositionsInBurst_Bitmap 2
--gNBs.[0].NETWORK_INTERFACES.GNB_IPV4_ADDRESS_FOR_NG_AMF 172.21.19.99
--gNBs.[0].NETWORK_INTERFACES.GNB_IPV4_ADDRESS_FOR_NGU 172.21.19.99
--telnetsrv --telnetsrv.shrmod ci
--log_config.global_log_options level,nocolor,utc_time,line_num,function
devices:
- /dev/bus/usb/:/dev/bus/usb/
volumes:
- ../../conf_files/neighbour-config-ho.conf:/opt/oai-gnb/etc/neighbour-config-ho.conf
- ../../conf_files/gnb.sa.band77.51prb.usrpb200.n2-ho.conf:/opt/oai-gnb/etc/gnb.conf
network_mode: "host"
healthcheck:
test: /bin/bash -c "ps aux | grep -v grep | grep -c softmodem"
interval: 10s
timeout: 5s
retries: 5