Soft Patch Panel
 help / color / mirror / Atom feed
* [spp] [PATCH v2 0/9] spp-ctl: SPP controller with Web API
@ 2018-09-23  2:22 Itsuro ODA
  2018-09-23  2:25 ` [spp] [PATCH v2 1/9] docs: overview Itsuro ODA
                   ` (8 more replies)
  0 siblings, 9 replies; 17+ messages in thread
From: Itsuro ODA @ 2018-09-23  2:22 UTC (permalink / raw)
  To: spp

From: Itsuro Oda <oda@valinux.co.jp>

spp-ctl is a SPP controller with a REST like web API.

spp-ctl maintains the connections from the SPP processes and at
the same time exposes the API for the user to request for the
SPP processes.

Background and motivation:

Current CLI (spp.py/spp_vf.py) can be used by intaractive only.
Therefore, spp-agent, a component of networking-spp which make
SPP available on OpenStack environment, implements SPP controller
in itself. (see. https://github.com/openstack/networking-spp )

Either CLI or spp-agent, there is a problem that other people can
not request to SPP processes while using. spp-ctl is invented to
solve this problem.

Both CLI and spp-agent can be used spp-ctl to request SPP
processes instead of owning contoroller itself. In that case,
multiple people can request to SPP processes at the same time.
Note that spp-agent has a plan to change to use spp-ctl.
It is also available not using CLI but requesting spp-ctl
directly.

Itsuro Oda (9):
  docs: overview
  docs: api reference
  docs: index
  docs: top index
  add requirement.txt
  spp-ctl: executable
  spp-ctl: controller
  spp-ctl: web api handler
  spp-ctl: spp command interface

 docs/guides/index.rst                 |   1 +
 docs/guides/spp-ctl/api-reference.rst | 790 ++++++++++++++++++++++++++
 docs/guides/spp-ctl/index.rst         |  39 ++
 docs/guides/spp-ctl/overview.rst      | 102 ++++
 requirements.txt                      |   3 +
 src/spp-ctl/spp-ctl                   |  11 +
 src/spp-ctl/spp_ctl.py                | 158 ++++++
 src/spp-ctl/spp_proc.py               | 184 ++++++
 src/spp-ctl/spp_webapi.py             | 440 ++++++++++++++
 9 files changed, 1728 insertions(+)
 create mode 100644 docs/guides/spp-ctl/api-reference.rst
 create mode 100644 docs/guides/spp-ctl/index.rst
 create mode 100644 docs/guides/spp-ctl/overview.rst
 create mode 100644 requirements.txt
 create mode 100755 src/spp-ctl/spp-ctl
 create mode 100644 src/spp-ctl/spp_ctl.py
 create mode 100644 src/spp-ctl/spp_proc.py
 create mode 100644 src/spp-ctl/spp_webapi.py
-- 
Itsuro ODA <oda@valinux.co.jp>

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [spp] [PATCH v2 1/9] docs: overview
  2018-09-23  2:22 [spp] [PATCH v2 0/9] spp-ctl: SPP controller with Web API Itsuro ODA
@ 2018-09-23  2:25 ` Itsuro ODA
  2018-10-02  3:29   ` Yasufumi Ogawa
  2018-09-23  2:28 ` [spp] [PATCH v2 2/9] docs: api reference Itsuro ODA
                   ` (7 subsequent siblings)
  8 siblings, 1 reply; 17+ messages in thread
From: Itsuro ODA @ 2018-09-23  2:25 UTC (permalink / raw)
  To: spp

From: Itsuro Oda <oda at valinux.co.jp>

Signed-off-by: Itsuro Oda <oda@valinux.co.jp>
---
 docs/guides/spp-ctl/overview.rst | 102 +++++++++++++++++++++++++++++++
 1 file changed, 102 insertions(+)
 create mode 100644 docs/guides/spp-ctl/overview.rst

diff --git a/docs/guides/spp-ctl/overview.rst b/docs/guides/spp-ctl/overview.rst
new file mode 100644
index 0000000..847b9dc
--- /dev/null
+++ b/docs/guides/spp-ctl/overview.rst
@@ -0,0 +1,102 @@
+====================================
+spp-ctl: SPP controller with Web API
+====================================
+
+Overview
+========
+
+spp-ctl is a SPP controller with a REST like web API.
+
+spp-ctl maintains the connections from the SPP processes and at the same
+time exposes the API for the user to request for the SPP processes.
+
+Background and motivation
+-------------------------
+
+Current CLI (spp.py/spp_vf.py) can be used by intaractive only.
+Therefore, spp-agent, a component of networking-spp which make SPP
+available on OpenStack environment, implements SPP controller in
+itself. (see. https://github.com/openstack/networking-spp )
+
+Either CLI or spp-agent, there is a problem that other people can not
+request to SPP processes while using. spp-ctl is invented to solve this
+problem.
+
+Both CLI and spp-agent can be used spp-ctl to request SPP processes
+instead of owning contoroller itself. In that case, multiple people
+can request to SPP processes at the same time.
+Note that spp-agent has a plan to change to use spp-ctl.
+It is also available not using CLI but requesting spp-ctl directly.
+
+Architecture
+------------
+
+The design goal of spp-ctl is to be as simple as possible.
+It is stateless. Basically, spp-ctl only converts API requests into
+commands of SPP processes and throws request, thouth it does syntax and
+lexical check for API requests.
+
+spp-ctl adopts bottle (it is simple and well known) as a web framework
+and eventlet for parallel processing. spp-ctl can process multiple APIs
+at the same time, however, requests for per SPP process are serialized
+internally.
+
+
+Setup
+=====
+
+spp-ctl is a simple program written in python3. Installation of related
+packages is as follows (assume ubuntu).
+
+::
+
+  $ sudo apt update
+  $ sudo apt install python3
+  $ sudo apt install python3-pip
+  $ sudo pip3 install -r requirements.txt
+
+Usage
+-----
+
+::
+
+  usage: spp-ctl [-p PRI_PORT] [-s SEC_PORT] [-a API_PORT]
+
+  optional arguments:
+    -p PRI_PORT  primary port. default is 5555.
+    -s SEC_PORT  secondary port. default is 6666.
+    -a API_PORT  web api port. default is 7777.
+
+Using systemd
+-------------
+
+Although spp-ctl runs as a daemon process normaly, it assumes to the
+use of systemd and does not daemonize itself.
+
+The service file for systemd is simple as shown below::
+
+  [Unit]
+  Description = SPP Controller
+
+  [Service]
+  ExecStart = {SPP install path}/spp_ctl/spp-ctl -p 5555 -s 6666 -a 7777
+  User = root
+
+API Usage
+=========
+
+For API details, see API-reference_.
+
+.. _API-reference: ./api-reference.rst
+
+Since spp-ctl provides the web API, for example, you can use curl to execute
+requests as follows::
+
+  $ curl http://localhost:7777/v1/processes
+  [{"type": "primary"}, {"client-id": 1, "type": "vf"}, {"client-id": 2, "type": "vf"}]
+  $ curl http://localhost:7777/v1/vfs/1
+  ... snip
+  $ curl -X POST http://localhost:7777/v1/vfs/1/components \
+    -d '{"core": 2, "name": "forward_0_tx", "type": "forward"}'
+  $
+
-- 
2.17.0
-- 
Itsuro ODA <oda@valinux.co.jp>

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [spp] [PATCH v2 2/9] docs: api reference
  2018-09-23  2:22 [spp] [PATCH v2 0/9] spp-ctl: SPP controller with Web API Itsuro ODA
  2018-09-23  2:25 ` [spp] [PATCH v2 1/9] docs: overview Itsuro ODA
@ 2018-09-23  2:28 ` Itsuro ODA
  2018-10-02  3:42   ` Yasufumi Ogawa
  2018-09-23  2:30 ` [spp] [PATCH v2 3/9] docs: index Itsuro ODA
                   ` (6 subsequent siblings)
  8 siblings, 1 reply; 17+ messages in thread
From: Itsuro ODA @ 2018-09-23  2:28 UTC (permalink / raw)
  To: spp

From: Itsuro Oda <oda@valinux.co.jp>

Signed-off-by: Itsuro Oda <oda@valinux.co.jp>
---
 docs/guides/spp-ctl/api-reference.rst | 790 ++++++++++++++++++++++++++
 1 file changed, 790 insertions(+)
 create mode 100644 docs/guides/spp-ctl/api-reference.rst

diff --git a/docs/guides/spp-ctl/api-reference.rst b/docs/guides/spp-ctl/api-reference.rst
new file mode 100644
index 0000000..b8bf488
--- /dev/null
+++ b/docs/guides/spp-ctl/api-reference.rst
@@ -0,0 +1,790 @@
+=============
+API Reference
+=============
+
+Overview
+========
+
+Spp-ctl provides simple REST like API. It supports http only, not https.
+
+Request and Response
+--------------------
+
+Request body is json format. It is accepted both "text/plain" and "application/json" for the content-type header.
+
+Response of GET is json format and the content-type is "application/json" if the request success.
+
+If a request fails, the response is a text which shows error reason and the content-type is "text/plain".
+
+Error code
+----------
+
+Spp-ctl does basic syntax and lexical check of a request.
+
++-------+------------------------------------------------------------------------------+
+| Error | Description                                                                  |
++=======+==============================================================================+
+| 400   | Syntax or lexical error of a request.                                        |
+|       | Or an SPP process returns error for the command correspondings to a request. |
++-------+------------------------------------------------------------------------------+
+| 404   | URL is not supported for spp-ctl.                                            |
+|       | Or there is no SPP process of client-id in a URL.                            |
++-------+------------------------------------------------------------------------------+
+| 500   | A system error occurs in the spp-ctl.                                        |
+|       | ex. communication error between an SPP processes.                            |
++-------+------------------------------------------------------------------------------+
+
+
+API independent of the process type
+===================================
+
+GET /v1/processes
+-----------------
+
+Show the SPP processes connected to the spp-ctl.
+
+Normarl response codes: 200
+
+Response
+^^^^^^^^
+
+An array of process objects.
+
+process object:
+
++-----------+---------+-------------------------------------------------------------+
+| Name      | Type    | Description                                                 |
++===========+=========+=============================================================+
+| type      | string  | process type. one of "primary", "vf" or "nfv".              |
++-----------+---------+-------------------------------------------------------------+
+| client-id | integer | client id. if type is "primary" this member does not exist. |
++-----------+---------+-------------------------------------------------------------+
+
+Response example
+^^^^^^^^^^^^^^^^
+
+::
+
+[{"type": "primary"}, {"type": "vf", "client-id": 1}, {"type": "nfv", "client-id": 2}]
+
+
+API for spp_vf
+==============
+
+GET /v1/vfs/{client_id}
+-----------------------
+
+Get the information of the spp_vf process.
+
+Normal response codes: 200
+
+Error response codes: 400, 404
+
+Request(path)
+^^^^^^^^^^^^^
+
++-----------+---------+-------------------------------------------------------------+
+| Name      | Type    | Description                                                 |
++===========+=========+=============================================================+
+| client_id | integer | client id.                                                  |
++-----------+---------+-------------------------------------------------------------+
+
+Response
+^^^^^^^^
+
++------------------+---------+-------------------------------------------------------------+
+| Name             | Type    | Description                                                 |
++==================+=========+=============================================================+
+| client-id        | integer | client id.                                                  |
++------------------+---------+-------------------------------------------------------------+
+| ports            | array   | an array of port ids used by the process.                   |
++------------------+---------+-------------------------------------------------------------+
+| components       | array   | an array of component objects in the process.               |
++------------------+---------+-------------------------------------------------------------+
+| classifier_table | array   | an array of classifier tables in the process.               |
++------------------+---------+-------------------------------------------------------------+
+
+component object:
+
++---------+---------+---------------------------------------------------------------------+
+| Name    | Type    | Description                                                         |
++=========+=========+=====================================================================+
+| core    | integer | core id running on the component                                    |
++---------+---------+---------------------------------------------------------------------+
+| name    | string  | an array of port ids used by the process.                           |
++---------+---------+---------------------------------------------------------------------+
+| type    | string  | an array of component objects in the process.                       |
++---------+---------+---------------------------------------------------------------------+
+| rx_port | array   | an array of port objects connected to the rx side of the component. |
++---------+---------+---------------------------------------------------------------------+
+| tx_port | array   | an array of port objects connected to the tx side of the component. |
++---------+---------+---------------------------------------------------------------------+
+
+port object:
+
++---------+---------+---------------------------------------------------------------------+
+| Name    | Type    | Description                                                         |
++=========+=========+=====================================================================+
+| port    | string  | port id. port id is the form {interface_type}:{interface_id}.       |
++---------+---------+---------------------------------------------------------------------+
+| vlan    | object  | vlan operation which is applied to the port.                        |
++---------+---------+---------------------------------------------------------------------+
+
+vlan object:
+
++-----------+---------+-------------------------------------------------------------------+
+| Name      | Type    | Description                                                       |
++===========+=========+===================================================================+
+| operation | string  | "add", "del" or "none".                                           |
++-----------+---------+-------------------------------------------------------------------+
+| id        | integer | vlan id.                                                          |
++-----------+---------+-------------------------------------------------------------------+
+| pcp       | integer | vlan pcp.                                                         |
++-----------+---------+-------------------------------------------------------------------+
+
+classifier table:
+
++-----------+---------+----------------------------------------------------------------------+
+| Name      | Type    | Description                                                          |
++===========+=========+======================================================================+
+| type      | string  | "mac" or "vlan".                                                     |
++-----------+---------+----------------------------------------------------------------------+
+| value     | string  | "mac address" for "mac" type. "vlan id/mac address" for "vlan" type. |
++-----------+---------+----------------------------------------------------------------------+
+| port      | string  | port id applied to classify.                                         |
++-----------+---------+----------------------------------------------------------------------+
+
+Response example
+^^^^^^^^^^^^^^^^
+
+::
+
+{
+"client-id": 1,
+"ports": ["phy:0", "phy:1", "vhost:0", "vhost:1", "ring:0", "ring:1", "ring:2", "ring:3"],
+"components": [
+{
+"core": 2,
+"name": "forward_0_tx",
+"type": "forward",
+"rx_port": [
+{
+"port": "ring:0",
+"vlan": { "operation": "none", "id": 0, "pcp": 0 }
+}
+],
+"tx_port": [
+{
+"port": "vhost:0",
+"vlan": { "operation": "none", "id": 0, "pcp": 0 }
+}
+]
+},
+{
+"core": 3,
+"type": "unuse"
+},
+{
+"core": 4,
+"type": "unuse"
+},
+{
+"core": 5,
+"name": "forward_1_rx",
+"type": "forward",
+"rx_port": [
+{
+"port": "vhost:1",
+"vlan": { "operation": "none", "id": 0, "pcp": 0 }
+}
+],
+"tx_port": [
+{
+"port": "ring:3",
+"vlan": { "operation": "none", "id": 0, "pcp": 0 }
+}
+]
+},
+{
+"core": 6,
+"name": "classifier",
+"type": "classifier_mac",
+"rx_port": [
+{
+"port": "phy:0",
+"vlan": { "operation": "none", "id": 0, "pcp": 0 }
+}
+],
+"tx_port": [
+{
+"port": "ring:0",
+"vlan": { "operation": "none", "id": 0, "pcp": 0 }
+},
+{
+"port": "ring:2",
+"vlan": { "operation": "none", "id": 0, "pcp": 0 }
+}
+]
+},
+{
+"core": 7,
+"name": "merger",
+"type": "merge",
+"rx_port": [
+{
+"port": "ring:1",
+"vlan": { "operation": "none", "id": 0, "pcp": 0 }
+},
+{
+"port": "ring:3",
+"vlan": { "operation": "none", "id": 0, "pcp": 0 }
+}
+],
+"tx_port": [
+{
+"port": "phy:0",
+"vlan": { "operation": "none", "id": 0, "pcp": 0 }
+}
+]
+}
+},
+"classifier_table": [
+{
+"type": "mac",
+"value": "FA:16:3E:7D:CC:35",
+"port": "ring:0"
+}
+]
+}
+
+Note: The component which type is "unused" is to indicate unused core.
+
+Equivalent CLI command
+^^^^^^^^^^^^^^^^^^^^^^
+
+::
+
+sec {client_id};status
+
+
+POST /v1/vfs/{client_id}/components
+-----------------------------------
+
+Start the component.
+
+Normal response codes: 204
+
+Error response codes: 400, 404
+
+Request(path)
+^^^^^^^^^^^^^
+
++-----------+---------+-------------------------------------------------------------+
+| Name      | Type    | Description                                                 |
++===========+=========+=============================================================+
+| client_id | integer | client id.                                                  |
++-----------+---------+-------------------------------------------------------------+
+
+
+Request(body)
+^^^^^^^^^^^^^
+
++-----------+---------+----------------------------------------------------------------+
+| Name      | Type    | Description                                                    |
++===========+=========+================================================================+
+| name      | string  | component name. must be unique in the process.                 |
++-----------+---------+----------------------------------------------------------------+
+| core      | integer | core id.                                                       |
++-----------+---------+----------------------------------------------------------------+
+| type      | string  | component type. one of "forward", "merge" or "classifier_mac". |
++-----------+---------+----------------------------------------------------------------+
+
+Request example
+^^^^^^^^^^^^^^^
+::
+
+{"name": "forwarder1", "core": 12, "type": "forward"}
+
+Response
+^^^^^^^^
+
+There is no body content for the response of a successful POST request.
+
+Equivalent CLI command
+^^^^^^^^^^^^^^^^^^^^^^
+
+::
+
+sec {client_id};component start {name} {core} {type}
+
+
+DELETE /v1/vfs/{sec id}/components/{name}
+-----------------------------------------
+
+Stop the component.
+
+Normal response codes: 204
+
+Error response codes: 400, 404
+
+Request(path)
+^^^^^^^^^^^^^
+
++-----------+---------+-------------------------------------------------------------+
+| Name      | Type    | Description                                                 |
++===========+=========+=============================================================+
+| client_id | integer | client id.                                                  |
++-----------+---------+-------------------------------------------------------------+
+| name      | string  | component name.                                             |
++-----------+---------+-------------------------------------------------------------+
+
+Response
+^^^^^^^^
+
+There is no body content for the response of a successful POST request.
+
+Equivalent CLI command
+^^^^^^^^^^^^^^^^^^^^^^
+
+::
+
+sec {client_id};component stop {name}
+
+
+PUT /v1/vfs/{client_id}/components/{name}/ports
+-----------------------------------------------
+
+Add or Delete port to the component.
+
+Normal response codes: 204
+
+Error response codes: 400, 404
+
+Request(path)
+^^^^^^^^^^^^^
+
++-----------+---------+-------------------------------------------------------------+
+| Name      | Type    | Description                                                 |
++===========+=========+=============================================================+
+| client_id | integer | client id.                                                  |
++-----------+---------+-------------------------------------------------------------+
+| name      | string  | component name.                                             |
++-----------+---------+-------------------------------------------------------------+
+
+Request(body)
+^^^^^^^^^^^^^
+
++---------+---------+---------------------------------------------------------------------+
+| Name    | Type    | Description                                                         |
++=========+=========+=====================================================================+
+| action  | string  | "attach" or "detach".                                               |
++---------+---------+---------------------------------------------------------------------+
+| port    | string  | port id. port id is the form {interface_type}:{interface_id}.       |
++---------+---------+---------------------------------------------------------------------+
+| dir     | string  | "rx" or "tx".                                                       |
++---------+---------+---------------------------------------------------------------------+
+| vlan    | object  | vlan operation which is applied to the port. it can be omitted.     |
++---------+---------+---------------------------------------------------------------------+
+
+vlan object:
+
++-----------+---------+-------------------------------------------------------------------+
+| Name      | Type    | Description                                                       |
++===========+=========+===================================================================+
+| operation | string  | "add", "del" or "none".                                           |
++-----------+---------+-------------------------------------------------------------------+
+| id        | integer | vlan id. ignored when operation is "del" or "none".               |
++-----------+---------+-------------------------------------------------------------------+
+| pcp       | integer | vlan pcp. ignored when operation is "del" or "none".              |
++-----------+---------+-------------------------------------------------------------------+
+
+Request example
+^^^^^^^^^^^^^^^
+
+::
+
+{"action": "attach", "port": "vhost:1", "dir": "rx",
+"vlan": {"operation": "add", "id": 677, "pcp": 0}
+}
+
+::
+
+{"action": "detach", "port": "vhost:0", "dir": "tx"}
+
+
+Response
+^^^^^^^^
+
+There is no body content for the response of a successful PUT request.
+
+
+Equivalent CLI command
+^^^^^^^^^^^^^^^^^^^^^^
+action is "attach"
+
+::
+
+sec {client_id};port add {port} {dir} {name} [add_vlantag {id} {pcp} | del_vlantag]
+
+action is "detach"
+
+::
+
+sec {client_id};port del {port} {dir} {name}
+
+
+PUT /v1/vfs/{sec id}/classifier_table
+-------------------------------------
+
+Set or Unset classifier table.
+
+Normal response codes: 204
+
+Error response codes: 400, 404
+
+Request(path)
+^^^^^^^^^^^^^
+
++-----------+---------+-------------------------------------------------------------+
+| Name      | Type    | Description                                                 |
++===========+=========+=============================================================+
+| client_id | integer | client id.                                                  |
++-----------+---------+-------------------------------------------------------------+
+
+Request(body)
+^^^^^^^^^^^^^
+
++-------------+-----------------+-------------------------------------------------------------------+
+| Name        | Type            | Description                                                       |
++=============+=================+===================================================================+
+| action      | string          | "add" or "del".                                                   |
++-------------+-----------------+-------------------------------------------------------------------+
+| type        | string          | "mac" or "vlan".                                                  |
++-------------+-----------------+-------------------------------------------------------------------+
+| vlan        | integer or null | vlan id when type is "vlan. null or omitted when type is "mac".   |
++-------------+-----------------+-------------------------------------------------------------------+
+| mac_address | string          | mac address.                                                      |
++-------------+-----------------+-------------------------------------------------------------------+
+| port        | string          | port id.                                                          |
++-------------+-----------------+-------------------------------------------------------------------+
+
+Request example
+^^^^^^^^^^^^^^^
+
+::
+
+{"action": "add", "type": "mac",
+"mac_address": "FA:16:3E:7D:CC:35", "port": "ring:0"
+}
+
+::
+
+{"action": "del", "type": "vlan", "vlan": 475,
+"mac_address": "FA:16:3E:7D:CC:35", "port": "ring:0"
+}
+
+
+Response
+^^^^^^^^
+
+There is no body content for the response of a successful PUT request.
+
+Equivalent CLI command
+^^^^^^^^^^^^^^^^^^^^^^
+
+type is "mac"
+
+::
+
+classifier_table {action} mac {mac_address} {port}
+
+type is "vlan"
+
+::
+
+classifier_table {action} vlan {vlan} {mac_address} {port}
+
+
+API for spp_nfv/spp_vm
+======================
+
+GET /v1/nfvs/{client_id}
+------------------------
+
+Get the information of the spp_nfv/spp_vm process.
+
+Normal response codes: 200
+
+Error response codes: 400, 404
+
+Request(path)
+^^^^^^^^^^^^^
+
++-----------+---------+-------------------------------------------------------------+
+| Name      | Type    | Description                                                 |
++===========+=========+=============================================================+
+| client_id | integer | client id.                                                  |
++-----------+---------+-------------------------------------------------------------+
+
+Response
+^^^^^^^^
+
++------------------+---------+-------------------------------------------------------------+
+| Name             | Type    | Description                                                 |
++==================+=========+=============================================================+
+| client-id        | integer | client id.                                                  |
++------------------+---------+-------------------------------------------------------------+
+| status           | string  | "Running" or "Idle".                                        |
++------------------+---------+-------------------------------------------------------------+
+| ports            | array   | an array of port ids used by the process.                   |
++------------------+---------+-------------------------------------------------------------+
+| patches          | array   | an array of patches.                                        |
++------------------+---------+-------------------------------------------------------------+
+
+patch objest
+
++----------+---------+-------------------------------------------------------------+
+| Name     | Type    | Description                                                 |
++==========+=========+=============================================================+
+| src      | string  | source port id.                                             |
++----------+---------+-------------------------------------------------------------+
+| dst      | string  | destination port id.                                        |
++----------+---------+-------------------------------------------------------------+
+
+Response example
+^^^^^^^^^^^^^^^^
+
+::
+
+{
+"client-id": 1,
+"status": "Running",
+"ports": ["phy:0", "phy:1", "vhost:0", "vhost:1", "ring:0", "ring:1", "ring:2", "ring:3"],
+"patches": [
+{"src": "vhost:0", "dst": "ring:0"},
+{"src": "ring:1", "dst": "vhost:1"}
+]
+}
+
+Equivalent CLI command
+^^^^^^^^^^^^^^^^^^^^^^
+
+::
+
+sec {client_id};status
+
+
+PUT /v1/nfvs/{client_id}/forward
+--------------------------------
+
+Start or Stop forwarding.
+
+Normal response codes: 204
+
+Error response codes: 400, 404
+
+Request(path)
+^^^^^^^^^^^^^
+
++-----------+---------+-------------------------------------------------------------+
+| Name      | Type    | Description                                                 |
++===========+=========+=============================================================+
+| client_id | integer | client id.                                                  |
++-----------+---------+-------------------------------------------------------------+
+
+Request(body)
+^^^^^^^^^^^^^
+
++-----------+---------+-------------------------------------------------------------+
+| Name      | Type    | Description                                                 |
++===========+=========+=============================================================+
+| action    | string  | "start" or "stop".                                          |
++-----------+---------+-------------------------------------------------------------+
+
+Request example
+^^^^^^^^^^^^^^^
+
+::
+
+{"action": "start"}
+
+Response
+^^^^^^^^
+
+There is no body content for the response of a successful PUT request.
+
+Equivalent CLI command
+^^^^^^^^^^^^^^^^^^^^^^
+
+action is "start"
+
+::
+
+sec {client_id};forward
+
+action is "stop"
+
+::
+
+sec {client_id};stop
+
+
+PUT /v1/nfvs/{client_id}/ports
+------------------------------
+
+Add or Delete port.
+
+Normal response codes: 204
+
+Error response codes: 400, 404
+
+Request(path)
+^^^^^^^^^^^^^
+
++-----------+---------+-------------------------------------------------------------+
+| Name      | Type    | Description                                                 |
++===========+=========+=============================================================+
+| client_id | integer | client id.                                                  |
++-----------+---------+-------------------------------------------------------------+
+
+Request(body)
+^^^^^^^^^^^^^
+
++-----------+---------+---------------------------------------------------------------+
+| Name      | Type    | Description                                                   |
++===========+=========+===============================================================+
+| action    | string  | "add" or "del".                                               |
++-----------+---------+---------------------------------------------------------------+
+| port      | string  | port id. port id is the form {interface_type}:{interface_id}. |
++-----------+---------+---------------------------------------------------------------+
+
+Request example
+^^^^^^^^^^^^^^^
+
+::
+
+{"action": "add", "port": "vhost:0"}
+
+
+Response
+^^^^^^^^
+
+There is no body content for the response of a successful PUT request.
+
+Equivalent CLI command
+^^^^^^^^^^^^^^^^^^^^^^
+
+::
+
+sec {client_id};{action} {interface_type} {interface_id}
+
+
+PUT /v1/nfvs/{client_id}/patches
+--------------------------------
+
+Add a patch.
+
+Normal response codes: 204
+
+Error response codes: 400, 404
+
+Request(path)
+^^^^^^^^^^^^^
+
++-----------+---------+-------------------------------------------------------------+
+| Name      | Type    | Description                                                 |
++===========+=========+=============================================================+
+| client_id | integer | client id.                                                  |
++-----------+---------+-------------------------------------------------------------+
+
+Request(body)
+^^^^^^^^^^^^^
+
++----------+---------+-------------------------------------------------------------+
+| Name     | Type    | Description                                                 |
++==========+=========+=============================================================+
+| src      | string  | source port id.                                             |
++----------+---------+-------------------------------------------------------------+
+| dst      | string  | destination port id.                                        |
++----------+---------+-------------------------------------------------------------+
+
+Request example
+^^^^^^^^^^^^^^^
+
+::
+
+{"src": "vhost:0", "dst": "ring:0"}
+
+Response
+^^^^^^^^
+
+There is no body content for the response of a successful PUT request.
+
+Equivalent CLI command
+^^^^^^^^^^^^^^^^^^^^^^
+
+::
+
+sec {client_id};patch {src} {dst}
+
+
+DELETE /v1/nfvs/{client_id}/patches
+-----------------------------------
+
+Reset patches.
+
+Normal response codes: 204
+
+Error response codes: 400, 404
+
+Request(path)
+^^^^^^^^^^^^^
+
++-----------+---------+-------------------------------------------------------------+
+| Name      | Type    | Description                                                 |
++===========+=========+=============================================================+
+| client_id | integer | client id.                                                  |
++-----------+---------+-------------------------------------------------------------+
+
+Response
+^^^^^^^^
+
+There is no body content for the response of a successful DELETE request.
+
+Equivalent CLI command
+^^^^^^^^^^^^^^^^^^^^^^
+
+::
+
+sec {client_id};patch reset
+
+
+API for spp_primary
+===================
+
+GET /v1/primary/status
+----------------------
+
+Show statistical information.
+
+Normal response codes: 200
+
+Response
+^^^^^^^^
+
+There is no data at the moment. The statistical information will be returned when spp_primary
+implements it.
+
+
+DELETE /v1/primary/status
+-------------------------
+
+Clear statistical information.
+
+Normal response codes: 204
+
+Response
+^^^^^^^^
+
+There is no body content for the response of a successful PUT request.
+
-- 
2.17.0
-- 
Itsuro ODA <oda@valinux.co.jp>

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [spp] [PATCH v2 3/9] docs: index
  2018-09-23  2:22 [spp] [PATCH v2 0/9] spp-ctl: SPP controller with Web API Itsuro ODA
  2018-09-23  2:25 ` [spp] [PATCH v2 1/9] docs: overview Itsuro ODA
  2018-09-23  2:28 ` [spp] [PATCH v2 2/9] docs: api reference Itsuro ODA
@ 2018-09-23  2:30 ` Itsuro ODA
  2018-09-23  2:32 ` [spp] [PATCH v2 4/9] docs: top index Itsuro ODA
                   ` (5 subsequent siblings)
  8 siblings, 0 replies; 17+ messages in thread
From: Itsuro ODA @ 2018-09-23  2:30 UTC (permalink / raw)
  To: spp

From: Itsuro Oda <oda@valinux.co.jp>

Signed-off-by: Itsuro Oda <oda@valinux.co.jp>
---
 docs/guides/spp-ctl/index.rst | 39 +++++++++++++++++++++++++++++++++++
 1 file changed, 39 insertions(+)
 create mode 100644 docs/guides/spp-ctl/index.rst

diff --git a/docs/guides/spp-ctl/index.rst b/docs/guides/spp-ctl/index.rst
new file mode 100644
index 0000000..952d8f6
--- /dev/null
+++ b/docs/guides/spp-ctl/index.rst
@@ -0,0 +1,39 @@
+..  BSD LICENSE
+    Copyright(c) 2010-2014 Intel Corporation. All rights reserved.
+    All rights reserved.
+
+    Redistribution and use in source and binary forms, with or without
+    modification, are permitted provided that the following conditions
+    are met:
+
+    * Redistributions of source code must retain the above copyright
+    notice, this list of conditions and the following disclaimer.
+    * Redistributions in binary form must reproduce the above copyright
+    notice, this list of conditions and the following disclaimer in
+    the documentation and/or other materials provided with the
+    distribution.
+    * Neither the name of Intel Corporation nor the names of its
+    contributors may be used to endorse or promote products derived
+    from this software without specific prior written permission.
+
+    THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+    "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+    LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
+    A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
+    OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
+    SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
+    LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
+    DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
+    THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
+    (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
+    OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
+
+SPP-CTL
+===========
+
+.. toctree::
+   :maxdepth: 2
+   :numbered:
+
+   overview
+   api-reference
-- 
2.17.0
-- 
Itsuro ODA <oda@valinux.co.jp>

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [spp] [PATCH v2 4/9] docs: top index
  2018-09-23  2:22 [spp] [PATCH v2 0/9] spp-ctl: SPP controller with Web API Itsuro ODA
                   ` (2 preceding siblings ...)
  2018-09-23  2:30 ` [spp] [PATCH v2 3/9] docs: index Itsuro ODA
@ 2018-09-23  2:32 ` Itsuro ODA
  2018-09-23  2:33 ` [spp] PATCH v2 5/9] add requirements.txt Itsuro ODA
                   ` (4 subsequent siblings)
  8 siblings, 0 replies; 17+ messages in thread
From: Itsuro ODA @ 2018-09-23  2:32 UTC (permalink / raw)
  To: spp

From: Itsuro Oda <oda@valinux.co.jp>

Signed-off-by: Itsuro Oda <oda@valinux.co.jp>
---
 docs/guides/index.rst | 1 +
 1 file changed, 1 insertion(+)

diff --git a/docs/guides/index.rst b/docs/guides/index.rst
index d0242d6..9d62b30 100644
--- a/docs/guides/index.rst
+++ b/docs/guides/index.rst
@@ -39,3 +39,4 @@ SPP documentation
    commands/index
    tools/index
    spp_vf/index
+   spp-ctl/index
-- 
2.17.0
-- 
Itsuro ODA <oda@valinux.co.jp>

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [spp] PATCH v2 5/9] add requirements.txt
  2018-09-23  2:22 [spp] [PATCH v2 0/9] spp-ctl: SPP controller with Web API Itsuro ODA
                   ` (3 preceding siblings ...)
  2018-09-23  2:32 ` [spp] [PATCH v2 4/9] docs: top index Itsuro ODA
@ 2018-09-23  2:33 ` Itsuro ODA
  2018-09-23  2:35 ` [spp] [PATCH v2 6/9] spp-ctl: executable Itsuro ODA
                   ` (3 subsequent siblings)
  8 siblings, 0 replies; 17+ messages in thread
From: Itsuro ODA @ 2018-09-23  2:33 UTC (permalink / raw)
  To: spp

From: Itsuro Oda <oda@valinux.co.jp>

Signed-off-by: Itsuro Oda <oda@valinux.co.jp>
---
 requirements.txt | 3 +++
 1 file changed, 3 insertions(+)
 create mode 100644 requirements.txt

diff --git a/requirements.txt b/requirements.txt
new file mode 100644
index 0000000..cc52d48
--- /dev/null
+++ b/requirements.txt
@@ -0,0 +1,3 @@
+eventlet
+bottle
+netaddr
-- 
2.17.0
-- 
Itsuro ODA <oda@valinux.co.jp>

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [spp] [PATCH v2 6/9] spp-ctl: executable
  2018-09-23  2:22 [spp] [PATCH v2 0/9] spp-ctl: SPP controller with Web API Itsuro ODA
                   ` (4 preceding siblings ...)
  2018-09-23  2:33 ` [spp] PATCH v2 5/9] add requirements.txt Itsuro ODA
@ 2018-09-23  2:35 ` Itsuro ODA
  2018-10-02  5:47   ` Yasufumi Ogawa
  2018-09-23  2:36 ` [spp] [PATCH v2 7/9] spp-ctl: SPP controller with Web API Itsuro ODA
                   ` (2 subsequent siblings)
  8 siblings, 1 reply; 17+ messages in thread
From: Itsuro ODA @ 2018-09-23  2:35 UTC (permalink / raw)
  To: spp

From: Itsuro Oda <oda@valinux.co.jp>

Signed-off-by: Itsuro Oda <oda@valinux.co.jp>
---
 src/spp-ctl/spp-ctl | 11 +++++++++++
 1 file changed, 11 insertions(+)
 create mode 100755 src/spp-ctl/spp-ctl

diff --git a/src/spp-ctl/spp-ctl b/src/spp-ctl/spp-ctl
new file mode 100755
index 0000000..645611b
--- /dev/null
+++ b/src/spp-ctl/spp-ctl
@@ -0,0 +1,11 @@
+#!/usr/bin/python3
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2018 Nippon Telegraph and Telephone Corporation
+
+import sys
+
+from spp_ctl import main
+
+
+if __name__ == "__main__":
+    sys.exit(main())
-- 
2.17.0
-- 
Itsuro ODA <oda@valinux.co.jp>

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [spp] [PATCH v2 7/9] spp-ctl: SPP controller with Web API
  2018-09-23  2:22 [spp] [PATCH v2 0/9] spp-ctl: SPP controller with Web API Itsuro ODA
                   ` (5 preceding siblings ...)
  2018-09-23  2:35 ` [spp] [PATCH v2 6/9] spp-ctl: executable Itsuro ODA
@ 2018-09-23  2:36 ` Itsuro ODA
  2018-09-23  2:44   ` Itsuro ODA
  2018-09-23  2:38 ` [spp] [PATCH v2 8/9] spp-ctl: web api handler Itsuro ODA
  2018-09-23  2:39 ` [spp] [PATCH v2 9/9] spp-ctl: spp command interface Itsuro ODA
  8 siblings, 1 reply; 17+ messages in thread
From: Itsuro ODA @ 2018-09-23  2:36 UTC (permalink / raw)
  To: spp

From: Itsuro Oda <oda@valinux.co.jp>

Signed-off-by: Itsuro Oda <oda@valinux.co.jp>
---
 src/spp-ctl/spp_ctl.py | 158 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 158 insertions(+)
 create mode 100644 src/spp-ctl/spp_ctl.py

diff --git a/src/spp-ctl/spp_ctl.py b/src/spp-ctl/spp_ctl.py
new file mode 100644
index 0000000..e168747
--- /dev/null
+++ b/src/spp-ctl/spp_ctl.py
@@ -0,0 +1,158 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2018 Nippon Telegraph and Telephone Corporation
+
+import eventlet
+eventlet.monkey_patch()
+
+import argparse
+import errno
+import json
+import logging
+import socket
+import subprocess
+
+import spp_proc
+import spp_webapi
+
+
+LOG = logging.getLogger(__name__)
+
+
+MSG_SIZE = 4096
+
+
+class Controller(object):
+
+    def __init__(self, pri_port, sec_port, api_port):
+        self.web_server = spp_webapi.WebServer(self, api_port)
+        self.procs = {}
+        self.init_connection(pri_port, sec_port)
+
+    def start(self):
+        self.web_server.start()
+
+    def init_connection(self, pri_port, sec_port):
+        self.pri_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.pri_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        self.pri_sock.bind(('127.0.0.1', pri_port))
+        self.pri_sock.listen(1)
+        self.primary_listen_thread = eventlet.greenthread.spawn(
+            self.accept_primary)
+
+        self.sec_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
+        self.sec_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
+        self.sec_sock.bind(('127.0.0.1', sec_port))
+        self.sec_sock.listen(1)
+        self.secondary_listen_thread = eventlet.greenthread.spawn(
+            self.accept_secondary)
+
+    def accept_primary(self):
+        while True:
+            conn, _ = self.pri_sock.accept()
+            proc = self.procs.get(spp_proc.ID_PRIMARY)
+            if proc is not None:
+                LOG.warning("spp_primary reconnect !")
+                with proc.sem:
+                    try:
+                        proc.conn.close()
+                    except Exception:
+                        pass
+                    proc.conn = conn
+                # NOTE: when spp_primary restart, all secondarys must be
+                # restarted. this is out of controle of spp-ctl.
+            else:
+                LOG.info("primary connected.")
+                self.procs[spp_proc.ID_PRIMARY] = spp_proc.PrimaryProc(conn)
+
+    def accept_secondary(self):
+        while True:
+            conn, _ = self.sec_sock.accept()
+            LOG.debug("sec accepted: get process id")
+            proc = self._get_proc(conn)
+            if proc is None:
+                LOG.error("get process id failed")
+                conn.close()
+                continue
+            old_proc = self.procs.get(proc.id)
+            if old_proc:
+                LOG.warning("%s(%d) reconnect !", old_proc.type, old_proc.id)
+                if old_proc.type != proc.type:
+                    LOG.warning("type changed ! new type: %s", proc.type)
+                with old_proc.sem:
+                    try:
+                        old_proc.conn.close()
+                    except Exception:
+                        pass
+            else:
+                LOG.info("%s(%d) connected.", proc.type, proc.id)
+            self.procs[proc.id] = proc
+
+    @staticmethod
+    def _continue_recv(conn):
+        try:
+            # must set non-blocking to recieve remining data not to happen
+            # blocking here.
+            # NOTE: usually MSG_DONTWAIT flag is used for this purpose but
+            # this flag is not supported under eventlet.
+            conn.setblocking(False)
+            data = b""
+            while True:
+                try:
+                    rcv_data = conn.recv(MSG_SIZE)
+                    data += rcv_data
+                    if len(rcv_data) < MSG_SIZE:
+                        break
+                except socket.error as e:
+                    if e.args[0] == errno.EAGAIN:
+                        # OK, no data remining. this happens when recieve data
+                        # length is just multiple of MSG_SIZE.
+                        break
+                    raise e
+            return data
+        finally:
+            conn.setblocking(True)
+
+    @staticmethod
+    def _send_command(conn, command):
+        conn.sendall(command.encode())
+        data = conn.recv(MSG_SIZE)
+        if data and len(data) == MSG_SIZE:
+            # could not receive data at once. recieve remining data.
+            data += self._continue_recv(conn)
+        if data:
+            data = data.decode()
+        return data
+
+    def _get_proc(self, conn):
+        # it is a bit ad hoc. send "_get_clinet_id" command and try to
+        # decode reply for each proc type. if success, that is the type.
+        data = self._send_command(conn, "_get_client_id")
+        for proc in [spp_proc.VfProc, spp_proc.NfvProc]:
+            sec_id = proc._decode_client_id(data)
+            if sec_id is not None:
+                return proc(sec_id, conn)
+
+    def get_processes(self):
+        procs = []
+        for proc in self.procs.values():
+            p = {"type": proc.type}
+            if proc.id != spp_proc.ID_PRIMARY:
+                p["client-id"] = proc.id
+            procs.append(p)
+        return procs
+
+
+def main():
+    parser = argparse.ArgumentParser(description="SPP Controller")
+    parser.add_argument("-p", dest='pri_port', type=int, default=5555,
+                        action='store', help="primary port")
+    parser.add_argument("-s", dest='sec_port', type=int, default=6666,
+                        action='store', help="secondary port")
+    parser.add_argument("-a", dest='api_port', type=int, default=7777,
+                        action='store', help="web api port")
+    args = parser.parse_args()
+
+    logging.basicConfig(level=logging.DEBUG)
+
+    controller = Controller(args.pri_port, args.sec_port, args.api_port)
+    controller.start()
-- 
2.17.0
-- 
Itsuro ODA <oda@valinux.co.jp>

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [spp] [PATCH v2 8/9] spp-ctl: web api handler
  2018-09-23  2:22 [spp] [PATCH v2 0/9] spp-ctl: SPP controller with Web API Itsuro ODA
                   ` (6 preceding siblings ...)
  2018-09-23  2:36 ` [spp] [PATCH v2 7/9] spp-ctl: SPP controller with Web API Itsuro ODA
@ 2018-09-23  2:38 ` Itsuro ODA
  2018-10-02  4:03   ` Yasufumi Ogawa
  2018-09-23  2:39 ` [spp] [PATCH v2 9/9] spp-ctl: spp command interface Itsuro ODA
  8 siblings, 1 reply; 17+ messages in thread
From: Itsuro ODA @ 2018-09-23  2:38 UTC (permalink / raw)
  To: spp

From: Itsuro Oda <oda@valinux.co.jp>

Signed-off-by: Itsuro Oda <oda@valinux.co.jp>
---
 src/spp-ctl/spp_webapi.py | 440 ++++++++++++++++++++++++++++++++++++++
 1 file changed, 440 insertions(+)
 create mode 100644 src/spp-ctl/spp_webapi.py

diff --git a/src/spp-ctl/spp_webapi.py b/src/spp-ctl/spp_webapi.py
new file mode 100644
index 0000000..3ee7893
--- /dev/null
+++ b/src/spp-ctl/spp_webapi.py
@@ -0,0 +1,440 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2018 Nippon Telegraph and Telephone Corporation
+
+import bottle
+import errno
+import json
+import logging
+import netaddr
+import re
+import socket
+import subprocess
+import sys
+
+import spp_proc
+
+
+LOG = logging.getLogger(__name__)
+
+
+class KeyRequired(bottle.HTTPError):
+
+    def __init__(self, key):
+        msg = "key(%s) required." % key
+        super(KeyRequired, self).__init__(400, msg)
+
+
+class KeyInvalid(bottle.HTTPError):
+
+    def __init__(self, key, value):
+        msg = "invalid key(%s): %s." % (key, value)
+        super(KeyRequired, self).__init__(400, msg)
+
+
+class BaseHandler(bottle.Bottle):
+    """Define common methods for each handler."""
+
+    def __init__(self, controller):
+        super(BaseHandler, self).__init__()
+        self.ctrl = controller
+
+        self.default_error_handler = self._error_handler
+        bottle.response.default_status = 404
+
+    def _error_handler(self, res):
+        # use "text/plain" as content_type rather than bottle's default
+        # "html".
+        res.content_type = "text/plain"
+        return res.body
+
+    def _validate_port(self, port):
+        try:
+            if_type, if_num = port.split(":")
+            if if_type not in ["phy", "vhost", "ring"]:
+                raise
+            int(if_num)
+        except:
+            raise KeyInvalid('port', port)
+
+    def log_url(self):
+        LOG.info("%s %s called", bottle.request.method, bottle.request.path)
+
+    def log_response(self):
+        LOG.info("response: %s", bottle.response.status)
+
+    # following three decorators do common works for each API.
+    # each handler 'install' appropriate decorators.
+    #
+    def get_body(self, func):
+        """Get body and set it to method argument.
+        content-type is OK whether application/json or plain text.
+        """
+        def wrapper(*args, **kwargs):
+            req = bottle.request
+            if req.method in ["POST", "PUT"]:
+                if req.get_header('Content-Type') == "application/json":
+                    body = req.json
+                else:
+                    body = json.loads(req.body.read().decode())
+                kwargs['body'] = body
+                LOG.info("body: %s", body)
+            return func(*args, **kwargs)
+        return wrapper
+
+    def check_sec_id(self, func):
+        """Get and check proc and set it to method argument."""
+        def wrapper(*args, **kwargs):
+            sec_id = kwargs.pop('sec_id', None)
+            if sec_id is not None:
+                proc = self.ctrl.procs.get(sec_id)
+                if proc is None or proc.type != self.type:
+                    raise bottle.HTTPError(404,
+                                           "sec_id %d not found." % sec_id)
+                kwargs['proc'] = proc
+            return func(*args, **kwargs)
+        return wrapper
+
+    def make_response(self, func):
+        """Convert plain response to bottle.HTTPResponse."""
+        def wrapper(*args, **kwargs):
+            ret = func(*args, **kwargs)
+            if ret is None:
+                return bottle.HTTPResponse(status=204)
+            else:
+                r = bottle.HTTPResponse(status=200, body=json.dumps(ret))
+                r.content_type = "application/json"
+                return r
+        return wrapper
+
+
+class WebServer(BaseHandler):
+    """Top level handler.
+    handlers are hierarchized using 'mount' as follows:
+    /          WebServer
+    /v1          V1Handler
+       /vfs        V1VFHandler
+       /nfvs       V1NFVHandler
+       /primary    V1PrimaryHandler
+    """
+
+    def __init__(self, controller, api_port):
+        super(WebServer, self).__init__(controller)
+        self.api_port = api_port
+
+        self.mount("/v1", V1Handler(controller))
+
+        # request and response logging.
+        self.add_hook("before_request", self.log_url)
+        self.add_hook("after_request", self.log_response)
+
+    def start(self):
+        self.run(server='eventlet', host='localhost', port=self.api_port,
+                 quiet=True)
+
+
+class V1Handler(BaseHandler):
+    def __init__(self, controller):
+        super(V1Handler, self).__init__(controller)
+
+        self.set_route()
+
+        self.mount("/vfs", V1VFHandler(controller))
+        self.mount("/nfvs", V1NFVHandler(controller))
+        self.mount("/primary", V1PrimaryHandler(controller))
+
+        self.install(self.make_response)
+
+    def set_route(self):
+        self.route('/processes', 'GET', callback=self.get_processes)
+
+    def get_processes(self):
+        LOG.info("get processes called.")
+        return self.ctrl.get_processes()
+
+
+class V1VFHandler(BaseHandler):
+
+    def __init__(self, controller):
+        super(V1VFHandler, self).__init__(controller)
+        self.type = spp_proc.TYPE_VF
+
+        self.set_route()
+
+        self.install(self.check_sec_id)
+        self.install(self.get_body)
+        self.install(self.make_response)
+
+    def set_route(self):
+        self.route('/<sec_id:int>', 'GET', callback=self.vf_get)
+        self.route('/<sec_id:int>/components', 'POST',
+                   callback=self.vf_comp_start)
+        self.route('/<sec_id:int>/components/<name>', 'DELETE',
+                   callback=self.vf_comp_stop)
+        self.route('/<sec_id:int>/components/<name>', 'PUT',
+                   callback=self.vf_comp_port)
+        self.route('/<sec_id:int>/classifier_table', 'PUT',
+                   callback=self.vf_classifier)
+
+    def convert_vf_info(self, data):
+        info = data["info"]
+        vf = {}
+        vf["client-id"] = info["client-id"]
+        vf["ports"] = []
+        for key in ["phy", "vhost", "ring"]:
+            for idx in info[key]:
+                vf["ports"].append(key + ":" + str(idx))
+        vf["components"] = info["core"]
+        vf["classifier_table"] = info["classifier_table"]
+
+        return vf
+
+    def vf_get(self, proc):
+        return self.convert_vf_info(proc.get_status())
+
+    def _validate_vf_comp_start(self, body):
+        for key in ['name', 'core', 'type']:
+            if key not in body:
+                raise KeyRequired(key)
+        if not isinstance(body['name'], str):
+            raise KeyInvalid('name', body['name'])
+        if not isinstance(body['core'], int):
+            raise KeyInvalid('core', body['core'])
+        if body['type'] not in ["forward", "merge", "classifier_mac"]:
+            raise KeyInvalid('type', body['type'])
+
+    def vf_comp_start(self, proc, body):
+        self._validate_vf_comp_start(body)
+        proc.start_component(body['name'], body['core'], body['type'])
+
+    def vf_comp_stop(self, proc, name):
+        proc.stop_component(name)
+
+    def _validate_vf_comp_port(self, body):
+        for key in ['action', 'port', 'dir']:
+            if key not in body:
+                raise KeyRequired(key)
+        if body['action'] not in ["attach", "detach"]:
+            raise KeyInvalid('action', body['action'])
+        if body['dir'] not in ["rx", "tx"]:
+            raise KeyInvalid('dir', body['dir'])
+        self._validate_port(body['port'])
+
+        if body['action'] == "attach":
+            vlan = body.get('vlan')
+            if vlan:
+                try:
+                    if vlan['operation'] not in ["none", "add", "del"]:
+                        raise
+                    if vlan['operation'] == "add":
+                        int(vlan['id'])
+                        int(vlan['pcp'])
+                except:
+                    raise KeyInvalid('vlan', vlan)
+
+    def vf_comp_port(self, proc, name, body):
+        self._validate_vf_comp_port(body)
+
+        if body['action'] == "attach":
+            op = "none"
+            vlan_id = 0
+            pcp = 0
+            vlan = body.get('vlan')
+            if vlan:
+                if vlan['operation'] == "add":
+                    op = "add_vlantag"
+                    vlan_id = vlan['id']
+                    pcp = vlan['pcp']
+                elif vlan['operation'] == "del":
+                    op = "del_vlantag"
+            proc.port_add(body['port'], body['dir'],
+                          name, op, vlan_id, pcp)
+        else:
+            proc.port_del(body['port'], body['dir'], name)
+
+    def _validate_mac(self, mac_address):
+        try:
+            netaddr.EUI(mac_address)
+        except:
+            raise KeyInvalid('mac_address', mac_address)
+
+    def _validate_vf_classifier(self, body):
+        for key in ['action', 'type', 'port', 'mac_address']:
+            if key not in body:
+                raise KeyRequired(key)
+        if body['action'] not in ["add", "del"]:
+            raise KeyInvalid('action', body['action'])
+        if body['type'] not in ["mac", "vlan"]:
+            raise KeyInvalid('type', body['type'])
+        self._validate_port(body['port'])
+        self._validate_mac(body['mac_address'])
+
+        if body['type'] == "vlan":
+            try:
+                int(body['vlan'])
+            except:
+                raise KeyInvalid('vlan', body.get('vlan'))
+
+    def vf_classifier(self, proc, body):
+        self._validate_vf_classifier(body)
+
+        port = body['port']
+        mac_address = body['mac_address']
+
+        if body['action'] == "add":
+            if body['type'] == "mac":
+                proc.set_classifier_table(mac_address, port)
+            else:
+                proc.set_classifier_table_with_vlan(
+                    mac_address, port, body['vlan'])
+        else:
+            if body['type'] == "mac":
+                proc.clear_classifier_table(mac_address, port)
+            else:
+                proc.clear_classifier_table_with_vlan(
+                    mac_address, port, body['vlan'])
+
+
+class V1NFVHandler(BaseHandler):
+
+    def __init__(self, controller):
+        super(V1NFVHandler, self).__init__(controller)
+        self.type = spp_proc.TYPE_NFV
+
+        self.set_route()
+
+        self.install(self.check_sec_id)
+        self.install(self.get_body)
+        self.install(self.make_response)
+
+    def set_route(self):
+        self.route('/<sec_id:int>', 'GET', callback=self.nfv_get)
+        self.route('/<sec_id:int>/forward', 'PUT',
+                   callback=self.nfv_forward)
+        self.route('/<sec_id:int>/ports', 'PUT',
+                   callback=self.nfv_port)
+        self.route('/<sec_id:int>/patches', 'PUT',
+                   callback=self.nfv_patch_add)
+        self.route('/<sec_id:int>/patches', 'DELETE',
+                   callback=self.nfv_patch_del)
+
+    def convert_nfv_info(self, data):
+        nfv = {}
+        lines = data.splitlines()
+        if len(lines) < 3:
+            return {}
+        p = re.compile("Client ID (\d+) (\w+)")
+        m = p.match(lines[0])
+        if m:
+            nfv['client_id'] = int(m.group(1))
+            nfv['status'] = m.group(2)
+
+        ports = {}
+        outs = []
+        for line in lines[2:]:
+            if not line.startswith("port_id"):
+                break
+            arg1, _, arg2, arg3 = line.split(",")
+            _, port_id = arg1.split(":")
+            if arg2 == "PHY":
+                port = "phy:" + port_id
+            else:
+                if_type, rest = arg2.split("(")
+                if_num = rest.rstrip(")")
+                if if_type == "RING":
+                    port = "ring:" + if_num
+                elif if_type == "VHOST":
+                    port = "vhost:" + if_num
+                else:
+                    port = if_type + ":" + if_num
+            ports[port_id] = port
+            _, out_id = arg3.split(":")
+            if out_id != "none":
+                outs.append((port_id, out_id))
+        nfv['ports'] = list(ports.values())
+        patches = []
+        if outs:
+            for src_id, dst_id in outs:
+                patches.append({"src": ports[src_id], "dst": ports[dst_id]})
+        nfv['patches'] = patches
+
+        return nfv
+
+    def nfv_get(self, proc):
+        return self.convert_nfv_info(proc.get_status())
+
+    def _validate_nfv_forward(self, body):
+        if 'action' not in body:
+            raise KeyRequired('action')
+        if body['action'] not in ["start", "stop"]:
+            raise KeyInvalid('action', body['action'])
+
+    def nfv_forward(self, proc, body):
+        if body['action'] == "start":
+            proc.forward()
+        else:
+            proc.stop()
+
+    def _validate_nfv_port(self, body):
+        for key in ['action', 'port']:
+            if key not in body:
+                raise KeyRequired(key)
+        if body['action'] not in ["add", "del"]:
+            raise KeyInvalid('action', body['action'])
+        self._validate_port(body['port'])
+
+    def nfv_port(self, proc, body):
+        self._validate_nfv_port(body)
+
+        if_type, if_num = body['port'].split(":")
+        if body['action'] == "add":
+            proc.port_add(if_type, if_num)
+        else:
+            proc.port_del(if_type, if_num)
+
+    def _validate_nfv_patch(self, body):
+        for key in ['src', 'dst']:
+            if key not in body:
+                raise KeyRequired(key)
+        self._validate_port(body['src'])
+        self._validate_port(body['dst'])
+
+    def nfv_patch_add(self, proc, body):
+        self._validate_nfv_patch(body)
+        proc.patch_add(body['src'], body['dst'])
+
+    def nfv_patch_del(self, proc):
+        proc.patch_reset()
+
+
+class V1PrimaryHandler(BaseHandler):
+
+    def __init__(self, controller):
+        super(V1PrimaryHandler, self).__init__(controller)
+
+        self.set_route()
+
+        self.install(self.make_response)
+
+    def set_route(self):
+        self.route('/status', 'GET', callback=self.get_status)
+        self.route('/status', 'DELETE', callback=self.clear_status)
+
+    def _get_proc(self):
+        proc = self.ctrl.procs.get(spp_proc.ID_PRIMARY)
+        if proc is None:
+            raise bottle.HTTPError(404, "primary not found.")
+        return proc
+
+    def convert_status(self, data):
+        # no data returned at the moment.
+        # some data will be returned when the primary becomes to
+        # return statistical information.
+        return {}
+
+    def get_status(self):
+        proc = self._get_proc()
+        return self.convert_status(proc.status())
+
+    def clear_status(self):
+        proc = self._get_proc()
+        proc.clear()
-- 
2.17.0
-- 
Itsuro ODA <oda@valinux.co.jp>

^ permalink raw reply	[flat|nested] 17+ messages in thread

* [spp] [PATCH v2 9/9] spp-ctl: spp command interface
  2018-09-23  2:22 [spp] [PATCH v2 0/9] spp-ctl: SPP controller with Web API Itsuro ODA
                   ` (7 preceding siblings ...)
  2018-09-23  2:38 ` [spp] [PATCH v2 8/9] spp-ctl: web api handler Itsuro ODA
@ 2018-09-23  2:39 ` Itsuro ODA
  8 siblings, 0 replies; 17+ messages in thread
From: Itsuro ODA @ 2018-09-23  2:39 UTC (permalink / raw)
  To: spp

From: Itsuro Oda <oda@valinux.co.jp>

Signed-off-by: Itsuro Oda <oda@valinux.co.jp>
---
 src/spp-ctl/spp_proc.py | 184 ++++++++++++++++++++++++++++++++++++++++
 1 file changed, 184 insertions(+)
 create mode 100644 src/spp-ctl/spp_proc.py

diff --git a/src/spp-ctl/spp_proc.py b/src/spp-ctl/spp_proc.py
new file mode 100644
index 0000000..929942a
--- /dev/null
+++ b/src/spp-ctl/spp_proc.py
@@ -0,0 +1,184 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2018 Nippon Telegraph and Telephone Corporation
+
+import bottle
+import eventlet
+import json
+import logging
+
+import spp_ctl
+
+
+LOG = logging.getLogger(__name__)
+
+ID_PRIMARY = 0
+TYPE_PRIMARY = "primary"
+TYPE_VF = "vf"
+TYPE_NFV = "nfv"
+
+
+def exec_command(func):
+    """Define the common function of sending command & receiving reply
+    as a decorator.
+    each method for executing command has only to return command string.
+    ex.
+    @exec_command
+    def some_command(self, ...):
+        return "command string of some_command"
+    """
+    def wrapper(self, *args, **kwargs):
+        with self.sem:
+            command = func(self, *args, **kwargs)
+            LOG.info("%s(%d) command executed: %s", self.type, self.id,
+                     command)
+            data = spp_ctl.Controller._send_command(self.conn, command)
+            if data is None:
+                raise RuntimeError("%s(%d): %s: no-data returned" %
+                                   (self.type, self.id, command))
+            LOG.debug("reply: %s", data)
+            return self._decode_reply(data)
+    return wrapper
+
+
+class SppProc(object):
+    def __init__(self, proc_type, id, conn):
+        self.id = id
+        self.type = proc_type
+        # NOTE: executing command is serialized by using a semaphore
+        # for each process.
+        self.sem = eventlet.semaphore.Semaphore(value=1)
+        self.conn = conn
+
+
+class VfProc(SppProc):
+
+    def __init__(self, id, conn):
+        super(VfProc, self).__init__(TYPE_VF, id, conn)
+
+    @staticmethod
+    def _decode_reply(data):
+        data = json.loads(data)
+        result = data["results"][0]
+        if result["result"] == "error":
+            msg = result["error_details"]["message"]
+            raise bottle.HTTPError(400, "command error: %s" % msg)
+        return data
+
+    @staticmethod
+    def _decode_client_id(data):
+        try:
+            data = VfProc._decode_reply(data)
+            return data["client_id"]
+        except:
+            return None
+
+    @exec_command
+    def get_status(self):
+        return "status"
+
+    @exec_command
+    def start_component(self, comp_name, core_id, comp_type):
+        return ("component start {comp_name} {core_id} {comp_type}"
+                .format(**locals()))
+
+    @exec_command
+    def stop_component(self, comp_name):
+        return "component stop {comp_name}".format(**locals())
+
+    @exec_command
+    def port_add(self, port, direction, comp_name, op, vlan_id, pcp):
+        command = "port add {port} {direction} {comp_name}".format(**locals())
+        if op != "none":
+            command += " %s" % op
+            if op == "add_vlantag":
+                command += " %d %d" % (vlan_id, pcp)
+        return command
+
+    @exec_command
+    def port_del(self, port, direction, comp_name):
+        return "port del {port} {direction} {comp_name}".format(**locals())
+
+    @exec_command
+    def set_classifier_table(self, mac_address, port):
+        return ("classifier_table add mac {mac_address} {port}"
+                .format(**locals()))
+
+    @exec_command
+    def clear_classifier_table(self, mac_address, port):
+        return ("classifier_table del mac {mac_address} {port}"
+                .format(**locals()))
+
+    @exec_command
+    def set_classifier_table_with_vlan(self, mac_address, port,
+                                       vlan_id):
+        return ("classifier_table add vlan {vlan_id} {mac_address} {port}"
+                .format(**locals()))
+
+    @exec_command
+    def clear_classifier_table_with_vlan(self, mac_address, port,
+                                         vlan_id):
+        return ("classifier_table del vlan {vlan_id} {mac_address} {port}"
+                .format(**locals()))
+
+
+class NfvProc(SppProc):
+
+    def __init__(self, id, conn):
+        super(NfvProc, self).__init__(TYPE_NFV, id, conn)
+
+    @staticmethod
+    def _decode_reply(data):
+        return data.strip('\0')
+
+    @staticmethod
+    def _decode_client_id(data):
+        try:
+            return int(NfvProc._decode_reply(data))
+        except:
+            return None
+
+    @exec_command
+    def get_status(self):
+        return "status"
+
+    @exec_command
+    def port_add(self, if_type, if_num):
+        return "add {if_type} {if_num}".format(**locals())
+
+    @exec_command
+    def port_del(self, if_type, if_num):
+        return "del {if_type} {if_num}".format(**locals())
+
+    @exec_command
+    def patch_add(self, src_port, dst_port):
+        return "patch {src_port} {dst_port}".format(**locals())
+
+    @exec_command
+    def patch_reset(self):
+        return "patch reset"
+
+    @exec_command
+    def forward(self):
+        return "forward"
+
+    @exec_command
+    def stop(self):
+        return "stop"
+
+
+class PrimaryProc(SppProc):
+
+    def __init__(self, conn):
+        super(PrimaryProc, self).__init__(TYPE_PRIMARY, ID_PRIMARY, conn)
+
+    @staticmethod
+    def _decode_reply(data):
+        return data.strip('\0')
+
+    @exec_command
+    def status(self):
+        return "status"
+
+    @exec_command
+    def clear(self):
+        return "clear"
-- 
2.17.0
-- 
Itsuro ODA <oda@valinux.co.jp>

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [spp] [PATCH v2 7/9] spp-ctl: SPP controller with Web API
  2018-09-23  2:36 ` [spp] [PATCH v2 7/9] spp-ctl: SPP controller with Web API Itsuro ODA
@ 2018-09-23  2:44   ` Itsuro ODA
  2018-09-25  2:01     ` Yasufumi Ogawa
  0 siblings, 1 reply; 17+ messages in thread
From: Itsuro ODA @ 2018-09-23  2:44 UTC (permalink / raw)
  To: spp

I wrote the subject incorrectly.
It should be "[PATCH v2 7/9] spp-ctl: controller"

On Sun, 23 Sep 2018 11:36:21 +0900
Itsuro ODA <oda@valinux.co.jp> wrote:

> From: Itsuro Oda <oda@valinux.co.jp>
> 
> Signed-off-by: Itsuro Oda <oda@valinux.co.jp>
> ---
>  src/spp-ctl/spp_ctl.py | 158 +++++++++++++++++++++++++++++++++++++++++
>  1 file changed, 158 insertions(+)
>  create mode 100644 src/spp-ctl/spp_ctl.py
> 
> diff --git a/src/spp-ctl/spp_ctl.py b/src/spp-ctl/spp_ctl.py
> new file mode 100644
> index 0000000..e168747
> --- /dev/null
> +++ b/src/spp-ctl/spp_ctl.py
> @@ -0,0 +1,158 @@
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright(c) 2018 Nippon Telegraph and Telephone Corporation
> +
> +import eventlet
> +eventlet.monkey_patch()
> +
> +import argparse
> +import errno
> +import json
> +import logging
> +import socket
> +import subprocess
> +
> +import spp_proc
> +import spp_webapi
> +
> +
> +LOG = logging.getLogger(__name__)
> +
> +
> +MSG_SIZE = 4096
> +
> +
> +class Controller(object):
> +
> +    def __init__(self, pri_port, sec_port, api_port):
> +        self.web_server = spp_webapi.WebServer(self, api_port)
> +        self.procs = {}
> +        self.init_connection(pri_port, sec_port)
> +
> +    def start(self):
> +        self.web_server.start()
> +
> +    def init_connection(self, pri_port, sec_port):
> +        self.pri_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
> +        self.pri_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
> +        self.pri_sock.bind(('127.0.0.1', pri_port))
> +        self.pri_sock.listen(1)
> +        self.primary_listen_thread = eventlet.greenthread.spawn(
> +            self.accept_primary)
> +
> +        self.sec_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
> +        self.sec_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
> +        self.sec_sock.bind(('127.0.0.1', sec_port))
> +        self.sec_sock.listen(1)
> +        self.secondary_listen_thread = eventlet.greenthread.spawn(
> +            self.accept_secondary)
> +
> +    def accept_primary(self):
> +        while True:
> +            conn, _ = self.pri_sock.accept()
> +            proc = self.procs.get(spp_proc.ID_PRIMARY)
> +            if proc is not None:
> +                LOG.warning("spp_primary reconnect !")
> +                with proc.sem:
> +                    try:
> +                        proc.conn.close()
> +                    except Exception:
> +                        pass
> +                    proc.conn = conn
> +                # NOTE: when spp_primary restart, all secondarys must be
> +                # restarted. this is out of controle of spp-ctl.
> +            else:
> +                LOG.info("primary connected.")
> +                self.procs[spp_proc.ID_PRIMARY] = spp_proc.PrimaryProc(conn)
> +
> +    def accept_secondary(self):
> +        while True:
> +            conn, _ = self.sec_sock.accept()
> +            LOG.debug("sec accepted: get process id")
> +            proc = self._get_proc(conn)
> +            if proc is None:
> +                LOG.error("get process id failed")
> +                conn.close()
> +                continue
> +            old_proc = self.procs.get(proc.id)
> +            if old_proc:
> +                LOG.warning("%s(%d) reconnect !", old_proc.type, old_proc.id)
> +                if old_proc.type != proc.type:
> +                    LOG.warning("type changed ! new type: %s", proc.type)
> +                with old_proc.sem:
> +                    try:
> +                        old_proc.conn.close()
> +                    except Exception:
> +                        pass
> +            else:
> +                LOG.info("%s(%d) connected.", proc.type, proc.id)
> +            self.procs[proc.id] = proc
> +
> +    @staticmethod
> +    def _continue_recv(conn):
> +        try:
> +            # must set non-blocking to recieve remining data not to happen
> +            # blocking here.
> +            # NOTE: usually MSG_DONTWAIT flag is used for this purpose but
> +            # this flag is not supported under eventlet.
> +            conn.setblocking(False)
> +            data = b""
> +            while True:
> +                try:
> +                    rcv_data = conn.recv(MSG_SIZE)
> +                    data += rcv_data
> +                    if len(rcv_data) < MSG_SIZE:
> +                        break
> +                except socket.error as e:
> +                    if e.args[0] == errno.EAGAIN:
> +                        # OK, no data remining. this happens when recieve data
> +                        # length is just multiple of MSG_SIZE.
> +                        break
> +                    raise e
> +            return data
> +        finally:
> +            conn.setblocking(True)
> +
> +    @staticmethod
> +    def _send_command(conn, command):
> +        conn.sendall(command.encode())
> +        data = conn.recv(MSG_SIZE)
> +        if data and len(data) == MSG_SIZE:
> +            # could not receive data at once. recieve remining data.
> +            data += self._continue_recv(conn)
> +        if data:
> +            data = data.decode()
> +        return data
> +
> +    def _get_proc(self, conn):
> +        # it is a bit ad hoc. send "_get_clinet_id" command and try to
> +        # decode reply for each proc type. if success, that is the type.
> +        data = self._send_command(conn, "_get_client_id")
> +        for proc in [spp_proc.VfProc, spp_proc.NfvProc]:
> +            sec_id = proc._decode_client_id(data)
> +            if sec_id is not None:
> +                return proc(sec_id, conn)
> +
> +    def get_processes(self):
> +        procs = []
> +        for proc in self.procs.values():
> +            p = {"type": proc.type}
> +            if proc.id != spp_proc.ID_PRIMARY:
> +                p["client-id"] = proc.id
> +            procs.append(p)
> +        return procs
> +
> +
> +def main():
> +    parser = argparse.ArgumentParser(description="SPP Controller")
> +    parser.add_argument("-p", dest='pri_port', type=int, default=5555,
> +                        action='store', help="primary port")
> +    parser.add_argument("-s", dest='sec_port', type=int, default=6666,
> +                        action='store', help="secondary port")
> +    parser.add_argument("-a", dest='api_port', type=int, default=7777,
> +                        action='store', help="web api port")
> +    args = parser.parse_args()
> +
> +    logging.basicConfig(level=logging.DEBUG)
> +
> +    controller = Controller(args.pri_port, args.sec_port, args.api_port)
> +    controller.start()
> -- 
> 2.17.0
> -- 
> Itsuro ODA <oda@valinux.co.jp>

-- 
Itsuro ODA <oda@valinux.co.jp>

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [spp] [PATCH v2 7/9] spp-ctl: SPP controller with Web API
  2018-09-23  2:44   ` Itsuro ODA
@ 2018-09-25  2:01     ` Yasufumi Ogawa
  0 siblings, 0 replies; 17+ messages in thread
From: Yasufumi Ogawa @ 2018-09-25  2:01 UTC (permalink / raw)
  To: Itsuro ODA, spp

> I wrote the subject incorrectly.
> It should be "[PATCH v2 7/9] spp-ctl: controller"
Hi,

No problem! I would like to correct it if there is no need to revise for updating to v3.

Thanks,
Yasufumi
> 
> On Sun, 23 Sep 2018 11:36:21 +0900
> Itsuro ODA <oda@valinux.co.jp> wrote:
> 
>> From: Itsuro Oda <oda@valinux.co.jp>
>>
>> Signed-off-by: Itsuro Oda <oda@valinux.co.jp>
>> ---
>>   src/spp-ctl/spp_ctl.py | 158 +++++++++++++++++++++++++++++++++++++++++
>>   1 file changed, 158 insertions(+)
>>   create mode 100644 src/spp-ctl/spp_ctl.py
>>
>> diff --git a/src/spp-ctl/spp_ctl.py b/src/spp-ctl/spp_ctl.py
>> new file mode 100644
>> index 0000000..e168747
>> --- /dev/null
>> +++ b/src/spp-ctl/spp_ctl.py
>> @@ -0,0 +1,158 @@
>> +# SPDX-License-Identifier: BSD-3-Clause
>> +# Copyright(c) 2018 Nippon Telegraph and Telephone Corporation
>> +
>> +import eventlet
>> +eventlet.monkey_patch()
>> +
>> +import argparse
>> +import errno
>> +import json
>> +import logging
>> +import socket
>> +import subprocess
>> +
>> +import spp_proc
>> +import spp_webapi
>> +
>> +
>> +LOG = logging.getLogger(__name__)
>> +
>> +
>> +MSG_SIZE = 4096
>> +
>> +
>> +class Controller(object):
>> +
>> +    def __init__(self, pri_port, sec_port, api_port):
>> +        self.web_server = spp_webapi.WebServer(self, api_port)
>> +        self.procs = {}
>> +        self.init_connection(pri_port, sec_port)
>> +
>> +    def start(self):
>> +        self.web_server.start()
>> +
>> +    def init_connection(self, pri_port, sec_port):
>> +        self.pri_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>> +        self.pri_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
>> +        self.pri_sock.bind(('127.0.0.1', pri_port))
>> +        self.pri_sock.listen(1)
>> +        self.primary_listen_thread = eventlet.greenthread.spawn(
>> +            self.accept_primary)
>> +
>> +        self.sec_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
>> +        self.sec_sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
>> +        self.sec_sock.bind(('127.0.0.1', sec_port))
>> +        self.sec_sock.listen(1)
>> +        self.secondary_listen_thread = eventlet.greenthread.spawn(
>> +            self.accept_secondary)
>> +
>> +    def accept_primary(self):
>> +        while True:
>> +            conn, _ = self.pri_sock.accept()
>> +            proc = self.procs.get(spp_proc.ID_PRIMARY)
>> +            if proc is not None:
>> +                LOG.warning("spp_primary reconnect !")
>> +                with proc.sem:
>> +                    try:
>> +                        proc.conn.close()
>> +                    except Exception:
>> +                        pass
>> +                    proc.conn = conn
>> +                # NOTE: when spp_primary restart, all secondarys must be
>> +                # restarted. this is out of controle of spp-ctl.
>> +            else:
>> +                LOG.info("primary connected.")
>> +                self.procs[spp_proc.ID_PRIMARY] = spp_proc.PrimaryProc(conn)
>> +
>> +    def accept_secondary(self):
>> +        while True:
>> +            conn, _ = self.sec_sock.accept()
>> +            LOG.debug("sec accepted: get process id")
>> +            proc = self._get_proc(conn)
>> +            if proc is None:
>> +                LOG.error("get process id failed")
>> +                conn.close()
>> +                continue
>> +            old_proc = self.procs.get(proc.id)
>> +            if old_proc:
>> +                LOG.warning("%s(%d) reconnect !", old_proc.type, old_proc.id)
>> +                if old_proc.type != proc.type:
>> +                    LOG.warning("type changed ! new type: %s", proc.type)
>> +                with old_proc.sem:
>> +                    try:
>> +                        old_proc.conn.close()
>> +                    except Exception:
>> +                        pass
>> +            else:
>> +                LOG.info("%s(%d) connected.", proc.type, proc.id)
>> +            self.procs[proc.id] = proc
>> +
>> +    @staticmethod
>> +    def _continue_recv(conn):
>> +        try:
>> +            # must set non-blocking to recieve remining data not to happen
>> +            # blocking here.
>> +            # NOTE: usually MSG_DONTWAIT flag is used for this purpose but
>> +            # this flag is not supported under eventlet.
>> +            conn.setblocking(False)
>> +            data = b""
>> +            while True:
>> +                try:
>> +                    rcv_data = conn.recv(MSG_SIZE)
>> +                    data += rcv_data
>> +                    if len(rcv_data) < MSG_SIZE:
>> +                        break
>> +                except socket.error as e:
>> +                    if e.args[0] == errno.EAGAIN:
>> +                        # OK, no data remining. this happens when recieve data
>> +                        # length is just multiple of MSG_SIZE.
>> +                        break
>> +                    raise e
>> +            return data
>> +        finally:
>> +            conn.setblocking(True)
>> +
>> +    @staticmethod
>> +    def _send_command(conn, command):
>> +        conn.sendall(command.encode())
>> +        data = conn.recv(MSG_SIZE)
>> +        if data and len(data) == MSG_SIZE:
>> +            # could not receive data at once. recieve remining data.
>> +            data += self._continue_recv(conn)
>> +        if data:
>> +            data = data.decode()
>> +        return data
>> +
>> +    def _get_proc(self, conn):
>> +        # it is a bit ad hoc. send "_get_clinet_id" command and try to
>> +        # decode reply for each proc type. if success, that is the type.
>> +        data = self._send_command(conn, "_get_client_id")
>> +        for proc in [spp_proc.VfProc, spp_proc.NfvProc]:
>> +            sec_id = proc._decode_client_id(data)
>> +            if sec_id is not None:
>> +                return proc(sec_id, conn)
>> +
>> +    def get_processes(self):
>> +        procs = []
>> +        for proc in self.procs.values():
>> +            p = {"type": proc.type}
>> +            if proc.id != spp_proc.ID_PRIMARY:
>> +                p["client-id"] = proc.id
>> +            procs.append(p)
>> +        return procs
>> +
>> +
>> +def main():
>> +    parser = argparse.ArgumentParser(description="SPP Controller")
>> +    parser.add_argument("-p", dest='pri_port', type=int, default=5555,
>> +                        action='store', help="primary port")
>> +    parser.add_argument("-s", dest='sec_port', type=int, default=6666,
>> +                        action='store', help="secondary port")
>> +    parser.add_argument("-a", dest='api_port', type=int, default=7777,
>> +                        action='store', help="web api port")
>> +    args = parser.parse_args()
>> +
>> +    logging.basicConfig(level=logging.DEBUG)
>> +
>> +    controller = Controller(args.pri_port, args.sec_port, args.api_port)
>> +    controller.start()
>> -- 
>> 2.17.0
>> -- 
>> Itsuro ODA <oda@valinux.co.jp>
>

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [spp] [PATCH v2 1/9] docs: overview
  2018-09-23  2:25 ` [spp] [PATCH v2 1/9] docs: overview Itsuro ODA
@ 2018-10-02  3:29   ` Yasufumi Ogawa
  0 siblings, 0 replies; 17+ messages in thread
From: Yasufumi Ogawa @ 2018-10-02  3:29 UTC (permalink / raw)
  To: Itsuro ODA, spp

On 2018/09/23 11:25, Itsuro ODA wrote:
> From: Itsuro Oda <oda at valinux.co.jp>
> 
Hi,

Could you consider to update this patch for the listed below.

* Add license and copyright holder at the top of the file.

* Correct description of link to the API reference is not incorrect and does not work. Here is a right example.
For more details, see
:ref:`API Reference<spp_ctl_api_ref>`.

* Update "code-block" directive to specify the type of codes.
.. code-block:: console
.. code-block:: json
etc.

Thanks,
Yasufumi

> Signed-off-by: Itsuro Oda <oda@valinux.co.jp>
> ---
>   docs/guides/spp-ctl/overview.rst | 102 +++++++++++++++++++++++++++++++
>   1 file changed, 102 insertions(+)
>   create mode 100644 docs/guides/spp-ctl/overview.rst
> 
> diff --git a/docs/guides/spp-ctl/overview.rst b/docs/guides/spp-ctl/overview.rst
> new file mode 100644
> index 0000000..847b9dc
> --- /dev/null
> +++ b/docs/guides/spp-ctl/overview.rst
> @@ -0,0 +1,102 @@
> +====================================
> +spp-ctl: SPP controller with Web API
> +====================================
> +
> +Overview
> +========
> +
> +spp-ctl is a SPP controller with a REST like web API.
> +
> +spp-ctl maintains the connections from the SPP processes and at the same
> +time exposes the API for the user to request for the SPP processes.
> +
> +Background and motivation
> +-------------------------
> +
> +Current CLI (spp.py/spp_vf.py) can be used by intaractive only.
> +Therefore, spp-agent, a component of networking-spp which make SPP
> +available on OpenStack environment, implements SPP controller in
> +itself. (see. https://github.com/openstack/networking-spp )
> +
> +Either CLI or spp-agent, there is a problem that other people can not
> +request to SPP processes while using. spp-ctl is invented to solve this
> +problem.
> +
> +Both CLI and spp-agent can be used spp-ctl to request SPP processes
> +instead of owning contoroller itself. In that case, multiple people
> +can request to SPP processes at the same time.
> +Note that spp-agent has a plan to change to use spp-ctl.
> +It is also available not using CLI but requesting spp-ctl directly.
> +
> +Architecture
> +------------
> +
> +The design goal of spp-ctl is to be as simple as possible.
> +It is stateless. Basically, spp-ctl only converts API requests into
> +commands of SPP processes and throws request, thouth it does syntax and
> +lexical check for API requests.
> +
> +spp-ctl adopts bottle (it is simple and well known) as a web framework
> +and eventlet for parallel processing. spp-ctl can process multiple APIs
> +at the same time, however, requests for per SPP process are serialized
> +internally.
> +
> +
> +Setup
> +=====
> +
> +spp-ctl is a simple program written in python3. Installation of related
> +packages is as follows (assume ubuntu).
> +
> +::
> +
> +  $ sudo apt update
> +  $ sudo apt install python3
> +  $ sudo apt install python3-pip
> +  $ sudo pip3 install -r requirements.txt
> +
> +Usage
> +-----
> +
> +::
> +
> +  usage: spp-ctl [-p PRI_PORT] [-s SEC_PORT] [-a API_PORT]
> +
> +  optional arguments:
> +    -p PRI_PORT  primary port. default is 5555.
> +    -s SEC_PORT  secondary port. default is 6666.
> +    -a API_PORT  web api port. default is 7777.
> +
> +Using systemd
> +-------------
> +
> +Although spp-ctl runs as a daemon process normaly, it assumes to the
> +use of systemd and does not daemonize itself.
> +
> +The service file for systemd is simple as shown below::
> +
> +  [Unit]
> +  Description = SPP Controller
> +
> +  [Service]
> +  ExecStart = {SPP install path}/spp_ctl/spp-ctl -p 5555 -s 6666 -a 7777
> +  User = root
> +
> +API Usage
> +=========
> +
> +For API details, see API-reference_.
> +
> +.. _API-reference: ./api-reference.rst
> +
> +Since spp-ctl provides the web API, for example, you can use curl to execute
> +requests as follows::
> +
> +  $ curl http://localhost:7777/v1/processes
> +  [{"type": "primary"}, {"client-id": 1, "type": "vf"}, {"client-id": 2, "type": "vf"}]
> +  $ curl http://localhost:7777/v1/vfs/1
> +  ... snip
> +  $ curl -X POST http://localhost:7777/v1/vfs/1/components \
> +    -d '{"core": 2, "name": "forward_0_tx", "type": "forward"}'
> +  $
> +
> 


-- 
Yasufumi Ogawa
NTT Network Service Systems Labs

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [spp] [PATCH v2 2/9] docs: api reference
  2018-09-23  2:28 ` [spp] [PATCH v2 2/9] docs: api reference Itsuro ODA
@ 2018-10-02  3:42   ` Yasufumi Ogawa
  2018-10-02  4:10     ` Yasufumi Ogawa
  0 siblings, 1 reply; 17+ messages in thread
From: Yasufumi Ogawa @ 2018-10-02  3:42 UTC (permalink / raw)
  To: Itsuro ODA, spp

On 2018/09/23 11:28, Itsuro ODA wrote:
> From: Itsuro Oda <oda@valinux.co.jp>
> 
This API reference is well defined. However, some of descriptions are not applied with our documentation guidelines.

* Some of section headers are different from the guideline [1].

* All of tables should have a label and a caption, and the format of table is not correct [2].

* It is better to add examples of request with curl command.

You also need to add license and copyright holder at the top of the file.

[1] https://doc.dpdk.org/guides/contributing/documentation.html#section-headers
[2] https://doc.dpdk.org/guides/contributing/documentation.html#tables

Thanks,
> Signed-off-by: Itsuro Oda <oda@valinux.co.jp>
> ---
>   docs/guides/spp-ctl/api-reference.rst | 790 ++++++++++++++++++++++++++
>   1 file changed, 790 insertions(+)
>   create mode 100644 docs/guides/spp-ctl/api-reference.rst
> 
> diff --git a/docs/guides/spp-ctl/api-reference.rst b/docs/guides/spp-ctl/api-reference.rst
> new file mode 100644
> index 0000000..b8bf488
> --- /dev/null
> +++ b/docs/guides/spp-ctl/api-reference.rst
> @@ -0,0 +1,790 @@
> +=============
> +API Reference
> +=============
> +
> +Overview
> +========
> +
> +Spp-ctl provides simple REST like API. It supports http only, not https.
> +
> +Request and Response
> +--------------------
> +
> +Request body is json format. It is accepted both "text/plain" and "application/json" for the content-type header.
> +
> +Response of GET is json format and the content-type is "application/json" if the request success.
> +
> +If a request fails, the response is a text which shows error reason and the content-type is "text/plain".
> +
> +Error code
> +----------
> +
> +Spp-ctl does basic syntax and lexical check of a request.
> +
> ++-------+------------------------------------------------------------------------------+
> +| Error | Description                                                                  |
> ++=======+==============================================================================+
> +| 400   | Syntax or lexical error of a request.                                        |
> +|       | Or an SPP process returns error for the command correspondings to a request. |
> ++-------+------------------------------------------------------------------------------+
> +| 404   | URL is not supported for spp-ctl.                                            |
> +|       | Or there is no SPP process of client-id in a URL.                            |
> ++-------+------------------------------------------------------------------------------+
> +| 500   | A system error occurs in the spp-ctl.                                        |
> +|       | ex. communication error between an SPP processes.                            |
> ++-------+------------------------------------------------------------------------------+
> +
> +
> +API independent of the process type
> +===================================
> +
> +GET /v1/processes
> +-----------------
> +
> +Show the SPP processes connected to the spp-ctl.
> +
> +Normarl response codes: 200
> +
> +Response
> +^^^^^^^^
> +
> +An array of process objects.
> +
> +process object:
> +
> ++-----------+---------+-------------------------------------------------------------+
> +| Name      | Type    | Description                                                 |
> ++===========+=========+=============================================================+
> +| type      | string  | process type. one of "primary", "vf" or "nfv".              |
> ++-----------+---------+-------------------------------------------------------------+
> +| client-id | integer | client id. if type is "primary" this member does not exist. |
> ++-----------+---------+-------------------------------------------------------------+
> +
> +Response example
> +^^^^^^^^^^^^^^^^
> +
> +::
> +
> +[{"type": "primary"}, {"type": "vf", "client-id": 1}, {"type": "nfv", "client-id": 2}]
> +
> +
> +API for spp_vf
> +==============
> +
> +GET /v1/vfs/{client_id}
> +-----------------------
> +
> +Get the information of the spp_vf process.
> +
> +Normal response codes: 200
> +
> +Error response codes: 400, 404
> +
> +Request(path)
> +^^^^^^^^^^^^^
> +
> ++-----------+---------+-------------------------------------------------------------+
> +| Name      | Type    | Description                                                 |
> ++===========+=========+=============================================================+
> +| client_id | integer | client id.                                                  |
> ++-----------+---------+-------------------------------------------------------------+
> +
> +Response
> +^^^^^^^^
> +
> ++------------------+---------+-------------------------------------------------------------+
> +| Name             | Type    | Description                                                 |
> ++==================+=========+=============================================================+
> +| client-id        | integer | client id.                                                  |
> ++------------------+---------+-------------------------------------------------------------+
> +| ports            | array   | an array of port ids used by the process.                   |
> ++------------------+---------+-------------------------------------------------------------+
> +| components       | array   | an array of component objects in the process.               |
> ++------------------+---------+-------------------------------------------------------------+
> +| classifier_table | array   | an array of classifier tables in the process.               |
> ++------------------+---------+-------------------------------------------------------------+
> +
> +component object:
> +
> ++---------+---------+---------------------------------------------------------------------+
> +| Name    | Type    | Description                                                         |
> ++=========+=========+=====================================================================+
> +| core    | integer | core id running on the component                                    |
> ++---------+---------+---------------------------------------------------------------------+
> +| name    | string  | an array of port ids used by the process.                           |
> ++---------+---------+---------------------------------------------------------------------+
> +| type    | string  | an array of component objects in the process.                       |
> ++---------+---------+---------------------------------------------------------------------+
> +| rx_port | array   | an array of port objects connected to the rx side of the component. |
> ++---------+---------+---------------------------------------------------------------------+
> +| tx_port | array   | an array of port objects connected to the tx side of the component. |
> ++---------+---------+---------------------------------------------------------------------+
> +
> +port object:
> +
> ++---------+---------+---------------------------------------------------------------------+
> +| Name    | Type    | Description                                                         |
> ++=========+=========+=====================================================================+
> +| port    | string  | port id. port id is the form {interface_type}:{interface_id}.       |
> ++---------+---------+---------------------------------------------------------------------+
> +| vlan    | object  | vlan operation which is applied to the port.                        |
> ++---------+---------+---------------------------------------------------------------------+
> +
> +vlan object:
> +
> ++-----------+---------+-------------------------------------------------------------------+
> +| Name      | Type    | Description                                                       |
> ++===========+=========+===================================================================+
> +| operation | string  | "add", "del" or "none".                                           |
> ++-----------+---------+-------------------------------------------------------------------+
> +| id        | integer | vlan id.                                                          |
> ++-----------+---------+-------------------------------------------------------------------+
> +| pcp       | integer | vlan pcp.                                                         |
> ++-----------+---------+-------------------------------------------------------------------+
> +
> +classifier table:
> +
> ++-----------+---------+----------------------------------------------------------------------+
> +| Name      | Type    | Description                                                          |
> ++===========+=========+======================================================================+
> +| type      | string  | "mac" or "vlan".                                                     |
> ++-----------+---------+----------------------------------------------------------------------+
> +| value     | string  | "mac address" for "mac" type. "vlan id/mac address" for "vlan" type. |
> ++-----------+---------+----------------------------------------------------------------------+
> +| port      | string  | port id applied to classify.                                         |
> ++-----------+---------+----------------------------------------------------------------------+
> +
> +Response example
> +^^^^^^^^^^^^^^^^
> +
> +::
> +
> +{
> +"client-id": 1,
> +"ports": ["phy:0", "phy:1", "vhost:0", "vhost:1", "ring:0", "ring:1", "ring:2", "ring:3"],
> +"components": [
> +{
> +"core": 2,
> +"name": "forward_0_tx",
> +"type": "forward",
> +"rx_port": [
> +{
> +"port": "ring:0",
> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
> +}
> +],
> +"tx_port": [
> +{
> +"port": "vhost:0",
> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
> +}
> +]
> +},
> +{
> +"core": 3,
> +"type": "unuse"
> +},
> +{
> +"core": 4,
> +"type": "unuse"
> +},
> +{
> +"core": 5,
> +"name": "forward_1_rx",
> +"type": "forward",
> +"rx_port": [
> +{
> +"port": "vhost:1",
> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
> +}
> +],
> +"tx_port": [
> +{
> +"port": "ring:3",
> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
> +}
> +]
> +},
> +{
> +"core": 6,
> +"name": "classifier",
> +"type": "classifier_mac",
> +"rx_port": [
> +{
> +"port": "phy:0",
> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
> +}
> +],
> +"tx_port": [
> +{
> +"port": "ring:0",
> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
> +},
> +{
> +"port": "ring:2",
> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
> +}
> +]
> +},
> +{
> +"core": 7,
> +"name": "merger",
> +"type": "merge",
> +"rx_port": [
> +{
> +"port": "ring:1",
> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
> +},
> +{
> +"port": "ring:3",
> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
> +}
> +],
> +"tx_port": [
> +{
> +"port": "phy:0",
> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
> +}
> +]
> +}
> +},
> +"classifier_table": [
> +{
> +"type": "mac",
> +"value": "FA:16:3E:7D:CC:35",
> +"port": "ring:0"
> +}
> +]
> +}
> +
> +Note: The component which type is "unused" is to indicate unused core.
> +
> +Equivalent CLI command
> +^^^^^^^^^^^^^^^^^^^^^^
> +
> +::
> +
> +sec {client_id};status
> +
> +
> +POST /v1/vfs/{client_id}/components
> +-----------------------------------
> +
> +Start the component.
> +
> +Normal response codes: 204
> +
> +Error response codes: 400, 404
> +
> +Request(path)
> +^^^^^^^^^^^^^
> +
> ++-----------+---------+-------------------------------------------------------------+
> +| Name      | Type    | Description                                                 |
> ++===========+=========+=============================================================+
> +| client_id | integer | client id.                                                  |
> ++-----------+---------+-------------------------------------------------------------+
> +
> +
> +Request(body)
> +^^^^^^^^^^^^^
> +
> ++-----------+---------+----------------------------------------------------------------+
> +| Name      | Type    | Description                                                    |
> ++===========+=========+================================================================+
> +| name      | string  | component name. must be unique in the process.                 |
> ++-----------+---------+----------------------------------------------------------------+
> +| core      | integer | core id.                                                       |
> ++-----------+---------+----------------------------------------------------------------+
> +| type      | string  | component type. one of "forward", "merge" or "classifier_mac". |
> ++-----------+---------+----------------------------------------------------------------+
> +
> +Request example
> +^^^^^^^^^^^^^^^
> +::
> +
> +{"name": "forwarder1", "core": 12, "type": "forward"}
> +
> +Response
> +^^^^^^^^
> +
> +There is no body content for the response of a successful POST request.
> +
> +Equivalent CLI command
> +^^^^^^^^^^^^^^^^^^^^^^
> +
> +::
> +
> +sec {client_id};component start {name} {core} {type}
> +
> +
> +DELETE /v1/vfs/{sec id}/components/{name}
> +-----------------------------------------
> +
> +Stop the component.
> +
> +Normal response codes: 204
> +
> +Error response codes: 400, 404
> +
> +Request(path)
> +^^^^^^^^^^^^^
> +
> ++-----------+---------+-------------------------------------------------------------+
> +| Name      | Type    | Description                                                 |
> ++===========+=========+=============================================================+
> +| client_id | integer | client id.                                                  |
> ++-----------+---------+-------------------------------------------------------------+
> +| name      | string  | component name.                                             |
> ++-----------+---------+-------------------------------------------------------------+
> +
> +Response
> +^^^^^^^^
> +
> +There is no body content for the response of a successful POST request.
> +
> +Equivalent CLI command
> +^^^^^^^^^^^^^^^^^^^^^^
> +
> +::
> +
> +sec {client_id};component stop {name}
> +
> +
> +PUT /v1/vfs/{client_id}/components/{name}/ports
> +-----------------------------------------------
> +
> +Add or Delete port to the component.
> +
> +Normal response codes: 204
> +
> +Error response codes: 400, 404
> +
> +Request(path)
> +^^^^^^^^^^^^^
> +
> ++-----------+---------+-------------------------------------------------------------+
> +| Name      | Type    | Description                                                 |
> ++===========+=========+=============================================================+
> +| client_id | integer | client id.                                                  |
> ++-----------+---------+-------------------------------------------------------------+
> +| name      | string  | component name.                                             |
> ++-----------+---------+-------------------------------------------------------------+
> +
> +Request(body)
> +^^^^^^^^^^^^^
> +
> ++---------+---------+---------------------------------------------------------------------+
> +| Name    | Type    | Description                                                         |
> ++=========+=========+=====================================================================+
> +| action  | string  | "attach" or "detach".                                               |
> ++---------+---------+---------------------------------------------------------------------+
> +| port    | string  | port id. port id is the form {interface_type}:{interface_id}.       |
> ++---------+---------+---------------------------------------------------------------------+
> +| dir     | string  | "rx" or "tx".                                                       |
> ++---------+---------+---------------------------------------------------------------------+
> +| vlan    | object  | vlan operation which is applied to the port. it can be omitted.     |
> ++---------+---------+---------------------------------------------------------------------+
> +
> +vlan object:
> +
> ++-----------+---------+-------------------------------------------------------------------+
> +| Name      | Type    | Description                                                       |
> ++===========+=========+===================================================================+
> +| operation | string  | "add", "del" or "none".                                           |
> ++-----------+---------+-------------------------------------------------------------------+
> +| id        | integer | vlan id. ignored when operation is "del" or "none".               |
> ++-----------+---------+-------------------------------------------------------------------+
> +| pcp       | integer | vlan pcp. ignored when operation is "del" or "none".              |
> ++-----------+---------+-------------------------------------------------------------------+
> +
> +Request example
> +^^^^^^^^^^^^^^^
> +
> +::
> +
> +{"action": "attach", "port": "vhost:1", "dir": "rx",
> +"vlan": {"operation": "add", "id": 677, "pcp": 0}
> +}
> +
> +::
> +
> +{"action": "detach", "port": "vhost:0", "dir": "tx"}
> +
> +
> +Response
> +^^^^^^^^
> +
> +There is no body content for the response of a successful PUT request.
> +
> +
> +Equivalent CLI command
> +^^^^^^^^^^^^^^^^^^^^^^
> +action is "attach"
> +
> +::
> +
> +sec {client_id};port add {port} {dir} {name} [add_vlantag {id} {pcp} | del_vlantag]
> +
> +action is "detach"
> +
> +::
> +
> +sec {client_id};port del {port} {dir} {name}
> +
> +
> +PUT /v1/vfs/{sec id}/classifier_table
> +-------------------------------------
> +
> +Set or Unset classifier table.
> +
> +Normal response codes: 204
> +
> +Error response codes: 400, 404
> +
> +Request(path)
> +^^^^^^^^^^^^^
> +
> ++-----------+---------+-------------------------------------------------------------+
> +| Name      | Type    | Description                                                 |
> ++===========+=========+=============================================================+
> +| client_id | integer | client id.                                                  |
> ++-----------+---------+-------------------------------------------------------------+
> +
> +Request(body)
> +^^^^^^^^^^^^^
> +
> ++-------------+-----------------+-------------------------------------------------------------------+
> +| Name        | Type            | Description                                                       |
> ++=============+=================+===================================================================+
> +| action      | string          | "add" or "del".                                                   |
> ++-------------+-----------------+-------------------------------------------------------------------+
> +| type        | string          | "mac" or "vlan".                                                  |
> ++-------------+-----------------+-------------------------------------------------------------------+
> +| vlan        | integer or null | vlan id when type is "vlan. null or omitted when type is "mac".   |
> ++-------------+-----------------+-------------------------------------------------------------------+
> +| mac_address | string          | mac address.                                                      |
> ++-------------+-----------------+-------------------------------------------------------------------+
> +| port        | string          | port id.                                                          |
> ++-------------+-----------------+-------------------------------------------------------------------+
> +
> +Request example
> +^^^^^^^^^^^^^^^
> +
> +::
> +
> +{"action": "add", "type": "mac",
> +"mac_address": "FA:16:3E:7D:CC:35", "port": "ring:0"
> +}
> +
> +::
> +
> +{"action": "del", "type": "vlan", "vlan": 475,
> +"mac_address": "FA:16:3E:7D:CC:35", "port": "ring:0"
> +}
> +
> +
> +Response
> +^^^^^^^^
> +
> +There is no body content for the response of a successful PUT request.
> +
> +Equivalent CLI command
> +^^^^^^^^^^^^^^^^^^^^^^
> +
> +type is "mac"
> +
> +::
> +
> +classifier_table {action} mac {mac_address} {port}
> +
> +type is "vlan"
> +
> +::
> +
> +classifier_table {action} vlan {vlan} {mac_address} {port}
> +
> +
> +API for spp_nfv/spp_vm
> +======================
> +
> +GET /v1/nfvs/{client_id}
> +------------------------
> +
> +Get the information of the spp_nfv/spp_vm process.
> +
> +Normal response codes: 200
> +
> +Error response codes: 400, 404
> +
> +Request(path)
> +^^^^^^^^^^^^^
> +
> ++-----------+---------+-------------------------------------------------------------+
> +| Name      | Type    | Description                                                 |
> ++===========+=========+=============================================================+
> +| client_id | integer | client id.                                                  |
> ++-----------+---------+-------------------------------------------------------------+
> +
> +Response
> +^^^^^^^^
> +
> ++------------------+---------+-------------------------------------------------------------+
> +| Name             | Type    | Description                                                 |
> ++==================+=========+=============================================================+
> +| client-id        | integer | client id.                                                  |
> ++------------------+---------+-------------------------------------------------------------+
> +| status           | string  | "Running" or "Idle".                                        |
> ++------------------+---------+-------------------------------------------------------------+
> +| ports            | array   | an array of port ids used by the process.                   |
> ++------------------+---------+-------------------------------------------------------------+
> +| patches          | array   | an array of patches.                                        |
> ++------------------+---------+-------------------------------------------------------------+
> +
> +patch objest
> +
> ++----------+---------+-------------------------------------------------------------+
> +| Name     | Type    | Description                                                 |
> ++==========+=========+=============================================================+
> +| src      | string  | source port id.                                             |
> ++----------+---------+-------------------------------------------------------------+
> +| dst      | string  | destination port id.                                        |
> ++----------+---------+-------------------------------------------------------------+
> +
> +Response example
> +^^^^^^^^^^^^^^^^
> +
> +::
> +
> +{
> +"client-id": 1,
> +"status": "Running",
> +"ports": ["phy:0", "phy:1", "vhost:0", "vhost:1", "ring:0", "ring:1", "ring:2", "ring:3"],
> +"patches": [
> +{"src": "vhost:0", "dst": "ring:0"},
> +{"src": "ring:1", "dst": "vhost:1"}
> +]
> +}
> +
> +Equivalent CLI command
> +^^^^^^^^^^^^^^^^^^^^^^
> +
> +::
> +
> +sec {client_id};status
> +
> +
> +PUT /v1/nfvs/{client_id}/forward
> +--------------------------------
> +
> +Start or Stop forwarding.
> +
> +Normal response codes: 204
> +
> +Error response codes: 400, 404
> +
> +Request(path)
> +^^^^^^^^^^^^^
> +
> ++-----------+---------+-------------------------------------------------------------+
> +| Name      | Type    | Description                                                 |
> ++===========+=========+=============================================================+
> +| client_id | integer | client id.                                                  |
> ++-----------+---------+-------------------------------------------------------------+
> +
> +Request(body)
> +^^^^^^^^^^^^^
> +
> ++-----------+---------+-------------------------------------------------------------+
> +| Name      | Type    | Description                                                 |
> ++===========+=========+=============================================================+
> +| action    | string  | "start" or "stop".                                          |
> ++-----------+---------+-------------------------------------------------------------+
> +
> +Request example
> +^^^^^^^^^^^^^^^
> +
> +::
> +
> +{"action": "start"}
> +
> +Response
> +^^^^^^^^
> +
> +There is no body content for the response of a successful PUT request.
> +
> +Equivalent CLI command
> +^^^^^^^^^^^^^^^^^^^^^^
> +
> +action is "start"
> +
> +::
> +
> +sec {client_id};forward
> +
> +action is "stop"
> +
> +::
> +
> +sec {client_id};stop
> +
> +
> +PUT /v1/nfvs/{client_id}/ports
> +------------------------------
> +
> +Add or Delete port.
> +
> +Normal response codes: 204
> +
> +Error response codes: 400, 404
> +
> +Request(path)
> +^^^^^^^^^^^^^
> +
> ++-----------+---------+-------------------------------------------------------------+
> +| Name      | Type    | Description                                                 |
> ++===========+=========+=============================================================+
> +| client_id | integer | client id.                                                  |
> ++-----------+---------+-------------------------------------------------------------+
> +
> +Request(body)
> +^^^^^^^^^^^^^
> +
> ++-----------+---------+---------------------------------------------------------------+
> +| Name      | Type    | Description                                                   |
> ++===========+=========+===============================================================+
> +| action    | string  | "add" or "del".                                               |
> ++-----------+---------+---------------------------------------------------------------+
> +| port      | string  | port id. port id is the form {interface_type}:{interface_id}. |
> ++-----------+---------+---------------------------------------------------------------+
> +
> +Request example
> +^^^^^^^^^^^^^^^
> +
> +::
> +
> +{"action": "add", "port": "vhost:0"}
> +
> +
> +Response
> +^^^^^^^^
> +
> +There is no body content for the response of a successful PUT request.
> +
> +Equivalent CLI command
> +^^^^^^^^^^^^^^^^^^^^^^
> +
> +::
> +
> +sec {client_id};{action} {interface_type} {interface_id}
> +
> +
> +PUT /v1/nfvs/{client_id}/patches
> +--------------------------------
> +
> +Add a patch.
> +
> +Normal response codes: 204
> +
> +Error response codes: 400, 404
> +
> +Request(path)
> +^^^^^^^^^^^^^
> +
> ++-----------+---------+-------------------------------------------------------------+
> +| Name      | Type    | Description                                                 |
> ++===========+=========+=============================================================+
> +| client_id | integer | client id.                                                  |
> ++-----------+---------+-------------------------------------------------------------+
> +
> +Request(body)
> +^^^^^^^^^^^^^
> +
> ++----------+---------+-------------------------------------------------------------+
> +| Name     | Type    | Description                                                 |
> ++==========+=========+=============================================================+
> +| src      | string  | source port id.                                             |
> ++----------+---------+-------------------------------------------------------------+
> +| dst      | string  | destination port id.                                        |
> ++----------+---------+-------------------------------------------------------------+
> +
> +Request example
> +^^^^^^^^^^^^^^^
> +
> +::
> +
> +{"src": "vhost:0", "dst": "ring:0"}
> +
> +Response
> +^^^^^^^^
> +
> +There is no body content for the response of a successful PUT request.
> +
> +Equivalent CLI command
> +^^^^^^^^^^^^^^^^^^^^^^
> +
> +::
> +
> +sec {client_id};patch {src} {dst}
> +
> +
> +DELETE /v1/nfvs/{client_id}/patches
> +-----------------------------------
> +
> +Reset patches.
> +
> +Normal response codes: 204
> +
> +Error response codes: 400, 404
> +
> +Request(path)
> +^^^^^^^^^^^^^
> +
> ++-----------+---------+-------------------------------------------------------------+
> +| Name      | Type    | Description                                                 |
> ++===========+=========+=============================================================+
> +| client_id | integer | client id.                                                  |
> ++-----------+---------+-------------------------------------------------------------+
> +
> +Response
> +^^^^^^^^
> +
> +There is no body content for the response of a successful DELETE request.
> +
> +Equivalent CLI command
> +^^^^^^^^^^^^^^^^^^^^^^
> +
> +::
> +
> +sec {client_id};patch reset
> +
> +
> +API for spp_primary
> +===================
> +
> +GET /v1/primary/status
> +----------------------
> +
> +Show statistical information.
> +
> +Normal response codes: 200
> +
> +Response
> +^^^^^^^^
> +
> +There is no data at the moment. The statistical information will be returned when spp_primary
> +implements it.
> +
> +
> +DELETE /v1/primary/status
> +-------------------------
> +
> +Clear statistical information.
> +
> +Normal response codes: 204
> +
> +Response
> +^^^^^^^^
> +
> +There is no body content for the response of a successful PUT request.
> +
> 


-- 
Yasufumi Ogawa
NTT Network Service Systems Labs

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [spp] [PATCH v2 8/9] spp-ctl: web api handler
  2018-09-23  2:38 ` [spp] [PATCH v2 8/9] spp-ctl: web api handler Itsuro ODA
@ 2018-10-02  4:03   ` Yasufumi Ogawa
  0 siblings, 0 replies; 17+ messages in thread
From: Yasufumi Ogawa @ 2018-10-02  4:03 UTC (permalink / raw)
  To: Itsuro ODA, spp

> From: Itsuro Oda <oda@valinux.co.jp>
> 
It is a great work to enable client processes to communicate with SPP processes via REST APIs. It could be useful for users!

However, it does not work only for "GET /v1/nfvs/{client_id}" because the expected format of response is old and cannot parse. 
It should be updated for the latest format.

Thanks,
> Signed-off-by: Itsuro Oda <oda@valinux.co.jp>
> ---
>   src/spp-ctl/spp_webapi.py | 440 ++++++++++++++++++++++++++++++++++++++
>   1 file changed, 440 insertions(+)
>   create mode 100644 src/spp-ctl/spp_webapi.py
> 
> diff --git a/src/spp-ctl/spp_webapi.py b/src/spp-ctl/spp_webapi.py
> new file mode 100644
> index 0000000..3ee7893
> --- /dev/null
> +++ b/src/spp-ctl/spp_webapi.py
> @@ -0,0 +1,440 @@
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright(c) 2018 Nippon Telegraph and Telephone Corporation
> +
> +import bottle
> +import errno
> +import json
> +import logging
> +import netaddr
> +import re
> +import socket
> +import subprocess
> +import sys
> +
> +import spp_proc
> +
> +
> +LOG = logging.getLogger(__name__)
> +
> +
> +class KeyRequired(bottle.HTTPError):
> +
> +    def __init__(self, key):
> +        msg = "key(%s) required." % key
> +        super(KeyRequired, self).__init__(400, msg)
> +
> +
> +class KeyInvalid(bottle.HTTPError):
> +
> +    def __init__(self, key, value):
> +        msg = "invalid key(%s): %s." % (key, value)
> +        super(KeyRequired, self).__init__(400, msg)
> +
> +
> +class BaseHandler(bottle.Bottle):
> +    """Define common methods for each handler."""
> +
> +    def __init__(self, controller):
> +        super(BaseHandler, self).__init__()
> +        self.ctrl = controller
> +
> +        self.default_error_handler = self._error_handler
> +        bottle.response.default_status = 404
> +
> +    def _error_handler(self, res):
> +        # use "text/plain" as content_type rather than bottle's default
> +        # "html".
> +        res.content_type = "text/plain"
> +        return res.body
> +
> +    def _validate_port(self, port):
> +        try:
> +            if_type, if_num = port.split(":")
> +            if if_type not in ["phy", "vhost", "ring"]:
> +                raise
> +            int(if_num)
> +        except:
> +            raise KeyInvalid('port', port)
> +
> +    def log_url(self):
> +        LOG.info("%s %s called", bottle.request.method, bottle.request.path)
> +
> +    def log_response(self):
> +        LOG.info("response: %s", bottle.response.status)
> +
> +    # following three decorators do common works for each API.
> +    # each handler 'install' appropriate decorators.
> +    #
> +    def get_body(self, func):
> +        """Get body and set it to method argument.
> +        content-type is OK whether application/json or plain text.
> +        """
> +        def wrapper(*args, **kwargs):
> +            req = bottle.request
> +            if req.method in ["POST", "PUT"]:
> +                if req.get_header('Content-Type') == "application/json":
> +                    body = req.json
> +                else:
> +                    body = json.loads(req.body.read().decode())
> +                kwargs['body'] = body
> +                LOG.info("body: %s", body)
> +            return func(*args, **kwargs)
> +        return wrapper
> +
> +    def check_sec_id(self, func):
> +        """Get and check proc and set it to method argument."""
> +        def wrapper(*args, **kwargs):
> +            sec_id = kwargs.pop('sec_id', None)
> +            if sec_id is not None:
> +                proc = self.ctrl.procs.get(sec_id)
> +                if proc is None or proc.type != self.type:
> +                    raise bottle.HTTPError(404,
> +                                           "sec_id %d not found." % sec_id)
> +                kwargs['proc'] = proc
> +            return func(*args, **kwargs)
> +        return wrapper
> +
> +    def make_response(self, func):
> +        """Convert plain response to bottle.HTTPResponse."""
> +        def wrapper(*args, **kwargs):
> +            ret = func(*args, **kwargs)
> +            if ret is None:
> +                return bottle.HTTPResponse(status=204)
> +            else:
> +                r = bottle.HTTPResponse(status=200, body=json.dumps(ret))
> +                r.content_type = "application/json"
> +                return r
> +        return wrapper
> +
> +
> +class WebServer(BaseHandler):
> +    """Top level handler.
> +    handlers are hierarchized using 'mount' as follows:
> +    /          WebServer
> +    /v1          V1Handler
> +       /vfs        V1VFHandler
> +       /nfvs       V1NFVHandler
> +       /primary    V1PrimaryHandler
> +    """
> +
> +    def __init__(self, controller, api_port):
> +        super(WebServer, self).__init__(controller)
> +        self.api_port = api_port
> +
> +        self.mount("/v1", V1Handler(controller))
> +
> +        # request and response logging.
> +        self.add_hook("before_request", self.log_url)
> +        self.add_hook("after_request", self.log_response)
> +
> +    def start(self):
> +        self.run(server='eventlet', host='localhost', port=self.api_port,
> +                 quiet=True)
> +
> +
> +class V1Handler(BaseHandler):
> +    def __init__(self, controller):
> +        super(V1Handler, self).__init__(controller)
> +
> +        self.set_route()
> +
> +        self.mount("/vfs", V1VFHandler(controller))
> +        self.mount("/nfvs", V1NFVHandler(controller))
> +        self.mount("/primary", V1PrimaryHandler(controller))
> +
> +        self.install(self.make_response)
> +
> +    def set_route(self):
> +        self.route('/processes', 'GET', callback=self.get_processes)
> +
> +    def get_processes(self):
> +        LOG.info("get processes called.")
> +        return self.ctrl.get_processes()
> +
> +
> +class V1VFHandler(BaseHandler):
> +
> +    def __init__(self, controller):
> +        super(V1VFHandler, self).__init__(controller)
> +        self.type = spp_proc.TYPE_VF
> +
> +        self.set_route()
> +
> +        self.install(self.check_sec_id)
> +        self.install(self.get_body)
> +        self.install(self.make_response)
> +
> +    def set_route(self):
> +        self.route('/<sec_id:int>', 'GET', callback=self.vf_get)
> +        self.route('/<sec_id:int>/components', 'POST',
> +                   callback=self.vf_comp_start)
> +        self.route('/<sec_id:int>/components/<name>', 'DELETE',
> +                   callback=self.vf_comp_stop)
> +        self.route('/<sec_id:int>/components/<name>', 'PUT',
> +                   callback=self.vf_comp_port)
> +        self.route('/<sec_id:int>/classifier_table', 'PUT',
> +                   callback=self.vf_classifier)
> +
> +    def convert_vf_info(self, data):
> +        info = data["info"]
> +        vf = {}
> +        vf["client-id"] = info["client-id"]
> +        vf["ports"] = []
> +        for key in ["phy", "vhost", "ring"]:
> +            for idx in info[key]:
> +                vf["ports"].append(key + ":" + str(idx))
> +        vf["components"] = info["core"]
> +        vf["classifier_table"] = info["classifier_table"]
> +
> +        return vf
> +
> +    def vf_get(self, proc):
> +        return self.convert_vf_info(proc.get_status())
> +
> +    def _validate_vf_comp_start(self, body):
> +        for key in ['name', 'core', 'type']:
> +            if key not in body:
> +                raise KeyRequired(key)
> +        if not isinstance(body['name'], str):
> +            raise KeyInvalid('name', body['name'])
> +        if not isinstance(body['core'], int):
> +            raise KeyInvalid('core', body['core'])
> +        if body['type'] not in ["forward", "merge", "classifier_mac"]:
> +            raise KeyInvalid('type', body['type'])
> +
> +    def vf_comp_start(self, proc, body):
> +        self._validate_vf_comp_start(body)
> +        proc.start_component(body['name'], body['core'], body['type'])
> +
> +    def vf_comp_stop(self, proc, name):
> +        proc.stop_component(name)
> +
> +    def _validate_vf_comp_port(self, body):
> +        for key in ['action', 'port', 'dir']:
> +            if key not in body:
> +                raise KeyRequired(key)
> +        if body['action'] not in ["attach", "detach"]:
> +            raise KeyInvalid('action', body['action'])
> +        if body['dir'] not in ["rx", "tx"]:
> +            raise KeyInvalid('dir', body['dir'])
> +        self._validate_port(body['port'])
> +
> +        if body['action'] == "attach":
> +            vlan = body.get('vlan')
> +            if vlan:
> +                try:
> +                    if vlan['operation'] not in ["none", "add", "del"]:
> +                        raise
> +                    if vlan['operation'] == "add":
> +                        int(vlan['id'])
> +                        int(vlan['pcp'])
> +                except:
> +                    raise KeyInvalid('vlan', vlan)
> +
> +    def vf_comp_port(self, proc, name, body):
> +        self._validate_vf_comp_port(body)
> +
> +        if body['action'] == "attach":
> +            op = "none"
> +            vlan_id = 0
> +            pcp = 0
> +            vlan = body.get('vlan')
> +            if vlan:
> +                if vlan['operation'] == "add":
> +                    op = "add_vlantag"
> +                    vlan_id = vlan['id']
> +                    pcp = vlan['pcp']
> +                elif vlan['operation'] == "del":
> +                    op = "del_vlantag"
> +            proc.port_add(body['port'], body['dir'],
> +                          name, op, vlan_id, pcp)
> +        else:
> +            proc.port_del(body['port'], body['dir'], name)
> +
> +    def _validate_mac(self, mac_address):
> +        try:
> +            netaddr.EUI(mac_address)
> +        except:
> +            raise KeyInvalid('mac_address', mac_address)
> +
> +    def _validate_vf_classifier(self, body):
> +        for key in ['action', 'type', 'port', 'mac_address']:
> +            if key not in body:
> +                raise KeyRequired(key)
> +        if body['action'] not in ["add", "del"]:
> +            raise KeyInvalid('action', body['action'])
> +        if body['type'] not in ["mac", "vlan"]:
> +            raise KeyInvalid('type', body['type'])
> +        self._validate_port(body['port'])
> +        self._validate_mac(body['mac_address'])
> +
> +        if body['type'] == "vlan":
> +            try:
> +                int(body['vlan'])
> +            except:
> +                raise KeyInvalid('vlan', body.get('vlan'))
> +
> +    def vf_classifier(self, proc, body):
> +        self._validate_vf_classifier(body)
> +
> +        port = body['port']
> +        mac_address = body['mac_address']
> +
> +        if body['action'] == "add":
> +            if body['type'] == "mac":
> +                proc.set_classifier_table(mac_address, port)
> +            else:
> +                proc.set_classifier_table_with_vlan(
> +                    mac_address, port, body['vlan'])
> +        else:
> +            if body['type'] == "mac":
> +                proc.clear_classifier_table(mac_address, port)
> +            else:
> +                proc.clear_classifier_table_with_vlan(
> +                    mac_address, port, body['vlan'])
> +
> +
> +class V1NFVHandler(BaseHandler):
> +
> +    def __init__(self, controller):
> +        super(V1NFVHandler, self).__init__(controller)
> +        self.type = spp_proc.TYPE_NFV
> +
> +        self.set_route()
> +
> +        self.install(self.check_sec_id)
> +        self.install(self.get_body)
> +        self.install(self.make_response)
> +
> +    def set_route(self):
> +        self.route('/<sec_id:int>', 'GET', callback=self.nfv_get)
> +        self.route('/<sec_id:int>/forward', 'PUT',
> +                   callback=self.nfv_forward)
> +        self.route('/<sec_id:int>/ports', 'PUT',
> +                   callback=self.nfv_port)
> +        self.route('/<sec_id:int>/patches', 'PUT',
> +                   callback=self.nfv_patch_add)
> +        self.route('/<sec_id:int>/patches', 'DELETE',
> +                   callback=self.nfv_patch_del)
> +
> +    def convert_nfv_info(self, data):
> +        nfv = {}
> +        lines = data.splitlines()
> +        if len(lines) < 3:
> +            return {}
> +        p = re.compile("Client ID (\d+) (\w+)")
> +        m = p.match(lines[0])
> +        if m:
> +            nfv['client_id'] = int(m.group(1))
> +            nfv['status'] = m.group(2)
> +
> +        ports = {}
> +        outs = []
> +        for line in lines[2:]:
> +            if not line.startswith("port_id"):
> +                break
> +            arg1, _, arg2, arg3 = line.split(",")
> +            _, port_id = arg1.split(":")
> +            if arg2 == "PHY":
> +                port = "phy:" + port_id
> +            else:
> +                if_type, rest = arg2.split("(")
> +                if_num = rest.rstrip(")")
> +                if if_type == "RING":
> +                    port = "ring:" + if_num
> +                elif if_type == "VHOST":
> +                    port = "vhost:" + if_num
> +                else:
> +                    port = if_type + ":" + if_num
> +            ports[port_id] = port
> +            _, out_id = arg3.split(":")
> +            if out_id != "none":
> +                outs.append((port_id, out_id))
> +        nfv['ports'] = list(ports.values())
> +        patches = []
> +        if outs:
> +            for src_id, dst_id in outs:
> +                patches.append({"src": ports[src_id], "dst": ports[dst_id]})
> +        nfv['patches'] = patches
> +
> +        return nfv
> +
> +    def nfv_get(self, proc):
> +        return self.convert_nfv_info(proc.get_status())
> +
> +    def _validate_nfv_forward(self, body):
> +        if 'action' not in body:
> +            raise KeyRequired('action')
> +        if body['action'] not in ["start", "stop"]:
> +            raise KeyInvalid('action', body['action'])
> +
> +    def nfv_forward(self, proc, body):
> +        if body['action'] == "start":
> +            proc.forward()
> +        else:
> +            proc.stop()
> +
> +    def _validate_nfv_port(self, body):
> +        for key in ['action', 'port']:
> +            if key not in body:
> +                raise KeyRequired(key)
> +        if body['action'] not in ["add", "del"]:
> +            raise KeyInvalid('action', body['action'])
> +        self._validate_port(body['port'])
> +
> +    def nfv_port(self, proc, body):
> +        self._validate_nfv_port(body)
> +
> +        if_type, if_num = body['port'].split(":")
> +        if body['action'] == "add":
> +            proc.port_add(if_type, if_num)
> +        else:
> +            proc.port_del(if_type, if_num)
> +
> +    def _validate_nfv_patch(self, body):
> +        for key in ['src', 'dst']:
> +            if key not in body:
> +                raise KeyRequired(key)
> +        self._validate_port(body['src'])
> +        self._validate_port(body['dst'])
> +
> +    def nfv_patch_add(self, proc, body):
> +        self._validate_nfv_patch(body)
> +        proc.patch_add(body['src'], body['dst'])
> +
> +    def nfv_patch_del(self, proc):
> +        proc.patch_reset()
> +
> +
> +class V1PrimaryHandler(BaseHandler):
> +
> +    def __init__(self, controller):
> +        super(V1PrimaryHandler, self).__init__(controller)
> +
> +        self.set_route()
> +
> +        self.install(self.make_response)
> +
> +    def set_route(self):
> +        self.route('/status', 'GET', callback=self.get_status)
> +        self.route('/status', 'DELETE', callback=self.clear_status)
> +
> +    def _get_proc(self):
> +        proc = self.ctrl.procs.get(spp_proc.ID_PRIMARY)
> +        if proc is None:
> +            raise bottle.HTTPError(404, "primary not found.")
> +        return proc
> +
> +    def convert_status(self, data):
> +        # no data returned at the moment.
> +        # some data will be returned when the primary becomes to
> +        # return statistical information.
> +        return {}
> +
> +    def get_status(self):
> +        proc = self._get_proc()
> +        return self.convert_status(proc.status())
> +
> +    def clear_status(self):
> +        proc = self._get_proc()
> +        proc.clear()
> 

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [spp] [PATCH v2 2/9] docs: api reference
  2018-10-02  3:42   ` Yasufumi Ogawa
@ 2018-10-02  4:10     ` Yasufumi Ogawa
  0 siblings, 0 replies; 17+ messages in thread
From: Yasufumi Ogawa @ 2018-10-02  4:10 UTC (permalink / raw)
  To: Itsuro ODA, spp

On 2018/10/02 12:42, Yasufumi Ogawa wrote:
> On 2018/09/23 11:28, Itsuro ODA wrote:
>> From: Itsuro Oda <oda@valinux.co.jp>
>>
> This API reference is well defined. However, some of descriptions are not applied with our documentation guidelines.
> 
> * Some of section headers are different from the guideline [1].
> 
> * All of tables should have a label and a caption, and the format of table is not correct [2].
> 
> * It is better to add examples of request with curl command.
Hi,

I have forgotten one thing. Could you change the order of SPP processes in the reference? In SPP documentation, it is explained 
as primary, spp_nfv, spp_vm and then spp_vf basically and I would like to keep the order also in the API reference.

Thanks
> 
> You also need to add license and copyright holder at the top of the file.
> 
> [1] https://doc.dpdk.org/guides/contributing/documentation.html#section-headers
> [2] https://doc.dpdk.org/guides/contributing/documentation.html#tables
> 
> Thanks,
>> Signed-off-by: Itsuro Oda <oda@valinux.co.jp>
>> ---
>>   docs/guides/spp-ctl/api-reference.rst | 790 ++++++++++++++++++++++++++
>>   1 file changed, 790 insertions(+)
>>   create mode 100644 docs/guides/spp-ctl/api-reference.rst
>>
>> diff --git a/docs/guides/spp-ctl/api-reference.rst b/docs/guides/spp-ctl/api-reference.rst
>> new file mode 100644
>> index 0000000..b8bf488
>> --- /dev/null
>> +++ b/docs/guides/spp-ctl/api-reference.rst
>> @@ -0,0 +1,790 @@
>> +=============
>> +API Reference
>> +=============
>> +
>> +Overview
>> +========
>> +
>> +Spp-ctl provides simple REST like API. It supports http only, not https.
>> +
>> +Request and Response
>> +--------------------
>> +
>> +Request body is json format. It is accepted both "text/plain" and "application/json" for the content-type header.
>> +
>> +Response of GET is json format and the content-type is "application/json" if the request success.
>> +
>> +If a request fails, the response is a text which shows error reason and the content-type is "text/plain".
>> +
>> +Error code
>> +----------
>> +
>> +Spp-ctl does basic syntax and lexical check of a request.
>> +
>> ++-------+------------------------------------------------------------------------------+
>> +| Error | Description                                                                  |
>> ++=======+==============================================================================+
>> +| 400   | Syntax or lexical error of a request.                                        |
>> +|       | Or an SPP process returns error for the command correspondings to a request. |
>> ++-------+------------------------------------------------------------------------------+
>> +| 404   | URL is not supported for spp-ctl.                                            |
>> +|       | Or there is no SPP process of client-id in a URL.                            |
>> ++-------+------------------------------------------------------------------------------+
>> +| 500   | A system error occurs in the spp-ctl.                                        |
>> +|       | ex. communication error between an SPP processes.                            |
>> ++-------+------------------------------------------------------------------------------+
>> +
>> +
>> +API independent of the process type
>> +===================================
>> +
>> +GET /v1/processes
>> +-----------------
>> +
>> +Show the SPP processes connected to the spp-ctl.
>> +
>> +Normarl response codes: 200
>> +
>> +Response
>> +^^^^^^^^
>> +
>> +An array of process objects.
>> +
>> +process object:
>> +
>> ++-----------+---------+-------------------------------------------------------------+
>> +| Name      | Type    | Description                                                 |
>> ++===========+=========+=============================================================+
>> +| type      | string  | process type. one of "primary", "vf" or "nfv".              |
>> ++-----------+---------+-------------------------------------------------------------+
>> +| client-id | integer | client id. if type is "primary" this member does not exist. |
>> ++-----------+---------+-------------------------------------------------------------+
>> +
>> +Response example
>> +^^^^^^^^^^^^^^^^
>> +
>> +::
>> +
>> +[{"type": "primary"}, {"type": "vf", "client-id": 1}, {"type": "nfv", "client-id": 2}]
>> +
>> +
>> +API for spp_vf
>> +==============
>> +
>> +GET /v1/vfs/{client_id}
>> +-----------------------
>> +
>> +Get the information of the spp_vf process.
>> +
>> +Normal response codes: 200
>> +
>> +Error response codes: 400, 404
>> +
>> +Request(path)
>> +^^^^^^^^^^^^^
>> +
>> ++-----------+---------+-------------------------------------------------------------+
>> +| Name      | Type    | Description                                                 |
>> ++===========+=========+=============================================================+
>> +| client_id | integer | client id.                                                  |
>> ++-----------+---------+-------------------------------------------------------------+
>> +
>> +Response
>> +^^^^^^^^
>> +
>> ++------------------+---------+-------------------------------------------------------------+
>> +| Name             | Type    | Description                                                 |
>> ++==================+=========+=============================================================+
>> +| client-id        | integer | client id.                                                  |
>> ++------------------+---------+-------------------------------------------------------------+
>> +| ports            | array   | an array of port ids used by the process.                   |
>> ++------------------+---------+-------------------------------------------------------------+
>> +| components       | array   | an array of component objects in the process.               |
>> ++------------------+---------+-------------------------------------------------------------+
>> +| classifier_table | array   | an array of classifier tables in the process.               |
>> ++------------------+---------+-------------------------------------------------------------+
>> +
>> +component object:
>> +
>> ++---------+---------+---------------------------------------------------------------------+
>> +| Name    | Type    | Description                                                         |
>> ++=========+=========+=====================================================================+
>> +| core    | integer | core id running on the component                                    |
>> ++---------+---------+---------------------------------------------------------------------+
>> +| name    | string  | an array of port ids used by the process.                           |
>> ++---------+---------+---------------------------------------------------------------------+
>> +| type    | string  | an array of component objects in the process.                       |
>> ++---------+---------+---------------------------------------------------------------------+
>> +| rx_port | array   | an array of port objects connected to the rx side of the component. |
>> ++---------+---------+---------------------------------------------------------------------+
>> +| tx_port | array   | an array of port objects connected to the tx side of the component. |
>> ++---------+---------+---------------------------------------------------------------------+
>> +
>> +port object:
>> +
>> ++---------+---------+---------------------------------------------------------------------+
>> +| Name    | Type    | Description                                                         |
>> ++=========+=========+=====================================================================+
>> +| port    | string  | port id. port id is the form {interface_type}:{interface_id}.       |
>> ++---------+---------+---------------------------------------------------------------------+
>> +| vlan    | object  | vlan operation which is applied to the port.                        |
>> ++---------+---------+---------------------------------------------------------------------+
>> +
>> +vlan object:
>> +
>> ++-----------+---------+-------------------------------------------------------------------+
>> +| Name      | Type    | Description                                                       |
>> ++===========+=========+===================================================================+
>> +| operation | string  | "add", "del" or "none".                                           |
>> ++-----------+---------+-------------------------------------------------------------------+
>> +| id        | integer | vlan id.                                                          |
>> ++-----------+---------+-------------------------------------------------------------------+
>> +| pcp       | integer | vlan pcp.                                                         |
>> ++-----------+---------+-------------------------------------------------------------------+
>> +
>> +classifier table:
>> +
>> ++-----------+---------+----------------------------------------------------------------------+
>> +| Name      | Type    | Description                                                          |
>> ++===========+=========+======================================================================+
>> +| type      | string  | "mac" or "vlan".                                                     |
>> ++-----------+---------+----------------------------------------------------------------------+
>> +| value     | string  | "mac address" for "mac" type. "vlan id/mac address" for "vlan" type. |
>> ++-----------+---------+----------------------------------------------------------------------+
>> +| port      | string  | port id applied to classify.                                         |
>> ++-----------+---------+----------------------------------------------------------------------+
>> +
>> +Response example
>> +^^^^^^^^^^^^^^^^
>> +
>> +::
>> +
>> +{
>> +"client-id": 1,
>> +"ports": ["phy:0", "phy:1", "vhost:0", "vhost:1", "ring:0", "ring:1", "ring:2", "ring:3"],
>> +"components": [
>> +{
>> +"core": 2,
>> +"name": "forward_0_tx",
>> +"type": "forward",
>> +"rx_port": [
>> +{
>> +"port": "ring:0",
>> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
>> +}
>> +],
>> +"tx_port": [
>> +{
>> +"port": "vhost:0",
>> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
>> +}
>> +]
>> +},
>> +{
>> +"core": 3,
>> +"type": "unuse"
>> +},
>> +{
>> +"core": 4,
>> +"type": "unuse"
>> +},
>> +{
>> +"core": 5,
>> +"name": "forward_1_rx",
>> +"type": "forward",
>> +"rx_port": [
>> +{
>> +"port": "vhost:1",
>> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
>> +}
>> +],
>> +"tx_port": [
>> +{
>> +"port": "ring:3",
>> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
>> +}
>> +]
>> +},
>> +{
>> +"core": 6,
>> +"name": "classifier",
>> +"type": "classifier_mac",
>> +"rx_port": [
>> +{
>> +"port": "phy:0",
>> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
>> +}
>> +],
>> +"tx_port": [
>> +{
>> +"port": "ring:0",
>> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
>> +},
>> +{
>> +"port": "ring:2",
>> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
>> +}
>> +]
>> +},
>> +{
>> +"core": 7,
>> +"name": "merger",
>> +"type": "merge",
>> +"rx_port": [
>> +{
>> +"port": "ring:1",
>> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
>> +},
>> +{
>> +"port": "ring:3",
>> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
>> +}
>> +],
>> +"tx_port": [
>> +{
>> +"port": "phy:0",
>> +"vlan": { "operation": "none", "id": 0, "pcp": 0 }
>> +}
>> +]
>> +}
>> +},
>> +"classifier_table": [
>> +{
>> +"type": "mac",
>> +"value": "FA:16:3E:7D:CC:35",
>> +"port": "ring:0"
>> +}
>> +]
>> +}
>> +
>> +Note: The component which type is "unused" is to indicate unused core.
>> +
>> +Equivalent CLI command
>> +^^^^^^^^^^^^^^^^^^^^^^
>> +
>> +::
>> +
>> +sec {client_id};status
>> +
>> +
>> +POST /v1/vfs/{client_id}/components
>> +-----------------------------------
>> +
>> +Start the component.
>> +
>> +Normal response codes: 204
>> +
>> +Error response codes: 400, 404
>> +
>> +Request(path)
>> +^^^^^^^^^^^^^
>> +
>> ++-----------+---------+-------------------------------------------------------------+
>> +| Name      | Type    | Description                                                 |
>> ++===========+=========+=============================================================+
>> +| client_id | integer | client id.                                                  |
>> ++-----------+---------+-------------------------------------------------------------+
>> +
>> +
>> +Request(body)
>> +^^^^^^^^^^^^^
>> +
>> ++-----------+---------+----------------------------------------------------------------+
>> +| Name      | Type    | Description                                                    |
>> ++===========+=========+================================================================+
>> +| name      | string  | component name. must be unique in the process.                 |
>> ++-----------+---------+----------------------------------------------------------------+
>> +| core      | integer | core id.                                                       |
>> ++-----------+---------+----------------------------------------------------------------+
>> +| type      | string  | component type. one of "forward", "merge" or "classifier_mac". |
>> ++-----------+---------+----------------------------------------------------------------+
>> +
>> +Request example
>> +^^^^^^^^^^^^^^^
>> +::
>> +
>> +{"name": "forwarder1", "core": 12, "type": "forward"}
>> +
>> +Response
>> +^^^^^^^^
>> +
>> +There is no body content for the response of a successful POST request.
>> +
>> +Equivalent CLI command
>> +^^^^^^^^^^^^^^^^^^^^^^
>> +
>> +::
>> +
>> +sec {client_id};component start {name} {core} {type}
>> +
>> +
>> +DELETE /v1/vfs/{sec id}/components/{name}
>> +-----------------------------------------
>> +
>> +Stop the component.
>> +
>> +Normal response codes: 204
>> +
>> +Error response codes: 400, 404
>> +
>> +Request(path)
>> +^^^^^^^^^^^^^
>> +
>> ++-----------+---------+-------------------------------------------------------------+
>> +| Name      | Type    | Description                                                 |
>> ++===========+=========+=============================================================+
>> +| client_id | integer | client id.                                                  |
>> ++-----------+---------+-------------------------------------------------------------+
>> +| name      | string  | component name.                                             |
>> ++-----------+---------+-------------------------------------------------------------+
>> +
>> +Response
>> +^^^^^^^^
>> +
>> +There is no body content for the response of a successful POST request.
>> +
>> +Equivalent CLI command
>> +^^^^^^^^^^^^^^^^^^^^^^
>> +
>> +::
>> +
>> +sec {client_id};component stop {name}
>> +
>> +
>> +PUT /v1/vfs/{client_id}/components/{name}/ports
>> +-----------------------------------------------
>> +
>> +Add or Delete port to the component.
>> +
>> +Normal response codes: 204
>> +
>> +Error response codes: 400, 404
>> +
>> +Request(path)
>> +^^^^^^^^^^^^^
>> +
>> ++-----------+---------+-------------------------------------------------------------+
>> +| Name      | Type    | Description                                                 |
>> ++===========+=========+=============================================================+
>> +| client_id | integer | client id.                                                  |
>> ++-----------+---------+-------------------------------------------------------------+
>> +| name      | string  | component name.                                             |
>> ++-----------+---------+-------------------------------------------------------------+
>> +
>> +Request(body)
>> +^^^^^^^^^^^^^
>> +
>> ++---------+---------+---------------------------------------------------------------------+
>> +| Name    | Type    | Description                                                         |
>> ++=========+=========+=====================================================================+
>> +| action  | string  | "attach" or "detach".                                               |
>> ++---------+---------+---------------------------------------------------------------------+
>> +| port    | string  | port id. port id is the form {interface_type}:{interface_id}.       |
>> ++---------+---------+---------------------------------------------------------------------+
>> +| dir     | string  | "rx" or "tx".                                                       |
>> ++---------+---------+---------------------------------------------------------------------+
>> +| vlan    | object  | vlan operation which is applied to the port. it can be omitted.     |
>> ++---------+---------+---------------------------------------------------------------------+
>> +
>> +vlan object:
>> +
>> ++-----------+---------+-------------------------------------------------------------------+
>> +| Name      | Type    | Description                                                       |
>> ++===========+=========+===================================================================+
>> +| operation | string  | "add", "del" or "none".                                           |
>> ++-----------+---------+-------------------------------------------------------------------+
>> +| id        | integer | vlan id. ignored when operation is "del" or "none".               |
>> ++-----------+---------+-------------------------------------------------------------------+
>> +| pcp       | integer | vlan pcp. ignored when operation is "del" or "none".              |
>> ++-----------+---------+-------------------------------------------------------------------+
>> +
>> +Request example
>> +^^^^^^^^^^^^^^^
>> +
>> +::
>> +
>> +{"action": "attach", "port": "vhost:1", "dir": "rx",
>> +"vlan": {"operation": "add", "id": 677, "pcp": 0}
>> +}
>> +
>> +::
>> +
>> +{"action": "detach", "port": "vhost:0", "dir": "tx"}
>> +
>> +
>> +Response
>> +^^^^^^^^
>> +
>> +There is no body content for the response of a successful PUT request.
>> +
>> +
>> +Equivalent CLI command
>> +^^^^^^^^^^^^^^^^^^^^^^
>> +action is "attach"
>> +
>> +::
>> +
>> +sec {client_id};port add {port} {dir} {name} [add_vlantag {id} {pcp} | del_vlantag]
>> +
>> +action is "detach"
>> +
>> +::
>> +
>> +sec {client_id};port del {port} {dir} {name}
>> +
>> +
>> +PUT /v1/vfs/{sec id}/classifier_table
>> +-------------------------------------
>> +
>> +Set or Unset classifier table.
>> +
>> +Normal response codes: 204
>> +
>> +Error response codes: 400, 404
>> +
>> +Request(path)
>> +^^^^^^^^^^^^^
>> +
>> ++-----------+---------+-------------------------------------------------------------+
>> +| Name      | Type    | Description                                                 |
>> ++===========+=========+=============================================================+
>> +| client_id | integer | client id.                                                  |
>> ++-----------+---------+-------------------------------------------------------------+
>> +
>> +Request(body)
>> +^^^^^^^^^^^^^
>> +
>> ++-------------+-----------------+-------------------------------------------------------------------+
>> +| Name        | Type            | Description                                                       |
>> ++=============+=================+===================================================================+
>> +| action      | string          | "add" or "del".                                                   |
>> ++-------------+-----------------+-------------------------------------------------------------------+
>> +| type        | string          | "mac" or "vlan".                                                  |
>> ++-------------+-----------------+-------------------------------------------------------------------+
>> +| vlan        | integer or null | vlan id when type is "vlan. null or omitted when type is "mac".   |
>> ++-------------+-----------------+-------------------------------------------------------------------+
>> +| mac_address | string          | mac address.                                                      |
>> ++-------------+-----------------+-------------------------------------------------------------------+
>> +| port        | string          | port id.                                                          |
>> ++-------------+-----------------+-------------------------------------------------------------------+
>> +
>> +Request example
>> +^^^^^^^^^^^^^^^
>> +
>> +::
>> +
>> +{"action": "add", "type": "mac",
>> +"mac_address": "FA:16:3E:7D:CC:35", "port": "ring:0"
>> +}
>> +
>> +::
>> +
>> +{"action": "del", "type": "vlan", "vlan": 475,
>> +"mac_address": "FA:16:3E:7D:CC:35", "port": "ring:0"
>> +}
>> +
>> +
>> +Response
>> +^^^^^^^^
>> +
>> +There is no body content for the response of a successful PUT request.
>> +
>> +Equivalent CLI command
>> +^^^^^^^^^^^^^^^^^^^^^^
>> +
>> +type is "mac"
>> +
>> +::
>> +
>> +classifier_table {action} mac {mac_address} {port}
>> +
>> +type is "vlan"
>> +
>> +::
>> +
>> +classifier_table {action} vlan {vlan} {mac_address} {port}
>> +
>> +
>> +API for spp_nfv/spp_vm
>> +======================
>> +
>> +GET /v1/nfvs/{client_id}
>> +------------------------
>> +
>> +Get the information of the spp_nfv/spp_vm process.
>> +
>> +Normal response codes: 200
>> +
>> +Error response codes: 400, 404
>> +
>> +Request(path)
>> +^^^^^^^^^^^^^
>> +
>> ++-----------+---------+-------------------------------------------------------------+
>> +| Name      | Type    | Description                                                 |
>> ++===========+=========+=============================================================+
>> +| client_id | integer | client id.                                                  |
>> ++-----------+---------+-------------------------------------------------------------+
>> +
>> +Response
>> +^^^^^^^^
>> +
>> ++------------------+---------+-------------------------------------------------------------+
>> +| Name             | Type    | Description                                                 |
>> ++==================+=========+=============================================================+
>> +| client-id        | integer | client id.                                                  |
>> ++------------------+---------+-------------------------------------------------------------+
>> +| status           | string  | "Running" or "Idle".                                        |
>> ++------------------+---------+-------------------------------------------------------------+
>> +| ports            | array   | an array of port ids used by the process.                   |
>> ++------------------+---------+-------------------------------------------------------------+
>> +| patches          | array   | an array of patches.                                        |
>> ++------------------+---------+-------------------------------------------------------------+
>> +
>> +patch objest
>> +
>> ++----------+---------+-------------------------------------------------------------+
>> +| Name     | Type    | Description                                                 |
>> ++==========+=========+=============================================================+
>> +| src      | string  | source port id.                                             |
>> ++----------+---------+-------------------------------------------------------------+
>> +| dst      | string  | destination port id.                                        |
>> ++----------+---------+-------------------------------------------------------------+
>> +
>> +Response example
>> +^^^^^^^^^^^^^^^^
>> +
>> +::
>> +
>> +{
>> +"client-id": 1,
>> +"status": "Running",
>> +"ports": ["phy:0", "phy:1", "vhost:0", "vhost:1", "ring:0", "ring:1", "ring:2", "ring:3"],
>> +"patches": [
>> +{"src": "vhost:0", "dst": "ring:0"},
>> +{"src": "ring:1", "dst": "vhost:1"}
>> +]
>> +}
>> +
>> +Equivalent CLI command
>> +^^^^^^^^^^^^^^^^^^^^^^
>> +
>> +::
>> +
>> +sec {client_id};status
>> +
>> +
>> +PUT /v1/nfvs/{client_id}/forward
>> +--------------------------------
>> +
>> +Start or Stop forwarding.
>> +
>> +Normal response codes: 204
>> +
>> +Error response codes: 400, 404
>> +
>> +Request(path)
>> +^^^^^^^^^^^^^
>> +
>> ++-----------+---------+-------------------------------------------------------------+
>> +| Name      | Type    | Description                                                 |
>> ++===========+=========+=============================================================+
>> +| client_id | integer | client id.                                                  |
>> ++-----------+---------+-------------------------------------------------------------+
>> +
>> +Request(body)
>> +^^^^^^^^^^^^^
>> +
>> ++-----------+---------+-------------------------------------------------------------+
>> +| Name      | Type    | Description                                                 |
>> ++===========+=========+=============================================================+
>> +| action    | string  | "start" or "stop".                                          |
>> ++-----------+---------+-------------------------------------------------------------+
>> +
>> +Request example
>> +^^^^^^^^^^^^^^^
>> +
>> +::
>> +
>> +{"action": "start"}
>> +
>> +Response
>> +^^^^^^^^
>> +
>> +There is no body content for the response of a successful PUT request.
>> +
>> +Equivalent CLI command
>> +^^^^^^^^^^^^^^^^^^^^^^
>> +
>> +action is "start"
>> +
>> +::
>> +
>> +sec {client_id};forward
>> +
>> +action is "stop"
>> +
>> +::
>> +
>> +sec {client_id};stop
>> +
>> +
>> +PUT /v1/nfvs/{client_id}/ports
>> +------------------------------
>> +
>> +Add or Delete port.
>> +
>> +Normal response codes: 204
>> +
>> +Error response codes: 400, 404
>> +
>> +Request(path)
>> +^^^^^^^^^^^^^
>> +
>> ++-----------+---------+-------------------------------------------------------------+
>> +| Name      | Type    | Description                                                 |
>> ++===========+=========+=============================================================+
>> +| client_id | integer | client id.                                                  |
>> ++-----------+---------+-------------------------------------------------------------+
>> +
>> +Request(body)
>> +^^^^^^^^^^^^^
>> +
>> ++-----------+---------+---------------------------------------------------------------+
>> +| Name      | Type    | Description                                                   |
>> ++===========+=========+===============================================================+
>> +| action    | string  | "add" or "del".                                               |
>> ++-----------+---------+---------------------------------------------------------------+
>> +| port      | string  | port id. port id is the form {interface_type}:{interface_id}. |
>> ++-----------+---------+---------------------------------------------------------------+
>> +
>> +Request example
>> +^^^^^^^^^^^^^^^
>> +
>> +::
>> +
>> +{"action": "add", "port": "vhost:0"}
>> +
>> +
>> +Response
>> +^^^^^^^^
>> +
>> +There is no body content for the response of a successful PUT request.
>> +
>> +Equivalent CLI command
>> +^^^^^^^^^^^^^^^^^^^^^^
>> +
>> +::
>> +
>> +sec {client_id};{action} {interface_type} {interface_id}
>> +
>> +
>> +PUT /v1/nfvs/{client_id}/patches
>> +--------------------------------
>> +
>> +Add a patch.
>> +
>> +Normal response codes: 204
>> +
>> +Error response codes: 400, 404
>> +
>> +Request(path)
>> +^^^^^^^^^^^^^
>> +
>> ++-----------+---------+-------------------------------------------------------------+
>> +| Name      | Type    | Description                                                 |
>> ++===========+=========+=============================================================+
>> +| client_id | integer | client id.                                                  |
>> ++-----------+---------+-------------------------------------------------------------+
>> +
>> +Request(body)
>> +^^^^^^^^^^^^^
>> +
>> ++----------+---------+-------------------------------------------------------------+
>> +| Name     | Type    | Description                                                 |
>> ++==========+=========+=============================================================+
>> +| src      | string  | source port id.                                             |
>> ++----------+---------+-------------------------------------------------------------+
>> +| dst      | string  | destination port id.                                        |
>> ++----------+---------+-------------------------------------------------------------+
>> +
>> +Request example
>> +^^^^^^^^^^^^^^^
>> +
>> +::
>> +
>> +{"src": "vhost:0", "dst": "ring:0"}
>> +
>> +Response
>> +^^^^^^^^
>> +
>> +There is no body content for the response of a successful PUT request.
>> +
>> +Equivalent CLI command
>> +^^^^^^^^^^^^^^^^^^^^^^
>> +
>> +::
>> +
>> +sec {client_id};patch {src} {dst}
>> +
>> +
>> +DELETE /v1/nfvs/{client_id}/patches
>> +-----------------------------------
>> +
>> +Reset patches.
>> +
>> +Normal response codes: 204
>> +
>> +Error response codes: 400, 404
>> +
>> +Request(path)
>> +^^^^^^^^^^^^^
>> +
>> ++-----------+---------+-------------------------------------------------------------+
>> +| Name      | Type    | Description                                                 |
>> ++===========+=========+=============================================================+
>> +| client_id | integer | client id.                                                  |
>> ++-----------+---------+-------------------------------------------------------------+
>> +
>> +Response
>> +^^^^^^^^
>> +
>> +There is no body content for the response of a successful DELETE request.
>> +
>> +Equivalent CLI command
>> +^^^^^^^^^^^^^^^^^^^^^^
>> +
>> +::
>> +
>> +sec {client_id};patch reset
>> +
>> +
>> +API for spp_primary
>> +===================
>> +
>> +GET /v1/primary/status
>> +----------------------
>> +
>> +Show statistical information.
>> +
>> +Normal response codes: 200
>> +
>> +Response
>> +^^^^^^^^
>> +
>> +There is no data at the moment. The statistical information will be returned when spp_primary
>> +implements it.
>> +
>> +
>> +DELETE /v1/primary/status
>> +-------------------------
>> +
>> +Clear statistical information.
>> +
>> +Normal response codes: 204
>> +
>> +Response
>> +^^^^^^^^
>> +
>> +There is no body content for the response of a successful PUT request.
>> +
>>
> 
> 


-- 
Yasufumi Ogawa
NTT Network Service Systems Labs

^ permalink raw reply	[flat|nested] 17+ messages in thread

* Re: [spp] [PATCH v2 6/9] spp-ctl: executable
  2018-09-23  2:35 ` [spp] [PATCH v2 6/9] spp-ctl: executable Itsuro ODA
@ 2018-10-02  5:47   ` Yasufumi Ogawa
  0 siblings, 0 replies; 17+ messages in thread
From: Yasufumi Ogawa @ 2018-10-02  5:47 UTC (permalink / raw)
  To: Itsuro ODA, spp

> From: Itsuro Oda <oda@valinux.co.jp>
> 
You cannot include executable because it is not allowed by checkpatches.sh to set execute permission for source files.

Thanks
> Signed-off-by: Itsuro Oda <oda@valinux.co.jp>
> ---
>   src/spp-ctl/spp-ctl | 11 +++++++++++
>   1 file changed, 11 insertions(+)
>   create mode 100755 src/spp-ctl/spp-ctl
> 
> diff --git a/src/spp-ctl/spp-ctl b/src/spp-ctl/spp-ctl
> new file mode 100755
> index 0000000..645611b
> --- /dev/null
> +++ b/src/spp-ctl/spp-ctl
> @@ -0,0 +1,11 @@
> +#!/usr/bin/python3
> +# SPDX-License-Identifier: BSD-3-Clause
> +# Copyright(c) 2018 Nippon Telegraph and Telephone Corporation
> +
> +import sys
> +
> +from spp_ctl import main
> +
> +
> +if __name__ == "__main__":
> +    sys.exit(main())
> 

^ permalink raw reply	[flat|nested] 17+ messages in thread

end of thread, other threads:[~2018-10-02  5:49 UTC | newest]

Thread overview: 17+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-09-23  2:22 [spp] [PATCH v2 0/9] spp-ctl: SPP controller with Web API Itsuro ODA
2018-09-23  2:25 ` [spp] [PATCH v2 1/9] docs: overview Itsuro ODA
2018-10-02  3:29   ` Yasufumi Ogawa
2018-09-23  2:28 ` [spp] [PATCH v2 2/9] docs: api reference Itsuro ODA
2018-10-02  3:42   ` Yasufumi Ogawa
2018-10-02  4:10     ` Yasufumi Ogawa
2018-09-23  2:30 ` [spp] [PATCH v2 3/9] docs: index Itsuro ODA
2018-09-23  2:32 ` [spp] [PATCH v2 4/9] docs: top index Itsuro ODA
2018-09-23  2:33 ` [spp] PATCH v2 5/9] add requirements.txt Itsuro ODA
2018-09-23  2:35 ` [spp] [PATCH v2 6/9] spp-ctl: executable Itsuro ODA
2018-10-02  5:47   ` Yasufumi Ogawa
2018-09-23  2:36 ` [spp] [PATCH v2 7/9] spp-ctl: SPP controller with Web API Itsuro ODA
2018-09-23  2:44   ` Itsuro ODA
2018-09-25  2:01     ` Yasufumi Ogawa
2018-09-23  2:38 ` [spp] [PATCH v2 8/9] spp-ctl: web api handler Itsuro ODA
2018-10-02  4:03   ` Yasufumi Ogawa
2018-09-23  2:39 ` [spp] [PATCH v2 9/9] spp-ctl: spp command interface Itsuro ODA

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).