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),

-- 
To view, visit https://gerrit.osmocom.org/c/erlang/osmo-s1gw/+/41282?usp=email
To unsubscribe, or for help writing mail filters, visit 
https://gerrit.osmocom.org/settings?usp=email

Gerrit-MessageType: newchange
Gerrit-Project: erlang/osmo-s1gw
Gerrit-Branch: master
Gerrit-Change-Id: Iad249aed99face9e35fd19e0596cf2364ade4c77
Gerrit-Change-Number: 41282
Gerrit-PatchSet: 1
Gerrit-Owner: fixeria <[email protected]>

Reply via email to