From mboxrd@z Thu Jan 1 00:00:00 1970 Return-Path: Received: from dpdk.org (dpdk.org [92.243.14.124]) by inbox.dpdk.org (Postfix) with ESMTP id 831FAA0471 for ; Mon, 12 Aug 2019 09:12:59 +0200 (CEST) Received: from [92.243.14.124] (localhost [127.0.0.1]) by dpdk.org (Postfix) with ESMTP id 7D4151252; Mon, 12 Aug 2019 09:12:59 +0200 (CEST) Received: from mail-pf1-f170.google.com (mail-pf1-f170.google.com [209.85.210.170]) by dpdk.org (Postfix) with ESMTP id 74D182C55 for ; Mon, 12 Aug 2019 09:12:58 +0200 (CEST) Received: by mail-pf1-f170.google.com with SMTP id g2so49259162pfq.0 for ; Mon, 12 Aug 2019 00:12:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20161025; h=from:to:subject:date:message-id:in-reply-to:references; bh=oR7Kp4lS8EzhUdS+n406DKOBhP+lVsAOsnw/AeCAvk8=; b=DMkAEAbaqYi17soBhuMufIPlHbAh8ACI+Kki/wsMGaIzV8maiJr/yyDHKp0Oq7JSrP qwdE4qchhSz3XTALO5RNPLOPnKKGyaazUQYGVzeCtglYAuYJ1Ri0QLYoVQlGM287o3yj sTQPsTzvb8/gsIUb0hYYfB94gizqWM71oSvDfWQ+VasxU8LZHfLzOszoK6XRUAXPzXKz wtKGSvqEIIKD3Ep1o8Eahdh4BA8dFZO1YiD67bXbAB4kc4/6RjzqgQKIPVtiqSCjHxGj TLUHWvXNZ5BmtdSmoStauLOIQnF8x7tzGUdxhWEz299BNzafwpfZgLGDBemxJ7lAgO5d 5mAA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20161025; h=x-gm-message-state:from:to:subject:date:message-id:in-reply-to :references; bh=oR7Kp4lS8EzhUdS+n406DKOBhP+lVsAOsnw/AeCAvk8=; b=XKRLwcu9caWY+eyqwJVfUSTjjG0rxZcZlIpvLmrcUstnZE/QFtJD+iaRD6MG2Wb/Dy WHDONIgCuYwsL4zgit9D51pL329bdWkx0Z7IVqrQgbp96ZAHepHUC+N63exqjXEXqoUc sbcv+cx6/jtTa6qlmYr2FGbvQQowBEqRlMN3w+J0Bzly0Ajt2jqc87HXDtkUMRQ4vu10 TjReGMp4xQKWn3teaOCaYXEZhyuf6mDP2G2yu7hJPEfBJbfAUUxC3XybA0rnW9JHCuqo kjq0WgF4tvprb1sQkiCoBJhOls/Yb6CInCb0B/9JLYBPQazKw29zgKtu2wWmaDazNGhR 2bTw== X-Gm-Message-State: APjAAAUGKlVW7fUq3me1/oWiS0b2pVTT4HbrXQmZl+C0OrJKQCWvQTkh jK/lAnFZPh42odFvbHn1OWcr34vZ X-Google-Smtp-Source: APXvYqyHdTskD+21T5TRNfpIEHSBRp8/buy1g77My/LsyWJvJwYJ5zT6HoVXakhwNM7gWonSWizXJQ== X-Received: by 2002:a17:90a:a114:: with SMTP id s20mr7013692pjp.20.1565593977369; Mon, 12 Aug 2019 00:12:57 -0700 (PDT) Received: from localhost.localdomain ([2400:4050:c8c2:de00:8000:cb51:dfcb:76c]) by smtp.gmail.com with ESMTPSA id h9sm94675326pgh.51.2019.08.12.00.12.56 (version=TLS1_3 cipher=AEAD-AES256-GCM-SHA384 bits=256/256); Mon, 12 Aug 2019 00:12:57 -0700 (PDT) From: Yasufumi Ogawa To: spp@dpdk.org, ferruh.yigit@intel.com, yasufum.o@gmail.com Date: Mon, 12 Aug 2019 16:12:41 +0900 Message-Id: <20190812071242.18934-8-yasufum.o@gmail.com> X-Mailer: git-send-email 2.17.1 In-Reply-To: <20190812071242.18934-1-yasufum.o@gmail.com> References: <20190812071242.18934-1-yasufum.o@gmail.com> Subject: [spp] [PATCH 7/8] cli: add checking JSON objs in topo X-BeenThere: spp@dpdk.org X-Mailman-Version: 2.1.15 Precedence: list List-Id: Soft Patch Panel List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: spp-bounces@dpdk.org Sender: "spp" 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 --- 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