From: ogawa.yasufumi@lab.ntt.co.jp
To: spp@dpdk.org, ferruh.yigit@intel.com, ogawa.yasufumi@lab.ntt.co.jp
Subject: [spp] [PATCH 7/9] controller: move topo command to SppTopo
Date: Thu, 18 Oct 2018 20:25:16 +0900 [thread overview]
Message-ID: <20181018112518.77556-8-ogawa.yasufumi@lab.ntt.co.jp> (raw)
In-Reply-To: <20181018112518.77556-1-ogawa.yasufumi@lab.ntt.co.jp>
From: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>
SppTopo defines 'topo' command and its completion as in a separated
module. It is intended to be used from Shell, which is derived from
'cmd.Cmd'.
Signed-off-by: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>
---
src/controller/{ => commands}/topo.py | 247 +++++++++++++++++++++++-----------
src/controller/shell.py | 104 ++++----------
src/controller/spp_common.py | 3 -
3 files changed, 190 insertions(+), 164 deletions(-)
rename src/controller/{ => commands}/topo.py (56%)
diff --git a/src/controller/topo.py b/src/controller/commands/topo.py
similarity index 56%
rename from src/controller/topo.py
rename to src/controller/commands/topo.py
index e608961..fc22a98 100644
--- a/src/controller/topo.py
+++ b/src/controller/commands/topo.py
@@ -4,15 +4,14 @@
import os
import re
-from . import spp_common
-from .spp_common import logger
+import socket
import subprocess
import traceback
import uuid
import yaml
-class Topo(object):
+class SppTopo(object):
"""Setup and output network topology for topo command
Topo command supports four types of output.
@@ -22,37 +21,64 @@ class Topo(object):
* text (dot, json, yaml)
"""
- def __init__(self, sec_ids, m2s_queues, s2m_queues, sub_graphs):
- logger.info("Topo initialized with sec IDs %s" % sec_ids)
+ delim_node = '_'
+
+ def __init__(self, spp_ctl_cli, sec_ids, subgraphs):
+ self.spp_ctl_cli = spp_ctl_cli
self.sec_ids = sec_ids
- self.m2s_queues = m2s_queues
- self.s2m_queues = s2m_queues
- self.sub_graphs = sub_graphs
+ self.subgraphs = subgraphs
+
+ def run(self, args, topo_size):
+ args_ary = args.split()
+ if len(args_ary) == 0:
+ print("Usage: topo dst [ftype]")
+ return False
+ elif (args_ary[0] == "term") or (args_ary[0] == "http"):
+ self.show(args_ary[0], topo_size)
+ elif len(args_ary) == 1:
+ ftype = args_ary[0].split(".")[-1]
+ self.output(args_ary[0], ftype)
+ elif len(args_ary) == 2:
+ self.output(args_ary[0], args_ary[1])
+ else:
+ print("Usage: topo dst [ftype]")
def show(self, dtype, size):
res_ary = []
+ error_codes = self.spp_ctl_cli.rest_common_error_codes
+
for sec_id in self.sec_ids:
- msg = "status"
- self.m2s_queues[sec_id].put(msg.encode('utf-8'))
- res = self.format_sec_status(
- sec_id, self.s2m_queues[sec_id].get(True))
- res_ary.append(res)
+ res = self.spp_ctl_cli.get('nfvs/%d' % sec_id)
+ if res.status_code == 200:
+ res_ary.append(res.json())
+ elif res.status_code in error_codes:
+ # Print default error message
+ pass
+ else:
+ # Ignore unknown response because no problem for drawing graph
+ pass
+
if dtype == "http":
self.to_http(res_ary)
elif dtype == "term":
self.to_term(res_ary, size)
else:
print("Invalid file type")
- return res_ary
def output(self, fname, ftype="dot"):
res_ary = []
+ error_codes = self.spp_ctl_cli.rest_common_error_codes
+
for sec_id in self.sec_ids:
- msg = "status"
- self.m2s_queues[sec_id].put(msg.encode('utf-8'))
- res = self.format_sec_status(
- sec_id, self.s2m_queues[sec_id].get(True))
- res_ary.append(res)
+ res = self.spp_ctl_cli.get('nfvs/%d' % sec_id)
+ if res.status_code == 200:
+ res_ary.append(res.json())
+ elif res.status_code in error_codes:
+ # Print default error message
+ pass
+ else:
+ # Ignore unknown response because no problem for drawing graph
+ pass
if ftype == "dot":
self.to_dot(res_ary, fname)
@@ -69,8 +95,6 @@ class Topo(object):
return res_ary
def to_dot(self, sec_list, output_fname):
- # Label given if outport is "none"
- NO_PORT = None
# Graphviz params
# TODO(yasufum) consider to move gviz params to config file.
@@ -89,7 +113,7 @@ class Topo(object):
node_attrs = 'node[shape="rectangle", style="filled"];'
- node_template = '%s' + spp_common.delim_node + '%s'
+ node_template = '%s' + self.delim_node + '%s'
phys = []
rings = []
@@ -100,38 +124,45 @@ class Topo(object):
for sec in sec_list:
if sec is None:
continue
- for port in sec["ports"]:
- if port["iface"]["type"] == "phy":
- phys.append(port)
- elif port["iface"]["type"] == "ring":
- rings.append(port)
- elif port["iface"]["type"] == "vhost":
- vhosts.append(port)
- else:
- raise ValueError(
- "Invaid interface type: %s" % port["iface"]["type"])
-
- if port['out'] != NO_PORT:
- out_type, out_id = port['out'].split(':')
- if sec['status'] == 'running':
- l_style = LINE_STYLE["running"]
+ for port in sec['ports']:
+ if self._is_valid_port(port):
+ r_type = port.split(':')[0]
+ # TODO(yasufum) change decision of r_type smarter
+ if r_type == 'phy':
+ phys.append(port)
+ elif r_type == 'ring':
+ rings.append(port)
+ elif r_type == 'vhost':
+ vhosts.append(port)
+ # TODO(yasufum) add drawing pcap and nullpmd
+ elif r_type == 'pcap':
+ pass
+ elif r_type == 'nullpmd':
+ pass
else:
- l_style = LINE_STYLE["idling"]
- attrs = '[label="%s", color="%s", style="%s"]' % (
- "sec%d" % sec["sec_id"],
- SEC_COLORS[sec["sec_id"]],
- l_style
- )
- link_style = node_template + ' %s ' + node_template + '%s;'
- tmp = link_style % (
- port["iface"]["type"],
- port["iface"]["id"],
- LINK_TYPE,
- out_type,
- out_id,
- attrs
- )
- links.append(tmp)
+ raise ValueError(
+ "Invaid interface type: %s" % r_type)
+
+ for patch in sec['patches']:
+ if sec['status'] == 'running':
+ l_style = LINE_STYLE["running"]
+ else:
+ l_style = LINE_STYLE["idling"]
+ attrs = '[label="%s", color="%s", style="%s"]' % (
+ "sec%d" % sec["client-id"],
+ SEC_COLORS[sec["client-id"]],
+ l_style
+ )
+ link_style = node_template + ' %s ' + node_template + '%s;'
+
+ if self._is_valid_port(patch['src']):
+ src_type, src_id = patch['src'].split(':')
+ if self._is_valid_port(patch['dst']):
+ dst_type, dst_id = patch['dst'].split(':')
+
+ tmp = link_style % (src_type, src_id, LINK_TYPE,
+ dst_type, dst_id, attrs)
+ links.append(tmp)
output = ["%s spp{" % GRAPH_TYPE]
output.append("newrank=true;")
@@ -139,70 +170,65 @@ class Topo(object):
phy_nodes = []
for node in phys:
+ r_type, r_id = node.split(':')
phy_nodes.append(
- node_template % (node['iface']['type'], node['iface']['id']))
+ node_template % (r_type, r_id))
phy_nodes = list(set(phy_nodes))
for node in phy_nodes:
- label = re.sub(
- r'%s' % spp_common.delim_node, spp_common.delim_label, node)
+ label = re.sub(r'%s' % self.delim_node, ':', node)
output.append(
'%s[label="%s", fillcolor="%s"];' % (
node, label, PORT_COLORS["phy"]))
ring_nodes = []
- for p in rings:
- ring_nodes.append(
- node_template % (p['iface']['type'], p['iface']['id']))
+ for node in rings:
+ r_type, r_id = node.split(':')
+ ring_nodes.append(node_template % (r_type, r_id))
ring_nodes = list(set(ring_nodes))
for node in ring_nodes:
- label = re.sub(
- r'%s' % spp_common.delim_node, spp_common.delim_label, node)
+ label = re.sub(r'%s' % self.delim_node, ':', node)
output.append(
'%s[label="%s", fillcolor="%s"];' % (
node, label, PORT_COLORS["ring"]))
vhost_nodes = []
- for p in vhosts:
- vhost_nodes.append(
- node_template % (p["iface"]["type"], p["iface"]["id"]))
+ for node in vhosts:
+ r_type, r_id = node.split(':')
+ vhost_nodes.append(node_template % (r_type, r_id))
vhost_nodes = list(set(vhost_nodes))
for node in vhost_nodes:
- label = re.sub(
- r'%s' % spp_common.delim_node, spp_common.delim_label, node)
+ label = re.sub(r'%s' % self.delim_node, ':', node)
output.append(
'%s[label="%s", fillcolor="%s"];' % (
node, label, PORT_COLORS["vhost"]))
- # rank
+ # Align the same type of nodes with rank attribute
output.append(
'{rank=same; %s}' % ("; ".join(ring_nodes)))
output.append(
'{rank=same; %s}' % ("; ".join(vhost_nodes)))
+ # Decide the bottom, phy or vhost
rank_style = '{rank=max; %s}' % node_template
if len(phys) > 0:
- output.append(
- rank_style % (
- phys[0]["iface"]["type"], phys[0]["iface"]["id"]))
+ r_type, r_id = phys[0].split(':')
elif len(vhosts) > 0:
- output.append(
- rank_style % (
- vhosts[0]["iface"]["type"], vhosts[0]["iface"]["id"]))
+ r_type, r_id = vhosts[0].split(':')
+ output.append(rank_style % (r_type, r_id))
+ # TODO(yasufum) check if it is needed, or is not needed for vhost_nodes
if len(phy_nodes) > 0:
output.append(
'{rank=same; %s}' % ("; ".join(phy_nodes)))
# Add subgraph
ssgs = []
- if len(self.sub_graphs) > 0:
+ if len(self.subgraphs) > 0:
cnt = 1
- for label, val in self.sub_graphs.items():
+ for label, val in self.subgraphs.items():
cluster_id = "cluster%d" % cnt
ssg_label = label
- ssg_ports = re.sub(
- r'%s' % spp_common.delim_label,
- spp_common.delim_node, val)
+ ssg_ports = re.sub(r'%s' % ':', self.delim_node, val)
ssg = 'subgraph %s {label="%s" %s}' % (
cluster_id, ssg_label, ssg_ports)
ssgs.append(ssg)
@@ -258,16 +284,19 @@ class Topo(object):
msg = open(tmpfile).read()
subprocess.call("rm -f %s" % tmpfile, shell=True)
ws_url = "ws://localhost:8989/spp_ws"
- ws = websocket.create_connection(ws_url)
- ws.send(msg)
- ws.close()
+ try:
+ ws = websocket.create_connection(ws_url)
+ ws.send(msg)
+ ws.close()
+ except socket.error:
+ print('Error: Connection refused! Is running websocket server?')
def to_term(self, sec_list, size):
tmpfile = "%s.jpg" % uuid.uuid4().hex
self.to_img(sec_list, tmpfile)
from distutils import spawn
- # TODO(yasufum) Add check for using only supported terminal
+ # TODO(yasufum) add check for using only supported terminal
if spawn.find_executable("img2sixel") is not None:
img_cmd = "img2sixel"
else:
@@ -362,3 +391,57 @@ class Topo(object):
except Exception:
traceback.print_exc()
return None
+
+ def complete(self, text, line, begidx, endidx):
+ """Complete topo command
+
+ If no token given, return 'term' and 'http'.
+ On the other hand, complete 'term' or 'http' if token starts
+ from it, or complete file name if is one of supported formats.
+ """
+
+ terms = ['term', 'http']
+ # Supported formats
+ img_exts = ['jpg', 'png', 'bmp']
+ txt_exts = ['dot', 'yml', 'js']
+
+ # Number of given tokens is expected as two. First one is
+ # 'topo'. If it is three or more, this methods returns nothing.
+ tokens = re.sub(r"\s+", " ", line).split(' ')
+ if (len(tokens) == 2):
+ if (text == ''):
+ completions = terms
+ else:
+ completions = []
+ # Check if 2nd token is a part of terms.
+ for t in terms:
+ if t.startswith(tokens[1]):
+ completions.append(t)
+ # Not a part of terms, so check for completion for
+ # output file name.
+ if len(completions) == 0:
+ if tokens[1].endswith('.'):
+ completions = img_exts + txt_exts
+ elif ('.' in tokens[1]):
+ fname = tokens[1].split('.')[0]
+ token = tokens[1].split('.')[-1]
+ for ext in img_exts:
+ if ext.startswith(token):
+ completions.append(fname + '.' + ext)
+ for ext in txt_exts:
+ if ext.startswith(token):
+ completions.append(fname + '.' + ext)
+ return completions
+ else: # do nothing for three or more tokens
+ pass
+
+ def _is_valid_port(self, port):
+ """Check if port's format is valid.
+
+ Return True if the format is 'r_type:r_id', for example, 'phy:0'.
+ """
+
+ if (':' in port) and (len(port.split(':')) == 2):
+ return True
+ else:
+ return False
diff --git a/src/controller/shell.py b/src/controller/shell.py
index ca4775b..0f95447 100644
--- a/src/controller/shell.py
+++ b/src/controller/shell.py
@@ -6,6 +6,7 @@ from __future__ import absolute_import
import cmd
from .commands import pri
from .commands import sec
+from .commands import topo
import os
import re
import readline
@@ -13,7 +14,6 @@ from .shell_lib import common
from . import spp_common
from .spp_common import logger
import subprocess
-from . import topo
class Shell(cmd.Cmd, object):
@@ -33,7 +33,6 @@ class Shell(cmd.Cmd, object):
HIST_EXCEPT = ['bye', 'exit', 'history', 'redo']
PLUGIN_DIR = 'command'
- subgraphs = {}
topo_size = '60%'
# setup history file
@@ -47,6 +46,9 @@ 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_topo = topo.SppTopo(self.spp_ctl_cli,
+ self.get_sec_ids('nfv'),
+ {})
def default(self, line):
"""Define defualt behaviour.
@@ -77,10 +79,11 @@ class Shell(cmd.Cmd, object):
ids = []
res = self.spp_ctl_cli.get('processes')
- if res.status_code == 200:
- for ent in res.json():
- if ent['type'] == ptype:
- ids.append(ent['client-id'])
+ if res is not None:
+ if res.status_code == 200:
+ for ent in res.json():
+ if ent['type'] == ptype:
+ ids.append(ent['client-id'])
return ids
def clean_hist_file(self):
@@ -589,13 +592,18 @@ class Shell(cmd.Cmd, object):
label: vm1 subgraph: "vhost:1;vhost:2"
"""
+ #logger.info("Topo initialized with sec IDs %s" % sec_ids)
+
+ # delimiter of node in dot file
+ delim_node = '_'
+
args_cleaned = re.sub(r"\s+", ' ', args).strip()
# Show subgraphs if given no argments
if (args_cleaned == ''):
- if len(self.subgraphs) == 0:
+ if len(self.spp_topo.subgraphs) == 0:
print("No subgraph.")
else:
- for label, subg in self.subgraphs.items():
+ for label, subg in self.spp_topo.subgraphs.items():
print('label: %s\tsubgraph: "%s"' % (label, subg))
else: # add or del
tokens = args_cleaned.split(' ')
@@ -605,13 +613,11 @@ class Shell(cmd.Cmd, object):
label = tokens[1]
subg = tokens[2]
if ',' in subg:
- subg = re.sub(
- r'%s' % spp_common.delim_node,
- spp_common.delim_label, subg)
+ subg = re.sub(r'%s' % delim_node, ':', subg)
subg = re.sub(r",", ";", subg)
# TODO(yasufum) add validation for subgraph
- self.subgraphs[label] = subg
+ self.spp_topo.subgraphs[label] = subg
print("Add subgraph '%s'" % label)
else:
print("Invalid syntax '%s'!" % args_cleaned)
@@ -619,7 +625,7 @@ class Shell(cmd.Cmd, object):
elif ((tokens[0] == 'del') or
(tokens[0] == 'delete') or
(tokens[0] == 'remove')):
- del(self.subgraphs[tokens[1]])
+ del(self.spp_topo.subgraphs[tokens[1]])
print("Delete subgraph '%s'" % tokens[1])
else:
@@ -633,11 +639,11 @@ class Shell(cmd.Cmd, object):
if len(tokens) == 1:
return terms
elif len(tokens) == 2 and tokens[1] == 'del':
- return self.subgraphs.keys()
+ return self.spp_topo.subgraphs.keys()
elif text != '':
completions = []
if len(tokens) == 3 and tokens[1] == 'del':
- for t in self.subgraphs.keys():
+ for t in self.spp_topo.subgraphs.keys():
if t.startswith(tokens[2]):
completions.append(t)
elif len(tokens) == 2:
@@ -679,80 +685,20 @@ class Shell(cmd.Cmd, object):
* terminal (but very few terminals supporting to display images)
* browser (websocket server is required)
* image file (jpg, png, bmp)
- * text (dot, json, yaml)
+ * text (dot, js or json, yml or yaml)
spp > topo term # terminal
spp > topo http # browser
spp > topo network_conf.jpg # image
spp > topo network_conf.dot # text
+ spp > topo network_conf.js# text
"""
- if len(spp_common.SECONDARY_LIST) == 0:
- message = "secondary not exist"
- print(message)
- else:
- tp = topo.Topo(
- spp_common.SECONDARY_LIST,
- spp_common.MAIN2SEC,
- spp_common.SEC2MAIN,
- self.subgraphs)
- args_ary = args.split()
- if len(args_ary) == 0:
- print("Usage: topo dst [ftype]")
- return False
- elif (args_ary[0] == "term") or (args_ary[0] == "http"):
- res_ary = tp.show(args_ary[0], self.topo_size)
- elif len(args_ary) == 1:
- ftype = args_ary[0].split(".")[-1]
- res_ary = tp.output(args_ary[0], ftype)
- elif len(args_ary) == 2:
- res_ary = tp.output(args_ary[0], args_ary[1])
- else:
- print("Usage: topo dst [ftype]")
- return False
+ self.spp_topo.run(args, self.topo_size)
def complete_topo(self, text, line, begidx, endidx):
- """Complete topo command
- If no token given, return 'term' and 'http'.
- On the other hand, complete 'term' or 'http' if token starts
- from it, or complete file name if is one of supported formats.
- """
-
- terms = ['term', 'http']
- # Supported formats
- img_exts = ['jpg', 'png', 'bmp']
- txt_exts = ['dot', 'yml', 'js']
-
- # Number of given tokens is expected as two. First one is
- # 'topo'. If it is three or more, this methods returns nothing.
- tokens = re.sub(r"\s+", " ", line).split(' ')
- if (len(tokens) == 2):
- if (text == ''):
- completions = terms
- else:
- completions = []
- # Check if 2nd token is a part of terms.
- for t in terms:
- if t.startswith(tokens[1]):
- completions.append(t)
- # Not a part of terms, so check for completion for
- # output file name.
- if len(completions) == 0:
- if tokens[1].endswith('.'):
- completions = img_exts + txt_exts
- elif ('.' in tokens[1]):
- fname = tokens[1].split('.')[0]
- token = tokens[1].split('.')[-1]
- for ext in img_exts:
- if ext.startswith(token):
- completions.append(fname + '.' + ext)
- for ext in txt_exts:
- if ext.startswith(token):
- completions.append(fname + '.' + ext)
- return completions
- else: # do nothing for three or more tokens
- pass
+ return self.spp_topo.complete(text, line, begidx, endidx)
def do_load_cmd(self, args):
"""Load command plugin.
diff --git a/src/controller/spp_common.py b/src/controller/spp_common.py
index 0986918..809bee5 100644
--- a/src/controller/spp_common.py
+++ b/src/controller/spp_common.py
@@ -23,6 +23,3 @@ SECONDARY_LIST = []
# Maximum num of sock queues for secondaries
MAX_SECONDARY = 16
-
-delim_node = '_'
-delim_label = ':'
--
2.13.1
next prev parent reply other threads:[~2018-10-18 11:25 UTC|newest]
Thread overview: 10+ messages / expand[flat|nested] mbox.gz Atom feed top
2018-10-18 11:25 [spp] [PATCH 0/9] Change SPP controller to use spp-ctl ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 1/9] controller: add spp-ctl client for SPP controller ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 2/9] controller: change controller to use spp-ctl ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 3/9] spp-ctl: add IP address binding ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 4/9] controller: add IP address and port binding ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 5/9] controller: move pri command to SppPrimary ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 6/9] controller: move sec command to SppSecondary ogawa.yasufumi
2018-10-18 11:25 ` ogawa.yasufumi [this message]
2018-10-18 11:25 ` [spp] [PATCH 8/9] controller: move topo_resize command to SppTopo ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 9/9] controller: move bye command to SppBye 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=20181018112518.77556-8-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).