fixeria has submitted this change. ( 
https://gerrit.osmocom.org/c/erlang/osmo-s1gw/+/41100?usp=email )

Change subject: [REST] Implement Pfcp{AssocState,Heartbeat}
......................................................................

[REST] Implement Pfcp{AssocState,Heartbeat}

Change-Id: Idc98952d46d8e224969da343dc29ef323c6ed813
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, 307 insertions(+), 1 deletion(-)

Approvals:
  Jenkins Builder: Verified
  osmith: Looks good to me, but someone else must approve
  laforge: Looks good to me, but someone else must approve
  fixeria: Looks good to me, approved




diff --git a/contrib/openapi.yaml b/contrib/openapi.yaml
index 696e7a2..df079a9 100644
--- a/contrib/openapi.yaml
+++ b/contrib/openapi.yaml
@@ -32,8 +32,61 @@
               schema:
                 $ref: '#/components/schemas/MetricsList'

+  /pfcp/assoc:
+    get:
+      summary: Get the PFCP association state
+      operationId: PfcpAssocState
+      responses:
+        '200':
+          description: PFCP association state
+          content:
+            application/json:
+              schema:
+                $ref: '#/components/schemas/PfcpAssocState'
+    post:
+      summary: Initiate the PFCP Association Setup procedure
+      operationId: PfcpAssocSetup
+      responses:
+        '200':
+          $ref: '#/components/responses/OperationResult'
+    delete:
+      summary: Initiate the PFCP Association Release procedure
+      operationId: PfcpAssocRelease
+      responses:
+        '200':
+          $ref: '#/components/responses/OperationResult'
+
+  /pfcp/heartbeat:
+    post:
+      summary: Send a PFCP Heartbeat Request to the peer
+      operationId: PfcpHeartbeat
+      responses:
+        '200':
+          $ref: '#/components/responses/OperationResult'
+
 components:
+  responses:
+    OperationResult:
+      description: Generic operation result response
+      content:
+        application/json:
+          schema:
+            $ref: '#/components/schemas/OperationResult'
+
   schemas:
+    OperationResult:
+      type: object
+      required:
+        - success
+      properties:
+        success:
+          type: boolean
+          description: Indicates whether the operation was successful
+        message:
+          type: string
+          nullable: true
+          description: Human-readable explanation of the result
+
     MetricsList:
       type: array
       items:
@@ -53,3 +106,27 @@
           type: string
         value:
           type: integer
+
+    PfcpAssocState:
+      type: object
+      required:
+        - state
+        - laddr
+        - raddr
+      properties:
+        state:
+          type: string
+          enum: [connecting, connected]
+          description: Current association state
+        laddr:
+          type: string
+          description: Local (bind) IP address
+        raddr:
+          type: string
+          description: Remote (connect) IP address
+        lrts:
+          type: integer
+          description: Local Recovery TimeStamp
+        rrts:
+          type: integer
+          description: Remote Recovery TimeStamp
diff --git a/contrib/osmo-s1gw-cli.py b/contrib/osmo-s1gw-cli.py
index 76c444e..77014ad 100755
--- a/contrib/osmo-s1gw-cli.py
+++ b/contrib/osmo-s1gw-cli.py
@@ -82,11 +82,32 @@
         with self.send_get_req('metrics-list', query) as f:
             return json.load(f)

+    def pfcp_assoc_state(self) -> RESTResponse:
+        ''' PfcpAssocState :: Get the PFCP association state '''
+        with self.send_get_req('pfcp/assoc') as f:
+            return json.load(f)
+
+    def pfcp_assoc_setup(self) -> RESTResponse:
+        ''' PfcpAssocSetup :: Initiate the PFCP Association Setup procedure '''
+        with self.send_post_req('pfcp/assoc') as f:
+            return json.load(f)
+
+    def pfcp_assoc_release(self) -> RESTResponse:
+        ''' PfcpAssocRelease :: Initiate the PFCP Association Release 
procedure '''
+        with self.send_delete_req('pfcp/assoc') as f:
+            return json.load(f)
+
+    def pfcp_heartbeat(self) -> RESTResponse:
+        ''' PfcpHeartbeat :: Send a PFCP Heartbeat Request to the peer '''
+        with self.send_post_req('pfcp/heartbeat') as f:
+            return json.load(f)
+

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

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

     def __init__(self, argv):
         super().__init__(allow_cli_args=False, include_py=True)
@@ -136,6 +157,40 @@
         self.poutput(tabulate.tabulate(map(self.metrics_list_item, data),
                                        headers='keys', tablefmt=self.tablefmt))

+    @cmd2.with_category(CAT_PFCP)
+    def do_pfcp_assoc_state(self, opts) -> None:
+        ''' Get the PFCP association state '''
+        data = self.iface.pfcp_assoc_state()
+        table = [] # [param, value]
+        table.append(['State', data['state']])
+        table.append(['Local address', data['laddr']])
+        table.append(['Remote address', data['raddr']])
+        table.append(['Local Recovery TimeStamp', data['lrts']])
+        if 'rrts' in data:
+            table.append(['Remote Recovery TimeStamp', data['rrts']])
+        self.poutput(tabulate.tabulate(table,
+                                       headers=['Parameter', 'Value'],
+                                       tablefmt=self.tablefmt))
+
+    @cmd2.with_category(CAT_PFCP)
+    def do_pfcp_assoc_setup(self, opts) -> None:
+        ''' Initiate the PFCP Association Setup procedure '''
+        raise NotImplementedError
+
+    @cmd2.with_category(CAT_PFCP)
+    def do_pfcp_assoc_release(self, opts) -> None:
+        ''' Initiate the PFCP Association Release procedure '''
+        raise NotImplementedError
+
+    @cmd2.with_category(CAT_PFCP)
+    def do_pfcp_heartbeat(self, opts) -> None:
+        ''' Send a PFCP Heartbeat Request '''
+        data = self.iface.pfcp_heartbeat()
+        if data['success']:
+            self.poutput('Heartbeat succeeded')
+        else:
+            self.perror('Heartbeat failed: {message}'.format(**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 d5ceb94..64298ab 100644
--- a/doc/osmo-s1gw-cli.md
+++ b/doc/osmo-s1gw-cli.md
@@ -124,3 +124,29 @@
 | s1ap.enb.num_sctp_connections    | gauge  |       0 |
 | s1ap.proxy.uplink_packets_queued | gauge  |       0 |
 ```
+
+### `pfcp_assoc_state`
+
+Get the PFCP association state.
+
+```
+| Parameter                 | Value      |
+|---------------------------|------------|
+| State                     | connected  |
+| Local address             | 127.0.3.1  |
+| Remote address            | 127.0.3.10 |
+| Local Recovery TimeStamp  | 3967211233 |
+| Remote Recovery TimeStamp | 3965211123 |
+```
+
+### `pfcp_heartbeat`
+
+Send a PFCP Heartbeat Request.
+
+```
+OsmoS1GW# pfcp_heartbeat
+Heartbeat succeeded
+
+OsmoS1GW# pfcp_heartbeat
+Heartbeat failed: timeout
+```
diff --git a/priv/openapi.json b/priv/openapi.json
index 7323e9a..c55c312 100644
--- a/priv/openapi.json
+++ b/priv/openapi.json
@@ -49,10 +49,86 @@
                     }
                 }
             }
+        },
+        "/pfcp/assoc": {
+            "get": {
+                "summary": "Get the PFCP association state",
+                "operationId": "PfcpAssocState",
+                "responses": {
+                    "200": {
+                        "description": "PFCP association state",
+                        "content": {
+                            "application/json": {
+                                "schema": {
+                                    "$ref": 
"#/components/schemas/PfcpAssocState"
+                                }
+                            }
+                        }
+                    }
+                }
+            },
+            "post": {
+                "summary": "Initiate the PFCP Association Setup procedure",
+                "operationId": "PfcpAssocSetup",
+                "responses": {
+                    "200": {
+                        "$ref": "#/components/responses/OperationResult"
+                    }
+                }
+            },
+            "delete": {
+                "summary": "Initiate the PFCP Association Release procedure",
+                "operationId": "PfcpAssocRelease",
+                "responses": {
+                    "200": {
+                        "$ref": "#/components/responses/OperationResult"
+                    }
+                }
+            }
+        },
+        "/pfcp/heartbeat": {
+            "post": {
+                "summary": "Send a PFCP Heartbeat Request to the peer",
+                "operationId": "PfcpHeartbeat",
+                "responses": {
+                    "200": {
+                        "$ref": "#/components/responses/OperationResult"
+                    }
+                }
+            }
         }
     },
     "components": {
+        "responses": {
+            "OperationResult": {
+                "description": "Generic operation result response",
+                "content": {
+                    "application/json": {
+                        "schema": {
+                            "$ref": "#/components/schemas/OperationResult"
+                        }
+                    }
+                }
+            }
+        },
         "schemas": {
+            "OperationResult": {
+                "type": "object",
+                "required": [
+                    "success"
+                ],
+                "properties": {
+                    "success": {
+                        "type": "boolean",
+                        "description": "Indicates whether the operation was 
successful"
+                    },
+                    "message": {
+                        "type": "string",
+                        "nullable": true,
+                        "description": "Human-readable explanation of the 
result"
+                    }
+                }
+            },
             "MetricsList": {
                 "type": "array",
                 "items": {
@@ -81,6 +157,40 @@
                         "type": "integer"
                     }
                 }
+            },
+            "PfcpAssocState": {
+                "type": "object",
+                "required": [
+                    "state",
+                    "laddr",
+                    "raddr"
+                ],
+                "properties": {
+                    "state": {
+                        "type": "string",
+                        "enum": [
+                            "connecting",
+                            "connected"
+                        ],
+                        "description": "Current association state"
+                    },
+                    "laddr": {
+                        "type": "string",
+                        "description": "Local (bind) IP address"
+                    },
+                    "raddr": {
+                        "type": "string",
+                        "description": "Remote (connect) IP address"
+                    },
+                    "lrts": {
+                        "type": "integer",
+                        "description": "Local Recovery TimeStamp"
+                    },
+                    "rrts": {
+                        "type": "integer",
+                        "description": "Remote Recovery TimeStamp"
+                    }
+                }
             }
         }
     }
diff --git a/src/rest_server.erl b/src/rest_server.erl
index ee0a7de..d1597a9 100644
--- a/src/rest_server.erl
+++ b/src/rest_server.erl
@@ -34,7 +34,9 @@

 -module(rest_server).

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

 -include_lib("kernel/include/logger.hrl").
@@ -60,6 +62,28 @@
     {200, [], L1}.


+%% PfcpAssocState :: Get the PFCP association state
+pfcp_assoc_state(#{}) ->
+    Info0 = pfcp_peer:fetch_info(),
+    Info1 = maps:update_with(laddr, fun inet:ntoa/1, Info0),
+    Info2 = maps:update_with(raddr, fun inet:ntoa/1, Info1),
+    {200, [], rsp_map(Info2)}.
+
+%% TODO: PfcpAssocSetup :: Initiate the PFCP Association Setup procedure
+%% TODO: PfcpAssocRelease :: Initiate the PFCP Association Release procedure
+
+
+%% PfcpHeartbeat :: Send a PFCP Heartbeat Request to the peer
+pfcp_heartbeat(#{}) ->
+    case pfcp_peer:heartbeat_req() of
+        ok ->
+            {200, [], rsp_map(#{success => true})};
+        {error, Error} ->
+            {200, [], rsp_map(#{success => false,
+                                message => Error})}
+    end.
+
+
 %% ------------------------------------------------------------------
 %% private API
 %% ------------------------------------------------------------------
@@ -107,4 +131,18 @@
 thing_to_list(X) when is_list(X) -> X.


+%% Convert the given response map to a format acceptable by the erf
+-spec rsp_map(map()) -> map().
+rsp_map(M) ->
+    Fun = fun(K, V) -> {bval(K), bval(V)} end,
+    maps:from_list([Fun(K, V) || {K, V} <- maps:to_list(M)]).
+
+
+bval(V) when is_boolean(V) -> V;
+bval(V) when is_atom(V) -> atom_to_binary(V);
+bval(V) when is_list(V) -> list_to_binary(V);
+bval(V) when is_map(V) -> rsp_map(V);
+bval(V) -> V.
+
+
 %% vim:set ts=4 sw=4 et:

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

Gerrit-MessageType: merged
Gerrit-Project: erlang/osmo-s1gw
Gerrit-Branch: master
Gerrit-Change-Id: Idc98952d46d8e224969da343dc29ef323c6ed813
Gerrit-Change-Number: 41100
Gerrit-PatchSet: 8
Gerrit-Owner: fixeria <[email protected]>
Gerrit-Reviewer: Jenkins Builder
Gerrit-Reviewer: fixeria <[email protected]>
Gerrit-Reviewer: laforge <[email protected]>
Gerrit-Reviewer: osmith <[email protected]>
Gerrit-Reviewer: pespin <[email protected]>

Reply via email to