Soft Patch Panel
 help / color / Atom feed
* [spp] [PATCH 0/8] Update topo to support other than spp_nfv
@ 2019-08-12  7:12 Yasufumi Ogawa
  2019-08-12  7:12 ` [spp] [PATCH 1/8] cli: remove topo_resize command Yasufumi Ogawa
                   ` (7 more replies)
  0 siblings, 8 replies; 9+ messages in thread
From: Yasufumi Ogawa @ 2019-08-12  7:12 UTC (permalink / raw)
  To: spp, ferruh.yigit, yasufum.o

Topo command is to output a network diagram graphically and useful for
users to understand the network configuration. However, it is still
experimental and the feature is very limited. It only supports spp_nfv,
and three types of ports.

This series of update is to add supported types of secondaries and
ports. Now all of secondaries and almost of ports are supprted in topo.

This update is also fix a problem in which graph cannot be resized if
config param `topo_size` is changed. For this fix, `topo_resize`
command is removed because it is not required anymore.

Yasufumi Ogawa (8):
  cli: remove topo_resize command
  cli: revise composing dot script
  cli: move style params for dot to config
  cli: add to get sec ID and procs to SppCtlClient
  cli: support other than spp_nfv in topo command
  cli: add port types for topo command
  cli: add checking JSON objs in topo
  cli: revise description for imgcat

 docs/guides/commands/experimental.rst |  15 +-
 src/cli/commands/topo.py              | 630 +++++++++++++++++---------
 src/cli/config/default.yml            |   8 +-
 src/cli/config/topo.yml               |   8 +
 src/cli/shell.py                      |  61 +--
 src/cli/spp_ctl_client.py             |  42 ++
 6 files changed, 498 insertions(+), 266 deletions(-)
 create mode 100644 src/cli/config/topo.yml

-- 
2.17.1


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

* [spp] [PATCH 1/8] cli: remove topo_resize command
  2019-08-12  7:12 [spp] [PATCH 0/8] Update topo to support other than spp_nfv Yasufumi Ogawa
@ 2019-08-12  7:12 ` Yasufumi Ogawa
  2019-08-12  7:12 ` [spp] [PATCH 2/8] cli: revise composing dot script Yasufumi Ogawa
                   ` (6 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Yasufumi Ogawa @ 2019-08-12  7:12 UTC (permalink / raw)
  To: spp, ferruh.yigit, yasufum.o

As `topo_size` is introduced in config and `topo_resize` command is not
required anymore, remove this command.

Signed-off-by: Yasufumi Ogawa <yasufum.o@gmail.com>
---
 src/cli/commands/topo.py | 59 +++++++++++++++++++++-------------------
 src/cli/shell.py         | 10 ++-----
 2 files changed, 33 insertions(+), 36 deletions(-)

diff --git a/src/cli/commands/topo.py b/src/cli/commands/topo.py
index 21ef1ec..58c59d1 100644
--- a/src/cli/commands/topo.py
+++ b/src/cli/commands/topo.py
@@ -25,7 +25,11 @@ class SppTopo(object):
     def __init__(self, spp_ctl_cli, subgraphs, size):
         self.spp_ctl_cli = spp_ctl_cli
         self.subgraphs = subgraphs
-        self.graph_size = size
+        self.graph_size = None
+
+        if self.resize(size) is not True:
+            print('Config "topo_size" is invalid value.')
+            exit()
 
     def run(self, args, sec_ids):
         args_ary = args.split()
@@ -42,6 +46,32 @@ class SppTopo(object):
         else:
             print("Usage: topo dst [ftype]")
 
+    def resize(self, size):
+        """Parse given size and set to self.graph_size.
+
+        The format of `size` is percentage or ratio. Return True if succeeded
+        to parse, or False if invalid format.
+        """
+
+        size = str(size)
+        matched = re.match(r'(\d+)%$', size)
+        if matched:  # percentage
+            i = int(matched.group(1))
+            if i > 0 and  i <= 100:
+                self.graph_size = size
+                return True
+            else:
+                return False
+        elif re.match(r'0\.\d+$',size):  # ratio
+            i = float(size) * 100
+            self.graph_size = str(i) + '%'
+            return True
+        elif size == '1':
+            self.graph_size = '100%'
+            return True
+        else:
+            return False
+
     def show(self, dtype, sec_ids, size):
         res_ary = []
         error_codes = self.spp_ctl_cli.rest_common_error_codes
@@ -319,21 +349,6 @@ class SppTopo(object):
             topo_doc += "commands/experimental.html"
             print("See '%s' for required packages." % topo_doc)
 
-    def resize_graph(self, args):
-        if args == '':
-            print(self.graph_size)
-        else:
-            if '%' in args:
-                self.graph_size = args
-                print(self.graph_size)
-            elif '.' in args:
-                ii = float(args) * 100
-                self.graph_size = str(ii) + '%'
-                print(self.graph_size)
-            else:  # TODO(yasufum) add check for no number
-                self.graph_size = str(float(args) * 100) + '%'
-                print(self.graph_size)
-
     def format_sec_status(self, sec_id, stat):
         """Return formatted secondary status as a hash
 
@@ -479,18 +494,6 @@ class SppTopo(object):
 
         print(msg)
 
-    @classmethod
-    def help_resize(cls):
-        msg = """Change the size of the image of topo command.
-
-        You can specify the size by percentage or ratio.
-
-        spp > topo resize 60%  # percentage
-        spp > topo resize 0.6  # ratio
-        """
-
-        print(msg)
-
     @classmethod
     def help_subgraph(cls):
         msg = """Edit subgarph for topo command.
diff --git a/src/cli/shell.py b/src/cli/shell.py
index 34c12a1..d98cc8b 100644
--- a/src/cli/shell.py
+++ b/src/cli/shell.py
@@ -627,6 +627,8 @@ class Shell(cmd.Cmd, object):
                 # Command prompt should be updated immediately
                 if key == 'prompt':
                     self.prompt = self.cli_config['prompt']['val']
+                elif key == 'topo_size':
+                    self.spp_topo.resize(self.cli_config['topo_size']['val'])
 
     def help_config(self):
         """Print help message of config command."""
@@ -904,14 +906,6 @@ class Shell(cmd.Cmd, object):
         else:
             pass
 
-    def do_topo_resize(self, args):
-        """Change the size of the image of topo_resize command."""
-        self.spp_topo.resize_graph(args)
-
-    def help_topo_resize(self):
-        """Print help message of topo command."""
-        topo.SppTopo.help_resize()
-
     def do_topo(self, args):
         """Output network topology."""
         self.spp_topo.run(args, self.get_sec_ids('nfv'))
-- 
2.17.1


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

* [spp] [PATCH 2/8] cli: revise composing dot script
  2019-08-12  7:12 [spp] [PATCH 0/8] Update topo to support other than spp_nfv Yasufumi Ogawa
  2019-08-12  7:12 ` [spp] [PATCH 1/8] cli: remove topo_resize command Yasufumi Ogawa
@ 2019-08-12  7:12 ` Yasufumi Ogawa
  2019-08-12  7:12 ` [spp] [PATCH 3/8] cli: move style params for dot to config Yasufumi Ogawa
                   ` (5 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Yasufumi Ogawa @ 2019-08-12  7:12 UTC (permalink / raw)
  To: spp, ferruh.yigit, yasufum.o

SppTopo uses old style formatter, such as '"%s %s" % (foo, var)', for
composing dot script. It could be complex if several placeholders are
included in a string. It is better to replace latest style using
format() method.

This update is to replace all formatters for using format(), and use
labels in placeholders to be more understandable.

Signed-off-by: Yasufumi Ogawa <yasufum.o@gmail.com>
---
 src/cli/commands/topo.py | 93 +++++++++++++++++++++-------------------
 1 file changed, 50 insertions(+), 43 deletions(-)

diff --git a/src/cli/commands/topo.py b/src/cli/commands/topo.py
index 58c59d1..0d613cb 100644
--- a/src/cli/commands/topo.py
+++ b/src/cli/commands/topo.py
@@ -77,7 +77,7 @@ class SppTopo(object):
         error_codes = self.spp_ctl_cli.rest_common_error_codes
 
         for sec_id in sec_ids:
-            res = self.spp_ctl_cli.get('nfvs/%d' % sec_id)
+            res = self.spp_ctl_cli.get('nfvs/{sid}'.format(sid=sec_id))
             if res.status_code == 200:
                 res_ary.append(res.json())
             elif res.status_code in error_codes:
@@ -99,7 +99,7 @@ class SppTopo(object):
         error_codes = self.spp_ctl_cli.rest_common_error_codes
 
         for sec_id in sec_ids:
-            res = self.spp_ctl_cli.get('nfvs/%d' % sec_id)
+            res = self.spp_ctl_cli.get('nfvs/{sid}'.format(sid=sec_id))
             if res.status_code == 200:
                 res_ary.append(res.json())
             elif res.status_code in error_codes:
@@ -120,10 +120,11 @@ class SppTopo(object):
         else:
             print("Invalid file type")
             return res_ary
-        print("Create topology: '%s'" % fname)
+        print("Create topology: '{fname}'".format(fname=fname))
         return res_ary
 
     def to_dot(self, sec_list, output_fname):
+        """Output dot script."""
 
         # Graphviz params
         # TODO(yasufum) consider to move gviz params to config file.
@@ -142,7 +143,7 @@ class SppTopo(object):
 
         node_attrs = 'node[shape="rectangle", style="filled"];'
 
-        node_template = '%s' + self.delim_node + '%s'
+        node_template = '{}' + self.delim_node + '{}'
 
         phys = []
         rings = []
@@ -170,7 +171,8 @@ class SppTopo(object):
                         pass
                     else:
                         raise ValueError(
-                            "Invaid interface type: %s" % r_type)
+                            "Invaid interface type: {rtype}".format(
+                                rtype=r_type))
 
             for patch in sec['patches']:
                 if sec['status'] == 'running':
@@ -182,18 +184,18 @@ class SppTopo(object):
                     SEC_COLORS[sec["client-id"]],
                     l_style
                 )
-                link_style = node_template + ' %s ' + node_template + '%s;'
+                link_style = node_template + ' {} ' + node_template + '{};'
 
                 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,
+                tmp = link_style.format(src_type, src_id, LINK_TYPE,
                                     dst_type, dst_id, attrs)
                 links.append(tmp)
 
-        output = ["%s spp{" % GRAPH_TYPE]
+        output = ["{} spp{{".format(GRAPH_TYPE)]
         output.append("newrank=true;")
         output.append(node_attrs)
 
@@ -201,65 +203,65 @@ class SppTopo(object):
         for node in phys:
             r_type, r_id = node.split(':')
             phy_nodes.append(
-                node_template % (r_type, r_id))
+                node_template.format(r_type, r_id))
         phy_nodes = list(set(phy_nodes))
         for node in phy_nodes:
-            label = re.sub(r'%s' % self.delim_node, ':', node)
+            label = re.sub(r'{}'.format(self.delim_node), ':', node)
             output.append(
-                '%s[label="%s", fillcolor="%s"];' % (
-                    node, label, PORT_COLORS["phy"]))
+                '{n}[label="{l}", fillcolor="{c}"];'.format(
+                    n=node, l=label, c=PORT_COLORS["phy"]))
 
         ring_nodes = []
         for node in rings:
             r_type, r_id = node.split(':')
-            ring_nodes.append(node_template % (r_type, r_id))
+            ring_nodes.append(node_template.format(r_type, r_id))
         ring_nodes = list(set(ring_nodes))
         for node in ring_nodes:
-            label = re.sub(r'%s' % self.delim_node, ':', node)
+            label = re.sub(r'{}'.format(self.delim_node), ':', node)
             output.append(
-                '%s[label="%s", fillcolor="%s"];' % (
-                    node, label, PORT_COLORS["ring"]))
+                '{n}[label="{l}", fillcolor="{c}"];'.format(
+                    n=node, l=label, c=PORT_COLORS["ring"]))
 
         vhost_nodes = []
         for node in vhosts:
             r_type, r_id = node.split(':')
-            vhost_nodes.append(node_template % (r_type, r_id))
+            vhost_nodes.append(node_template.format(r_type, r_id))
         vhost_nodes = list(set(vhost_nodes))
         for node in vhost_nodes:
-            label = re.sub(r'%s' % self.delim_node, ':', node)
+            label = re.sub(r'{}'.format(self.delim_node), ':', node)
             output.append(
-                '%s[label="%s", fillcolor="%s"];' % (
-                    node, label, PORT_COLORS["vhost"]))
+                '{n}[label="{l}", fillcolor="{c}"];'.format(
+                    n=node, l=label, c=PORT_COLORS["vhost"]))
 
         # Align the same type of nodes with rank attribute
         output.append(
-            '{rank=same; %s}' % ("; ".join(ring_nodes)))
+            '{{rank=same; {rn}}}'.format(rn="; ".join(ring_nodes)))
         output.append(
-            '{rank=same; %s}' % ("; ".join(vhost_nodes)))
+            '{{rank=same; {vn}}}'.format(vn="; ".join(vhost_nodes)))
 
         # Decide the bottom, phy or vhost
-        rank_style = '{rank=max; %s}' % node_template
+        rank_style = '{{rank=max; ' + node_template + '}}'
         if len(phys) > 0:
             r_type, r_id = phys[0].split(':')
         elif len(vhosts) > 0:
             r_type, r_id = vhosts[0].split(':')
-        output.append(rank_style % (r_type, r_id))
+        output.append(rank_style.format(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)))
+                '{{rank=same; {pn}}}'.format(pn="; ".join(phy_nodes)))
 
         # Add subgraph
         ssgs = []
         if len(self.subgraphs) > 0:
             cnt = 1
             for label, val in self.subgraphs.items():
-                cluster_id = "cluster%d" % cnt
+                cluster_id = "cluster{}".format(cnt)
                 ssg_label = label
                 ssg_ports = re.sub(r'%s' % ':', self.delim_node, val)
-                ssg = 'subgraph %s {label="%s" %s}' % (
-                    cluster_id, ssg_label, ssg_ports)
+                ssg = 'subgraph {cid} {{label="{ssgl}" {ssgp}}}'.format(
+                    cid=cluster_id, ssgl=ssg_label, ssgp=ssg_ports)
                 ssgs.append(ssg)
                 cnt += 1
 
@@ -268,13 +270,14 @@ class SppTopo(object):
         sg_ports = "; ".join(phy_nodes + ring_nodes)
         if len(ssgs) == 0:
             output.append(
-                'subgraph %s {label="%s" %s}' % (
-                    cluster_id, sg_label, sg_ports))
+                'subgraph {cid} {{label="{sgl}" {sgp}}}'.format(
+                    cid=cluster_id, sgl=sg_label, sgp=sg_ports))
         else:
-            tmp = 'label="%s" %s' % (sg_label, sg_ports)
+            tmp = 'label="{sgl}" {sgp}'.format(sgl=sg_label, sgp=sg_ports)
             contents = [tmp] + ssgs
             output.append(
-                'subgraph %s {%s}' % (cluster_id, '; '.join(contents)))
+                'subgraph {cid} {{{cont}}}'.format(
+                    cid=cluster_id, cont='; '.join(contents)))
 
         # Add links
         for link in links:
@@ -299,19 +302,21 @@ class SppTopo(object):
         f.close()
 
     def to_img(self, sec_list, output_fname):
-        tmpfile = "%s.dot" % uuid.uuid4().hex
+        tmpfile = "{fn}.dot".format(fn=uuid.uuid4().hex)
         self.to_dot(sec_list, tmpfile)
         fmt = output_fname.split(".")[-1]
-        cmd = "dot -T%s %s -o %s" % (fmt, tmpfile, output_fname)
+        cmd = "dot -T{fmt} {dotf} -o {of}".format(
+                fmt=fmt, dotf=tmpfile, of=output_fname)
         subprocess.call(cmd, shell=True)
-        subprocess.call("rm -f %s" % tmpfile, shell=True)
+        subprocess.call("rm -f {tmpf}".format(tmpf=tmpfile), shell=True)
 
     def to_http(self, sec_list):
         import websocket
-        tmpfile = "%s.dot" % uuid.uuid4().hex
+        tmpfile = "{fn}.dot".format(fn=uuid.uuid4().hex)
         self.to_dot(sec_list, tmpfile)
         msg = open(tmpfile).read()
-        subprocess.call("rm -f %s" % tmpfile, shell=True)
+        subprocess.call("rm -f {tmpf}".format(tmpf=tmpfile), shell=True)
+        # TODO(yasufum) change to be able to use other than `localhost`.
         ws_url = "ws://localhost:8989/spp_ws"
         try:
             ws = websocket.create_connection(ws_url)
@@ -321,7 +326,7 @@ class SppTopo(object):
             print('Error: Connection refused! Is running websocket server?')
 
     def to_term(self, sec_list, size):
-        tmpfile = "%s.jpg" % uuid.uuid4().hex
+        tmpfile = "{fn}.jpg".format(fn=uuid.uuid4().hex)
         self.to_img(sec_list, tmpfile)
         from distutils import spawn
 
@@ -329,8 +334,8 @@ class SppTopo(object):
         if spawn.find_executable("img2sixel") is not None:
             img_cmd = "img2sixel"
         else:
-            imgcat = "%s/%s/imgcat" % (
-                os.path.dirname(__file__), '3rd_party')
+            imgcat = "{pdir}/{sdir}/imgcat".format(
+                pdir=os.path.dirname(__file__), sdir='3rd_party')
             if os.path.exists(imgcat) is True:
                 img_cmd = imgcat
             else:
@@ -339,15 +344,17 @@ class SppTopo(object):
         if img_cmd is not None:
             # Resize image to fit the terminal
             img_size = size
-            cmd = "convert -resize %s %s %s" % (img_size, tmpfile, tmpfile)
+            cmd = "convert -resize {size} {fn1} {fn2}".format(
+                    size=img_size, fn1=tmpfile, fn2=tmpfile)
             subprocess.call(cmd, shell=True)
-            subprocess.call("%s %s" % (img_cmd, tmpfile), shell=True)
+            subprocess.call("{cmd} {fn}".format(cmd=img_cmd, fn=tmpfile),
+                    shell=True)
             subprocess.call(["rm", "-f", tmpfile])
         else:
             print("img2sixel (or imgcat.sh for MacOS) not found!")
             topo_doc = "https://spp.readthedocs.io/en/latest/"
             topo_doc += "commands/experimental.html"
-            print("See '%s' for required packages." % topo_doc)
+            print("See '{url}' for required packages.".format(url=topo_doc))
 
     def format_sec_status(self, sec_id, stat):
         """Return formatted secondary status as a hash
-- 
2.17.1


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

* [spp] [PATCH 3/8] cli: move style params for dot to config
  2019-08-12  7:12 [spp] [PATCH 0/8] Update topo to support other than spp_nfv Yasufumi Ogawa
  2019-08-12  7:12 ` [spp] [PATCH 1/8] cli: remove topo_resize command Yasufumi Ogawa
  2019-08-12  7:12 ` [spp] [PATCH 2/8] cli: revise composing dot script Yasufumi Ogawa
@ 2019-08-12  7:12 ` Yasufumi Ogawa
  2019-08-12  7:12 ` [spp] [PATCH 4/8] cli: add to get sec ID and procs to SppCtlClient Yasufumi Ogawa
                   ` (4 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Yasufumi Ogawa @ 2019-08-12  7:12 UTC (permalink / raw)
  To: spp, ferruh.yigit, yasufum.o

This update is to move style parameters defined in `topo.py` , such as
node colors or link colors, to `topo.yml`.

Signed-off-by: Yasufumi Ogawa <yasufum.o@gmail.com>
---
 src/cli/commands/topo.py   | 44 ++++++++++++++++----------------------
 src/cli/config/default.yml |  8 ++++---
 src/cli/config/topo.yml    |  8 +++++++
 src/cli/shell.py           |  2 +-
 4 files changed, 33 insertions(+), 29 deletions(-)
 create mode 100644 src/cli/config/topo.yml

diff --git a/src/cli/commands/topo.py b/src/cli/commands/topo.py
index 0d613cb..86495c8 100644
--- a/src/cli/commands/topo.py
+++ b/src/cli/commands/topo.py
@@ -22,12 +22,21 @@ class SppTopo(object):
 
     delim_node = '_'
 
-    def __init__(self, spp_ctl_cli, subgraphs, size):
+    def __init__(self, spp_ctl_cli, subgraphs, cli_config):
         self.spp_ctl_cli = spp_ctl_cli
         self.subgraphs = subgraphs
         self.graph_size = None
 
-        if self.resize(size) is not True:
+        # Graphviz params
+        topo_file = '{dir}/../config/topo.yml'.format(dir=os.path.dirname(__file__))
+        topo_conf = yaml.load(open(topo_file), Loader=yaml.FullLoader)
+        self.SEC_COLORS = topo_conf['topo_sec_colors']['val']
+        self.PORT_COLORS = topo_conf['topo_port_colors']['val']
+        self.LINE_STYLE = {"running": "solid", "idling": "dashed"}
+        self.GRAPH_TYPE = "digraph"
+        self.LINK_TYPE = "->"
+
+        if self.resize(cli_config['topo_size']['val']) is not True:
             print('Config "topo_size" is invalid value.')
             exit()
 
@@ -126,21 +135,6 @@ class SppTopo(object):
     def to_dot(self, sec_list, output_fname):
         """Output dot script."""
 
-        # Graphviz params
-        # TODO(yasufum) consider to move gviz params to config file.
-        SEC_COLORS = [
-            "blue", "green", "orange", "chocolate", "black",
-            "cyan3", "green3", "indianred", "lawngreen", "limegreen"]
-        PORT_COLORS = {
-            "phy": "white",
-            "ring": "yellow",
-            "vhost": "limegreen"}
-        LINE_STYLE = {
-            "running": "solid",
-            "idling": "dashed"}
-        GRAPH_TYPE = "digraph"
-        LINK_TYPE = "->"
-
         node_attrs = 'node[shape="rectangle", style="filled"];'
 
         node_template = '{}' + self.delim_node + '{}'
@@ -176,12 +170,12 @@ class SppTopo(object):
 
             for patch in sec['patches']:
                 if sec['status'] == 'running':
-                    l_style = LINE_STYLE["running"]
+                    l_style = self.LINE_STYLE["running"]
                 else:
-                    l_style = LINE_STYLE["idling"]
+                    l_style = self.LINE_STYLE["idling"]
                 attrs = '[label="%s", color="%s", style="%s"]' % (
                     "sec%d" % sec["client-id"],
-                    SEC_COLORS[sec["client-id"]],
+                    self.SEC_COLORS[sec["client-id"]],
                     l_style
                 )
                 link_style = node_template + ' {} ' + node_template + '{};'
@@ -191,11 +185,11 @@ class SppTopo(object):
                 if self._is_valid_port(patch['dst']):
                     dst_type, dst_id = patch['dst'].split(':')
 
-                tmp = link_style.format(src_type, src_id, LINK_TYPE,
+                tmp = link_style.format(src_type, src_id, self.LINK_TYPE,
                                     dst_type, dst_id, attrs)
                 links.append(tmp)
 
-        output = ["{} spp{{".format(GRAPH_TYPE)]
+        output = ["{} spp{{".format(self.GRAPH_TYPE)]
         output.append("newrank=true;")
         output.append(node_attrs)
 
@@ -209,7 +203,7 @@ class SppTopo(object):
             label = re.sub(r'{}'.format(self.delim_node), ':', node)
             output.append(
                 '{n}[label="{l}", fillcolor="{c}"];'.format(
-                    n=node, l=label, c=PORT_COLORS["phy"]))
+                    n=node, l=label, c=self.PORT_COLORS["phy"]))
 
         ring_nodes = []
         for node in rings:
@@ -220,7 +214,7 @@ class SppTopo(object):
             label = re.sub(r'{}'.format(self.delim_node), ':', node)
             output.append(
                 '{n}[label="{l}", fillcolor="{c}"];'.format(
-                    n=node, l=label, c=PORT_COLORS["ring"]))
+                    n=node, l=label, c=self.PORT_COLORS["ring"]))
 
         vhost_nodes = []
         for node in vhosts:
@@ -231,7 +225,7 @@ class SppTopo(object):
             label = re.sub(r'{}'.format(self.delim_node), ':', node)
             output.append(
                 '{n}[label="{l}", fillcolor="{c}"];'.format(
-                    n=node, l=label, c=PORT_COLORS["vhost"]))
+                    n=node, l=label, c=self.PORT_COLORS["vhost"]))
 
         # Align the same type of nodes with rank attribute
         output.append(
diff --git a/src/cli/config/default.yml b/src/cli/config/default.yml
index a746c9a..60aaef7 100644
--- a/src/cli/config/default.yml
+++ b/src/cli/config/default.yml
@@ -5,9 +5,6 @@ max_secondary:
 prompt:
     val: "spp > "
     desc: Command prompt
-topo_size:
-    val: 60%
-    desc: Percentage or ratio of topo
 
 # Secondary common config
 sec_mem:
@@ -45,3 +42,8 @@ sec_pcap_nof_lcores:
 sec_pcap_port:
     val: "phy:0"
     desc: Default captured port
+
+# topo
+topo_size:
+    val: 60%
+    desc: Percentage or ratio of topo
diff --git a/src/cli/config/topo.yml b/src/cli/config/topo.yml
new file mode 100644
index 0000000..fa5497e
--- /dev/null
+++ b/src/cli/config/topo.yml
@@ -0,0 +1,8 @@
+topo_sec_colors:
+    val: ["blue", "green", "orange", "chocolate", "black", "cyan3",
+          "green3", "indianred", "lawngreen", "limegreen"]
+    desc: Line colors for secondary processes
+topo_port_colors:
+    val: {"phy": "white", "ring": "yellow", "vhost": "azure",
+          "tap": "cornsilk", "nullpmd": "cyan"}
+    desc: Port colors
diff --git a/src/cli/shell.py b/src/cli/shell.py
index d98cc8b..8eae982 100644
--- a/src/cli/shell.py
+++ b/src/cli/shell.py
@@ -73,7 +73,7 @@ class Shell(cmd.Cmd, object):
         self.use_cache = use_cache
         self.init_spp_procs()
         self.spp_topo = topo.SppTopo(
-                self.spp_ctl_cli, {}, self.cli_config['topo_size']['val'])
+                self.spp_ctl_cli, {}, self.cli_config)
 
         common.set_current_server_addr(
                 self.spp_ctl_cli.ip_addr, self.spp_ctl_cli.port)
-- 
2.17.1


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

* [spp] [PATCH 4/8] cli: add to get sec ID and procs to SppCtlClient
  2019-08-12  7:12 [spp] [PATCH 0/8] Update topo to support other than spp_nfv Yasufumi Ogawa
                   ` (2 preceding siblings ...)
  2019-08-12  7:12 ` [spp] [PATCH 3/8] cli: move style params for dot to config Yasufumi Ogawa
@ 2019-08-12  7:12 ` Yasufumi Ogawa
  2019-08-12  7:12 ` [spp] [PATCH 5/8] cli: support other than spp_nfv in topo command Yasufumi Ogawa
                   ` (3 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Yasufumi Ogawa @ 2019-08-12  7:12 UTC (permalink / raw)
  To: spp, ferruh.yigit, yasufum.o

Shell class has get_sec_ids() for retrieving secondary IDs of given
type by requesting all processes information to spp-ctl. However, it is
better to request the information from SppCtlClient which a delegator
for the requesting.

This update is to remove get_sec_ids() from Shell and add the same
method to SppCtlClient.

In addition to above update,  add get_sec_procs() to SppCtlClient to be
able to retrieve other than spp_nfv in topo. By this change, all types
of secondary process can be referred from anywhere in SppTopo so that
there is no need to pass `sec_list` as an argument of member methods.

Supporting other than spp_nfv in topo command is added in the next
patches.

Signed-off-by: Yasufumi Ogawa <yasufum.o@gmail.com>
---
 src/cli/commands/topo.py  | 111 ++++++++++++++------------------------
 src/cli/shell.py          |  48 ++++++-----------
 src/cli/spp_ctl_client.py |  42 +++++++++++++++
 3 files changed, 99 insertions(+), 102 deletions(-)

diff --git a/src/cli/commands/topo.py b/src/cli/commands/topo.py
index 86495c8..96284c7 100644
--- a/src/cli/commands/topo.py
+++ b/src/cli/commands/topo.py
@@ -28,7 +28,8 @@ class SppTopo(object):
         self.graph_size = None
 
         # Graphviz params
-        topo_file = '{dir}/../config/topo.yml'.format(dir=os.path.dirname(__file__))
+        topo_file = '{dir}/../config/topo.yml'.format(
+                dir=os.path.dirname(__file__))
         topo_conf = yaml.load(open(topo_file), Loader=yaml.FullLoader)
         self.SEC_COLORS = topo_conf['topo_sec_colors']['val']
         self.PORT_COLORS = topo_conf['topo_port_colors']['val']
@@ -40,18 +41,20 @@ class SppTopo(object):
             print('Config "topo_size" is invalid value.')
             exit()
 
-    def run(self, args, sec_ids):
+    def run(self, args):
         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], sec_ids, self.graph_size)
-        elif len(args_ary) == 1:
+        elif args_ary[0] == "term":
+            self.to_term(self.graph_size)
+        elif args_ary[0] == "http":
+            self.to_http()
+        elif len(args_ary) == 1:  # find ftype from filename
             ftype = args_ary[0].split(".")[-1]
-            self.output(args_ary[0], sec_ids, ftype)
-        elif len(args_ary) == 2:
-            self.output(args_ary[0], sec_ids, args_ary[1])
+            self.to_file(args_ary[0], ftype)
+        elif len(args_ary) == 2:  # ftype is given as args_ary[1]
+            self.to_file(args_ary[0], args_ary[1])
         else:
             print("Usage: topo dst [ftype]")
 
@@ -66,12 +69,12 @@ class SppTopo(object):
         matched = re.match(r'(\d+)%$', size)
         if matched:  # percentage
             i = int(matched.group(1))
-            if i > 0 and  i <= 100:
+            if i > 0 and i <= 100:
                 self.graph_size = size
                 return True
             else:
                 return False
-        elif re.match(r'0\.\d+$',size):  # ratio
+        elif re.match(r'0\.\d+$', size):  # ratio
             i = float(size) * 100
             self.graph_size = str(i) + '%'
             return True
@@ -81,58 +84,22 @@ class SppTopo(object):
         else:
             return False
 
-    def show(self, dtype, sec_ids, size):
-        res_ary = []
-        error_codes = self.spp_ctl_cli.rest_common_error_codes
-
-        for sec_id in sec_ids:
-            res = self.spp_ctl_cli.get('nfvs/{sid}'.format(sid=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")
-
-    def output(self, fname, sec_ids, ftype="dot"):
-        res_ary = []
-        error_codes = self.spp_ctl_cli.rest_common_error_codes
-
-        for sec_id in sec_ids:
-            res = self.spp_ctl_cli.get('nfvs/{sid}'.format(sid=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
-
+    def to_file(self, fname, ftype="dot"):
         if ftype == "dot":
-            self.to_dot(res_ary, fname)
+            self.to_dot(fname)
         elif ftype == "json" or ftype == "js":
-            self.to_json(res_ary, fname)
+            self.to_json(fname)
         elif ftype == "yaml" or ftype == "yml":
-            self.to_yaml(res_ary, fname)
+            self.to_yaml(fname)
         elif ftype == "jpg" or ftype == "png" or ftype == "bmp":
-            self.to_img(res_ary, fname)
+            self.to_img(fname)
         else:
             print("Invalid file type")
-            return res_ary
+            return False
         print("Create topology: '{fname}'".format(fname=fname))
-        return res_ary
+        return True
 
-    def to_dot(self, sec_list, output_fname):
+    def to_dot(self, output_fname):
         """Output dot script."""
 
         node_attrs = 'node[shape="rectangle", style="filled"];'
@@ -145,7 +112,7 @@ class SppTopo(object):
         links = []
 
         # parse status message from sec.
-        for sec in sec_list:
+        for sec in self.spp_ctl_cli.get_sec_procs('nfv'):
             if sec is None:
                 continue
             for port in sec['ports']:
@@ -186,7 +153,7 @@ class SppTopo(object):
                     dst_type, dst_id = patch['dst'].split(':')
 
                 tmp = link_style.format(src_type, src_id, self.LINK_TYPE,
-                                    dst_type, dst_id, attrs)
+                                        dst_type, dst_id, attrs)
                 links.append(tmp)
 
         output = ["{} spp{{".format(self.GRAPH_TYPE)]
@@ -202,8 +169,8 @@ class SppTopo(object):
         for node in phy_nodes:
             label = re.sub(r'{}'.format(self.delim_node), ':', node)
             output.append(
-                '{n}[label="{l}", fillcolor="{c}"];'.format(
-                    n=node, l=label, c=self.PORT_COLORS["phy"]))
+                '{nd}[label="{lbl}", fillcolor="{col}"];'.format(
+                    nd=node, lbl=label, col=self.PORT_COLORS["phy"]))
 
         ring_nodes = []
         for node in rings:
@@ -213,8 +180,8 @@ class SppTopo(object):
         for node in ring_nodes:
             label = re.sub(r'{}'.format(self.delim_node), ':', node)
             output.append(
-                '{n}[label="{l}", fillcolor="{c}"];'.format(
-                    n=node, l=label, c=self.PORT_COLORS["ring"]))
+                '{nd}[label="{lbl}", fillcolor="{col}"];'.format(
+                    nd=node, lbl=label, col=self.PORT_COLORS["ring"]))
 
         vhost_nodes = []
         for node in vhosts:
@@ -224,8 +191,8 @@ class SppTopo(object):
         for node in vhost_nodes:
             label = re.sub(r'{}'.format(self.delim_node), ':', node)
             output.append(
-                '{n}[label="{l}", fillcolor="{c}"];'.format(
-                    n=node, l=label, c=self.PORT_COLORS["vhost"]))
+                '{nd}[label="{lbl}", fillcolor="{col}"];'.format(
+                    nd=node, lbl=label, col=self.PORT_COLORS["vhost"]))
 
         # Align the same type of nodes with rank attribute
         output.append(
@@ -283,31 +250,33 @@ class SppTopo(object):
         f.write("\n".join(output))
         f.close()
 
-    def to_json(self, sec_list, output_fname):
+    def to_json(self, output_fname):
         import json
         f = open(output_fname, "w+")
+        sec_list = self.spp_ctl_cli.get_sec_procs('nfv')
         f.write(json.dumps(sec_list))
         f.close()
 
-    def to_yaml(self, sec_list, output_fname):
+    def to_yaml(self, output_fname):
         import yaml
         f = open(output_fname, "w+")
+        sec_list = self.spp_ctl_cli.get_sec_procs('nfv')
         f.write(yaml.dump(sec_list))
         f.close()
 
-    def to_img(self, sec_list, output_fname):
+    def to_img(self, output_fname):
         tmpfile = "{fn}.dot".format(fn=uuid.uuid4().hex)
-        self.to_dot(sec_list, tmpfile)
+        self.to_dot(tmpfile)
         fmt = output_fname.split(".")[-1]
         cmd = "dot -T{fmt} {dotf} -o {of}".format(
                 fmt=fmt, dotf=tmpfile, of=output_fname)
         subprocess.call(cmd, shell=True)
         subprocess.call("rm -f {tmpf}".format(tmpf=tmpfile), shell=True)
 
-    def to_http(self, sec_list):
+    def to_http(self):
         import websocket
         tmpfile = "{fn}.dot".format(fn=uuid.uuid4().hex)
-        self.to_dot(sec_list, tmpfile)
+        self.to_dot(tmpfile)
         msg = open(tmpfile).read()
         subprocess.call("rm -f {tmpf}".format(tmpf=tmpfile), shell=True)
         # TODO(yasufum) change to be able to use other than `localhost`.
@@ -319,9 +288,9 @@ class SppTopo(object):
         except socket.error:
             print('Error: Connection refused! Is running websocket server?')
 
-    def to_term(self, sec_list, size):
+    def to_term(self, size):
         tmpfile = "{fn}.jpg".format(fn=uuid.uuid4().hex)
-        self.to_img(sec_list, tmpfile)
+        self.to_img(tmpfile)
         from distutils import spawn
 
         # TODO(yasufum) add check for using only supported terminal
@@ -342,7 +311,7 @@ class SppTopo(object):
                     size=img_size, fn1=tmpfile, fn2=tmpfile)
             subprocess.call(cmd, shell=True)
             subprocess.call("{cmd} {fn}".format(cmd=img_cmd, fn=tmpfile),
-                    shell=True)
+                            shell=True)
             subprocess.call(["rm", "-f", tmpfile])
         else:
             print("img2sixel (or imgcat.sh for MacOS) not found!")
diff --git a/src/cli/shell.py b/src/cli/shell.py
index 8eae982..ebea911 100644
--- a/src/cli/shell.py
+++ b/src/cli/shell.py
@@ -90,22 +90,22 @@ class Shell(cmd.Cmd, object):
 
         self.secondaries = {}
         self.secondaries['nfv'] = {}
-        for sec_id in self.get_sec_ids('nfv'):
+        for sec_id in self.spp_ctl_cli.get_sec_ids('nfv'):
             self.secondaries['nfv'][sec_id] = nfv.SppNfv(
                     self.spp_ctl_cli, sec_id)
 
         self.secondaries['vf'] = {}
-        for sec_id in self.get_sec_ids('vf'):
+        for sec_id in self.spp_ctl_cli.get_sec_ids('vf'):
             self.secondaries['vf'][sec_id] = vf.SppVf(
                     self.spp_ctl_cli, sec_id)
 
         self.secondaries['mirror'] = {}
-        for sec_id in self.get_sec_ids('mirror'):
+        for sec_id in self.spp_ctl_cli.get_sec_ids('mirror'):
             self.secondaries['mirror'][sec_id] = mirror.SppMirror(
                     self.spp_ctl_cli, sec_id)
 
         self.secondaries['pcap'] = {}
-        for sec_id in self.get_sec_ids('pcap'):
+        for sec_id in self.spp_ctl_cli.get_sec_ids('pcap'):
             self.secondaries['pcap'][sec_id] = pcap.SppPcap(
                     self.spp_ctl_cli, sec_id)
 
@@ -142,24 +142,6 @@ class Shell(cmd.Cmd, object):
         """
         pass
 
-    def get_sec_ids(self, ptype):
-        """Return a list of IDs of running secondary processes.
-
-        'ptype' is 'nfv', 'vf' or 'mirror' or 'pcap'.
-        """
-
-        ids = []
-        res = self.spp_ctl_cli.get('processes')
-        if res is not None:
-            if res.status_code == 200:
-                try:
-                    for ent in res.json():
-                        if ent['type'] == ptype:
-                            ids.append(ent['client-id'])
-                except KeyError as e:
-                    print('Error: {} is not defined!'.format(e))
-        return ids
-
     def print_status(self):
         """Display information about connected clients."""
 
@@ -347,7 +329,7 @@ class Shell(cmd.Cmd, object):
         tokens = line.split(';')
         if len(tokens) == 1:
             # Add SppNfv of sec_id if it is not exist
-            sec_ids = self.get_sec_ids('nfv')
+            sec_ids = self.spp_ctl_cli.get_sec_ids('nfv')
             for idx in sec_ids:
                 if self.secondaries['nfv'][idx] is None:
                     self.secondaries['nfv'][idx] = nfv.SppNfv(
@@ -372,7 +354,8 @@ class Shell(cmd.Cmd, object):
                             self.spp_ctl_cli, idx)
 
                 res = self.secondaries['nfv'][idx].complete(
-                        self.get_sec_ids('nfv'), text, line, begidx, endidx)
+                        self.spp_ctl_cli.get_sec_ids('nfv'),
+                        text, line, begidx, endidx)
 
                 # logger.info(res)
                 return res
@@ -407,7 +390,7 @@ class Shell(cmd.Cmd, object):
         tokens = line.split(';')
         if len(tokens) == 1:
             # Add SppVf of sec_id if it is not exist
-            sec_ids = self.get_sec_ids('vf')
+            sec_ids = self.spp_ctl_cli.get_sec_ids('vf')
             for idx in sec_ids:
                 if self.secondaries['vf'][idx] is None:
                     self.secondaries['vf'][idx] = vf.SppVf(
@@ -432,7 +415,8 @@ class Shell(cmd.Cmd, object):
                             self.spp_ctl_cli, idx)
 
                 return self.secondaries['vf'][idx].complete(
-                        self.get_sec_ids('vf'), text, line, begidx, endidx)
+                        self.spp_ctl_cli.get_sec_ids('vf'),
+                        text, line, begidx, endidx)
 
     def do_mirror(self, cmd):
         """Send a command to spp_mirror."""
@@ -463,7 +447,7 @@ class Shell(cmd.Cmd, object):
         tokens = line.split(';')
         if len(tokens) == 1:
             # Add SppMirror of sec_id if it is not exist
-            sec_ids = self.get_sec_ids('mirror')
+            sec_ids = self.spp_ctl_cli.get_sec_ids('mirror')
             for idx in sec_ids:
                 if self.secondaries['mirror'][idx] is None:
                     self.secondaries['mirror'][idx] = mirror.SppMirror(
@@ -489,7 +473,8 @@ class Shell(cmd.Cmd, object):
                             self.spp_ctl_cli, idx)
 
                 return self.secondaries['mirror'][idx].complete(
-                        self.get_sec_ids('mirror'), text, line, begidx, endidx)
+                        self.spp_ctl_cli.get_sec_ids('mirror'),
+                        text, line, begidx, endidx)
 
     def do_pcap(self, cmd):
         """Send a command to spp_pcap."""
@@ -520,7 +505,7 @@ class Shell(cmd.Cmd, object):
         tokens = line.split(';')
         if len(tokens) == 1:
             # Add SppPcap of sec_id if it is not exist
-            sec_ids = self.get_sec_ids('pcap')
+            sec_ids = self.spp_ctl_cli.get_sec_ids('pcap')
             for idx in sec_ids:
                 if self.secondaries['pcap'][idx] is None:
                     self.secondaries['pcap'][idx] = pcap.SppPcap(
@@ -546,7 +531,8 @@ class Shell(cmd.Cmd, object):
                             self.spp_ctl_cli, idx)
 
                 return self.secondaries['pcap'][idx].complete(
-                        self.get_sec_ids('pcap'), text, line, begidx, endidx)
+                        self.spp_ctl_cli.get_sec_ids('pcap'),
+                        text, line, begidx, endidx)
 
     def do_record(self, fname):
         """Save commands as a recipe file."""
@@ -908,7 +894,7 @@ class Shell(cmd.Cmd, object):
 
     def do_topo(self, args):
         """Output network topology."""
-        self.spp_topo.run(args, self.get_sec_ids('nfv'))
+        self.spp_topo.run(args)
 
     def help_topo(self):
         """Print help message of topo command."""
diff --git a/src/cli/spp_ctl_client.py b/src/cli/spp_ctl_client.py
index 3cce628..ff95136 100644
--- a/src/cli/spp_ctl_client.py
+++ b/src/cli/spp_ctl_client.py
@@ -68,3 +68,45 @@ class SppCtlClient(object):
             return False
         else:
             return True
+
+    def get_sec_ids(self, ptype):
+        """Return a list of IDs of running secondary processes.
+
+        'ptype' is 'nfv', 'vf' or 'mirror' or 'pcap'.
+        """
+
+        ids = []
+        res = self.get('processes')
+        if res is not None:
+            if res.status_code == 200:
+                try:
+                    for ent in res.json():
+                        if ent['type'] == ptype:
+                            ids.append(ent['client-id'])
+                except KeyError as e:
+                    print('Error: {} is not defined!'.format(e))
+        return ids
+
+    def get_sec_procs(self, ptype):
+        """Get secondary processes info of given type.
+
+        Processes info from spp-ctl is retrieved as JSON, then loaded with
+        json() before returned.
+        """
+
+        sec_procs = []
+        error_codes = self.rest_common_error_codes
+
+        for sec_id in self.get_sec_ids(ptype):
+            # NOTE: take care API's proc type are defined as plural such as
+            # 'nfvs', 'vfs' or so.
+            res = self.get('{pt}s/{sid}'.format(pt=ptype, sid=sec_id))
+            if res.status_code == 200:
+                sec_procs.append(res.json())
+            elif res.status_code in error_codes:
+                # TODO(yasufum) Print default error message
+                pass
+            else:
+                # Ignore unknown response because no problem for drawing graph
+                pass
+        return sec_procs
-- 
2.17.1


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

* [spp] [PATCH 5/8] cli: support other than spp_nfv in topo command
  2019-08-12  7:12 [spp] [PATCH 0/8] Update topo to support other than spp_nfv Yasufumi Ogawa
                   ` (3 preceding siblings ...)
  2019-08-12  7:12 ` [spp] [PATCH 4/8] cli: add to get sec ID and procs to SppCtlClient Yasufumi Ogawa
@ 2019-08-12  7:12 ` Yasufumi Ogawa
  2019-08-12  7:12 ` [spp] [PATCH 6/8] cli: add port types for " Yasufumi Ogawa
                   ` (2 subsequent siblings)
  7 siblings, 0 replies; 9+ messages in thread
From: Yasufumi Ogawa @ 2019-08-12  7:12 UTC (permalink / raw)
  To: spp, ferruh.yigit, yasufum.o

This update is to add support for spp_vf, spp_mirror and spp_pcap in
addition to spp_nfv.

Signed-off-by: Yasufumi Ogawa <yasufum.o@gmail.com>
---
 src/cli/commands/topo.py | 330 +++++++++++++++++++++++++++------------
 src/cli/config/topo.yml  |   2 +-
 2 files changed, 233 insertions(+), 99 deletions(-)

diff --git a/src/cli/commands/topo.py b/src/cli/commands/topo.py
index 96284c7..bfd92f0 100644
--- a/src/cli/commands/topo.py
+++ b/src/cli/commands/topo.py
@@ -8,6 +8,7 @@ import subprocess
 import traceback
 import uuid
 import yaml
+from .. import spp_common
 
 
 class SppTopo(object):
@@ -36,8 +37,12 @@ class SppTopo(object):
         self.LINE_STYLE = {"running": "solid", "idling": "dashed"}
         self.GRAPH_TYPE = "digraph"
         self.LINK_TYPE = "->"
+        self.SPP_PCAP_LABEL = 'spppcap'  # Label of dummy port of spp_pcap
 
-        if self.resize(cli_config['topo_size']['val']) is not True:
+        # Add colors for custom ports
+        self.PORT_COLORS[self.SPP_PCAP_LABEL] = "gold2"
+
+        if self.resize_graph(cli_config['topo_size']['val']) is not True:
             print('Config "topo_size" is invalid value.')
             exit()
 
@@ -58,7 +63,7 @@ class SppTopo(object):
         else:
             print("Usage: topo dst [ftype]")
 
-    def resize(self, size):
+    def resize_graph(self, size):
         """Parse given size and set to self.graph_size.
 
         The format of `size` is percentage or ratio. Return True if succeeded
@@ -103,116 +108,49 @@ class SppTopo(object):
         """Output dot script."""
 
         node_attrs = 'node[shape="rectangle", style="filled"];'
-
         node_template = '{}' + self.delim_node + '{}'
 
-        phys = []
-        rings = []
-        vhosts = []
-        links = []
+        ports, links = self._get_dot_elements(node_template)
 
-        # parse status message from sec.
-        for sec in self.spp_ctl_cli.get_sec_procs('nfv'):
-            if sec is None:
-                continue
-            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:
-                        raise ValueError(
-                            "Invaid interface type: {rtype}".format(
-                                rtype=r_type))
-
-            for patch in sec['patches']:
-                if sec['status'] == 'running':
-                    l_style = self.LINE_STYLE["running"]
-                else:
-                    l_style = self.LINE_STYLE["idling"]
-                attrs = '[label="%s", color="%s", style="%s"]' % (
-                    "sec%d" % sec["client-id"],
-                    self.SEC_COLORS[sec["client-id"]],
-                    l_style
-                )
-                link_style = node_template + ' {} ' + node_template + '{};'
-
-                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.format(src_type, src_id, self.LINK_TYPE,
-                                        dst_type, dst_id, attrs)
-                links.append(tmp)
+        # Remove duplicated entries.
+        for ptype in spp_common.PORT_TYPES:
+            ports[ptype] = list(set(ports[ptype]))
 
         output = ["{} spp{{".format(self.GRAPH_TYPE)]
         output.append("newrank=true;")
         output.append(node_attrs)
 
-        phy_nodes = []
-        for node in phys:
-            r_type, r_id = node.split(':')
-            phy_nodes.append(
-                node_template.format(r_type, r_id))
-        phy_nodes = list(set(phy_nodes))
-        for node in phy_nodes:
-            label = re.sub(r'{}'.format(self.delim_node), ':', node)
-            output.append(
-                '{nd}[label="{lbl}", fillcolor="{col}"];'.format(
-                    nd=node, lbl=label, col=self.PORT_COLORS["phy"]))
-
-        ring_nodes = []
-        for node in rings:
-            r_type, r_id = node.split(':')
-            ring_nodes.append(node_template.format(r_type, r_id))
-        ring_nodes = list(set(ring_nodes))
-        for node in ring_nodes:
-            label = re.sub(r'{}'.format(self.delim_node), ':', node)
-            output.append(
-                '{nd}[label="{lbl}", fillcolor="{col}"];'.format(
-                    nd=node, lbl=label, col=self.PORT_COLORS["ring"]))
-
-        vhost_nodes = []
-        for node in vhosts:
-            r_type, r_id = node.split(':')
-            vhost_nodes.append(node_template.format(r_type, r_id))
-        vhost_nodes = list(set(vhost_nodes))
-        for node in vhost_nodes:
-            label = re.sub(r'{}'.format(self.delim_node), ':', node)
-            output.append(
-                '{nd}[label="{lbl}", fillcolor="{col}"];'.format(
-                    nd=node, lbl=label, col=self.PORT_COLORS["vhost"]))
+        # Setup node entries
+        all_port_types = spp_common.PORT_TYPES + [self.SPP_PCAP_LABEL]
+        nodes = {}
+        for ptype in all_port_types:
+            nodes[ptype] = []
+            for node in ports[ptype]:
+                r_type, r_id = node.split(':')
+                nodes[ptype].append(
+                    node_template.format(r_type, r_id))
+            nodes[ptype] = list(set(nodes[ptype]))
+            for node in nodes[ptype]:
+                label = re.sub(r'{}'.format(self.delim_node), ':', node)
+                output.append(
+                    '{nd}[label="{lbl}", fillcolor="{col}"];'.format(
+                        nd=node, lbl=label, col=self.PORT_COLORS[ptype]))
 
         # Align the same type of nodes with rank attribute
-        output.append(
-            '{{rank=same; {rn}}}'.format(rn="; ".join(ring_nodes)))
-        output.append(
-            '{{rank=same; {vn}}}'.format(vn="; ".join(vhost_nodes)))
+        for ptype in all_port_types:
+            if len(nodes[ptype]) > 0:
+                output.append(
+                    '{{rank=same; {}}}'.format("; ".join(nodes[ptype])))
 
         # Decide the bottom, phy or vhost
+        # TODO(yasufum) revise how to decide bottom
         rank_style = '{{rank=max; ' + node_template + '}}'
-        if len(phys) > 0:
-            r_type, r_id = phys[0].split(':')
-        elif len(vhosts) > 0:
-            r_type, r_id = vhosts[0].split(':')
+        if len(ports['phy']) > 0:
+            r_type, r_id = ports['phy'][0].split(':')
+        elif len(ports['vhost']) > 0:
+            r_type, r_id = ports['vhost'][0].split(':')
         output.append(rank_style.format(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; {pn}}}'.format(pn="; ".join(phy_nodes)))
-
         # Add subgraph
         ssgs = []
         if len(self.subgraphs) > 0:
@@ -226,9 +164,14 @@ class SppTopo(object):
                 ssgs.append(ssg)
                 cnt += 1
 
+        # Setup ports included in Host subgraph
+        host_nodes = []
+        for ptype in all_port_types:
+            host_nodes = host_nodes + nodes[ptype]
+
         cluster_id = "cluster0"
         sg_label = "Host"
-        sg_ports = "; ".join(phy_nodes + ring_nodes)
+        sg_ports = "; ".join(host_nodes)
         if len(ssgs) == 0:
             output.append(
                 'subgraph {cid} {{label="{sgl}" {sgp}}}'.format(
@@ -445,6 +388,197 @@ class SppTopo(object):
         else:
             return False
 
+    def _is_comp_running(self, comp):
+        """Find out given comp is running or not for spp_vf or spp_mirror.
+
+        Each of component has its condition for starting packet processing.
+        Return True if it is already running, or False if not.
+        """
+
+        # TODO(yasufum) implement it.
+        if comp['type'] == 'forward':  # TODO change to forwarder
+            pass
+        if comp['type'] == 'classifier':
+            pass
+        if comp['type'] == 'merge':  # TODO change to merger
+            pass
+        elif comp['type'] == 'mirror':
+            pass
+        return True
+
+    def _get_dot_elements(self, node_template):
+        """Get entries of nodes and links.
+
+        To generate dot script, this method returns ports as nodes and links
+        which are used as a part of element in dot language.
+        """
+
+        ports = {}
+        links = []
+
+        # Initialize ports
+        all_port_types = spp_common.PORT_TYPES + [self.SPP_PCAP_LABEL]
+        for ptype in all_port_types:
+            ports[ptype] = []
+
+        # parse status message from sec.
+        for proc_t in ['nfv', 'vf', 'mirror', 'pcap']:
+            for sec in self.spp_ctl_cli.get_sec_procs(proc_t):
+                if sec is None:
+                    continue
+
+                # Get ports
+                # TODO(yasufum) add try statement for handling key error
+                if proc_t in ['nfv', 'vf', 'mirror']:
+                    for port in sec['ports']:
+                        # TODO make it to a method
+                        if self._is_valid_port(port):
+                            r_type = port.split(':')[0]
+                            if r_type in spp_common.PORT_TYPES:
+                                ports[r_type].append(port)
+                            else:
+                                raise ValueError(
+                                    "Invaid interface type: {rtype}".format(
+                                        rtype=r_type))
+                elif proc_t == 'pcap':
+                    for c in sec['core']:
+                        if c['role'] == 'receive':
+                            port = c['rx_port'][0]['port']
+                            if self._is_valid_port(port):
+                                r_type = port.split(':')[0]
+                                if r_type in spp_common.PORT_TYPES:
+                                    ports[r_type].append(port)
+                                else:
+                                    raise ValueError(
+                                        "Invaid interface type: {}".format(
+                                            r_type))
+                    ports[self.SPP_PCAP_LABEL].append(
+                            '{}:{}'.format(self.SPP_PCAP_LABEL,
+                                sec['client-id']))
+
+                # Get links
+                if proc_t == 'nfv':
+                    for patch in sec['patches']:
+                        if sec['status'] == 'running':
+                            l_style = self.LINE_STYLE["running"]
+                        else:
+                            l_style = self.LINE_STYLE["idling"]
+                        attrs = '[label="{}", color="{}", style="{}"]'.format(
+                                "nfv:{}".format(sec["client-id"]),
+                                self.SEC_COLORS[sec["client-id"]],
+                                l_style)
+                        link_style = node_template + ' {} ' + node_template + '{};'
+
+                        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.format(src_type, src_id, self.LINK_TYPE,
+                                                dst_type, dst_id, attrs)
+                        links.append(tmp)
+
+                elif proc_t == 'vf':
+                    for comp in sec['components']:
+                        if self._is_comp_running(comp):
+                            l_style = self.LINE_STYLE["running"]
+                        else:
+                            l_style = self.LINE_STYLE["idling"]
+                        attrs = '[label="{}", color="{}", style="{}"]'.format(
+                                "vf:{}:{}".format(
+                                    sec["client-id"], comp['type'][0]),
+                                self.SEC_COLORS[sec["client-id"]],
+                                l_style)
+                        link_style = node_template + ' {} ' + \
+                                node_template + '{};'
+
+                        if comp['type'] == 'forward':  # TODO change to forwarder
+                            rxport = comp['rx_port'][0]['port']
+                            if self._is_valid_port(rxport):
+                                src_type, src_id = rxport.split(':')
+                            txport = comp['tx_port'][0]['port']
+                            if self._is_valid_port(txport):
+                                dst_type, dst_id = txport.split(':')
+
+                            tmp = link_style.format(src_type, src_id, self.LINK_TYPE,
+                                            dst_type, dst_id, attrs)
+                            links.append(tmp)
+
+                        elif comp['type'] == 'classifier':
+                            rxport = comp['rx_port'][0]['port']
+                            if self._is_valid_port(rxport):
+                                src_type, src_id = rxport.split(':')
+                            for txp in comp['tx_port']:
+                                if self._is_valid_port(txp['port']):
+                                    dst_type, dst_id = txp['port'].split(':')
+
+                                tmp = link_style.format(src_type, src_id, self.LINK_TYPE,
+                                                dst_type, dst_id, attrs)
+                                links.append(tmp)
+                        elif comp['type'] == 'merge':  # TODO change to merger
+                            txport = comp['tx_port'][0]['port']
+                            if self._is_valid_port(txport):
+                                dst_type, dst_id = txport.split(':')
+                            for rxp in comp['rx_port']:
+                                if self._is_valid_port(rxp['port']):
+                                    src_type, src_id = rxp['port'].split(':')
+
+                                tmp = link_style.format(src_type, src_id, self.LINK_TYPE,
+                                                dst_type, dst_id, attrs)
+                                links.append(tmp)
+
+                elif proc_t == 'mirror':
+                    for comp in sec['components']:
+                        if self._is_comp_running(comp):
+                            l_style = self.LINE_STYLE["running"]
+                        else:
+                            l_style = self.LINE_STYLE["idling"]
+                        attrs = '[label="{}", color="{}", style="{}"]'.format(
+                                "vf:{}".format(sec["client-id"]),
+                                self.SEC_COLORS[sec["client-id"]],
+                                l_style)
+                        link_style = node_template + ' {} ' + \
+                                node_template + '{};'
+
+                        rxport = comp['rx_port'][0]['port']
+                        if self._is_valid_port(rxport):
+                            src_type, src_id = rxport.split(':')
+                        for txp in comp['tx_port']:
+                            if self._is_valid_port(txp['port']):
+                                dst_type, dst_id = txp['port'].split(':')
+
+                            tmp = link_style.format(src_type, src_id, self.LINK_TYPE,
+                                            dst_type, dst_id, attrs)
+                            links.append(tmp)
+
+                elif proc_t == 'pcap':
+                    if sec['status'] == 'running':
+                        l_style = self.LINE_STYLE["running"]
+                    else:
+                        l_style = self.LINE_STYLE["idling"]
+                    attrs = '[label="{}", color="{}", style="{}"]'.format(
+                            "pcap:{}".format(sec["client-id"]),
+                            self.SEC_COLORS[sec["client-id"]],
+                            l_style)
+                    link_style = node_template + ' {} ' + node_template + '{};'
+
+                    for c in sec['core']:  # TODO consider change to component
+                        if c['role'] == 'receive':  # TODO change to receiver
+                            rxport = c['rx_port'][0]['port']
+                            if self._is_valid_port(rxport):
+                                src_type, src_id = rxport.split(':')
+                            dst_type, dst_id = self.SPP_PCAP_LABEL, sec['client-id']
+
+                    tmp = link_style.format(src_type, src_id, self.LINK_TYPE,
+                                            dst_type, dst_id, attrs)
+                    links.append(tmp)
+
+                else:
+                    # TODO(yasufum) add error handling for invalid proc types
+                    pass
+
+        return ports, links
+
     @classmethod
     def help(cls):
         msg = """Output network topology.
diff --git a/src/cli/config/topo.yml b/src/cli/config/topo.yml
index fa5497e..6ce1af2 100644
--- a/src/cli/config/topo.yml
+++ b/src/cli/config/topo.yml
@@ -3,6 +3,6 @@ topo_sec_colors:
           "green3", "indianred", "lawngreen", "limegreen"]
     desc: Line colors for secondary processes
 topo_port_colors:
-    val: {"phy": "white", "ring": "yellow", "vhost": "azure",
+    val: {"phy": "white", "ring": "yellow", "vhost": "antiquewhite",
           "tap": "cornsilk", "nullpmd": "cyan"}
     desc: Port colors
-- 
2.17.1


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

* [spp] [PATCH 6/8] cli: add port types for topo command
  2019-08-12  7:12 [spp] [PATCH 0/8] Update topo to support other than spp_nfv Yasufumi Ogawa
                   ` (4 preceding siblings ...)
  2019-08-12  7:12 ` [spp] [PATCH 5/8] cli: support other than spp_nfv in topo command Yasufumi Ogawa
@ 2019-08-12  7:12 ` " Yasufumi Ogawa
  2019-08-12  7:12 ` [spp] [PATCH 7/8] cli: add checking JSON objs in topo Yasufumi Ogawa
  2019-08-12  7:12 ` [spp] [PATCH 8/8] cli: revise description for imgcat Yasufumi Ogawa
  7 siblings, 0 replies; 9+ messages in thread
From: Yasufumi Ogawa @ 2019-08-12  7:12 UTC (permalink / raw)
  To: spp, ferruh.yigit, yasufum.o

To depict spp_pcap's output file and tap device as a port, add these
types to port type list. This update is also to fix wrong coding style.

Signed-off-by: Yasufumi Ogawa <yasufum.o@gmail.com>
---
 src/cli/commands/topo.py | 66 +++++++++++++++++++++++-----------------
 src/cli/shell.py         |  3 +-
 2 files changed, 40 insertions(+), 29 deletions(-)

diff --git a/src/cli/commands/topo.py b/src/cli/commands/topo.py
index bfd92f0..2e1bc3d 100644
--- a/src/cli/commands/topo.py
+++ b/src/cli/commands/topo.py
@@ -108,9 +108,9 @@ class SppTopo(object):
         """Output dot script."""
 
         node_attrs = 'node[shape="rectangle", style="filled"];'
-        node_template = '{}' + self.delim_node + '{}'
+        node_temp = '{}' + self.delim_node + '{}'
 
-        ports, links = self._get_dot_elements(node_template)
+        ports, links = self._get_dot_elements(node_temp)
 
         # Remove duplicated entries.
         for ptype in spp_common.PORT_TYPES:
@@ -120,15 +120,18 @@ class SppTopo(object):
         output.append("newrank=true;")
         output.append(node_attrs)
 
+        # topo should support spp_pcap's file and tap port
+        all_port_types = spp_common.PORT_TYPES + [
+                self.SPP_PCAP_LABEL, 'tap']
+
         # Setup node entries
-        all_port_types = spp_common.PORT_TYPES + [self.SPP_PCAP_LABEL]
         nodes = {}
         for ptype in all_port_types:
             nodes[ptype] = []
             for node in ports[ptype]:
                 r_type, r_id = node.split(':')
                 nodes[ptype].append(
-                    node_template.format(r_type, r_id))
+                    node_temp.format(r_type, r_id))
             nodes[ptype] = list(set(nodes[ptype]))
             for node in nodes[ptype]:
                 label = re.sub(r'{}'.format(self.delim_node), ':', node)
@@ -144,7 +147,7 @@ class SppTopo(object):
 
         # Decide the bottom, phy or vhost
         # TODO(yasufum) revise how to decide bottom
-        rank_style = '{{rank=max; ' + node_template + '}}'
+        rank_style = '{{rank=max; ' + node_temp + '}}'
         if len(ports['phy']) > 0:
             r_type, r_id = ports['phy'][0].split(':')
         elif len(ports['vhost']) > 0:
@@ -406,7 +409,7 @@ class SppTopo(object):
             pass
         return True
 
-    def _get_dot_elements(self, node_template):
+    def _get_dot_elements(self, node_temp):
         """Get entries of nodes and links.
 
         To generate dot script, this method returns ports as nodes and links
@@ -416,8 +419,10 @@ class SppTopo(object):
         ports = {}
         links = []
 
+        # topo should support spp_pcap's file and tap port
+        all_port_types = spp_common.PORT_TYPES + [self.SPP_PCAP_LABEL, 'tap']
+
         # Initialize ports
-        all_port_types = spp_common.PORT_TYPES + [self.SPP_PCAP_LABEL]
         for ptype in all_port_types:
             ports[ptype] = []
 
@@ -434,7 +439,7 @@ class SppTopo(object):
                         # TODO make it to a method
                         if self._is_valid_port(port):
                             r_type = port.split(':')[0]
-                            if r_type in spp_common.PORT_TYPES:
+                            if r_type in all_port_types:
                                 ports[r_type].append(port)
                             else:
                                 raise ValueError(
@@ -446,7 +451,7 @@ class SppTopo(object):
                             port = c['rx_port'][0]['port']
                             if self._is_valid_port(port):
                                 r_type = port.split(':')[0]
-                                if r_type in spp_common.PORT_TYPES:
+                                if r_type in all_port_types:
                                     ports[r_type].append(port)
                                 else:
                                     raise ValueError(
@@ -454,7 +459,7 @@ class SppTopo(object):
                                             r_type))
                     ports[self.SPP_PCAP_LABEL].append(
                             '{}:{}'.format(self.SPP_PCAP_LABEL,
-                                sec['client-id']))
+                                           sec['client-id']))
 
                 # Get links
                 if proc_t == 'nfv':
@@ -467,14 +472,15 @@ class SppTopo(object):
                                 "nfv:{}".format(sec["client-id"]),
                                 self.SEC_COLORS[sec["client-id"]],
                                 l_style)
-                        link_style = node_template + ' {} ' + node_template + '{};'
+                        link_style = node_temp + ' {} ' + node_temp + '{};'
 
                         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.format(src_type, src_id, self.LINK_TYPE,
+                        tmp = link_style.format(src_type, src_id,
+                                                self.LINK_TYPE,
                                                 dst_type, dst_id, attrs)
                         links.append(tmp)
 
@@ -489,10 +495,9 @@ class SppTopo(object):
                                     sec["client-id"], comp['type'][0]),
                                 self.SEC_COLORS[sec["client-id"]],
                                 l_style)
-                        link_style = node_template + ' {} ' + \
-                                node_template + '{};'
+                        link_style = node_temp + ' {} ' + node_temp + '{};'
 
-                        if comp['type'] == 'forward':  # TODO change to forwarder
+                        if comp['type'] == 'forward':
                             rxport = comp['rx_port'][0]['port']
                             if self._is_valid_port(rxport):
                                 src_type, src_id = rxport.split(':')
@@ -500,8 +505,9 @@ class SppTopo(object):
                             if self._is_valid_port(txport):
                                 dst_type, dst_id = txport.split(':')
 
-                            tmp = link_style.format(src_type, src_id, self.LINK_TYPE,
-                                            dst_type, dst_id, attrs)
+                            tmp = link_style.format(src_type, src_id,
+                                                    self.LINK_TYPE,
+                                                    dst_type, dst_id, attrs)
                             links.append(tmp)
 
                         elif comp['type'] == 'classifier':
@@ -512,8 +518,10 @@ class SppTopo(object):
                                 if self._is_valid_port(txp['port']):
                                     dst_type, dst_id = txp['port'].split(':')
 
-                                tmp = link_style.format(src_type, src_id, self.LINK_TYPE,
-                                                dst_type, dst_id, attrs)
+                                tmp = link_style.format(src_type, src_id,
+                                                        self.LINK_TYPE,
+                                                        dst_type, dst_id,
+                                                        attrs)
                                 links.append(tmp)
                         elif comp['type'] == 'merge':  # TODO change to merger
                             txport = comp['tx_port'][0]['port']
@@ -523,8 +531,10 @@ class SppTopo(object):
                                 if self._is_valid_port(rxp['port']):
                                     src_type, src_id = rxp['port'].split(':')
 
-                                tmp = link_style.format(src_type, src_id, self.LINK_TYPE,
-                                                dst_type, dst_id, attrs)
+                                tmp = link_style.format(src_type, src_id,
+                                                        self.LINK_TYPE,
+                                                        dst_type, dst_id,
+                                                        attrs)
                                 links.append(tmp)
 
                 elif proc_t == 'mirror':
@@ -537,8 +547,7 @@ class SppTopo(object):
                                 "vf:{}".format(sec["client-id"]),
                                 self.SEC_COLORS[sec["client-id"]],
                                 l_style)
-                        link_style = node_template + ' {} ' + \
-                                node_template + '{};'
+                        link_style = node_temp + ' {} ' + node_temp + '{};'
 
                         rxport = comp['rx_port'][0]['port']
                         if self._is_valid_port(rxport):
@@ -547,8 +556,9 @@ class SppTopo(object):
                             if self._is_valid_port(txp['port']):
                                 dst_type, dst_id = txp['port'].split(':')
 
-                            tmp = link_style.format(src_type, src_id, self.LINK_TYPE,
-                                            dst_type, dst_id, attrs)
+                            tmp = link_style.format(src_type, src_id,
+                                                    self.LINK_TYPE,
+                                                    dst_type, dst_id, attrs)
                             links.append(tmp)
 
                 elif proc_t == 'pcap':
@@ -560,15 +570,15 @@ class SppTopo(object):
                             "pcap:{}".format(sec["client-id"]),
                             self.SEC_COLORS[sec["client-id"]],
                             l_style)
-                    link_style = node_template + ' {} ' + node_template + '{};'
+                    link_style = node_temp + ' {} ' + node_temp + '{};'
 
                     for c in sec['core']:  # TODO consider change to component
                         if c['role'] == 'receive':  # TODO change to receiver
                             rxport = c['rx_port'][0]['port']
                             if self._is_valid_port(rxport):
                                 src_type, src_id = rxport.split(':')
-                            dst_type, dst_id = self.SPP_PCAP_LABEL, sec['client-id']
-
+                            dst_type = self.SPP_PCAP_LABEL
+                            dst_id = sec['client-id']
                     tmp = link_style.format(src_type, src_id, self.LINK_TYPE,
                                             dst_type, dst_id, attrs)
                     links.append(tmp)
diff --git a/src/cli/shell.py b/src/cli/shell.py
index ebea911..925c231 100644
--- a/src/cli/shell.py
+++ b/src/cli/shell.py
@@ -614,7 +614,8 @@ class Shell(cmd.Cmd, object):
                 if key == 'prompt':
                     self.prompt = self.cli_config['prompt']['val']
                 elif key == 'topo_size':
-                    self.spp_topo.resize(self.cli_config['topo_size']['val'])
+                    self.spp_topo.resize(
+                            self.cli_config['topo_size']['val'])
 
     def help_config(self):
         """Print help message of config command."""
-- 
2.17.1


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

* [spp] [PATCH 7/8] cli: add checking JSON objs in topo
  2019-08-12  7:12 [spp] [PATCH 0/8] Update topo to support other than spp_nfv Yasufumi Ogawa
                   ` (5 preceding siblings ...)
  2019-08-12  7:12 ` [spp] [PATCH 6/8] cli: add port types for " Yasufumi Ogawa
@ 2019-08-12  7:12 ` Yasufumi Ogawa
  2019-08-12  7:12 ` [spp] [PATCH 8/8] cli: revise description for imgcat Yasufumi Ogawa
  7 siblings, 0 replies; 9+ messages in thread
From: Yasufumi Ogawa @ 2019-08-12  7:12 UTC (permalink / raw)
  To: spp, ferruh.yigit, yasufum.o

To avoid CLI is terminated if some expected value does not exist in JSON
object while parsing values for generating dot script, add checking the
JSON object has each of expected values.

Signed-off-by: Yasufumi Ogawa <yasufum.o@gmail.com>
---
 src/cli/commands/topo.py | 345 ++++++++++++++++++++++++---------------
 1 file changed, 212 insertions(+), 133 deletions(-)

diff --git a/src/cli/commands/topo.py b/src/cli/commands/topo.py
index 2e1bc3d..f01b98e 100644
--- a/src/cli/commands/topo.py
+++ b/src/cli/commands/topo.py
@@ -5,10 +5,12 @@ import os
 import re
 import socket
 import subprocess
+import sys
 import traceback
 import uuid
 import yaml
 from .. import spp_common
+from ..spp_common import logger
 
 
 class SppTopo(object):
@@ -21,8 +23,6 @@ class SppTopo(object):
     * text (dot, json, yaml)
     """
 
-    delim_node = '_'
-
     def __init__(self, spp_ctl_cli, subgraphs, cli_config):
         self.spp_ctl_cli = spp_ctl_cli
         self.subgraphs = subgraphs
@@ -38,6 +38,12 @@ class SppTopo(object):
         self.GRAPH_TYPE = "digraph"
         self.LINK_TYPE = "->"
         self.SPP_PCAP_LABEL = 'spppcap'  # Label of dummy port of spp_pcap
+        self.delim_node = '_'  # used for node ID such as 'phy_0' or 'ring_1'
+        self.node_temp = '{}' + self.delim_node + '{}'  # template of node ID
+
+        # topo should support spp_pcap's file and tap port
+        self.all_port_types = spp_common.PORT_TYPES + [
+                self.SPP_PCAP_LABEL, 'tap']
 
         # Add colors for custom ports
         self.PORT_COLORS[self.SPP_PCAP_LABEL] = "gold2"
@@ -91,16 +97,19 @@ class SppTopo(object):
 
     def to_file(self, fname, ftype="dot"):
         if ftype == "dot":
-            self.to_dot(fname)
+            if self.to_dot(fname) is not True:
+                return False
         elif ftype == "json" or ftype == "js":
             self.to_json(fname)
         elif ftype == "yaml" or ftype == "yml":
             self.to_yaml(fname)
         elif ftype == "jpg" or ftype == "png" or ftype == "bmp":
-            self.to_img(fname)
+            if self.to_img(fname) is not True:
+                return False
         else:
             print("Invalid file type")
             return False
+
         print("Create topology: '{fname}'".format(fname=fname))
         return True
 
@@ -108,9 +117,16 @@ class SppTopo(object):
         """Output dot script."""
 
         node_attrs = 'node[shape="rectangle", style="filled"];'
-        node_temp = '{}' + self.delim_node + '{}'
 
-        ports, links = self._get_dot_elements(node_temp)
+        ports, links = self._get_dot_elements()
+
+        # Check if one or more ports exist.
+        port_cnt = 0
+        for val in ports.values():
+            port_cnt += len(val)
+        if port_cnt == 0:
+            print('No secondary process exist!')
+            return False
 
         # Remove duplicated entries.
         for ptype in spp_common.PORT_TYPES:
@@ -120,18 +136,14 @@ class SppTopo(object):
         output.append("newrank=true;")
         output.append(node_attrs)
 
-        # topo should support spp_pcap's file and tap port
-        all_port_types = spp_common.PORT_TYPES + [
-                self.SPP_PCAP_LABEL, 'tap']
-
         # Setup node entries
         nodes = {}
-        for ptype in all_port_types:
+        for ptype in self.all_port_types:
             nodes[ptype] = []
             for node in ports[ptype]:
                 r_type, r_id = node.split(':')
                 nodes[ptype].append(
-                    node_temp.format(r_type, r_id))
+                    self.node_temp.format(r_type, r_id))
             nodes[ptype] = list(set(nodes[ptype]))
             for node in nodes[ptype]:
                 label = re.sub(r'{}'.format(self.delim_node), ':', node)
@@ -140,14 +152,14 @@ class SppTopo(object):
                         nd=node, lbl=label, col=self.PORT_COLORS[ptype]))
 
         # Align the same type of nodes with rank attribute
-        for ptype in all_port_types:
+        for ptype in self.all_port_types:
             if len(nodes[ptype]) > 0:
                 output.append(
                     '{{rank=same; {}}}'.format("; ".join(nodes[ptype])))
 
         # Decide the bottom, phy or vhost
         # TODO(yasufum) revise how to decide bottom
-        rank_style = '{{rank=max; ' + node_temp + '}}'
+        rank_style = '{{rank=max; ' + self.node_temp + '}}'
         if len(ports['phy']) > 0:
             r_type, r_id = ports['phy'][0].split(':')
         elif len(ports['vhost']) > 0:
@@ -169,7 +181,7 @@ class SppTopo(object):
 
         # Setup ports included in Host subgraph
         host_nodes = []
-        for ptype in all_port_types:
+        for ptype in self.all_port_types:
             host_nodes = host_nodes + nodes[ptype]
 
         cluster_id = "cluster0"
@@ -196,6 +208,8 @@ class SppTopo(object):
         f.write("\n".join(output))
         f.close()
 
+        return True
+
     def to_json(self, output_fname):
         import json
         f = open(output_fname, "w+")
@@ -212,17 +226,23 @@ class SppTopo(object):
 
     def to_img(self, output_fname):
         tmpfile = "{fn}.dot".format(fn=uuid.uuid4().hex)
-        self.to_dot(tmpfile)
+        if self.to_dot(tmpfile) is not True:
+            return False
+
         fmt = output_fname.split(".")[-1]
         cmd = "dot -T{fmt} {dotf} -o {of}".format(
                 fmt=fmt, dotf=tmpfile, of=output_fname)
         subprocess.call(cmd, shell=True)
         subprocess.call("rm -f {tmpf}".format(tmpf=tmpfile), shell=True)
 
+        return True
+
     def to_http(self):
         import websocket
         tmpfile = "{fn}.dot".format(fn=uuid.uuid4().hex)
-        self.to_dot(tmpfile)
+        if self.to_dot(tmpfile) is not True:
+            return False
+
         msg = open(tmpfile).read()
         subprocess.call("rm -f {tmpf}".format(tmpf=tmpfile), shell=True)
         # TODO(yasufum) change to be able to use other than `localhost`.
@@ -234,9 +254,13 @@ class SppTopo(object):
         except socket.error:
             print('Error: Connection refused! Is running websocket server?')
 
+        return True
+
     def to_term(self, size):
         tmpfile = "{fn}.jpg".format(fn=uuid.uuid4().hex)
-        self.to_img(tmpfile)
+        if self.to_img(tmpfile) is not True:
+            return False
+
         from distutils import spawn
 
         # TODO(yasufum) add check for using only supported terminal
@@ -409,7 +433,7 @@ class SppTopo(object):
             pass
         return True
 
-    def _get_dot_elements(self, node_temp):
+    def _get_dot_elements(self):
         """Get entries of nodes and links.
 
         To generate dot script, this method returns ports as nodes and links
@@ -419,11 +443,8 @@ class SppTopo(object):
         ports = {}
         links = []
 
-        # topo should support spp_pcap's file and tap port
-        all_port_types = spp_common.PORT_TYPES + [self.SPP_PCAP_LABEL, 'tap']
-
         # Initialize ports
-        for ptype in all_port_types:
+        for ptype in self.all_port_types:
             ports[ptype] = []
 
         # parse status message from sec.
@@ -431,163 +452,221 @@ class SppTopo(object):
             for sec in self.spp_ctl_cli.get_sec_procs(proc_t):
                 if sec is None:
                     continue
+                self._setup_dot_ports(ports, sec, proc_t)
+                self._setup_dot_links(links, sec, proc_t)
 
-                # Get ports
-                # TODO(yasufum) add try statement for handling key error
-                if proc_t in ['nfv', 'vf', 'mirror']:
-                    for port in sec['ports']:
-                        # TODO make it to a method
-                        if self._is_valid_port(port):
-                            r_type = port.split(':')[0]
-                            if r_type in all_port_types:
-                                ports[r_type].append(port)
-                            else:
-                                raise ValueError(
-                                    "Invaid interface type: {rtype}".format(
-                                        rtype=r_type))
-                elif proc_t == 'pcap':
-                    for c in sec['core']:
-                        if c['role'] == 'receive':
+        return ports, links
+
+    def _setup_dot_ports(self, ports, sec, proc_t):
+        """Parse sec obj and append port to `ports`."""
+
+        try:
+            if proc_t in ['nfv', 'vf', 'mirror']:
+                for port in sec['ports']:
+                    if self._is_valid_port(port):
+                        r_type = port.split(':')[0]
+                        if r_type in self.all_port_types:
+                            ports[r_type].append(port)
+                        else:
+                            raise ValueError(
+                                "Invaid interface type: {}".format(r_type))
+
+            elif proc_t == 'pcap':
+                for c in sec['core']:
+                    if c['role'] == 'receive':
+                        if len(c['rx_port']) > 0:
                             port = c['rx_port'][0]['port']
                             if self._is_valid_port(port):
                                 r_type = port.split(':')[0]
-                                if r_type in all_port_types:
+                                if r_type in self.all_port_types:
                                     ports[r_type].append(port)
                                 else:
                                     raise ValueError(
                                         "Invaid interface type: {}".format(
                                             r_type))
-                    ports[self.SPP_PCAP_LABEL].append(
-                            '{}:{}'.format(self.SPP_PCAP_LABEL,
-                                           sec['client-id']))
-
-                # Get links
-                if proc_t == 'nfv':
-                    for patch in sec['patches']:
-                        if sec['status'] == 'running':
-                            l_style = self.LINE_STYLE["running"]
                         else:
-                            l_style = self.LINE_STYLE["idling"]
-                        attrs = '[label="{}", color="{}", style="{}"]'.format(
-                                "nfv:{}".format(sec["client-id"]),
-                                self.SEC_COLORS[sec["client-id"]],
-                                l_style)
-                        link_style = node_temp + ' {} ' + node_temp + '{};'
-
-                        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(':')
+                            print('Error: No rx port in {}:{}.'.format(
+                                'pcap', sec['client-id']))
+                            return False
+                ports[self.SPP_PCAP_LABEL].append(
+                        '{}:{}'.format(self.SPP_PCAP_LABEL, sec['client-id']))
+
+            else:
+                logger.error('Invlaid secondary type {}.'.format(proc_t))
+
+            return True
+
+        except IndexError as e:
+            print('Error: Failed to parse ports. ' + e)
+        except KeyError as e:
+            print('Error: Failed to parse ports. ' + e)
+
+    def _setup_dot_links(self, links, sec, proc_t):
+        """Parse sec obj and append links to `links`."""
+
+        link_style = self.node_temp + ' {} ' + self.node_temp + '{};'
+        try:
+            # Get links
+            src_type, src_id, dst_type, dst_id = None, None, None, None
+            if proc_t == 'nfv':
+                for patch in sec['patches']:
+                    if sec['status'] == 'running':
+                        l_style = self.LINE_STYLE["running"]
+                    else:
+                        l_style = self.LINE_STYLE["idling"]
+                    attrs = '[label="{}", color="{}", style="{}"]'.format(
+                            "nfv:{}".format(sec["client-id"]),
+                            self.SEC_COLORS[sec["client-id"]],
+                            l_style)
+
+                    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(':')
+
+                    if src_type is None or dst_type is None:
+                        print('Error: Failed to parse links in {}:{}.'.format(
+                            'nfv', sec['client-id']))
+                        return False
+
+                    tmp = link_style.format(src_type, src_id,
+                                            self.LINK_TYPE,
+                                            dst_type, dst_id, attrs)
+                    links.append(tmp)
+
+            elif proc_t == 'vf':
+                for comp in sec['components']:
+                    if self._is_comp_running(comp):
+                        l_style = self.LINE_STYLE["running"]
+                    else:
+                        l_style = self.LINE_STYLE["idling"]
+                    attrs = '[label="{}", color="{}", style="{}"]'.format(
+                            "vf:{}:{}".format(
+                                sec["client-id"], comp['type'][0]),
+                            self.SEC_COLORS[sec["client-id"]],
+                            l_style)
+
+                    if comp['type'] == 'forward':
+                        if len(comp['rx_port']) > 0:
+                            rxport = comp['rx_port'][0]['port']
+                            if self._is_valid_port(rxport):
+                                src_type, src_id = rxport.split(':')
+                        if len(comp['tx_port']) > 0:
+                            txport = comp['tx_port'][0]['port']
+                            if self._is_valid_port(txport):
+                                dst_type, dst_id = txport.split(':')
+
+                        if src_type is None or dst_type is None:
+                            print('Error: {msg} {comp}:{sid} {ct}'.format(
+                                msg='Falied to parse links in', comp='vf',
+                                sid=sec['client-id'], ct=comp['type']))
+                            return False
 
                         tmp = link_style.format(src_type, src_id,
                                                 self.LINK_TYPE,
                                                 dst_type, dst_id, attrs)
                         links.append(tmp)
 
-                elif proc_t == 'vf':
-                    for comp in sec['components']:
-                        if self._is_comp_running(comp):
-                            l_style = self.LINE_STYLE["running"]
-                        else:
-                            l_style = self.LINE_STYLE["idling"]
-                        attrs = '[label="{}", color="{}", style="{}"]'.format(
-                                "vf:{}:{}".format(
-                                    sec["client-id"], comp['type'][0]),
-                                self.SEC_COLORS[sec["client-id"]],
-                                l_style)
-                        link_style = node_temp + ' {} ' + node_temp + '{};'
-
-                        if comp['type'] == 'forward':
+                    elif comp['type'] == 'classifier':
+                        if len(comp['rx_port']) > 0:
                             rxport = comp['rx_port'][0]['port']
                             if self._is_valid_port(rxport):
                                 src_type, src_id = rxport.split(':')
-                            txport = comp['tx_port'][0]['port']
-                            if self._is_valid_port(txport):
-                                dst_type, dst_id = txport.split(':')
+                        for txp in comp['tx_port']:
+                            if self._is_valid_port(txp['port']):
+                                dst_type, dst_id = txp['port'].split(':')
+
+                            if src_type is None or dst_type is None:
+                                print('Error: {msg} {comp}:{sid} {ct}'.format(
+                                    msg='Falied to parse links in', comp='vf',
+                                    sid=sec['client-id'], ct=comp['type']))
+                                return False
 
                             tmp = link_style.format(src_type, src_id,
                                                     self.LINK_TYPE,
                                                     dst_type, dst_id, attrs)
                             links.append(tmp)
 
-                        elif comp['type'] == 'classifier':
-                            rxport = comp['rx_port'][0]['port']
-                            if self._is_valid_port(rxport):
-                                src_type, src_id = rxport.split(':')
-                            for txp in comp['tx_port']:
-                                if self._is_valid_port(txp['port']):
-                                    dst_type, dst_id = txp['port'].split(':')
-
-                                tmp = link_style.format(src_type, src_id,
-                                                        self.LINK_TYPE,
-                                                        dst_type, dst_id,
-                                                        attrs)
-                                links.append(tmp)
-                        elif comp['type'] == 'merge':  # TODO change to merger
+                    elif comp['type'] == 'merge':  # TODO change to merger
+                        if len(comp['tx_port']) > 0:
                             txport = comp['tx_port'][0]['port']
                             if self._is_valid_port(txport):
                                 dst_type, dst_id = txport.split(':')
-                            for rxp in comp['rx_port']:
-                                if self._is_valid_port(rxp['port']):
-                                    src_type, src_id = rxp['port'].split(':')
-
-                                tmp = link_style.format(src_type, src_id,
-                                                        self.LINK_TYPE,
-                                                        dst_type, dst_id,
-                                                        attrs)
-                                links.append(tmp)
-
-                elif proc_t == 'mirror':
-                    for comp in sec['components']:
-                        if self._is_comp_running(comp):
-                            l_style = self.LINE_STYLE["running"]
-                        else:
-                            l_style = self.LINE_STYLE["idling"]
-                        attrs = '[label="{}", color="{}", style="{}"]'.format(
-                                "vf:{}".format(sec["client-id"]),
-                                self.SEC_COLORS[sec["client-id"]],
-                                l_style)
-                        link_style = node_temp + ' {} ' + node_temp + '{};'
+                        for rxp in comp['rx_port']:
+                            if self._is_valid_port(rxp['port']):
+                                src_type, src_id = rxp['port'].split(':')
 
-                        rxport = comp['rx_port'][0]['port']
-                        if self._is_valid_port(rxport):
-                            src_type, src_id = rxport.split(':')
-                        for txp in comp['tx_port']:
-                            if self._is_valid_port(txp['port']):
-                                dst_type, dst_id = txp['port'].split(':')
+                            if src_type is None or dst_type is None:
+                                print('Error: {msg} {comp}:{sid} {ct}'.format(
+                                    msg='Falied to parse links in', comp='vf',
+                                    sid=sec['client-id'], ct=comp['type']))
+                                return False
 
                             tmp = link_style.format(src_type, src_id,
                                                     self.LINK_TYPE,
                                                     dst_type, dst_id, attrs)
                             links.append(tmp)
 
-                elif proc_t == 'pcap':
-                    if sec['status'] == 'running':
+            elif proc_t == 'mirror':
+                for comp in sec['components']:
+                    if self._is_comp_running(comp):
                         l_style = self.LINE_STYLE["running"]
                     else:
                         l_style = self.LINE_STYLE["idling"]
                     attrs = '[label="{}", color="{}", style="{}"]'.format(
-                            "pcap:{}".format(sec["client-id"]),
+                            "vf:{}".format(sec["client-id"]),
                             self.SEC_COLORS[sec["client-id"]],
                             l_style)
-                    link_style = node_temp + ' {} ' + node_temp + '{};'
 
-                    for c in sec['core']:  # TODO consider change to component
-                        if c['role'] == 'receive':  # TODO change to receiver
+                    if len(comp['rx_port']) > 0:
+                        rxport = comp['rx_port'][0]['port']
+                        if self._is_valid_port(rxport):
+                            src_type, src_id = rxport.split(':')
+                    for txp in comp['tx_port']:
+                        if self._is_valid_port(txp['port']):
+                            dst_type, dst_id = txp['port'].split(':')
+
+                        if src_type is None or dst_type is None:
+                            print('Error: {msg} {comp}:{sid} {ct}'.format(
+                                msg='Falied to parse links in', comp='vf',
+                                sid=sec['client-id'], ct=comp['type']))
+                            return False
+
+                        tmp = link_style.format(src_type, src_id,
+                                                self.LINK_TYPE,
+                                                dst_type, dst_id, attrs)
+                        links.append(tmp)
+
+            elif proc_t == 'pcap':
+                if sec['status'] == 'running':
+                    l_style = self.LINE_STYLE["running"]
+                else:
+                    l_style = self.LINE_STYLE["idling"]
+                attrs = '[label="{}", color="{}", style="{}"]'.format(
+                        "pcap:{}".format(sec["client-id"]),
+                        self.SEC_COLORS[sec["client-id"]], l_style)
+
+                for c in sec['core']:  # TODO consider change to component
+                    if c['role'] == 'receive':  # TODO change to receiver
+                        if len(comp['rx_port']) > 0:
                             rxport = c['rx_port'][0]['port']
                             if self._is_valid_port(rxport):
                                 src_type, src_id = rxport.split(':')
-                            dst_type = self.SPP_PCAP_LABEL
-                            dst_id = sec['client-id']
-                    tmp = link_style.format(src_type, src_id, self.LINK_TYPE,
-                                            dst_type, dst_id, attrs)
-                    links.append(tmp)
+                        dst_type = self.SPP_PCAP_LABEL
+                        dst_id = sec['client-id']
+                tmp = link_style.format(src_type, src_id, self.LINK_TYPE,
+                                        dst_type, dst_id, attrs)
+                links.append(tmp)
 
-                else:
-                    # TODO(yasufum) add error handling for invalid proc types
-                    pass
+            else:
+                logger.error('Invlaid secondary type {}.'.format(proc_t))
 
-        return ports, links
+            return True
+
+        except IndexError as e:
+            print('Error: Failed to parse links. "{}"'.format(e))
+        except KeyError as e:
+            print('Error: Failed to parse links. "{}"'.format(e))
 
     @classmethod
     def help(cls):
-- 
2.17.1


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

* [spp] [PATCH 8/8] cli: revise description for imgcat
  2019-08-12  7:12 [spp] [PATCH 0/8] Update topo to support other than spp_nfv Yasufumi Ogawa
                   ` (6 preceding siblings ...)
  2019-08-12  7:12 ` [spp] [PATCH 7/8] cli: add checking JSON objs in topo Yasufumi Ogawa
@ 2019-08-12  7:12 ` Yasufumi Ogawa
  7 siblings, 0 replies; 9+ messages in thread
From: Yasufumi Ogawa @ 2019-08-12  7:12 UTC (permalink / raw)
  To: spp, ferruh.yigit, yasufum.o

This update is to revise how to setup 3rd party tool `imgcat` for
iTerm2 on MacOS.

Signed-off-by: Yasufumi Ogawa <yasufum.o@gmail.com>
---
 docs/guides/commands/experimental.rst | 15 +++++++++------
 1 file changed, 9 insertions(+), 6 deletions(-)

diff --git a/docs/guides/commands/experimental.rst b/docs/guides/commands/experimental.rst
index 576d11e..c328c73 100644
--- a/docs/guides/commands/experimental.rst
+++ b/docs/guides/commands/experimental.rst
@@ -27,7 +27,7 @@ Support four types of output.
 
 This command uses `graphviz
 <https://www.graphviz.org/>`_
-gfor generating topology file.
+for generating topology file.
 You can also generate a dot formatted file or image files supported by
 graphviz.
 
@@ -40,8 +40,9 @@ to output in a terminal.
       imagemagick \
       libsixel-bin
 
-MacOS is also supported optionally for using SPP CLI runs on a remote host.
-In this case, iTerm2 and imgcat are required.
+MacOS is also supported optionally for using topo runs on a remote host.
+In this case, iTerm2 and imgcat are required as described in the next
+section.
 
 To output in browser with ``topo http`` command, install required packages
 by using ``requirements.txt`` as described in
@@ -62,16 +63,18 @@ Output an image of network configuration in terminal.
 
     spp > topo term
 
-There are few terminal applications to output image with ``topo``.
+There are few terminal applications supporting to output image with ``topo``.
 You can use mlterm, xterm or other terminals supported by `img2sixel
 <https://github.com/saitoha/libsixel>`_.
 You can also use `iTerm2
 <https://iterm2.com/index.html>`_ on MacOS.
-If you use iTerm2, you have to get a shell script
+If you use iTerm2, you need to downloada a shell script
 ``imgcat`` from `iTerm2's displaying support site
 <https://iterm2.com/documentation-images.html>`_
 and save this script as
-``spp/src/controller/3rd_party/imgcat``.
+``src/controller/3rd_party/imgcat`` with permission ``775``.
+``topo`` command tries to ``img2sixel`` first, then ``imgcat``
+in the ``3rd_party`` directory.
 
 .. _figure_topo_term_exp:
 
-- 
2.17.1


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

end of thread, back to index

Thread overview: 9+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2019-08-12  7:12 [spp] [PATCH 0/8] Update topo to support other than spp_nfv Yasufumi Ogawa
2019-08-12  7:12 ` [spp] [PATCH 1/8] cli: remove topo_resize command Yasufumi Ogawa
2019-08-12  7:12 ` [spp] [PATCH 2/8] cli: revise composing dot script Yasufumi Ogawa
2019-08-12  7:12 ` [spp] [PATCH 3/8] cli: move style params for dot to config Yasufumi Ogawa
2019-08-12  7:12 ` [spp] [PATCH 4/8] cli: add to get sec ID and procs to SppCtlClient Yasufumi Ogawa
2019-08-12  7:12 ` [spp] [PATCH 5/8] cli: support other than spp_nfv in topo command Yasufumi Ogawa
2019-08-12  7:12 ` [spp] [PATCH 6/8] cli: add port types for " Yasufumi Ogawa
2019-08-12  7:12 ` [spp] [PATCH 7/8] cli: add checking JSON objs in topo Yasufumi Ogawa
2019-08-12  7:12 ` [spp] [PATCH 8/8] cli: revise description for imgcat Yasufumi Ogawa

Soft Patch Panel

Archives are clonable:
	git clone --mirror http://inbox.dpdk.org/spp/0 spp/git/0.git

	# If you have public-inbox 1.1+ installed, you may
	# initialize and index your mirror using the following commands:
	public-inbox-init -V2 spp spp/ http://inbox.dpdk.org/spp \
		spp@dpdk.org
	public-inbox-index spp


Newsgroup available over NNTP:
	nntp://inbox.dpdk.org/inbox.dpdk.spp


AGPL code for this site: git clone https://public-inbox.org/ public-inbox