fixeria has submitted this change. ( https://gerrit.osmocom.org/c/erlang/osmo-s1gw/+/41101?usp=email )
Change subject: [REST] Implement EnbList and EnbInfo ......................................................................
[REST] Implement EnbList and EnbInfo
Change-Id: Iba5a36678cf4bc245e9ae8cecc20ced76dfb7d75 Related: SYS#7066 --- M contrib/openapi.yaml M contrib/osmo-s1gw-cli.py M doc/osmo-s1gw-cli.md M priv/openapi.json M src/rest_server.erl 5 files changed, 571 insertions(+), 1 deletion(-)
Approvals: fixeria: Looks good to me, approved osmith: Looks good to me, but someone else must approve Jenkins Builder: Verified laforge: Looks good to me, but someone else must approve
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 77014ad..56d08c3 100755 --- a/contrib/osmo-s1gw-cli.py +++ b/contrib/osmo-s1gw-cli.py @@ -102,12 +102,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) @@ -191,6 +202,87 @@ else: self.perror('Heartbeat failed: {message}'.format(**data))
+ @staticmethod + def enb_list_item(item: dict) -> dict: + ''' Generate a table row for the given eNB ''' + 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: + ''' Print a list of eNBs in tabular form ''' + self.poutput(tabulate.tabulate(map(self.enb_list_item, items), + headers='keys', tablefmt=self.tablefmt)) + + def enb_info_print(self, item: dict) -> None: + ''' Print eNB info in tabular form ''' + self.poutput(tabulate.tabulate(self.enb_list_item(item).items(), + headers=['Parameter', 'Value'], + tablefmt=self.tablefmt)) + + @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: + ''' Generate the EnbId parameter value (for URL) ''' + 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 + + @staticmethod + def add_enb_id_group(parser): + ''' Add argparse group for the EnbId parameter ''' + enb_id_group = 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)') + return enb_id_group + + enb_info_parser = cmd2.Cmd2ArgumentParser() + add_enb_id_group(enb_info_parser) + + @cmd2.with_argparser(enb_info_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_info_print(data) +
ap = argparse.ArgumentParser(prog='osmo-s1gw-cli', description=OsmoS1GWCli.DESC)
diff --git a/doc/osmo-s1gw-cli.md b/doc/osmo-s1gw-cli.md index 64298ab..3ae2da6 100644 --- a/doc/osmo-s1gw-cli.md +++ b/doc/osmo-s1gw-cli.md @@ -150,3 +150,89 @@ OsmoS1GW# pfcp_heartbeat Heartbeat failed: timeout ``` + +### `do_enb_list` + +Get a list of eNB connections. + +``` +OsmoS1GW# enb_list +| eNB handle | PID | Global-eNB-ID | State | eNB addr:port (aid) | MME addr:port (aid) | Uptime (s) | # E-RABs | +|--------------|-----------|-----------------|---------|-------------------------|-------------------------|--------------|------------| +| 0 | <0.699.0> | 001-01-0 | s1setup | 127.0.1.10:56767 (5706) | 127.0.2.10:36412 (5707) | 418 | 0 | +| 1 | <0.701.0> | 001-01-1 | s1setup | 127.0.1.10:54140 (5710) | 127.0.2.10:36412 (5711) | 33 | 3 | +| 2 | <0.703.0> | 001-01-2 | s1setup | 127.0.1.10:34076 (5714) | 127.0.2.10:36412 (5715) | 3600 | 20 | +| 3 | <0.705.0> | 001-01-3 | s1setup | 127.0.1.10:46501 (5718) | 127.0.2.10:36412 (5719) | 869 | 13 | +| 4 | <0.707.0> | 001-01-4 | s1setup | 127.0.1.10:35610 (5722) | 127.0.2.10:36412 (5723) | 18 | 0 | +| 5 | <0.709.0> | 001-01-5 | s1setup | 127.0.1.10:37610 (5726) | 127.0.2.10:36412 (5727) | 933 | 129 | +| 6 | <0.711.0> | 001-01-6 | s1setup | 127.0.1.10:58447 (5730) | 127.0.2.10:36412 (5741) | 535 | 6 | +| 7 | <0.713.0> | 001-01-7 | s1setup | 127.0.1.10:35842 (5732) | 127.0.2.10:36412 (5743) | 736 | 8 | +| 8 | <0.715.0> | 001-01-8 | s1setup | 127.0.1.10:57362 (5734) | 127.0.2.10:36412 (5745) | 521 | 99 | +| 9 | <0.717.0> | 001-01-9 | s1setup | 127.0.1.10:50955 (5736) | 127.0.2.10:36412 (5747) | 33 | 1 | +``` + +### `enb_info` + +Get information about a specific eNB. + +``` +Usage: enb_info [-h] (-H HANDLE | -P PID | -G GENBID | + --enb-sctp-aid AID | + --mme-sctp-aid AID) + +Get information about a specific eNB + +optional arguments: + -h, --help show this help message and exit + +eNB ID: + -H, --handle HANDLE eNB handle (example: 0) + -P, --pid PID eNB process ID (example: 0.33.1) + -G, --genbid GENBID Global-eNB-ID (example: 262-42-1337) + --enb-sctp-aid AID eNB association identifier (example: 42) + --mme-sctp-aid AID MME association identifier (example: 42) +``` + +Example: Getting eNB information using a handle. + +``` +OsmoS1GW# enb_info -H8 +| Parameter | Value | +|---------------------|-------------------------| +| eNB handle | 8 | +| PID | <0.715.0> | +| Global-eNB-ID | 001-01-8 | +| State | s1setup | +| eNB addr:port (aid) | 127.0.1.10:57362 (5734) | +| MME addr:port (aid) | 127.0.2.10:36412 (5745) | +| Uptime (s) | 521 | +| # E-RABs | 99 | +``` + +Example: Getting eNB information using a pricess ID. + +``` +OsmoS1GW# enb_info --pid 0.715.0 +| Parameter | Value | +|---------------------|-------------------------| +| eNB handle | 8 | +| PID | <0.715.0> | +| Global-eNB-ID | 001-01-8 | +| State | s1setup | +| eNB addr:port (aid) | 127.0.1.10:57362 (5734) | +| MME addr:port (aid) | 127.0.2.10:36412 (5745) | +| Uptime (s) | 521 | +| # E-RABs | 99 | +``` + +Example: Unsuccessful outcome for an unknown/invalid Global-eNB-ID. + +``` +OsmoS1GW# enb_info -G 001-01-42 +EXCEPTION of type 'HTTPError' occurred with message: HTTP Error 404: Not Found +To enable full traceback, run the following command: 'set debug true' + +OsmoS1GW# enb_info -G 123456 +EXCEPTION of type 'HTTPError' occurred with message: HTTP Error 400: Bad Request +To enable full traceback, run the following command: 'set debug true' +``` 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) ->