[spp] [PATCH 7/8] cli: add checking JSON objs in topo

Yasufumi Ogawa yasufum.o at gmail.com
Mon Aug 12 09:12:41 CEST 2019


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 at 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



More information about the spp mailing list