fixeria has uploaded this change for review.

View Change

s1gw: implement the REST interface client

OsmoS1GW implements a custom (non-standard) REST interface, primarily
intended for state introspection. The interface is specified using
OpenAPI, with the specification available in osmo-s1gw.git.

Change-Id: Ic6e8b46b7de4fb72139b017fc9a9c83f92fc1541
Related: SYS#7066
---
M s1gw/S1GW_ConnHdlr.ttcn
A s1gw/S1GW_REST_Functions.ttcn
A s1gw/S1GW_REST_Types.ttcn
M s1gw/S1GW_Tests.cfg
M s1gw/S1GW_Tests.default
M s1gw/S1GW_Tests.ttcn
M s1gw/gen_links.sh
M s1gw/regen_makefile.sh
8 files changed, 290 insertions(+), 2 deletions(-)

git pull ssh://gerrit.osmocom.org:29418/osmo-ttcn3-hacks refs/changes/25/41125/1
diff --git a/s1gw/S1GW_ConnHdlr.ttcn b/s1gw/S1GW_ConnHdlr.ttcn
index 4e47544..15942cb 100644
--- a/s1gw/S1GW_ConnHdlr.ttcn
+++ b/s1gw/S1GW_ConnHdlr.ttcn
@@ -36,12 +36,15 @@

import from SCTP_Templates all;

+import from HTTP_Adapter all;
+import from HTTPmsg_Types all;
+
import from StatsD_Types all;
import from StatsD_Checker all;

import from S1AP_Server all;

-type component ConnHdlr extends MutexCT, S1APSRV_ConnHdlr, PFCP_ConnHdlr, StatsD_ConnHdlr {
+type component ConnHdlr extends MutexCT, S1APSRV_ConnHdlr, PFCP_ConnHdlr, StatsD_ConnHdlr, http_CT {
var ConnHdlrPars g_pars;
port S1AP_CODEC_PT S1AP_ENB;
var ConnectionId g_s1ap_conn_id := -1;
diff --git a/s1gw/S1GW_REST_Functions.ttcn b/s1gw/S1GW_REST_Functions.ttcn
new file mode 100644
index 0000000..fb8731b
--- /dev/null
+++ b/s1gw/S1GW_REST_Functions.ttcn
@@ -0,0 +1,94 @@
+/* REST interface API for OsmoS1GW
+ *
+ * (C) 2025 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de>
+ *
+ * All rights reserved.
+ *
+ * Released under the terms of GNU General Public License, Version 2 or
+ * (at your option) any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+module S1GW_REST_Functions {
+
+import from HTTP_Adapter all;
+import from HTTPmsg_Types all;
+import from S1GW_REST_Types all;
+
+/* Get a list of metrics */
+function f_REST_MetricsList(charstring metric_type := "all",
+ charstring path := "")
+runs on http_CT return MetricsList {
+ var HTTPMessage rsp := f_http_transact("/metrics-list?type=" & metric_type & "&path=" & path);
+ return dec_MetricsList(char2oct(rsp.response.body));
+}
+
+/* Get the PFCP association state */
+function f_REST_PfcpAssocState()
+runs on http_CT return PfcpAssocInfo {
+ var HTTPMessage rsp := f_http_transact("/pfcp/assoc", method := "GET");
+ return dec_PfcpAssocInfo(char2oct(rsp.response.body));
+}
+
+/* Initiate the PFCP Association Setup procedure */
+function f_REST_PfcpAssocSetup()
+runs on http_CT return OperationResult {
+ var HTTPMessage rsp := f_http_transact("/pfcp/assoc", method := "POST");
+ return dec_OperationResult(char2oct(rsp.response.body));
+}
+
+/* Initiate the PFCP Association Release procedure */
+function f_REST_PfcpAssocRelease()
+runs on http_CT return OperationResult {
+ var HTTPMessage rsp := f_http_transact("/pfcp/assoc", method := "DELETE");
+ return dec_OperationResult(char2oct(rsp.response.body));
+}
+
+/* Send a PFCP Heartbeat Request to the peer */
+function f_REST_PfcpHeartbeat()
+runs on http_CT return OperationResult {
+ var HTTPMessage rsp := f_http_transact("/pfcp/heartbeat", method := "POST");
+ return dec_OperationResult(char2oct(rsp.response.body));
+}
+
+/* Get a list of eNB connections */
+function f_REST_EnbList()
+runs on http_CT return EnbList {
+ var HTTPMessage rsp := f_http_transact("/enb-list", method := "GET");
+ return dec_EnbList(char2oct(rsp.response.body));
+}
+
+/* Get information about a specific eNB */
+function f_REST_EnbInfo(ParamEnbId enb_id)
+runs on http_CT return EnbItem {
+ var charstring p_enb_id := enc_ParamEnbId(enb_id);
+ var HTTPMessage rsp := f_http_transact("/enb/" & p_enb_id, method := "GET");
+ return dec_EnbItem(char2oct(rsp.response.body));
+}
+
+/* Get E-RAB list for a specific eNB */
+function f_REST_EnbErabList(ParamEnbId enb_id)
+runs on http_CT return ErabList {
+ var charstring p_enb_id := enc_ParamEnbId(enb_id);
+ var HTTPMessage rsp := f_http_transact("/enb/" & p_enb_id & "/erab-list", method := "GET");
+ return dec_ErabList(char2oct(rsp.response.body));
+}
+
+/* Get E-RAB list for all eNBs */
+function f_REST_ErabList()
+runs on http_CT return ErabList {
+ var HTTPMessage rsp := f_http_transact("/erab-list", method := "GET");
+ return dec_ErabList(char2oct(rsp.response.body));
+}
+
+/* Get information about a specific E-RAB */
+function f_REST_ErabInfo(ParamErabId erab_id)
+runs on http_CT return ErabItem {
+ var charstring p_erab_id := enc_ParamErabId(erab_id);
+ var HTTPMessage rsp := f_http_transact("/erab/" & p_erab_id, method := "GET");
+ return dec_ErabItem(char2oct(rsp.response.body));
+}
+
+}
diff --git a/s1gw/S1GW_REST_Types.ttcn b/s1gw/S1GW_REST_Types.ttcn
new file mode 100644
index 0000000..8ddbc20
--- /dev/null
+++ b/s1gw/S1GW_REST_Types.ttcn
@@ -0,0 +1,135 @@
+/* REST interface definitions for OsmoS1GW
+ *
+ * (C) 2025 by sysmocom - s.f.m.c. GmbH <info@sysmocom.de>
+ * Author: Vadim Yanitskiy <vyanitskiy@sysmocom.de>
+ *
+ * All rights reserved.
+ *
+ * Released under the terms of GNU General Public License, Version 2 or
+ * (at your option) any later version.
+ *
+ * SPDX-License-Identifier: GPL-2.0-or-later
+ */
+
+module S1GW_REST_Types {
+
+type charstring GlobalEnbId (pattern "[0-9]#3-[0-9]#(2,3)-[0-9]+");
+type charstring Pid (pattern "<[0-9]+.[0-9]+.[0-9]+>");
+
+type integer EnbHandle (0..infinity);
+type integer SEID (0..18446744073709551615);
+type integer TEID (0..4294967295);
+type integer Port (0..65535);
+
+type union ParamEnbId {
+ charstring handle (pattern "handle:[0-9]+"),
+ charstring pid (pattern "pid:[0-9]+.[0-9]+.[0-9]+"),
+ charstring genbid (pattern "genbid:[0-9]#3-[0-9]#(2,3)-[0-9]+"),
+ charstring sctp_aid (pattern "(enb|mme)-sctp-aid:[0-9]+"),
+ charstring enb_conn (pattern "enb-conn:[0-9:.]+-[0-9]+")
+} with { encode "TEXT" };
+
+type union ParamErabId {
+ charstring pid (pattern "pid:[0-9]+.[0-9]+.[0-9]+")
+} with { encode "TEXT" };
+
+type set OperationResult {
+ boolean success,
+ charstring msg optional
+} with {
+ variant(msg) "JSON:name as message";
+};
+
+type set of MetricsItem MetricsList;
+type set MetricsItem {
+ MetricType metric_type,
+ charstring metric_name,
+ integer metric_value
+} with {
+ variant(metric_type) "JSON:name as type";
+ variant(metric_name) "JSON:name as name";
+ variant(metric_value) "JSON:name as value";
+};
+
+type enumerated MetricType {
+ counter,
+ gauge
+};
+
+type set PfcpAssocInfo {
+ PfcpAssocState state, /* Current association state */
+ charstring laddr, /* Local (bind) IP address */
+ charstring raddr, /* Remote (connect) IP address */
+ integer lrts optional, /* Local Recovery TimeStamp */
+ integer rrts optional /* Remote Recovery TimeStamp */
+};
+
+type enumerated PfcpAssocState {
+ connecting,
+ connected
+};
+
+type set of EnbItem EnbList;
+type set EnbItem {
+ EnbHandle handle, /* Unique number in the eNB registry */
+ Pid pid, /* Process ID */
+ EnbState state, /* Connection state */
+ integer uptime, /* Uptime (in seconds) */
+ integer erab_count, /* Total number of E-RABs */
+ GlobalEnbId genb_id optional, /* Global-eNB-ID (e.g. "001-01-1337") */
+ charstring enb_saddr optional, /* Source (remote) address of the eNB */
+ charstring mme_daddr optional, /* Destination (remote) address of the MME */
+ Port enb_sport optional, /* Source (remote) port of the eNB-S1GW connection */
+ Port mme_sport optional, /* Source (local) port of the S1GW-MME connection */
+ Port mme_dport optional, /* Destination (remote) port of the S1GW-MME connection */
+ integer enb_sctp_aid optional, /* SCTP association identifier of the eNB-S1GW connection */
+ integer mme_sctp_aid optional /* SCTP association identifier of the eNB-S1GW connection */
+};
+
+type enumerated EnbState {
+ connecting,
+ connected,
+ s1setup
+};
+
+type set of ErabItem ErabList;
+type set ErabItem {
+ integer mme_ue_id, /* MME-UE-S1AP-ID */
+ integer erab_id, /* E-RAB-ID */
+ charstring state, /* E-RAB FSM state */
+ Pid pid, /* E-RAB FSM process ID */
+ SEID pfcp_lseid optional, /* PFCP SEID (local) */
+ SEID pfcp_rseid optional, /* PFCP SEID (remote) */
+ FTEID f_teid_u2c optional, /* GTP-U F-TEID for UPF -> Core */
+ FTEID f_teid_c2u optional, /* GTP-U F-TEID for UPF <- Core */
+ FTEID f_teid_a2u optional, /* GTP-U F-TEID for UPF <- Access */
+ FTEID f_teid_u2a optional /* GTP-U F-TEID for UPF -> Access */
+
+};
+
+type set FTEID {
+ TEID teid, /* GTP-U TEID */
+ charstring tla /* GTP-U TLA (Transport Layer Address) */
+};
+
+external function enc_ParamEnbId(in ParamEnbId enb_id) return charstring
+ with { extension "prototype(convert) encode(TEXT)" }
+external function enc_ParamErabId(in ParamErabId erab_id) return charstring
+ with { extension "prototype(convert) encode(TEXT)" }
+
+external function dec_OperationResult(in octetstring data) return OperationResult
+ with { extension "prototype(convert) decode(JSON)" }
+external function dec_MetricsList(in octetstring data) return MetricsList
+ with { extension "prototype(convert) decode(JSON)" }
+external function dec_PfcpAssocInfo(in octetstring data) return PfcpAssocInfo
+ with { extension "prototype(convert) decode(JSON)" }
+external function dec_EnbList(in octetstring data) return EnbList
+ with { extension "prototype(convert) decode(JSON)" }
+external function dec_EnbItem(in octetstring data) return EnbItem
+ with { extension "prototype(convert) decode(JSON)" }
+external function dec_ErabList(in octetstring data) return ErabList
+ with { extension "prototype(convert) decode(JSON)" }
+external function dec_ErabItem(in octetstring data) return ErabItem
+ with { extension "prototype(convert) decode(JSON)" }
+
+} with { encode "JSON" }
diff --git a/s1gw/S1GW_Tests.cfg b/s1gw/S1GW_Tests.cfg
index 54e7992..94c7591 100644
--- a/s1gw/S1GW_Tests.cfg
+++ b/s1gw/S1GW_Tests.cfg
@@ -19,6 +19,9 @@
S1GW_Tests.mp_upf_bind_ip := "127.0.3.10";
S1GW_Tests.mp_local_statsd_ip := "127.0.4.10";
S1GW_Tests.mp_local_statsd_port := 8125;
+S1GW_Tests.mp_rest_host := "127.0.0.1";
+S1GW_Tests.mp_rest_port := 8125;
+S1GW_Tests.mp_rest_enable := fase;

[MAIN_CONTROLLER]

diff --git a/s1gw/S1GW_Tests.default b/s1gw/S1GW_Tests.default
index 8f9cfdc..48169e1 100644
--- a/s1gw/S1GW_Tests.default
+++ b/s1gw/S1GW_Tests.default
@@ -5,6 +5,7 @@
[TESTPORT_PARAMETERS]
*.S1AP_ENB.noDelay := "yes" // eNB: turn off Nagle
*.S1AP.noDelay := "yes" // MME: turn off Nagle
+*.HTTP.use_notification_ASPs := "yes"

[MODULE_PARAMETERS]

diff --git a/s1gw/S1GW_Tests.ttcn b/s1gw/S1GW_Tests.ttcn
index 296c234..466e377 100644
--- a/s1gw/S1GW_Tests.ttcn
+++ b/s1gw/S1GW_Tests.ttcn
@@ -41,10 +41,16 @@
import from StatsD_CodecPort_CtrlFunct all;
import from StatsD_Checker all;

+import from HTTP_Adapter all;
+import from HTTPmsg_Types all;
+
import from S1AP_Server all;
import from S1GW_ConnHdlr all;
import from S1GW_UEMux all;

+import from S1GW_REST_Types all;
+import from S1GW_REST_Functions all;
+
modulepar {
charstring mp_s1gw_enb_ip := "127.0.1.1"; /* eNB facing address of the S1GW */
charstring mp_enb_bind_ip := "127.0.1.10"; /* eNB address to use locally when connecting to S1GW */
@@ -60,9 +66,13 @@

integer mp_multi_enb_num := 42; /* number of eNBs in _multi TCs */
integer mp_multi_ue_num := 128; /* number of UEs in _multi TCs */
+
+ boolean mp_rest_enable := false; /* whether enable the HTTP/REST interface */
+ charstring mp_rest_host := "127.0.0.1";
+ integer mp_rest_port := 8080;
}

-type component test_CT extends StatsD_Checker_CT {
+type component test_CT extends StatsD_Checker_CT, http_CT {
timer g_Tguard;
var MutexDispCT vc_mutex_disp;
var S1AP_Server_CT vc_S1APSRV;
@@ -93,6 +103,9 @@
f_init_pfcp();
f_pfcp_assoc();
}
+ if (mp_rest_enable) {
+ f_init_rest();
+ }
}

function f_init_s1ap_srv() runs on test_CT {
@@ -131,6 +144,16 @@
}
}

+function f_init_rest() runs on test_CT {
+ f_http_init(valueof(t_HTTP_Adapter_Params));
+}
+
+template (value) HTTP_Adapter_Params t_HTTP_Adapter_Params := {
+ http_host := mp_rest_host,
+ http_port := mp_rest_port,
+ use_ssl := false
+}
+
template (value) ConnHdlrPars
t_ConnHdlrPars(integer idx := 0,
integer num_erabs := 1,
@@ -193,6 +216,14 @@

vc_conn := ConnHdlr.create(id) alive;
f_ConnHdlr_connect(vc_conn);
+
+ if (mp_rest_enable) {
+ /* Init the HTTP client (M2M/REST interface) */
+ vc_conn.start(f_http_init(valueof(t_HTTP_Adapter_Params)));
+ vc_conn.done;
+ }
+
+ /* run the actual testcase logic */
vc_conn.start(f_ConnHdlr_init(fn, id, pars));

return vc_conn;
diff --git a/s1gw/gen_links.sh b/s1gw/gen_links.sh
index 6328ab8..58a3e57 100755
--- a/s1gw/gen_links.sh
+++ b/s1gw/gen_links.sh
@@ -21,6 +21,22 @@
FILES="PFCP_Types.ttcn"
gen_links $DIR $FILES

+DIR=$BASEDIR/titan.TestPorts.Common_Components.Socket-API/src
+FILES="Socket_API_Definitions.ttcn"
+gen_links $DIR $FILES
+
+DIR=$BASEDIR/titan.TestPorts.Common_Components.Abstract_Socket/src
+FILES="Abstract_Socket.cc Abstract_Socket.hh "
+gen_links $DIR $FILES
+
+DIR=$BASEDIR/titan.TestPorts.HTTPmsg/src
+FILES="HTTPmsg_MessageLen.ttcn HTTPmsg_MessageLen_Function.cc HTTPmsg_PT.cc HTTPmsg_PT.hh HTTPmsg_PortType.ttcn HTTPmsg_Types.ttcn "
+gen_links $DIR $FILES
+
+DIR=$BASEDIR/titan.ProtocolModules.JSON_v07_2006/src
+FILES="JSON_EncDec.cc JSON_Types.ttcn "
+gen_links $DIR $FILES
+
DIR=../library/s1ap
FILES="S1AP_CommonDataTypes.asn S1AP_Constants.asn S1AP_Containers.asn S1AP_IEs.asn "
FILES+="S1AP_PDU_Contents.asn S1AP_PDU_Descriptions.asn "
@@ -33,6 +49,7 @@
FILES+="S1AP_CodecPort.ttcn S1AP_CodecPort_CtrlFunctDef.cc S1AP_CodecPort_CtrlFunct.ttcn S1AP_Functions.ttcn "
FILES+="SCTP_Templates.ttcn "
FILES+="StatsD_Types.ttcn StatsD_CodecPort.ttcn StatsD_CodecPort_CtrlFunct.ttcn StatsD_CodecPort_CtrlFunctdef.cc StatsD_Checker.ttcnpp "
+FILES+="HTTP_Adapter.ttcn "
gen_links $DIR $FILES

gen_links_finish
diff --git a/s1gw/regen_makefile.sh b/s1gw/regen_makefile.sh
index 3c48af2..cc7e2bb 100755
--- a/s1gw/regen_makefile.sh
+++ b/s1gw/regen_makefile.sh
@@ -6,8 +6,12 @@
*.asn
*.ttcn
*.ttcnpp
+ Abstract_Socket.cc
+ HTTPmsg_MessageLen_Function.cc
+ HTTPmsg_PT.cc
IPL4asp_PT.cc
IPL4asp_discovery.cc
+ JSON_EncDec.cc
Native_FunctionDefs.cc
PFCP_CodecPort_CtrlFunctDef.cc
S1AP_CodecPort_CtrlFunctDef.cc

To view, visit change 41125. To unsubscribe, or for help writing mail filters, visit settings.

Gerrit-MessageType: newchange
Gerrit-Project: osmo-ttcn3-hacks
Gerrit-Branch: master
Gerrit-Change-Id: Ic6e8b46b7de4fb72139b017fc9a9c83f92fc1541
Gerrit-Change-Number: 41125
Gerrit-PatchSet: 1
Gerrit-Owner: fixeria <vyanitskiy@sysmocom.de>