fixeria has uploaded this change for review.

View Change

[REST] Implement EnbList and EnbInfo

Change-Id: Iba5a36678cf4bc245e9ae8cecc20ced76dfb7d75
Related: SYS#7066
---
M contrib/openapi.yaml
M contrib/osmo-s1gw-cli.py
M priv/openapi.json
M src/rest_server.erl
4 files changed, 470 insertions(+), 1 deletion(-)

git pull ssh://gerrit.osmocom.org:29418/erlang/osmo-s1gw refs/changes/01/41101/1
diff --git a/contrib/openapi.yaml b/contrib/openapi.yaml
index df079a9..24bc2c8 100644
--- a/contrib/openapi.yaml
+++ b/contrib/openapi.yaml
@@ -64,6 +64,34 @@
'200':
$ref: '#/components/responses/OperationResult'

+ /enb-list:
+ get:
+ summary: Get a list of eNB connections
+ operationId: EnbList
+ responses:
+ '200':
+ description: A list of eNB connections
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/EnbList'
+
+ /enb/{EnbId}:
+ get:
+ summary: Get information about a specific eNB
+ operationId: EnbInfo
+ parameters:
+ - $ref: '#/components/parameters/EnbId'
+ responses:
+ '200':
+ description: Successful outcome (eNB info)
+ content:
+ application/json:
+ schema:
+ $ref: '#/components/schemas/EnbItem'
+ '404':
+ description: Unsuccessful outcome (eNB not found)
+
components:
responses:
OperationResult:
@@ -73,6 +101,35 @@
schema:
$ref: '#/components/schemas/OperationResult'

+ parameters:
+ EnbId:
+ name: EnbId
+ in: path
+ description: eNB identifier (selector)
+ required: true
+ schema:
+ oneOf:
+ - type: string
+ pattern: '^handle:[0-9]+$'
+ description: Unique identifier in the eNB registry
+ example: handle:42
+ - type: string
+ pattern: '^pid:[0-9]+\.[0-9]+\.[0-9]+$'
+ description: Process ID
+ example: pid:0.33.1
+ - type: string
+ pattern: '^genbid:[0-9]{3}-[0-9]{2,3}-[0-9]+$'
+ description: Global-eNB-ID (MCC-MNC-eNBId)
+ example: genbid:999-70-1337
+ - type: string
+ pattern: '^(enb|mme)-sctp-aid:[0-9]+$'
+ description: eNB/MME SCTP association identifier
+ example: enb-aid:42
+ - type: string
+ pattern: '^enb-conn:[0-9:.]+-[0-9]+$'
+ description: eNB connection address/port
+ example: enb-conn:192.168.1.1-34650
+
schemas:
OperationResult:
type: object
@@ -87,6 +144,11 @@
nullable: true
description: Human-readable explanation of the result

+ Pid:
+ type: string
+ pattern: '^<[0-9]+\.[0-9]+\.[0-9]+>$'
+ description: Process ID
+
MetricsList:
type: array
items:
@@ -130,3 +192,59 @@
rrts:
type: integer
description: Remote Recovery TimeStamp
+
+ EnbList:
+ type: array
+ items:
+ $ref: '#/components/schemas/EnbItem'
+
+ EnbItem:
+ type: object
+ required:
+ - handle
+ - pid
+ - state
+ - uptime
+ - erab_count
+ properties:
+ handle:
+ type: integer
+ description: Unique number in the eNB registry
+ pid:
+ $ref: '#/components/schemas/Pid'
+ state:
+ type: string
+ enum: [connecting, connected, s1setup]
+ description: Connection state
+ uptime:
+ type: integer
+ description: Uptime (in seconds)
+ erab_count:
+ type: integer
+ description: Total number of E-RABs
+ genb_id:
+ type: string
+ pattern: '^[0-9]{3}-[0-9]{2,3}-[0-9]+$'
+ example: 001-01-1337
+ description: Global-eNB-ID
+ enb_saddr:
+ type: string
+ description: Source (remote) address of the eNB
+ mme_daddr:
+ type: string
+ description: Destination (remote) address of the MME
+ enb_sport:
+ type: integer
+ description: Source (remote) port of the eNB-S1GW connection
+ mme_sport:
+ type: integer
+ description: Source (local) port of the S1GW-MME connection
+ mme_dport:
+ type: integer
+ description: Destination (remote) port of the S1GW-MME connection
+ enb_sctp_aid:
+ type: integer
+ description: SCTP association identifier of the eNB-S1GW connection
+ mme_sctp_aid:
+ type: integer
+ description: SCTP association identifier of the S1GW-MME connection
diff --git a/contrib/osmo-s1gw-cli.py b/contrib/osmo-s1gw-cli.py
index 7b58b37..84036d7 100755
--- a/contrib/osmo-s1gw-cli.py
+++ b/contrib/osmo-s1gw-cli.py
@@ -101,12 +101,23 @@
with self.send_post_req('pfcp/heartbeat') as f:
return json.load(f)

+ def enb_list(self) -> RESTResponse:
+ ''' EnbList :: Get a list of eNB connections '''
+ with self.send_get_req('enb-list') as f:
+ return json.load(f)
+
+ def enb_info(self, enb_id: str) -> RESTResponse:
+ ''' EnbInfo :: Get information about a specific eNB '''
+ with self.send_get_req(f'enb/{enb_id}') as f:
+ return json.load(f)
+

class OsmoS1GWCli(cmd2.Cmd):
DESC = 'Interactive CLI for OsmoS1GW'

CAT_METRICS = 'Metrics commands'
CAT_PFCP = 'PFCP related commands'
+ CAT_ENB = 'eNB related commands'

def __init__(self, argv):
super().__init__(allow_cli_args=False, include_py=True)
@@ -186,6 +197,72 @@
else:
self.perror('Heartbeat failed: {message}'.format(**data))

+ @staticmethod
+ def enb_list_item(item: dict) -> dict:
+ enb_addr = lambda item: '{enb_saddr}:{enb_sport} ({enb_sctp_aid})'.format(**item)
+ mme_addr = lambda item: '{mme_daddr}:{mme_dport} ({mme_sctp_aid})'.format(**item)
+ return {
+ 'eNB handle': item.get('handle'),
+ 'PID': item.get('pid'),
+ 'Global-eNB-ID': item.get('genb_id', '(unknown)'),
+ 'State': item.get('state'),
+ 'eNB addr:port (aid)': enb_addr(item) if 'enb_saddr' in item else None,
+ 'MME addr:port (aid)': mme_addr(item) if 'mme_daddr' in item else None,
+ 'Uptime (s)': item.get('uptime'),
+ '# E-RABs': item.get('erab_count'),
+ }
+
+ def enb_list_print(self, items: list[dict]) -> None:
+ self.poutput(tabulate.tabulate(map(self.enb_list_item, items),
+ headers='keys', tablefmt='github'))
+
+ @cmd2.with_category(CAT_ENB)
+ def do_enb_list(self, opts) -> None:
+ ''' Get a list of eNB connections '''
+ data = self.iface.enb_list()
+ self.enb_list_print(data)
+
+ @staticmethod
+ def gen_enb_id(opts) -> str:
+ if opts.handle is not None:
+ return f'handle:{opts.handle}'
+ elif opts.pid is not None:
+ return f'pid:{opts.pid}'
+ elif opts.genbid is not None:
+ return f'genbid:{opts.genbid}'
+ elif opts.enb_sctp_aid is not None:
+ return f'enb-sctp-aid:{opts.enb_sctp_aid}'
+ elif opts.mme_sctp_aid is not None:
+ return f'mme-sctp-aid:{opts.mme_sctp_aid}'
+ raise ValueError # shall not happen
+
+ enb_id_parser = cmd2.Cmd2ArgumentParser()
+ enb_id_group = enb_id_parser.add_argument_group('eNB ID')
+ enb_id_group = enb_id_group.add_mutually_exclusive_group(required=True)
+ enb_id_group.add_argument('-H', '--handle',
+ type=int,
+ help='eNB handle (example: 0)')
+ enb_id_group.add_argument('-P', '--pid',
+ type=str,
+ help='eNB process ID (example: 0.33.1)')
+ enb_id_group.add_argument('-G', '--genbid',
+ type=str,
+ help='Global-eNB-ID (example: 262-42-1337)')
+ enb_id_group.add_argument('--enb-sctp-aid',
+ type=int, metavar='AID',
+ help='eNB association identifier (example: 42)')
+ enb_id_group.add_argument('--mme-sctp-aid',
+ type=int, metavar='AID',
+ help='MME association identifier (example: 42)')
+
+ @cmd2.with_argparser(enb_id_parser)
+ @cmd2.with_category(CAT_ENB)
+ def do_enb_info(self, opts) -> None:
+ ''' Get information about a specific eNB '''
+ enb_id = self.gen_enb_id(opts)
+ data = self.iface.enb_info(enb_id)
+ self.enb_list_print([data])
+

ap = argparse.ArgumentParser(prog='osmo-s1gw-cli', description=OsmoS1GWCli.DESC)

diff --git a/priv/openapi.json b/priv/openapi.json
index c55c312..3e8f22e 100644
--- a/priv/openapi.json
+++ b/priv/openapi.json
@@ -96,6 +96,50 @@
}
}
}
+ },
+ "/enb-list": {
+ "get": {
+ "summary": "Get a list of eNB connections",
+ "operationId": "EnbList",
+ "responses": {
+ "200": {
+ "description": "A list of eNB connections",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/EnbList"
+ }
+ }
+ }
+ }
+ }
+ }
+ },
+ "/enb/{EnbId}": {
+ "get": {
+ "summary": "Get information about a specific eNB",
+ "operationId": "EnbInfo",
+ "parameters": [
+ {
+ "$ref": "#/components/parameters/EnbId"
+ }
+ ],
+ "responses": {
+ "200": {
+ "description": "Successful outcome (eNB info)",
+ "content": {
+ "application/json": {
+ "schema": {
+ "$ref": "#/components/schemas/EnbItem"
+ }
+ }
+ }
+ },
+ "404": {
+ "description": "Unsuccessful outcome (eNB not found)"
+ }
+ }
+ }
}
},
"components": {
@@ -111,6 +155,48 @@
}
}
},
+ "parameters": {
+ "EnbId": {
+ "name": "EnbId",
+ "in": "path",
+ "description": "eNB identifier (selector)",
+ "required": true,
+ "schema": {
+ "oneOf": [
+ {
+ "type": "string",
+ "pattern": "^handle:[0-9]+$",
+ "description": "Unique identifier in the eNB registry",
+ "example": "handle:42"
+ },
+ {
+ "type": "string",
+ "pattern": "^pid:[0-9]+\\.[0-9]+\\.[0-9]+$",
+ "description": "Process ID",
+ "example": "pid:0.33.1"
+ },
+ {
+ "type": "string",
+ "pattern": "^genbid:[0-9]{3}-[0-9]{2,3}-[0-9]+$",
+ "description": "Global-eNB-ID (MCC-MNC-eNBId)",
+ "example": "genbid:999-70-1337"
+ },
+ {
+ "type": "string",
+ "pattern": "^(enb|mme)-sctp-aid:[0-9]+$",
+ "description": "eNB/MME SCTP association identifier",
+ "example": "enb-aid:42"
+ },
+ {
+ "type": "string",
+ "pattern": "^enb-conn:[0-9:.]+-[0-9]+$",
+ "description": "eNB connection address/port",
+ "example": "enb-conn:192.168.1.1-34650"
+ }
+ ]
+ }
+ }
+ },
"schemas": {
"OperationResult": {
"type": "object",
@@ -129,6 +215,11 @@
}
}
},
+ "Pid": {
+ "type": "string",
+ "pattern": "^<[0-9]+\\.[0-9]+\\.[0-9]+>$",
+ "description": "Process ID"
+ },
"MetricsList": {
"type": "array",
"items": {
@@ -191,6 +282,82 @@
"description": "Remote Recovery TimeStamp"
}
}
+ },
+ "EnbList": {
+ "type": "array",
+ "items": {
+ "$ref": "#/components/schemas/EnbItem"
+ }
+ },
+ "EnbItem": {
+ "type": "object",
+ "required": [
+ "handle",
+ "pid",
+ "state",
+ "uptime",
+ "erab_count"
+ ],
+ "properties": {
+ "handle": {
+ "type": "integer",
+ "description": "Unique number in the eNB registry"
+ },
+ "pid": {
+ "$ref": "#/components/schemas/Pid"
+ },
+ "state": {
+ "type": "string",
+ "enum": [
+ "connecting",
+ "connected",
+ "s1setup"
+ ],
+ "description": "Connection state"
+ },
+ "uptime": {
+ "type": "integer",
+ "description": "Uptime (in seconds)"
+ },
+ "erab_count": {
+ "type": "integer",
+ "description": "Total number of E-RABs"
+ },
+ "genb_id": {
+ "type": "string",
+ "pattern": "^[0-9]{3}-[0-9]{2,3}-[0-9]+$",
+ "example": "001-01-1337",
+ "description": "Global-eNB-ID"
+ },
+ "enb_saddr": {
+ "type": "string",
+ "description": "Source (remote) address of the eNB"
+ },
+ "mme_daddr": {
+ "type": "string",
+ "description": "Destination (remote) address of the MME"
+ },
+ "enb_sport": {
+ "type": "integer",
+ "description": "Source (remote) port of the eNB-S1GW connection"
+ },
+ "mme_sport": {
+ "type": "integer",
+ "description": "Source (local) port of the S1GW-MME connection"
+ },
+ "mme_dport": {
+ "type": "integer",
+ "description": "Destination (remote) port of the S1GW-MME connection"
+ },
+ "enb_sctp_aid": {
+ "type": "integer",
+ "description": "SCTP association identifier of the eNB-S1GW connection"
+ },
+ "mme_sctp_aid": {
+ "type": "integer",
+ "description": "SCTP association identifier of the S1GW-MME connection"
+ }
+ }
}
}
}
diff --git a/src/rest_server.erl b/src/rest_server.erl
index d1597a9..f546ad9 100644
--- a/src/rest_server.erl
+++ b/src/rest_server.erl
@@ -36,7 +36,9 @@

-export([metrics_list/1,
pfcp_assoc_state/1,
- pfcp_heartbeat/1
+ pfcp_heartbeat/1,
+ enb_list/1,
+ enb_info/1
]).

-include_lib("kernel/include/logger.hrl").
@@ -84,6 +86,25 @@
end.


+%% EnbList :: Get a list of eNB connections
+enb_list(#{}) ->
+ EnbList = enb_registry:fetch_enb_list(),
+ {200, [], lists:map(fun enb_item/1, EnbList)}.
+
+
+%% EnbInfo :: Get information about a specific eNB
+enb_info(#{path_parameters := PP}) ->
+ [{<< "EnbId" >>, << ID/bytes >>}] = PP,
+ case fetch_enb_info(ID) of
+ [EnbInfo | _] ->
+ {200, [], enb_item(EnbInfo)};
+ [] ->
+ {404, [], undefined};
+ error ->
+ {500, [], undefined}
+ end.
+
+
%% ------------------------------------------------------------------
%% private API
%% ------------------------------------------------------------------
@@ -131,6 +152,92 @@
thing_to_list(X) when is_list(X) -> X.


+-spec enb_item(enb_registry:enb_info()) -> map().
+enb_item(EnbInfo) ->
+ M0 = #{handle => maps:get(handle, EnbInfo),
+ pid => pid_to_list(maps:get(pid, EnbInfo)),
+ state => maps:get(state, EnbInfo),
+ uptime => maps:get(uptime, EnbInfo),
+ erab_count => 0},
+ M1 = enb_item_add_enb_info(M0, EnbInfo),
+ M2 = enb_item_add_enb_conn_info(M1, EnbInfo),
+ M3 = enb_item_add_mme_conn_info(M2, EnbInfo),
+ rsp_map(M3).
+
+
+-spec enb_item_add_enb_info(map(), enb_registry:enb_info()) -> map().
+enb_item_add_enb_info(M0, #{genb_id_str := GlobalENBId}) ->
+ %% TODO: add enb_id and plmn_id
+ M0#{genb_id => GlobalENBId};
+
+enb_item_add_enb_info(M0, _) -> M0.
+
+
+-spec enb_item_add_enb_conn_info(map(), enb_registry:enb_info()) -> map().
+enb_item_add_enb_conn_info(M0, #{enb_conn_info := ConnInfo}) ->
+ M0#{enb_saddr => inet:ntoa(maps:get(addr, ConnInfo)),
+ enb_sport => maps:get(port, ConnInfo),
+ enb_sctp_aid => maps:get(aid, ConnInfo)
+ };
+
+enb_item_add_enb_conn_info(M0, _) -> M0.
+
+
+-spec enb_item_add_mme_conn_info(map(), enb_registry:enb_info()) -> map().
+enb_item_add_mme_conn_info(M0, #{mme_conn_info := ConnInfo}) ->
+ Pid = maps:get(handler, ConnInfo),
+ ERABs = s1ap_proxy:fetch_erab_list(Pid),
+ M0#{mme_daddr => maps:get(mme_addr, ConnInfo), %% XXX inet:ntoa
+ mme_dport => maps:get(mme_port, ConnInfo),
+ %% TODO: mme_sport
+ mme_sctp_aid => maps:get(mme_aid, ConnInfo),
+ erab_count => length(ERABs)
+ };
+
+enb_item_add_mme_conn_info(M0, _) -> M0.
+
+
+-spec fetch_enb_info(binary()) -> [enb_registry:enb_info()] | error.
+fetch_enb_info(<< "handle:", Val/bytes >>) ->
+ Handle = binary_to_integer(Val),
+ case enb_registry:fetch_enb_info(Handle) of
+ {ok, EnbInfo} -> [EnbInfo];
+ error -> []
+ end;
+
+fetch_enb_info(<< "pid:", Val/bytes >>) ->
+ Pid = parse_pid(Val),
+ case enb_registry:fetch_enb_info(Pid) of
+ {ok, EnbInfo} -> [EnbInfo];
+ error -> []
+ end;
+
+fetch_enb_info(<< "genbid:", Val/bytes >>) ->
+ GlobalENBId = binary_to_list(Val),
+ enb_registry:fetch_enb_list({genb_id_str, GlobalENBId});
+
+fetch_enb_info(<< "enb-sctp-aid:", Val/bytes >>) ->
+ Aid = binary_to_integer(Val),
+ enb_registry:fetch_enb_list({enb_sctp_aid, Aid});
+
+fetch_enb_info(<< "mme-sctp-aid:", Val/bytes >>) ->
+ Aid = binary_to_integer(Val),
+ enb_registry:fetch_enb_list({mme_sctp_aid, Aid});
+
+%% TODO: '^enb-conn:[0-9:.]+-[0-9]+$'
+fetch_enb_info(ID) ->
+ ?LOG_ERROR("Unhandled eNB ID ~p", [ID]),
+ error.
+
+
+-spec parse_pid(binary() | list()) -> pid().
+parse_pid(Data) when is_binary(Data) ->
+ parse_pid(binary_to_list(Data));
+
+parse_pid(Data) when is_list(Data) ->
+ list_to_pid("<" ++ Data ++ ">").
+
+
%% Convert the given response map to a format acceptable by the erf
-spec rsp_map(map()) -> map().
rsp_map(M) ->

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

Gerrit-MessageType: newchange
Gerrit-Project: erlang/osmo-s1gw
Gerrit-Branch: master
Gerrit-Change-Id: Iba5a36678cf4bc245e9ae8cecc20ced76dfb7d75
Gerrit-Change-Number: 41101
Gerrit-PatchSet: 1
Gerrit-Owner: fixeria <vyanitskiy@sysmocom.de>