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 7/8] cli: add checking JSON objs in topo
Date: Mon, 12 Aug 2019 16:12:41 +0900	[thread overview]
Message-ID: <20190812071242.18934-8-yasufum.o@gmail.com> (raw)
In-Reply-To: <20190812071242.18934-1-yasufum.o@gmail.com>

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


  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 ` [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 ` Yasufumi Ogawa [this message]
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-8-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).