Soft Patch Panel
 help / color / mirror / Atom feed
* [spp] [PATCH 0/4] Add commands of spp_vf to SPP controller
@ 2018-10-18 11:28 ogawa.yasufumi
  2018-10-18 11:28 ` [spp] [PATCH 1/4] controller: add POST method for spp-ctl ogawa.yasufumi
                   ` (3 more replies)
  0 siblings, 4 replies; 5+ messages in thread
From: ogawa.yasufumi @ 2018-10-18 11:28 UTC (permalink / raw)
  To: spp, ferruh.yigit, ogawa.yasufumi

From: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>

Spp_vf has dedicated controller 'spp_vf.py' and is not managed from
'spp.py'. It is confusing to users.

Thanks to spp-ctl, it is easy to send a request to spp_vf from 'spp.py'
without any effort. This update is to add SppVf class behaviour as a
client for spp-ctl. An instance of the class is intended to be used
from do_vf() and complete_vf() methods of Shell class in 'spp.py'.

'spp_vf.py' is removed in this update because all of this features are
moved to 'spp.py' and is not used anymore.

Yasufumi Ogawa (4):
  controller: add POST method for spp-ctl
  controller: add SppVf class
  controller: add vf command to SPP controller
  spp_vf: remove spp_vf controller

 src/controller/commands/vf.py    | 470 ++++++++++++++++++++++++++++++++++++
 src/controller/shell.py          | 103 +++++++-
 src/controller/spp.py            |  19 +-
 src/controller/spp_ctl_client.py |   5 +
 src/spp_vf.py                    | 507 ---------------------------------------
 5 files changed, 585 insertions(+), 519 deletions(-)
 create mode 100644 src/controller/commands/vf.py
 delete mode 100755 src/spp_vf.py

-- 
2.13.1

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

* [spp] [PATCH 1/4] controller: add POST method for spp-ctl
  2018-10-18 11:28 [spp] [PATCH 0/4] Add commands of spp_vf to SPP controller ogawa.yasufumi
@ 2018-10-18 11:28 ` ogawa.yasufumi
  2018-10-18 11:28 ` [spp] [PATCH 2/4] controller: add SppVf class ogawa.yasufumi
                   ` (2 subsequent siblings)
  3 siblings, 0 replies; 5+ messages in thread
From: ogawa.yasufumi @ 2018-10-18 11:28 UTC (permalink / raw)
  To: spp, ferruh.yigit, ogawa.yasufumi

From: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>

To send a request of POST, add post() method to SppCtlClient class.

Signed-off-by: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>
---
 src/controller/spp_ctl_client.py | 5 +++++
 1 file changed, 5 insertions(+)

diff --git a/src/controller/spp_ctl_client.py b/src/controller/spp_ctl_client.py
index 0f8687a..3713326 100644
--- a/src/controller/spp_ctl_client.py
+++ b/src/controller/spp_ctl_client.py
@@ -53,6 +53,11 @@ class SppCtlClient(object):
         return requests.put(url, json=params)
 
     @request_handler
+    def post(self, req, params):
+        url = '%s/%s' % (self.base_url, req)
+        return requests.post(url, json=params)
+
+    @request_handler
     def delete(self, req):
         url = '%s/%s' % (self.base_url, req)
         return requests.delete(url)
-- 
2.13.1

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

* [spp] [PATCH 2/4] controller: add SppVf class
  2018-10-18 11:28 [spp] [PATCH 0/4] Add commands of spp_vf to SPP controller ogawa.yasufumi
  2018-10-18 11:28 ` [spp] [PATCH 1/4] controller: add POST method for spp-ctl ogawa.yasufumi
@ 2018-10-18 11:28 ` ogawa.yasufumi
  2018-10-18 11:28 ` [spp] [PATCH 3/4] controller: add vf command to SPP controller ogawa.yasufumi
  2018-10-18 11:28 ` [spp] [PATCH 4/4] spp_vf: remove spp_vf controller ogawa.yasufumi
  3 siblings, 0 replies; 5+ messages in thread
From: ogawa.yasufumi @ 2018-10-18 11:28 UTC (permalink / raw)
  To: spp, ferruh.yigit, ogawa.yasufumi

From: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>

Spp_vf has dedicated controller 'spp_vf.py' and is not managed from
'spp.py'. It is confusing to users.

Thanks to spp-ctl, it is easy to send a request to spp_vf from 'spp.py'
without any effort. This update is to add SppVf class behaviour as a
client for spp-ctl. An instance of the class is intended to be used
from do_vf() and complete_vf() methods of Shell class in 'spp.py'.

Signed-off-by: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>
---
 src/controller/commands/vf.py | 470 ++++++++++++++++++++++++++++++++++++++++++
 1 file changed, 470 insertions(+)
 create mode 100644 src/controller/commands/vf.py

diff --git a/src/controller/commands/vf.py b/src/controller/commands/vf.py
new file mode 100644
index 0000000..8828110
--- /dev/null
+++ b/src/controller/commands/vf.py
@@ -0,0 +1,470 @@
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2018 Nippon Telegraph and Telephone Corporation
+
+
+class SppVf(object):
+    """Exec SPP VF command.
+
+    SppVf class is intended to be used in Shell class as a delegator
+    for running 'vf' command.
+
+    'self.command()' is called from do_vf() and 'self.complete()' is called
+    from complete_vf() of both of which is defined in Shell.
+    """
+
+    # All of commands and sub-commands used for validation and completion.
+    VF_CMDS = {
+            'status': None,
+            'component': ['start', 'stop'],
+            'port': ['add', 'del'],
+            'classifier_table': ['add', 'del']}
+
+    WORKER_TYPES = ['forward', 'merge', 'classifier_mac']
+
+    def __init__(self, spp_ctl_cli, sec_id, use_cache=False):
+        self.spp_ctl_cli = spp_ctl_cli
+        self.sec_id = sec_id
+
+        # Update 'self.worker_names' and 'self.unused_core_ids' each time
+        # 'self.run()' is called if it is 'False'.
+        # True to 'True' if you do not wait for spp_vf's response.
+        self.use_cache = use_cache
+
+        # Names and core IDs of worker threads
+        vf_status = self._get_status(self.sec_id)
+
+        core_ids = vf_status['core_ids']
+        for wk in vf_status['workers']:
+            if wk['core_id'] in core_ids:
+                core_ids.remove(wk['core_id'])
+        self.unused_core_ids = core_ids  # used while completion to exclude
+
+        self.workers = vf_status['workers']
+        self.worker_names = [attr['name'] for attr in vf_status['workers']]
+
+    def run(self, cmdline):
+        """Called from do_sec() to Send command to secondary process."""
+
+        # update status each time if configured not to use cache
+        if self.use_cache is False:
+            vf_status = self._get_status(self.sec_id)
+
+            core_ids = vf_status['core_ids']
+            for wk in vf_status['workers']:
+                if wk['core_id'] in core_ids:
+                    core_ids.remove(wk['core_id'])
+            self.unused_core_ids = core_ids  # used while completion to exclude
+
+            self.workers = vf_status['workers']
+            self.worker_names = [attr['name'] for attr in vf_status['workers']]
+
+        cmd = cmdline.split(' ')[0]
+        params = cmdline.split(' ')[1:]
+
+        if cmd == 'status':
+            self._run_status()
+
+        elif cmd == 'component':
+            self._run_component(params)
+
+        elif cmd == 'port':
+            self._run_port(params)
+
+        elif cmd == 'classifier_table':
+            self._run_cls_table(params)
+
+        else:
+            print('Invalid command "%s".' % cmd)
+
+    def print_status(self, json_obj):
+        """Parse and print message from SPP VF.
+
+        Print status received from spp_vf.
+
+          spp > vf; status
+          Basic Information:
+            - client-id: 3
+            - ports: [phy:0, phy:1]
+          Classifier Table:
+            - "FA:16:3E:7D:CC:35", ring:0
+            - "FA:17:3E:7D:CC:55", ring:1
+          Components:
+            - core:1, "fwdr1" (type: forwarder)
+              - rx: ring:0
+              - tx: vhost:0
+            - core:2, "mgr11" (type: merger)
+              - rx: ring:1, vlan (operation: add, id: 101, pcp: 0)
+              - tx: ring:2, vlan (operation: del)
+            ...
+
+        """
+
+        # Basic Information
+        print('Basic Information:')
+        print('  - client-id: %d' % json_obj['client-id'])
+        print('  - ports: [%s]' % ', '.join(json_obj['ports']))
+
+        # Classifier Table
+        print('Classifier Table:')
+        if len(json_obj['classifier_table']) == 0:
+            print('  No entries.')
+        for ct in json_obj['classifier_table']:
+            print('  - %s, %s' % (ct['value'], ct['port']))
+
+        # Componennts
+        print('Components:')
+        for worker in json_obj['components']:
+            if 'name' in worker.keys():
+                print("  - core:%d '%s' (type: %s)" % (
+                      worker['core'], worker['name'], worker['type']))
+                for pt_dir in ['rx', 'tx']:
+                    pt = '%s_port' % pt_dir
+                    for attr in worker[pt]:
+                        if attr['vlan']['operation'] == 'add':
+                            msg = '    - %s: %s ' + \
+                                  '(vlan operation: %s, id: %d, pcp: %d)'
+                            print(msg % (pt_dir, attr['port'],
+                                         attr['vlan']['operation'],
+                                         attr['vlan']['id'],
+                                         attr['vlan']['pcp']))
+                        elif attr['vlan']['operation'] == 'del':
+                            msg = '    - %s: %s (vlan operation: %s)'
+                            print(msg % (pt_dir, attr['port'],
+                                  attr['vlan']['operation']))
+                        else:
+                            msg = '    - %s: %s'
+                            print(msg % (pt_dir, attr['port']))
+
+            else:
+                # TODO(yasufum) should change 'unuse' to 'unused'
+                print("  - core:%d '' (type: unuse)" % worker['core'])
+
+    def complete(self, sec_ids, text, line, begidx, endidx):
+        """Completion for spp_vf commands.
+
+        Called from complete_vf() to complete.
+        """
+
+        try:
+            completions = []
+            tokens = line.split(';')
+
+            if len(tokens) == 2:
+                sub_tokens = tokens[1].split(' ')
+
+                # VF_CMDS = {
+                #         'status': None,
+                #         'component': ['start', 'stop'],
+                #         'port': ['add', 'del'],
+                #         'classifier_table': ['add', 'del']}
+
+                if len(sub_tokens) == 1:
+                    if not (sub_tokens[0] in self.VF_CMDS.keys()):
+                        completions = self._compl_first_tokens(sub_tokens[0])
+                else:
+                    if sub_tokens[0] == 'status':
+                        if len(sub_tokens) < 2:
+                            if 'status'.startswith(sub_tokens[1]):
+                                completions = ['status']
+
+                    elif sub_tokens[0] == 'component':
+                        completions = self._compl_component(sub_tokens)
+
+                    elif sub_tokens[0] == 'port':
+                        completions = self._compl_port(sub_tokens)
+
+                    elif sub_tokens[0] == 'classifier_table':
+                        completions = self._compl_cls_table(sub_tokens)
+            return completions
+        except Exception as e:
+            print(e)
+
+    def _compl_first_tokens(self, token):
+        res = []
+        for kw in self.VF_CMDS.keys():
+            if kw.startswith(token):
+                res.append(kw)
+        return res
+
+    def _get_status(self, sec_id):
+        """Get status of spp_vf.
+
+        To update status of the instance of SppVf, get the status from
+        spp-ctl. This method returns the result as a dict. For considering
+        behaviour of spp_vf, it is enough to return worker's name and core
+        IDs as the status, but might need to be update for future updates.
+
+        # return worker's name and used core IDs, and all of core IDs.
+        {
+          'workers': [
+            {'name': 'fw1', 'core_id': 5},
+            {'name': 'mg1', 'core_id': 6},
+            ...
+          ],
+          'core_ids': [5, 6, 7, ...]
+        }
+
+        """
+
+        status = {'workers': [], 'core_ids': []}
+        res = self.spp_ctl_cli.get('vfs/%d' % self.sec_id)
+        if res is not None:
+            if res.status_code == 200:
+                json_obj = res.json()
+
+                if 'components' in json_obj.keys():
+                    for wk in json_obj['components']:
+                        if 'core' in wk.keys():
+                            if 'name' in wk.keys():
+                                status['workers'].append(
+                                        {'name': wk['name'],
+                                            'core_id': wk['core']})
+                            status['core_ids'].append(wk['core'])
+
+        return status
+
+    def _run_status(self):
+        res = self.spp_ctl_cli.get('vfs/%d' % self.sec_id)
+        if res is not None:
+            error_codes = self.spp_ctl_cli.rest_common_error_codes
+            if res.status_code == 200:
+                self.print_status(res.json())
+            elif res.status_code in error_codes:
+                pass
+            else:
+                print('Error: unknown response.')
+
+    def _run_component(self, params):
+        if params[0] == 'start':
+            req_params = {'name': params[1], 'core': int(params[2]),
+                          'type': params[3]}
+            res = self.spp_ctl_cli.post('vfs/%d/components' % self.sec_id,
+                                        req_params)
+            if res is not None:
+                error_codes = self.spp_ctl_cli.rest_common_error_codes
+                if res.status_code == 204:
+                    print("Succeeded to start component '%s' on core:%d"
+                          % (req_params['name'], req_params['core']))
+                    self.worker_names.append(req_params['name'])
+                    self.unused_core_ids.remove(req_params['core'])
+                elif res.status_code in error_codes:
+                    pass
+                else:
+                    print('Error: unknown response.')
+
+        elif params[0] == 'stop':
+            res = self.spp_ctl_cli.delete('vfs/%d/components/%s' % (
+                                          self.sec_id, params[1]))
+            if res is not None:
+                error_codes = self.spp_ctl_cli.rest_common_error_codes
+                if res.status_code == 204:
+                    print("Succeeded to delete component '%s'" % params[1])
+
+                    # update workers and core IDs
+                    if params[1] in self.worker_names:
+                        self.worker_names.remove(params[1])
+                    for wk in self.workers:
+                        if wk['name'] == params[1]:
+                            self.unused_core_ids.append(wk['core_id'])
+                            self.workers.remove(wk)
+                            break
+                elif res.status_code in error_codes:
+                    pass
+                else:
+                    print('Error: unknown response.')
+
+    def _run_port(self, params):
+        if len(params) == 4:
+            if params[0] == 'add':
+                action = 'attach'
+            elif params[0] == 'del':
+                action = 'detach'
+            else:
+                print('Error: Invalid action.')
+                return None
+
+            req_params = {'action': action, 'port': params[1],
+                          'dir': params[2],
+                          'vlan': {'operation': 'none',
+                                   'id': 'none',
+                                   'pcp': 'none'}}
+
+        elif len(params) == 5:  # delete vlan with 'port add' command
+            # TODO(yasufum) Syntax for deleting vlan should be modified
+            #               because deleting with 'port add' is terrible!
+            action = 'attach'
+            req_params = {'action': action, 'port': params[1],
+                          'dir': params[2],
+                          'vlan': {'operation': 'del',
+                                   'id': 'none',
+                                   'pcp': 'none'}}
+
+        elif len(params) == 7:
+            action = 'attach'
+            if params[4] == 'add_vlantag':
+                op = 'add'
+            elif params[4] == 'del_vlantag':
+                op = 'del'
+            req_params = {'action': action, 'port': params[1],
+                          'dir': params[2],
+                          'vlan': {'operation': op, 'id': int(params[5]),
+                                   'pcp': int(params[6])}}
+
+        res = self.spp_ctl_cli.put('vfs/%d/components/%s/ports'
+                                   % (self.sec_id, params[3]), req_params)
+        if res is not None:
+            error_codes = self.spp_ctl_cli.rest_common_error_codes
+            if res.status_code == 204:
+                print("Succeeded to %s port" % params[0])
+            elif res.status_code in error_codes:
+                pass
+            else:
+                print('Error: unknown response.')
+
+    def _run_cls_table(self, params):
+        req_params = None
+        if len(params) == 4:
+            req_params = {'action': params[0], 'type': params[1],
+                          'mac_address': params[2], 'port': params[3]}
+
+        elif len(params) == 5:
+            req_params = {'action': params[0], 'type': params[1],
+                          'vlan': params[2], 'mac_address': params[3],
+                          'port': params[4]}
+        else:
+            print('Error: Invalid params')
+
+        if req_params is not None:
+            req = 'vfs/%d/classifier_table' % self.sec_id
+            res = self.spp_ctl_cli.put(req, req_params)
+
+            if res is not None:
+                error_codes = self.spp_ctl_cli.rest_common_error_codes
+                if res.status_code == 204:
+                    print("Succeeded to %s" % params[0])
+                elif res.status_code in error_codes:
+                    pass
+                else:
+                    print('Error: unknown response.')
+
+    def _compl_component(self, sub_tokens):
+        if len(sub_tokens) < 6:
+            subsub_cmds = ['start', 'stop']
+            res = []
+            if len(sub_tokens) == 2:
+                for kw in subsub_cmds:
+                    if kw.startswith(sub_tokens[1]):
+                        res.append(kw)
+            elif len(sub_tokens) == 3:
+                # 'start' takes any of names and no need
+                #  check, required only for 'stop'.
+                if sub_tokens[1] == 'start':
+                    if 'NAME'.startswith(sub_tokens[2]):
+                        res.append('NAME')
+                if sub_tokens[1] == 'stop':
+                    for kw in self.worker_names:
+                        if kw.startswith(sub_tokens[2]):
+                            res.append(kw)
+            elif len(sub_tokens) == 4:
+                if sub_tokens[1] == 'start':
+                    for cid in [str(i) for i in self.unused_core_ids]:
+                        if cid.startswith(sub_tokens[3]):
+                            res.append(cid)
+            elif len(sub_tokens) == 5:
+                if sub_tokens[1] == 'start':
+                    for wk_type in self.WORKER_TYPES:
+                        if wk_type.startswith(sub_tokens[4]):
+                            res.append(wk_type)
+            return res
+
+    def _compl_port(self, sub_tokens):
+        if len(sub_tokens) < 9:
+            subsub_cmds = ['add', 'del']
+            res = []
+            if len(sub_tokens) == 2:
+                for kw in subsub_cmds:
+                    if kw.startswith(sub_tokens[1]):
+                        res.append(kw)
+            elif len(sub_tokens) == 3:
+                if sub_tokens[1] in subsub_cmds:
+                    if 'RES_UID'.startswith(sub_tokens[2]):
+                        res.append('RES_UID')
+            elif len(sub_tokens) == 4:
+                if sub_tokens[1] in subsub_cmds:
+                    for direction in ['rx', 'tx']:
+                        if direction.startswith(sub_tokens[3]):
+                            res.append(direction)
+            elif len(sub_tokens) == 5:
+                if sub_tokens[1] in subsub_cmds:
+                    for kw in self.worker_names:
+                        if kw.startswith(sub_tokens[4]):
+                            res.append(kw)
+            elif len(sub_tokens) == 6:
+                if sub_tokens[1] == 'add':
+                    for kw in ['add_vlantag', 'del_vlantag']:
+                        if kw.startswith(sub_tokens[5]):
+                            res.append(kw)
+            elif len(sub_tokens) == 7:
+                if sub_tokens[1] == 'add' and sub_tokens[5] == 'add_vlantag':
+                    if 'VID'.startswith(sub_tokens[6]):
+                        res.append('VID')
+            elif len(sub_tokens) == 8:
+                if sub_tokens[1] == 'add' and sub_tokens[5] == 'add_vlantag':
+                    if 'PCP'.startswith(sub_tokens[7]):
+                        res.append('PCP')
+            return res
+
+    def _compl_cls_table(self, sub_tokens):
+        if len(sub_tokens) < 7:
+            subsub_cmds = ['add', 'del']
+            res = []
+
+            if len(sub_tokens) == 2:
+                for kw in subsub_cmds:
+                    if kw.startswith(sub_tokens[1]):
+                        res.append(kw)
+
+            elif len(sub_tokens) == 3:
+                if sub_tokens[1] in subsub_cmds:
+                    for kw in ['mac', 'vlan']:
+                        if kw.startswith(sub_tokens[2]):
+                            res.append(kw)
+
+            elif len(sub_tokens) == 4:
+                if sub_tokens[1] == 'add':
+                    if sub_tokens[2] == 'mac':
+                        if 'MAC_ADDR'.startswith(sub_tokens[3]):
+                            res.append('MAC_ADDR')
+                    elif sub_tokens[2] == 'vlan':
+                        if 'VID'.startswith('VID'):
+                            res.append('VID')
+                elif sub_tokens[1] == 'del':
+                    if sub_tokens[2] == 'mac':
+                        if 'MAC_ADDR'.startswith(sub_tokens[3]):
+                            res.append('MAC_ADDR')
+                    if sub_tokens[2] == 'vlan':
+                        if 'VID'.startswith(sub_tokens[3]):
+                                res.append('VID')
+
+            elif len(sub_tokens) == 5:
+                if sub_tokens[1] == 'add':
+                    if sub_tokens[2] == 'mac':
+                        if 'RES_UID'.startswith(sub_tokens[4]):
+                            res.append('RES_UID')
+                    elif sub_tokens[2] == 'vlan':
+                        if 'MAC_ADDR'.startswith(sub_tokens[4]):
+                            res.append('MAC_ADDR')
+                if sub_tokens[1] == 'del':
+                    if sub_tokens[2] == 'mac':
+                        if 'RES_UID'.startswith(sub_tokens[4]):
+                            res.append('RES_UID')
+                    elif sub_tokens[2] == 'vlan':
+                        if 'MAC_ADDR'.startswith(sub_tokens[4]):
+                            res.append('MAC_ADDR')
+
+            elif len(sub_tokens) == 6:
+                if sub_tokens[1] in subsub_cmds and \
+                        sub_tokens[2] == 'vlan':
+                            if 'RES_UID'.startswith(sub_tokens[5]):
+                                res.append('RES_UID')
+            return res
-- 
2.13.1

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

* [spp] [PATCH 3/4] controller: add vf command to SPP controller
  2018-10-18 11:28 [spp] [PATCH 0/4] Add commands of spp_vf to SPP controller ogawa.yasufumi
  2018-10-18 11:28 ` [spp] [PATCH 1/4] controller: add POST method for spp-ctl ogawa.yasufumi
  2018-10-18 11:28 ` [spp] [PATCH 2/4] controller: add SppVf class ogawa.yasufumi
@ 2018-10-18 11:28 ` ogawa.yasufumi
  2018-10-18 11:28 ` [spp] [PATCH 4/4] spp_vf: remove spp_vf controller ogawa.yasufumi
  3 siblings, 0 replies; 5+ messages in thread
From: ogawa.yasufumi @ 2018-10-18 11:28 UTC (permalink / raw)
  To: spp, ferruh.yigit, ogawa.yasufumi

From: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>

To manage spp_vf from 'spp.py', add 'vf' command to Shell class. Spp_vf
might have several instances as similar to spp_nfv, and deciding which
of ones is also similar to. 'vf' command consists of a indicator and
actual command. Here is an example.

  spp > vf 3; component start fw1 5 forward

In this example, indicator 'vf 3;' is before spp_vf's command 'component
start ...'. The number in indicator is a secondary ID actually, so you
cannot assign the same ID of others.

You can refer the usage of 'vf' command with 'help' command.

  spp > help vf

Signed-off-by: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>
---
 src/controller/shell.py | 103 +++++++++++++++++++++++++++++++++++++++++++++++-
 src/controller/spp.py   |  19 ++++-----
 2 files changed, 110 insertions(+), 12 deletions(-)

diff --git a/src/controller/shell.py b/src/controller/shell.py
index 9fce6f4..529d61e 100644
--- a/src/controller/shell.py
+++ b/src/controller/shell.py
@@ -8,6 +8,7 @@ from .commands import bye
 from .commands import pri
 from .commands import sec
 from .commands import topo
+from .commands import vf
 import os
 import re
 import readline
@@ -41,6 +42,11 @@ class Shell(cmd.Cmd, object):
         self.spp_ctl_cli = spp_ctl_cli
         self.spp_primary = pri.SppPrimary(self.spp_ctl_cli)
         self.spp_secondary = sec.SppSecondary(self.spp_ctl_cli)
+
+        self.spp_vfs = {}
+        for sec_id in self.get_sec_ids('vf'):
+            self.spp_vfs[sec_id] = vf.SppVf(self.spp_ctl_cli, sec_id)
+
         self.spp_topo = topo.SppTopo(self.spp_ctl_cli, {}, self.topo_size)
         self.spp_bye = bye.SppBye(self.spp_ctl_cli, self.spp_primary,
                                   self.spp_secondary)
@@ -269,7 +275,7 @@ class Shell(cmd.Cmd, object):
         tmparg = self.clean_cmd(cmd)
         cmds = tmparg.split(';')
         if len(cmds) < 2:
-            print("Required an ID and ';' before command.")
+            print("Required an ID and ';' before the command.")
         elif str.isdigit(cmds[0]):
             sec_id = int(cmds[0])
             if self.check_sec_cmds(cmds[1]):
@@ -287,6 +293,101 @@ class Shell(cmd.Cmd, object):
         return self.spp_secondary.complete(
                 self.get_sec_ids('nfv'), text, line, begidx, endidx)
 
+    def do_vf(self, cmd):
+        """Send a command to spp_vf.
+
+        SPP VF is a secondary process for pseudo SR-IOV features. This
+        command has four sub commands.
+          * status
+          * component
+          * port
+          * classifier_table
+
+        Each of sub commands other than 'status' takes several parameters
+        for detailed operations. Notice that 'start' for launching a worker
+        is replaced with 'stop' for terminating. 'add' is also replaced with
+        'del' for deleting.
+
+        Examples:
+
+        # (1) show status of worker threads and resources
+        spp > vf 1; status
+
+        # (2) launch or terminate a worker thread with arbitrary name
+        #   NAME: arbitrary name used as identifier
+        #   CORE_ID: one of unused cores referred from status
+        #   ROLE: role of workers, 'forward', 'merge' or 'classifier_mac'
+        spp > vf 1; component start NAME CORE_ID ROLE
+        spp > vf 1; component stop NAME CORE_ID ROLE
+
+        # (3) add or delete a port to worker of NAME
+        #   RES_UID: resource UID such as 'ring:0' or 'vhost:1'
+        #   DIR: 'rx' or 'tx'
+        spp > vf 1; port add RES_UID DIR NAME
+        spp > vf 1; port del RES_UID DIR NAME
+
+        # (4) add or delete a port with vlan ID to worker of NAME
+        #   VID: vlan ID
+        #   PCP: priority code point defined in IEEE 802.1p
+        spp > vf 1; port add RES_UID DIR NAME add_vlantag VID PCP
+        spp > vf 1; port del RES_UID DIR NAME add_vlantag VID PCP
+
+        # (5) add a port of deleting vlan tag
+        spp > vf 1; port add RES_UID DIR NAME del_vlantag
+
+        # (6) add or delete an entry of MAC address and resource to classify
+        spp > vf 1; classifier_table add mac MAC_ADDR RES_UID
+        spp > vf 1; classifier_table del mac MAC_ADDR RES_UID
+
+        # (7) add or delete an entry of MAC address and resource with vlan ID
+        spp > vf 1; classifier_table add vlan VID MAC_ADDR RES_UID
+        spp > vf 1; classifier_table del vlan VID MAC_ADDR RES_UID
+        """
+
+        # remove unwanted spaces to avoid invalid command error
+        # TODO change self.spp_vf to self.spp_vfs
+        tmparg = self.clean_cmd(cmd)
+        cmds = tmparg.split(';')
+        if len(cmds) < 2:
+            print("Required an ID and ';' before the command.")
+        elif str.isdigit(cmds[0]):
+            self.spp_vfs[int(cmds[0])].run(cmds[1])
+        else:
+            print('Invalid command: %s' % tmparg)
+
+    def complete_vf(self, text, line, begidx, endidx):
+        """Completion for vf command"""
+
+        line = self.clean_cmd(line)
+
+        tokens = line.split(';')
+        if len(tokens) == 1:
+            # Add SppVf of sec_id if it is not exist
+            sec_ids = self.get_sec_ids('vf')
+            for idx in sec_ids:
+                if self.spp_vfs[idx] is None:
+                    self.spp_vfs[idx] = vf.SppVf(self.spp_ctl_cli, idx)
+
+            if len(line.split()) == 1:
+                res = [str(i)+';' for i in sec_ids]
+            else:
+                if not (';' in line):
+                    res = [str(i)+';'
+                           for i in sec_ids
+                           if (str(i)+';').startswith(text)]
+            return res
+        elif len(tokens) == 2:
+            first_tokens = tokens[0].split(' ')  # 'vf 1' => ['vf', '1']
+            if len(first_tokens) == 2:
+                idx = int(first_tokens[1])
+
+                # Add SppVf of sec_id if it is not exist
+                if self.spp_vfs[idx] is None:
+                    self.spp_vfs[idx] = vf.SppVf(self.spp_ctl_cli, idx)
+
+                return self.spp_vfs[idx].complete(self.get_sec_ids('vf'),
+                                                  text, line, begidx, endidx)
+
     def do_record(self, fname):
         """Save commands as a recipe file.
 
diff --git a/src/controller/spp.py b/src/controller/spp.py
index 5211ec9..2e1c173 100644
--- a/src/controller/spp.py
+++ b/src/controller/spp.py
@@ -19,17 +19,14 @@ def main(argv):
                         help='bind address, default=7777')
     args = parser.parse_args()
 
-    try:
-        spp_ctl_cli = spp_ctl_client.SppCtlClient(args.bind_addr,
-                                                  args.api_port)
-        if spp_ctl_cli.is_server_running() is False:
-            print('Is not spp-ctl running, nor correct IP address?')
-            exit()
-        shell = Shell(spp_ctl_cli)
-        shell.cmdloop()
-        shell = None
-    except Exception as e:
-        print(e)
+    spp_ctl_cli = spp_ctl_client.SppCtlClient(args.bind_addr,
+                                              args.api_port)
+    if spp_ctl_cli.is_server_running() is False:
+        print('Is not spp-ctl running, nor correct IP address?')
+        exit()
+    shell = Shell(spp_ctl_cli)
+    shell.cmdloop()
+    shell = None
 
 
 if __name__ == "__main__":
-- 
2.13.1

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

* [spp] [PATCH 4/4] spp_vf: remove spp_vf controller
  2018-10-18 11:28 [spp] [PATCH 0/4] Add commands of spp_vf to SPP controller ogawa.yasufumi
                   ` (2 preceding siblings ...)
  2018-10-18 11:28 ` [spp] [PATCH 3/4] controller: add vf command to SPP controller ogawa.yasufumi
@ 2018-10-18 11:28 ` ogawa.yasufumi
  3 siblings, 0 replies; 5+ messages in thread
From: ogawa.yasufumi @ 2018-10-18 11:28 UTC (permalink / raw)
  To: spp, ferruh.yigit, ogawa.yasufumi

From: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>

Remove 'spp_vf.py' because features of requesting to spp_vf are moved
to 'spp.py'.

Signed-off-by: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>
---
 src/spp_vf.py | 507 ----------------------------------------------------------
 1 file changed, 507 deletions(-)
 delete mode 100755 src/spp_vf.py

diff --git a/src/spp_vf.py b/src/spp_vf.py
deleted file mode 100755
index 124c714..0000000
--- a/src/spp_vf.py
+++ /dev/null
@@ -1,507 +0,0 @@
-#!/usr/bin/env python
-# SPDX-License-Identifier: BSD-3-Clause
-# Copyright(c) 2017-2018 Nippon Telegraph and Telephone Corporation
-
-from __future__ import print_function
-from Queue import Queue
-from thread import start_new_thread
-from threading import Thread
-import cmd
-import getopt
-import select
-import socket
-import sys
-
-import json
-
-
-class GrowingList(list):
-    """GrowingList"""
-
-    def __setitem__(self, index, value):
-        if index >= len(self):
-            self.extend([None]*(index + 1 - len(self)))
-        list.__setitem__(self, index, value)
-
-
-MAX_SECONDARY = 16
-
-# init
-PRIMARY = ''
-SECONDARY_LIST = []
-SECONDARY_COUNT = 0
-
-# init primary comm channel
-MAIN2PRIMARY = Queue()
-PRIMARY2MAIN = Queue()
-
-# init secondary comm channel list
-MAIN2SEC = GrowingList()
-SEC2MAIN = GrowingList()
-
-
-def connectionthread(name, client_id, conn, m2s, s2m):
-    """Manage secondary process connections"""
-
-    cmd_str = 'hello'
-    recv_str = 'recv'
-    recv_json = None
-
-    # infinite loop so that function do not terminate and thread do not end.
-    while True:
-        try:
-            _, _, _ = select.select([conn, ], [conn, ], [], 5)
-        except select.error:
-            break
-
-        # Sending message to connected secondary
-        try:
-            cmd_str = m2s.get(True)
-            conn.send(cmd_str)  # send only takes string
-        except KeyError:
-            break
-        except Exception, excep:
-            print(str(excep))
-            break
-
-        # Receiving from secondary
-        try:
-            recv_str = ""
-            while True:
-                # 1024 stands for bytes of data to be received
-                data = conn.recv(1024)
-                if data:
-                    recv_str = recv_str + data
-                    if len(data) < 1024:
-                        break
-                else:
-                    break
-            if len(recv_str) > 0:
-                recv_json = json.loads(recv_str)
-                recv_str = "recv:%d:len:%d:\n%s\n" % (
-                    conn.fileno(), len(recv_str),
-                    json.dumps(recv_json, indent=4))
-
-            if not data:
-                s2m.put(recv_str + "closing:" + str(conn))
-                break
-
-            # s2m.put("recv:" + str(conn.fileno()) + ":{" + recv_str + "}")
-            s2m.put(recv_str)
-        except Exception, excep:
-            print(str(excep))
-            break
-
-    SECONDARY_LIST.remove(client_id)
-    conn.close()
-
-
-def getclientid(conn):
-    """Get client_id from client"""
-
-    try:
-        conn.send("_get_client_id")
-        # conn.send("{\"commands\":[{\"command\":\"process\"}]}")
-    except KeyError:
-        return -1
-
-    data = conn.recv(1024)
-    if data is None:
-        return -1
-
-    # client_id = int(data.strip('\0'))
-    json_dict = json.loads(data)
-    client_id = int(json_dict['client_id'])
-
-    if client_id < 0 or client_id > MAX_SECONDARY:
-        return -1
-
-    print("secondary id %d" % client_id)
-    return client_id
-
-    found = 0
-    for i in SECONDARY_LIST:
-        if client_id == i:
-            found = 1
-            break
-
-    if found == 0:
-        return client_id
-
-    # client_id in use, find a free one
-    free_client_id = -1
-    for i in range(MAX_SECONDARY):
-        found = -1
-        for j in SECONDARY_LIST:
-            if i == j:
-                found = i
-                break
-        if found == -1:
-            free_client_id = i
-            break
-
-    if free_client_id < 0:
-        return -1
-
-    conn.send("_set_client_id %u" % free_client_id)
-    data = conn.recv(1024)
-
-    return free_client_id
-
-
-def acceptthread(sock, main2sec, sec2main):
-    """Listen for secondary processes"""
-
-    global SECONDARY_COUNT
-
-    try:
-        while True:
-            # Accepting incoming connections
-            conn, _ = sock.accept()
-
-            client_id = getclientid(conn)
-            if client_id < 0:
-                break
-
-            # Creating new thread.
-            # Calling secondarythread function for this function and passing
-            # conn as argument.
-
-            SECONDARY_LIST.append(client_id)
-            main2sec[client_id] = Queue()
-            sec2main[client_id] = Queue()
-            start_new_thread(connectionthread,
-                             ('secondary', client_id, conn,
-                              main2sec[client_id],
-                              sec2main[client_id], ))
-            SECONDARY_COUNT += 1
-    except Exception, excep:
-        print(str(excep))
-        sock.close()
-
-
-def command_primary(command):
-    """Send command to primary process"""
-
-    if PRIMARY:
-        MAIN2PRIMARY.put(command)
-        print(PRIMARY2MAIN.get(True))
-    else:
-        print("primary not started")
-
-
-def command_secondary(sec_id, command):
-    """Send command to secondary process with sec_id"""
-
-    if sec_id in SECONDARY_LIST:
-        MAIN2SEC[sec_id].put(command)
-        print(SEC2MAIN[sec_id].get(True))
-    else:
-        print("secondary id %d not exist" % sec_id)
-
-
-def print_status():
-    """Display information about connected clients"""
-
-    print("Soft Patch Panel Status :")
-    print("primary: %d" % PRIMARY)
-    print("secondary count: %d" % len(SECONDARY_LIST))
-    for i in SECONDARY_LIST:
-        print("Connected secondary id: %d" % i)
-
-
-def primarythread(sock, main2primary, primary2main):
-    """Manage primary process connection"""
-
-    global PRIMARY
-    cmd_str = ''
-
-    while True:
-        # waiting for connection
-        PRIMARY = False
-        conn, addr = sock.accept()
-        PRIMARY = True
-
-        while conn:
-            try:
-                _, _, _ = select.select([conn, ], [conn, ], [], 5)
-            except select.error:
-                break
-
-            # Sending message to connected primary
-            try:
-                cmd_str = main2primary.get(True)
-                conn.send(cmd_str)  # send only takes string
-            except KeyError:
-                break
-            except Exception, excep:
-                print(str(excep))
-                break
-
-            # Receiving from primary
-            try:
-                # 1024 stands for bytes of data to be received
-                data = conn.recv(1024)
-                if data:
-                    primary2main.put("recv:%s:{%s}" % (str(addr), data))
-                else:
-                    primary2main.put("closing:%s" % str(addr))
-                    conn.close()
-                    break
-            except Exception, excep:
-                print(str(excep))
-                break
-
-    print("primary communication thread end")
-
-
-def close_all_secondary():
-    """Exit all secondary processes"""
-
-    global SECONDARY_COUNT
-
-    tmp_list = []
-    for i in SECONDARY_LIST:
-        tmp_list.append(i)
-    for i in tmp_list:
-        command_secondary(i, 'exit')
-    SECONDARY_COUNT = 0
-
-
-def check_sec_cmds(cmds):
-    """Validate secondary commands before sending"""
-
-    return 1
-
-    level1 = ['status', 'exit', 'forward', 'stop']
-    level2 = ['add', 'patch', 'del']
-    patch_args = ['reset']
-    add_del_args = ['ring', 'vhost']
-    cmdlist = cmds.split(' ')
-    valid = 0
-
-    length = len(cmdlist)
-    if length == 1:
-        if cmdlist[0] in level1:
-            valid = 1
-    elif length == 2:
-        if cmdlist[0] == 'patch':
-            if cmdlist[1] in patch_args:
-                valid = 1
-    elif length == 3:
-        if cmdlist[0] in level2:
-            if cmdlist[0] == 'add' or cmdlist[0] == 'del':
-                if cmdlist[1] in add_del_args:
-                    if str.isdigit(cmdlist[2]):
-                        valid = 1
-            elif cmdlist[0] == 'patch':
-                if str.isdigit(cmdlist[1]) and str.isdigit(cmdlist[2]):
-                    valid = 1
-
-    return valid
-
-
-class Shell(cmd.Cmd, object):
-    """SPP command prompt"""
-
-    intro = 'Welcome to the spp.   Type help or ? to list commands.\n'
-    prompt = 'spp > '
-    recorded_file = None
-
-    COMMANDS = ['status', 'add', 'patch', 'ring', 'vhost',
-                'reset', 'exit', 'forward', 'stop', 'clear']
-
-    def is_comment_line(self, line):
-        input_line = line.strip()
-        if len(input_line) > 0:
-            if (input_line[0] == '#') or (input_line[0:2] == '//'):
-                return True
-            else:
-                return False
-
-    def default(self, line):
-        """Define defualt behaviour
-
-        If user input is commend styled, controller simply echo as a comment.
-        Supported styles are
-          - python ('#')
-          - C ('//')
-        """
-
-        if self.is_comment_line(line):
-            print("%s" % line.strip())
-        else:
-            super(Shell, self).default(line)
-
-    def emptyline(self):
-        """Do nothin for empty input
-
-        It override Cmd.emptyline() which runs previous input as default
-        to do nothing.
-        """
-        pass
-
-    def complete_pri(self, text, line, begidx, endidx):
-        """Completion for primary process commands"""
-
-        if not text:
-            completions = self.COMMANDS[:]
-        else:
-            completions = [p
-                           for p in self.COMMANDS
-                           if p.startswith(text)]
-
-        return completions
-
-    def complete_sec(self, text, line, begidx, endidx):
-        """Completion for secondary process commands"""
-
-        if not text:
-            completions = self.COMMANDS[:]
-        else:
-            completions = [p
-                           for p in self.COMMANDS
-                           if p.startswith(text)]
-
-        return completions
-
-    def do_status(self, _):
-        """Display Soft Patch Panel Status"""
-
-        print_status()
-
-    def do_pri(self, command):
-        """Send command to primary process"""
-
-        if command and command in self.COMMANDS:
-            command_primary(command)
-        else:
-            print("primary invalid command")
-
-    def do_sec(self, arg):
-        """Send command to secondary process"""
-
-        cmds = arg.split(';')
-        if len(cmds) < 2:
-            print("error")
-        elif str.isdigit(cmds[0]):
-            sec_id = int(cmds[0])
-            if check_sec_cmds(cmds[1]):
-                command_secondary(sec_id, cmds[1])
-            else:
-                print("invalid cmd")
-        else:
-            print(cmds[0])
-            print("first %s" % cmds[1])
-
-    def do_record(self, arg):
-        """Save future commands to filename:  RECORD filename.cmd"""
-
-        self.recorded_file = open(arg, 'w')
-
-    def do_playback(self, arg):
-        """Playback commands from a file:  PLAYBACK filename.cmd"""
-
-        self.close()
-        try:
-            with open(arg) as recorded_file:
-                lines = []
-                for line in recorded_file:
-                    if not self.is_comment_line(line):
-                        lines.append("# %s" % line)
-                    lines.append(line)
-                self.cmdqueue.extend(lines)
-        except IOError:
-            print("Error: File does not exist.")
-
-    def precmd(self, line):
-        if self.recorded_file and 'playback' not in line:
-            print(line, file=self.recorded_file)
-        return line
-
-    def close(self):
-        """Close record file"""
-
-        if self.recorded_file:
-            print("closing file")
-            self.recorded_file.close()
-            self.recorded_file = None
-
-    def do_bye(self, arg):
-        """Stop recording, close SPP, and exit: BYE"""
-
-        cmds = arg.split(' ')
-        if cmds[0] == 'sec':
-            close_all_secondary()
-        elif cmds[0] == 'all':
-            close_all_secondary()
-            command_primary('exit')
-        elif cmds[0] == '':
-            print('Thank you for using Soft Patch Panel')
-            self.close()
-            return True
-
-
-def main(argv):
-    """main"""
-
-    # Defining server address and port
-    host = ''  # 'localhost' or '127.0.0.1' or '' are all same
-
-    try:
-        opts, _ = getopt.getopt(
-            argv, "p:s:h", ["help", "primary = ", "secondary"])
-    except getopt.GetoptError:
-        print('spp.py -p <primary__port_number> -s <secondary_port_number>')
-        sys.exit(2)
-    for opt, arg in opts:
-        if opt in ("-h", "--help"):
-            print(
-                'spp.py -p <primary__port_number> -s <secondary_port_number>')
-            sys.exit()
-        elif opt in ("-p", "--primary"):
-            primary_port = int(arg)
-            print("primary port : %d" % primary_port)
-        elif opt in ("-s", "--secondary"):
-            secondary_port = int(arg)
-            print('secondary port : %d' % secondary_port)
-
-    # Creating primary socket object
-    primary_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-
-    # Binding primary socket to a address. bind() takes tuple of host and port.
-    primary_sock.bind((host, primary_port))
-
-    # Listening primary at the address
-    primary_sock.listen(1)  # 5 denotes the number of clients can queue
-
-    primary_thread = Thread(target=primarythread,
-                            args=(primary_sock, MAIN2PRIMARY, PRIMARY2MAIN,))
-    primary_thread.daemon = True
-    primary_thread.start()
-
-    # Creating secondary socket object
-    secondary_sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
-
-    # Binding secondary socket to a address. bind() takes tuple of host
-    # and port.
-    secondary_sock.bind((host, secondary_port))
-
-    # Listening secondary at the address
-    secondary_sock.listen(MAX_SECONDARY)
-
-    # secondary process handling thread
-    start_new_thread(acceptthread, (secondary_sock, MAIN2SEC, SEC2MAIN))
-
-    shell = Shell()
-    shell.cmdloop()
-    shell = None
-
-    primary_sock.shutdown(socket.SHUT_RDWR)
-    primary_sock.close()
-    secondary_sock.shutdown(socket.SHUT_RDWR)
-    secondary_sock.close()
-
-
-if __name__ == "__main__":
-    main(sys.argv[1:])
-- 
2.13.1

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

end of thread, other threads:[~2018-10-18 11:29 UTC | newest]

Thread overview: 5+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2018-10-18 11:28 [spp] [PATCH 0/4] Add commands of spp_vf to SPP controller ogawa.yasufumi
2018-10-18 11:28 ` [spp] [PATCH 1/4] controller: add POST method for spp-ctl ogawa.yasufumi
2018-10-18 11:28 ` [spp] [PATCH 2/4] controller: add SppVf class ogawa.yasufumi
2018-10-18 11:28 ` [spp] [PATCH 3/4] controller: add vf command to SPP controller ogawa.yasufumi
2018-10-18 11:28 ` [spp] [PATCH 4/4] spp_vf: remove spp_vf controller ogawa.yasufumi

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