summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYasufumi Ogawa <yasufum.o@gmail.com>2019-08-12 16:12:41 +0900
committerYasufumi Ogawa <yasufum.o@gmail.com>2019-08-13 17:08:08 +0900
commitfece7001dfb5871f43dfacc2dc04ef0daede1995 (patch)
tree49d7ec6d5394389b3ccac6a1ab7c59dd2f3aa6b6
parent931e326bb005ca86522147e5651ddaeaa065dde5 (diff)
downloadspp-fece7001dfb5871f43dfacc2dc04ef0daede1995.zip
spp-fece7001dfb5871f43dfacc2dc04ef0daede1995.tar.gz
spp-fece7001dfb5871f43dfacc2dc04ef0daede1995.tar.xz
cli: add checking JSON objs in topo
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>
-rw-r--r--src/cli/commands/topo.py345
1 files 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):