From: ogawa.yasufumi@lab.ntt.co.jp
To: spp@dpdk.org, ferruh.yigit@intel.com, ogawa.yasufumi@lab.ntt.co.jp
Subject: [spp] [PATCH 2/4] controller: add SppVf class
Date: Thu, 18 Oct 2018 20:28:47 +0900 [thread overview]
Message-ID: <20181018112849.77691-3-ogawa.yasufumi@lab.ntt.co.jp> (raw)
In-Reply-To: <20181018112849.77691-1-ogawa.yasufumi@lab.ntt.co.jp>
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
next prev parent reply other threads:[~2018-10-18 11:28 UTC|newest]
Thread overview: 5+ messages / expand[flat|nested] mbox.gz Atom feed top
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 [this message]
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
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20181018112849.77691-3-ogawa.yasufumi@lab.ntt.co.jp \
--to=ogawa.yasufumi@lab.ntt.co.jp \
--cc=ferruh.yigit@intel.com \
--cc=spp@dpdk.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
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).