Soft Patch Panel
 help / color / mirror / Atom feed
From: Yasufumi Ogawa <yasufum.o@gmail.com>
To: spp@dpdk.org, ferruh.yigit@intel.com, yasufum.o@gmail.com
Subject: [spp] [PATCH 5/8] cli: support other than spp_nfv in topo command
Date: Mon, 12 Aug 2019 16:12:39 +0900	[thread overview]
Message-ID: <20190812071242.18934-6-yasufum.o@gmail.com> (raw)
In-Reply-To: <20190812071242.18934-1-yasufum.o@gmail.com>

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


  parent reply	other threads:[~2019-08-12  7:12 UTC|newest]

Thread overview: 9+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
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 ` Yasufumi Ogawa [this message]
2019-08-12  7:12 ` [spp] [PATCH 6/8] cli: add port types for topo command 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

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20190812071242.18934-6-yasufum.o@gmail.com \
    --to=yasufum.o@gmail.com \
    --cc=ferruh.yigit@intel.com \
    --cc=spp@dpdk.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for NNTP newsgroup(s).