fixeria has uploaded this change for review. ( https://gerrit.osmocom.org/c/erlang/osmo-s1gw/+/41282?usp=email )
Change subject: [REST] Add MmeList, MmeAdd, MmeInfo, MmeDelete ......................................................................
[REST] Add MmeList, MmeAdd, MmeInfo, MmeDelete
Change-Id: Iad249aed99face9e35fd19e0596cf2364ade4c77 Related: SYS#7052 --- 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, 518 insertions(+), 15 deletions(-)
git pull ssh://gerrit.osmocom.org:29418/erlang/osmo-s1gw refs/changes/82/41282/1
diff --git a/contrib/openapi.yaml b/contrib/openapi.yaml index a56f876..b7e07ec 100644 --- a/contrib/openapi.yaml +++ b/contrib/openapi.yaml @@ -66,6 +66,58 @@ '200': $ref: '#/components/responses/OperationResult'
+ /mme-list: + get: + summary: Get a list of registered MMEs + operationId: MmeList + responses: + '200': + description: A list of registered MMEs + content: + application/json: + schema: + $ref: '#/components/schemas/MmeList' + post: + summary: Add an MME to the pool + operationId: MmeAdd + requestBody: + required: true + content: + application/json: + schema: + $ref: '#/components/schemas/MmeItem' + responses: + '201': + description: Successful outcome (MME added) + '409': + description: Unsuccessful outcome (MME already registered?) + + /mme/{MmeId}: + get: + summary: Get information about a specific MME + operationId: MmeInfo + parameters: + - $ref: '#/components/parameters/MmeId' + responses: + '200': + description: Successful outcome (MME info) + content: + application/json: + schema: + $ref: '#/components/schemas/MmeItem' + '404': + description: Unsuccessful outcome (MME not found) + delete: + summary: Delete an MME from the pool + operationId: MmeDelete + parameters: + - $ref: '#/components/parameters/MmeId' + responses: + '200': + description: Successful outcome (MME deleted) + '404': + description: Unsuccessful outcome (MME not found) + /enb-list: get: summary: Get a list of eNB connections @@ -168,6 +220,22 @@ $ref: '#/components/schemas/OperationResult'
parameters: + MmeId: + name: MmeId + in: path + description: MME identifier (selector) + required: true + schema: + oneOf: + - type: string + pattern: '^name:' + description: MME name + example: mme0 + - type: string + pattern: '^addr:[0-9a-f:.]+-[0-9]+$' + description: MME remote address/port + example: addr:192.168.1.1-36412 + EnbId: name: EnbId in: path @@ -271,6 +339,30 @@ type: integer description: Remote Recovery TimeStamp
+ MmeList: + type: array + items: + $ref: '#/components/schemas/MmeItem' + + MmeItem: + type: object + required: + - name + - raddr + properties: + name: + type: string + description: Unique, human-readable identifier + laddr: + type: string + description: Local (bind) IP address + raddr: + type: string + description: Remote (connect) IP address + rport: + type: integer + description: Remote port + EnbList: type: array items: diff --git a/contrib/osmo-s1gw-cli.py b/contrib/osmo-s1gw-cli.py index b51a667..2303a4a 100755 --- a/contrib/osmo-s1gw-cli.py +++ b/contrib/osmo-s1gw-cli.py @@ -102,6 +102,26 @@ with self.send_post_req('pfcp/heartbeat') as f: return json.load(f)
+ def mme_list(self) -> RESTResponse: + ''' MmeList :: Get a list of registered MMEs ''' + with self.send_get_req('mme-list') as f: + return json.load(f) + + def mme_add(self, mme_info: dict) -> int: + ''' MmeAdd :: Add an MME to the pool ''' + with self.send_post_req('mme-list', mme_info) as f: + return f.status + + def mme_info(self, name: str) -> RESTResponse: + ''' MmeInfo :: Get information about a specific MME ''' + with self.send_get_req(f'mme/name:{name}') as f: + return json.load(f) + + def mme_delete(self, name: str) -> int: + ''' MmeInfo :: Get information about a specific MME ''' + with self.send_delete_req(f'mme/name:{name}') as f: + return f.status + def enb_list(self) -> RESTResponse: ''' EnbList :: Get a list of eNB connections ''' with self.send_get_req('enb-list') as f: @@ -143,6 +163,7 @@
CAT_METRICS = 'Metrics commands' CAT_PFCP = 'PFCP related commands' + CAT_MME = 'MME related commands' CAT_ENB = 'eNB related commands' CAT_ERAB = 'E-RAB related commands'
@@ -229,6 +250,107 @@ self.perror('Heartbeat failed: {message}'.format(**data))
@staticmethod + def add_sort_group(parser, + default: str, + choices: set[str]) -> None: + ''' Add argparse group with sorting params ''' + sort_group = parser.add_argument_group('Sorting options') + sort_group.add_argument('-S', '--sort-by', + type=str, + default=default, + choices=choices, + help='Sort by (default: %(default)s)') + sort_group.add_argument('--reverse', + action='store_true', + help='Reverse order (default: %(default)s)') + + @staticmethod + def mme_list_item(item: dict) -> dict: + ''' Generate a table row for the given MME ''' + return { + 'Name': item.get('name'), + 'Local address': item.get('laddr'), + 'Remote address/port': '{raddr}:{rport}'.format(**item), + } + + def mme_list_print(self, items: list[dict], + sort_by: str = 'none', + reverse: bool = False) -> None: + ''' Print a [sorted] list of MMEs in tabular form ''' + if sort_by != 'none': + items.sort(key=lambda item: item.get(sort_by), reverse=reverse) + self.poutput(tabulate.tabulate(map(self.mme_list_item, items), + headers='keys', tablefmt=self.tablefmt)) + + def mme_info_print(self, item: dict) -> None: + ''' Print MME info in tabular form ''' + self.poutput(tabulate.tabulate(self.mme_list_item(item).items(), + headers=['Parameter', 'Value'], + tablefmt=self.tablefmt)) + + mme_list_parser = cmd2.Cmd2ArgumentParser() + add_sort_group(mme_list_parser, default='none', + choices=('none', 'name', 'laddr', 'raddr')) + + @cmd2.with_argparser(mme_list_parser) + @cmd2.with_category(CAT_MME) + def do_mme_list(self, opts) -> None: + ''' Get a list of registered MMEs ''' + data = self.iface.mme_list() + self.mme_list_print(data, opts.sort_by, opts.reverse) + + mme_add_parser = cmd2.Cmd2ArgumentParser() + mme_add_parser.add_argument('-N', '--name', + type=str, required=True, + help='MME name (example: mme0)') + mme_add_parser.add_argument('-la', '--laddr', + type=str, default='::', + help='Local address (default: %(default)s)') + mme_add_parser.add_argument('-ra', '--raddr', + type=str, required=True, + help='Remote address (example: 192.168.1.101)') + mme_add_parser.add_argument('-rp', '--rport', + type=int, default=36412, + help='Remote port (default: %(default)s)') + + @cmd2.with_argparser(mme_add_parser) + @cmd2.with_category(CAT_MME) + def do_mme_add(self, opts) -> None: + ''' Add an MME to the pool ''' + mme_info = dict(name=opts.name, + laddr=opts.laddr, + raddr=opts.raddr, + rport=opts.rport) + self.iface.mme_add(mme_info) + + @staticmethod + def add_mme_id_group(parser): + ''' Add argparse group for the MmeId parameter ''' + mme_id_group = parser.add_argument_group('MME ID') + mme_id_group = mme_id_group.add_mutually_exclusive_group(required=True) + mme_id_group.add_argument('-N', '--name', + type=str, + help='MME name (example: mme0)') + # TODO: address/port + return mme_id_group + + mme_info_parser = cmd2.Cmd2ArgumentParser() + add_mme_id_group(mme_info_parser) + + @cmd2.with_argparser(mme_info_parser) + @cmd2.with_category(CAT_MME) + def do_mme_info(self, opts) -> None: + ''' Get information about a specific MME ''' + data = self.iface.mme_info(opts.name) + self.mme_info_print(data) + + @cmd2.with_argparser(mme_info_parser) + @cmd2.with_category(CAT_MME) + def do_mme_delete(self, opts) -> None: + ''' Delete an MME from the pool ''' + self.iface.mme_delete(opts.name) + + @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) @@ -258,21 +380,6 @@ headers=['Parameter', 'Value'], tablefmt=self.tablefmt))
- @staticmethod - def add_sort_group(parser, - default: str, - choices: set[str]) -> None: - ''' Add argparse group with sorting params ''' - sort_group = parser.add_argument_group('Sorting options') - sort_group.add_argument('-S', '--sort-by', - type=str, - default=default, - choices=choices, - help='Sort by (default: %(default)s)') - sort_group.add_argument('--reverse', - action='store_true', - help='Reverse order (default: %(default)s)') - enb_list_parser = cmd2.Cmd2ArgumentParser() add_sort_group(enb_list_parser, default='handle', choices=('handle', 'pid', 'state', 'genb_id', 'uptime')) diff --git a/doc/osmo-s1gw-cli.md b/doc/osmo-s1gw-cli.md index 4a0bd22..41114d5 100644 --- a/doc/osmo-s1gw-cli.md +++ b/doc/osmo-s1gw-cli.md @@ -151,6 +151,109 @@ Heartbeat failed: timeout ```
+### `mme_list` + +Get a list of registered MMEs. + +``` +Usage: mme_list [-h] [-S {none, name, laddr, raddr}] [--reverse] + +Get a list of registered MMEs + +optional arguments: + -h, --help show this help message and exit + +Sorting options: + -S, --sort-by {none, name, laddr, raddr} + Sort by (default: none) + --reverse Reverse order (default: False) +``` + +Example: getting a list of MMEs (not sorted by default). + +``` +OsmoS1GW# mme_list +| Name | Local address | Remote address/port | +|--------|-----------------|-----------------------| +| mme0 | :: | 127.0.2.10:36412 | +| mme1 | :: | 127.0.2.20:36412 | +| mme2 | :: | 127.0.2.30:36412 | +``` + +### `mme_add` + +Add an MME to the pool. + +``` +Usage: mme_add -N NAME -ra RADDR [-h] [-la LADDR] [-rp RPORT] + +Add an MME to the pool + +required arguments: + -N, --name NAME MME name (example: mme0) + -ra, --raddr RADDR Remote address (example: 192.168.1.101) + +optional arguments: + -h, --help show this help message and exit + -la, --laddr LADDR Local address (default: ::) + -rp, --rport RPORT Remote port (default: 36412) +``` + +Example: adding an MME with remote address "192.168.1.101". + +``` +OsmoS1GW# mme_add --name mme42 --raddr 192.168.1.101 +``` + +### `mme_info` + +Get information about a specific MME. + +``` +Usage: mme_info [-h] -N NAME + +Get information about a specific MME + +optional arguments: + -h, --help show this help message and exit + +MME ID: + -N, --name NAME MME name (example: mme0) +``` + +Example: getting information about an MME with name "mme0". + +``` +OsmoS1GW# mme_info --name mme0 +| Parameter | Value | +|---------------------|------------------| +| Name | mme0 | +| Local address | :: | +| Remote address/port | 127.0.2.10:36412 | +``` + +### `mme_delete` + +Delete an MME from the pool. + +``` +Usage: mme_delete [-h] -N NAME + +Delete an MME from the pool + +optional arguments: + -h, --help show this help message and exit + +MME ID: + -N, --name NAME MME name (example: mme0) +``` + +Example: deleting an MME with name "mme0". + +``` +OsmoS1GW# mme_delete --name mme0 +``` + ### `enb_list`
Get a list of eNB connections. diff --git a/priv/openapi.json b/priv/openapi.json index 5422c03..262ad4d 100644 --- a/priv/openapi.json +++ b/priv/openapi.json @@ -100,6 +100,89 @@ } } }, + "/mme-list": { + "get": { + "summary": "Get a list of registered MMEs", + "operationId": "MmeList", + "responses": { + "200": { + "description": "A list of registered MMEs", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MmeList" + } + } + } + } + } + }, + "post": { + "summary": "Add an MME to the pool", + "operationId": "MmeAdd", + "requestBody": { + "required": true, + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MmeItem" + } + } + } + }, + "responses": { + "201": { + "description": "Successful outcome (MME added)" + }, + "409": { + "description": "Unsuccessful outcome (MME already registered?)" + } + } + } + }, + "/mme/{MmeId}": { + "get": { + "summary": "Get information about a specific MME", + "operationId": "MmeInfo", + "parameters": [ + { + "$ref": "#/components/parameters/MmeId" + } + ], + "responses": { + "200": { + "description": "Successful outcome (MME info)", + "content": { + "application/json": { + "schema": { + "$ref": "#/components/schemas/MmeItem" + } + } + } + }, + "404": { + "description": "Unsuccessful outcome (MME not found)" + } + } + }, + "delete": { + "summary": "Delete an MME from the pool", + "operationId": "MmeDelete", + "parameters": [ + { + "$ref": "#/components/parameters/MmeId" + } + ], + "responses": { + "200": { + "description": "Successful outcome (MME deleted)" + }, + "404": { + "description": "Unsuccessful outcome (MME not found)" + } + } + } + }, "/enb-list": { "get": { "summary": "Get a list of eNB connections", @@ -263,6 +346,28 @@ } }, "parameters": { + "MmeId": { + "name": "MmeId", + "in": "path", + "description": "MME identifier (selector)", + "required": true, + "schema": { + "oneOf": [ + { + "type": "string", + "pattern": "^name:", + "description": "MME name", + "example": "mme0" + }, + { + "type": "string", + "pattern": "^addr:[0-9a-f:.]+-[0-9]+$", + "description": "MME remote address/port", + "example": "addr:192.168.1.1-36412" + } + ] + } + }, "EnbId": { "name": "EnbId", "in": "path", @@ -406,6 +511,37 @@ } } }, + "MmeList": { + "type": "array", + "items": { + "$ref": "#/components/schemas/MmeItem" + } + }, + "MmeItem": { + "type": "object", + "required": [ + "name", + "raddr" + ], + "properties": { + "name": { + "type": "string", + "description": "Unique, human-readable identifier" + }, + "laddr": { + "type": "string", + "description": "Local (bind) IP address" + }, + "raddr": { + "type": "string", + "description": "Remote (connect) IP address" + }, + "rport": { + "type": "integer", + "description": "Remote port" + } + } + }, "EnbList": { "type": "array", "items": { diff --git a/src/rest_server.erl b/src/rest_server.erl index 6e1f892..a69c73c 100644 --- a/src/rest_server.erl +++ b/src/rest_server.erl @@ -37,6 +37,10 @@ -export([metrics_list/1, pfcp_assoc_state/1, pfcp_heartbeat/1, + mme_list/1, + mme_add/1, + mme_info/1, + mme_delete/1, enb_list/1, enb_info/1, enb_delete/1, @@ -95,6 +99,50 @@ end.
+%% MmeList :: Get a list of registered MMEs +mme_list(#{}) -> + MmeList = mme_registry:fetch_mme_list(), + {200, [], lists:map(fun mme_item/1, MmeList)}. + + +%% MmeAdd :: Add an MME to the pool +mme_add(#{body := Body}) -> + MmeInfo = #{name => binary_to_list(maps:get(<< "name" >>, Body)), + laddr => binary_to_list(maps:get(<< "laddr" >>, Body, "::")), + raddr => binary_to_list(maps:get(<< "raddr" >>, Body)), + rport => maps:get(<< "rport" >>, Body, 36412) + }, + case mme_registry:mme_register(MmeInfo) of + ok -> {201, [], undefined}; + {error, _} -> {409, [], undefined} + end. + + +%% MmeInfo :: Get information about a specific MME +mme_info(#{path_parameters := PP}) -> + [{<< "MmeId" >>, << ID/bytes >>}] = PP, + case fetch_mme_info(ID) of + {ok, MmeInfo} -> + {200, [], mme_item(MmeInfo)}; + error -> + {404, [], undefined} + end. + + +%% MmeDelete :: Delete an MME from the pool +mme_delete(#{path_parameters := PP}) -> + [{<< "MmeId" >>, << ID/bytes >>}] = PP, + case fetch_mme_info(ID) of + {ok, #{name := MmeName}} -> + case mme_registry:mme_unregister(MmeName) of + ok -> {200, [], undefined}; + {error, _} -> {404, [], undefined} + end; + error -> + {404, [], undefined} + end. + + %% EnbList :: Get a list of eNB connections enb_list(#{}) -> EnbList = enb_registry:fetch_enb_list(), @@ -220,6 +268,23 @@ thing_to_list(X) when is_list(X) -> X.
+-spec mme_item(mme_registry:mme_info()) -> map(). +mme_item(MmeInfo) -> + rsp_map(MmeInfo#{laddr => inet:ntoa(maps:get(laddr, MmeInfo)), + raddr => inet:ntoa(maps:get(raddr, MmeInfo))}). + + +-spec fetch_mme_info(binary()) -> {ok, mme_registry:mme_info()} | error. +fetch_mme_info(<< "name:", Val/bytes >>) -> + MmeName = binary_to_list(Val), + mme_registry:fetch_mme_info(MmeName); + +%% TODO: '^addr:[0-9a-f:.]+-[0-9]+$' +fetch_mme_info(ID) -> + ?LOG_ERROR("Unhandled MME ID ~p", [ID]), + error. + + -spec enb_item(enb_registry:enb_info()) -> map(). enb_item(EnbInfo) -> M0 = #{handle => maps:get(handle, EnbInfo),