Soft Patch Panel
 help / color / mirror / Atom feed
From: ogawa.yasufumi@lab.ntt.co.jp
To: spp@dpdk.org, ferruh.yigit@intel.com, ogawa.yasufumi@lab.ntt.co.jp
Subject: [spp] [PATCH 7/9] controller: move topo command to SppTopo
Date: Thu, 18 Oct 2018 20:25:16 +0900	[thread overview]
Message-ID: <20181018112518.77556-8-ogawa.yasufumi@lab.ntt.co.jp> (raw)
In-Reply-To: <20181018112518.77556-1-ogawa.yasufumi@lab.ntt.co.jp>

From: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>

SppTopo defines 'topo' command and its completion as in a separated
module. It is intended to be used from Shell, which is derived from
'cmd.Cmd'.

Signed-off-by: Yasufumi Ogawa <ogawa.yasufumi@lab.ntt.co.jp>
---
 src/controller/{ => commands}/topo.py | 247 +++++++++++++++++++++++-----------
 src/controller/shell.py               | 104 ++++----------
 src/controller/spp_common.py          |   3 -
 3 files changed, 190 insertions(+), 164 deletions(-)
 rename src/controller/{ => commands}/topo.py (56%)

diff --git a/src/controller/topo.py b/src/controller/commands/topo.py
similarity index 56%
rename from src/controller/topo.py
rename to src/controller/commands/topo.py
index e608961..fc22a98 100644
--- a/src/controller/topo.py
+++ b/src/controller/commands/topo.py
@@ -4,15 +4,14 @@
 
 import os
 import re
-from . import spp_common
-from .spp_common import logger
+import socket
 import subprocess
 import traceback
 import uuid
 import yaml
 
 
-class Topo(object):
+class SppTopo(object):
     """Setup and output network topology for topo command
 
     Topo command supports four types of output.
@@ -22,37 +21,64 @@ class Topo(object):
     * text (dot, json, yaml)
     """
 
-    def __init__(self, sec_ids, m2s_queues, s2m_queues, sub_graphs):
-        logger.info("Topo initialized with sec IDs %s" % sec_ids)
+    delim_node = '_'
+
+    def __init__(self, spp_ctl_cli, sec_ids, subgraphs):
+        self.spp_ctl_cli = spp_ctl_cli
         self.sec_ids = sec_ids
-        self.m2s_queues = m2s_queues
-        self.s2m_queues = s2m_queues
-        self.sub_graphs = sub_graphs
+        self.subgraphs = subgraphs
+
+    def run(self, args, topo_size):
+        args_ary = args.split()
+        if len(args_ary) == 0:
+            print("Usage: topo dst [ftype]")
+            return False
+        elif (args_ary[0] == "term") or (args_ary[0] == "http"):
+            self.show(args_ary[0], topo_size)
+        elif len(args_ary) == 1:
+            ftype = args_ary[0].split(".")[-1]
+            self.output(args_ary[0], ftype)
+        elif len(args_ary) == 2:
+            self.output(args_ary[0], args_ary[1])
+        else:
+            print("Usage: topo dst [ftype]")
 
     def show(self, dtype, size):
         res_ary = []
+        error_codes = self.spp_ctl_cli.rest_common_error_codes
+
         for sec_id in self.sec_ids:
-            msg = "status"
-            self.m2s_queues[sec_id].put(msg.encode('utf-8'))
-            res = self.format_sec_status(
-                sec_id, self.s2m_queues[sec_id].get(True))
-            res_ary.append(res)
+            res = self.spp_ctl_cli.get('nfvs/%d' % sec_id)
+            if res.status_code == 200:
+                res_ary.append(res.json())
+            elif res.status_code in error_codes:
+                # Print default error message
+                pass
+            else:
+                # Ignore unknown response because no problem for drawing graph
+                pass
+
         if dtype == "http":
             self.to_http(res_ary)
         elif dtype == "term":
             self.to_term(res_ary, size)
         else:
             print("Invalid file type")
-            return res_ary
 
     def output(self, fname, ftype="dot"):
         res_ary = []
+        error_codes = self.spp_ctl_cli.rest_common_error_codes
+
         for sec_id in self.sec_ids:
-            msg = "status"
-            self.m2s_queues[sec_id].put(msg.encode('utf-8'))
-            res = self.format_sec_status(
-                sec_id, self.s2m_queues[sec_id].get(True))
-            res_ary.append(res)
+            res = self.spp_ctl_cli.get('nfvs/%d' % sec_id)
+            if res.status_code == 200:
+                res_ary.append(res.json())
+            elif res.status_code in error_codes:
+                # Print default error message
+                pass
+            else:
+                # Ignore unknown response because no problem for drawing graph
+                pass
 
         if ftype == "dot":
             self.to_dot(res_ary, fname)
@@ -69,8 +95,6 @@ class Topo(object):
         return res_ary
 
     def to_dot(self, sec_list, output_fname):
-        # Label given if outport is "none"
-        NO_PORT = None
 
         # Graphviz params
         # TODO(yasufum) consider to move gviz params to config file.
@@ -89,7 +113,7 @@ class Topo(object):
 
         node_attrs = 'node[shape="rectangle", style="filled"];'
 
-        node_template = '%s' + spp_common.delim_node + '%s'
+        node_template = '%s' + self.delim_node + '%s'
 
         phys = []
         rings = []
@@ -100,38 +124,45 @@ class Topo(object):
         for sec in sec_list:
             if sec is None:
                 continue
-            for port in sec["ports"]:
-                if port["iface"]["type"] == "phy":
-                    phys.append(port)
-                elif port["iface"]["type"] == "ring":
-                    rings.append(port)
-                elif port["iface"]["type"] == "vhost":
-                    vhosts.append(port)
-                else:
-                    raise ValueError(
-                        "Invaid interface type: %s" % port["iface"]["type"])
-
-                if port['out'] != NO_PORT:
-                    out_type, out_id = port['out'].split(':')
-                    if sec['status'] == 'running':
-                        l_style = LINE_STYLE["running"]
+            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:
-                        l_style = LINE_STYLE["idling"]
-                    attrs = '[label="%s", color="%s", style="%s"]' % (
-                        "sec%d" % sec["sec_id"],
-                        SEC_COLORS[sec["sec_id"]],
-                        l_style
-                    )
-                    link_style = node_template + ' %s ' + node_template + '%s;'
-                    tmp = link_style % (
-                        port["iface"]["type"],
-                        port["iface"]["id"],
-                        LINK_TYPE,
-                        out_type,
-                        out_id,
-                        attrs
-                    )
-                    links.append(tmp)
+                        raise ValueError(
+                            "Invaid interface type: %s" % r_type)
+
+            for patch in sec['patches']:
+                if sec['status'] == 'running':
+                    l_style = LINE_STYLE["running"]
+                else:
+                    l_style = LINE_STYLE["idling"]
+                attrs = '[label="%s", color="%s", style="%s"]' % (
+                    "sec%d" % sec["client-id"],
+                    SEC_COLORS[sec["client-id"]],
+                    l_style
+                )
+                link_style = node_template + ' %s ' + node_template + '%s;'
+
+                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 % (src_type, src_id, LINK_TYPE,
+                                    dst_type, dst_id, attrs)
+                links.append(tmp)
 
         output = ["%s spp{" % GRAPH_TYPE]
         output.append("newrank=true;")
@@ -139,70 +170,65 @@ class Topo(object):
 
         phy_nodes = []
         for node in phys:
+            r_type, r_id = node.split(':')
             phy_nodes.append(
-                node_template % (node['iface']['type'], node['iface']['id']))
+                node_template % (r_type, r_id))
         phy_nodes = list(set(phy_nodes))
         for node in phy_nodes:
-            label = re.sub(
-                r'%s' % spp_common.delim_node, spp_common.delim_label, node)
+            label = re.sub(r'%s' % self.delim_node, ':', node)
             output.append(
                 '%s[label="%s", fillcolor="%s"];' % (
                     node, label, PORT_COLORS["phy"]))
 
         ring_nodes = []
-        for p in rings:
-            ring_nodes.append(
-                node_template % (p['iface']['type'], p['iface']['id']))
+        for node in rings:
+            r_type, r_id = node.split(':')
+            ring_nodes.append(node_template % (r_type, r_id))
         ring_nodes = list(set(ring_nodes))
         for node in ring_nodes:
-            label = re.sub(
-                r'%s' % spp_common.delim_node, spp_common.delim_label, node)
+            label = re.sub(r'%s' % self.delim_node, ':', node)
             output.append(
                 '%s[label="%s", fillcolor="%s"];' % (
                     node, label, PORT_COLORS["ring"]))
 
         vhost_nodes = []
-        for p in vhosts:
-            vhost_nodes.append(
-                node_template % (p["iface"]["type"], p["iface"]["id"]))
+        for node in vhosts:
+            r_type, r_id = node.split(':')
+            vhost_nodes.append(node_template % (r_type, r_id))
         vhost_nodes = list(set(vhost_nodes))
         for node in vhost_nodes:
-            label = re.sub(
-                r'%s' % spp_common.delim_node, spp_common.delim_label, node)
+            label = re.sub(r'%s' % self.delim_node, ':', node)
             output.append(
                 '%s[label="%s", fillcolor="%s"];' % (
                     node, label, PORT_COLORS["vhost"]))
 
-        # rank
+        # Align the same type of nodes with rank attribute
         output.append(
             '{rank=same; %s}' % ("; ".join(ring_nodes)))
         output.append(
             '{rank=same; %s}' % ("; ".join(vhost_nodes)))
 
+        # Decide the bottom, phy or vhost
         rank_style = '{rank=max; %s}' % node_template
         if len(phys) > 0:
-            output.append(
-                rank_style % (
-                    phys[0]["iface"]["type"], phys[0]["iface"]["id"]))
+            r_type, r_id = phys[0].split(':')
         elif len(vhosts) > 0:
-            output.append(
-                rank_style % (
-                    vhosts[0]["iface"]["type"], vhosts[0]["iface"]["id"]))
+            r_type, r_id = vhosts[0].split(':')
+        output.append(rank_style % (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; %s}' % ("; ".join(phy_nodes)))
 
         # Add subgraph
         ssgs = []
-        if len(self.sub_graphs) > 0:
+        if len(self.subgraphs) > 0:
             cnt = 1
-            for label, val in self.sub_graphs.items():
+            for label, val in self.subgraphs.items():
                 cluster_id = "cluster%d" % cnt
                 ssg_label = label
-                ssg_ports = re.sub(
-                    r'%s' % spp_common.delim_label,
-                    spp_common.delim_node, val)
+                ssg_ports = re.sub(r'%s' % ':', self.delim_node, val)
                 ssg = 'subgraph %s {label="%s" %s}' % (
                     cluster_id, ssg_label, ssg_ports)
                 ssgs.append(ssg)
@@ -258,16 +284,19 @@ class Topo(object):
         msg = open(tmpfile).read()
         subprocess.call("rm -f %s" % tmpfile, shell=True)
         ws_url = "ws://localhost:8989/spp_ws"
-        ws = websocket.create_connection(ws_url)
-        ws.send(msg)
-        ws.close()
+        try:
+            ws = websocket.create_connection(ws_url)
+            ws.send(msg)
+            ws.close()
+        except socket.error:
+            print('Error: Connection refused! Is running websocket server?')
 
     def to_term(self, sec_list, size):
         tmpfile = "%s.jpg" % uuid.uuid4().hex
         self.to_img(sec_list, tmpfile)
         from distutils import spawn
 
-        # TODO(yasufum) Add check for using only supported terminal
+        # TODO(yasufum) add check for using only supported terminal
         if spawn.find_executable("img2sixel") is not None:
             img_cmd = "img2sixel"
         else:
@@ -362,3 +391,57 @@ class Topo(object):
         except Exception:
             traceback.print_exc()
             return None
+
+    def complete(self, text, line, begidx, endidx):
+        """Complete topo command
+
+        If no token given, return 'term' and 'http'.
+        On the other hand, complete 'term' or 'http' if token starts
+        from it, or complete file name if is one of supported formats.
+        """
+
+        terms = ['term', 'http']
+        # Supported formats
+        img_exts = ['jpg', 'png', 'bmp']
+        txt_exts = ['dot', 'yml', 'js']
+
+        # Number of given tokens is expected as two. First one is
+        # 'topo'. If it is three or more, this methods returns nothing.
+        tokens = re.sub(r"\s+", " ", line).split(' ')
+        if (len(tokens) == 2):
+            if (text == ''):
+                completions = terms
+            else:
+                completions = []
+                # Check if 2nd token is a part of terms.
+                for t in terms:
+                    if t.startswith(tokens[1]):
+                        completions.append(t)
+                # Not a part of terms, so check for completion for
+                # output file name.
+                if len(completions) == 0:
+                    if tokens[1].endswith('.'):
+                        completions = img_exts + txt_exts
+                    elif ('.' in tokens[1]):
+                        fname = tokens[1].split('.')[0]
+                        token = tokens[1].split('.')[-1]
+                        for ext in img_exts:
+                            if ext.startswith(token):
+                                completions.append(fname + '.' + ext)
+                        for ext in txt_exts:
+                            if ext.startswith(token):
+                                completions.append(fname + '.' + ext)
+            return completions
+        else:  # do nothing for three or more tokens
+            pass
+
+    def _is_valid_port(self, port):
+        """Check if port's format is valid.
+
+        Return True if the format is 'r_type:r_id', for example, 'phy:0'.
+        """
+
+        if (':' in port) and (len(port.split(':')) == 2):
+            return True
+        else:
+            return False
diff --git a/src/controller/shell.py b/src/controller/shell.py
index ca4775b..0f95447 100644
--- a/src/controller/shell.py
+++ b/src/controller/shell.py
@@ -6,6 +6,7 @@ from __future__ import absolute_import
 import cmd
 from .commands import pri
 from .commands import sec
+from .commands import topo
 import os
 import re
 import readline
@@ -13,7 +14,6 @@ from .shell_lib import common
 from . import spp_common
 from .spp_common import logger
 import subprocess
-from . import topo
 
 
 class Shell(cmd.Cmd, object):
@@ -33,7 +33,6 @@ class Shell(cmd.Cmd, object):
     HIST_EXCEPT = ['bye', 'exit', 'history', 'redo']
 
     PLUGIN_DIR = 'command'
-    subgraphs = {}
     topo_size = '60%'
 
     # setup history file
@@ -47,6 +46,9 @@ class Shell(cmd.Cmd, object):
         self.spp_ctl_cli = spp_ctl_cli
         self.spp_primary = pri.SppPrimary(self.spp_ctl_cli)
         self.spp_secondary = sec.SppSecondary(self.spp_ctl_cli)
+        self.spp_topo = topo.SppTopo(self.spp_ctl_cli,
+                                     self.get_sec_ids('nfv'),
+                                     {})
 
     def default(self, line):
         """Define defualt behaviour.
@@ -77,10 +79,11 @@ class Shell(cmd.Cmd, object):
 
         ids = []
         res = self.spp_ctl_cli.get('processes')
-        if res.status_code == 200:
-            for ent in res.json():
-                if ent['type'] == ptype:
-                    ids.append(ent['client-id'])
+        if res is not None:
+            if res.status_code == 200:
+                for ent in res.json():
+                    if ent['type'] == ptype:
+                        ids.append(ent['client-id'])
         return ids
 
     def clean_hist_file(self):
@@ -589,13 +592,18 @@ class Shell(cmd.Cmd, object):
         label: vm1	subgraph: "vhost:1;vhost:2"
         """
 
+        #logger.info("Topo initialized with sec IDs %s" % sec_ids)
+
+        # delimiter of node in dot file
+        delim_node = '_'
+
         args_cleaned = re.sub(r"\s+", ' ', args).strip()
         # Show subgraphs if given no argments
         if (args_cleaned == ''):
-            if len(self.subgraphs) == 0:
+            if len(self.spp_topo.subgraphs) == 0:
                 print("No subgraph.")
             else:
-                for label, subg in self.subgraphs.items():
+                for label, subg in self.spp_topo.subgraphs.items():
                     print('label: %s\tsubgraph: "%s"' % (label, subg))
         else:  # add or del
             tokens = args_cleaned.split(' ')
@@ -605,13 +613,11 @@ class Shell(cmd.Cmd, object):
                     label = tokens[1]
                     subg = tokens[2]
                     if ',' in subg:
-                        subg = re.sub(
-                            r'%s' % spp_common.delim_node,
-                            spp_common.delim_label, subg)
+                        subg = re.sub(r'%s' % delim_node, ':', subg)
                         subg = re.sub(r",", ";", subg)
 
                     # TODO(yasufum) add validation for subgraph
-                    self.subgraphs[label] = subg
+                    self.spp_topo.subgraphs[label] = subg
                     print("Add subgraph '%s'" % label)
                 else:
                     print("Invalid syntax '%s'!" % args_cleaned)
@@ -619,7 +625,7 @@ class Shell(cmd.Cmd, object):
             elif ((tokens[0] == 'del') or
                     (tokens[0] == 'delete') or
                     (tokens[0] == 'remove')):
-                del(self.subgraphs[tokens[1]])
+                del(self.spp_topo.subgraphs[tokens[1]])
                 print("Delete subgraph '%s'" % tokens[1])
 
             else:
@@ -633,11 +639,11 @@ class Shell(cmd.Cmd, object):
             if len(tokens) == 1:
                 return terms
             elif len(tokens) == 2 and tokens[1] == 'del':
-                return self.subgraphs.keys()
+                return self.spp_topo.subgraphs.keys()
         elif text != '':
             completions = []
             if len(tokens) == 3 and tokens[1] == 'del':
-                for t in self.subgraphs.keys():
+                for t in self.spp_topo.subgraphs.keys():
                     if t.startswith(tokens[2]):
                         completions.append(t)
             elif len(tokens) == 2:
@@ -679,80 +685,20 @@ class Shell(cmd.Cmd, object):
         * terminal (but very few terminals supporting to display images)
         * browser (websocket server is required)
         * image file (jpg, png, bmp)
-        * text (dot, json, yaml)
+        * text (dot, js or json, yml or yaml)
 
         spp > topo term  # terminal
         spp > topo http  # browser
         spp > topo network_conf.jpg  # image
         spp > topo network_conf.dot  # text
+        spp > topo network_conf.js# text
         """
 
-        if len(spp_common.SECONDARY_LIST) == 0:
-            message = "secondary not exist"
-            print(message)
-        else:
-            tp = topo.Topo(
-                spp_common.SECONDARY_LIST,
-                spp_common.MAIN2SEC,
-                spp_common.SEC2MAIN,
-                self.subgraphs)
-            args_ary = args.split()
-            if len(args_ary) == 0:
-                print("Usage: topo dst [ftype]")
-                return False
-            elif (args_ary[0] == "term") or (args_ary[0] == "http"):
-                res_ary = tp.show(args_ary[0], self.topo_size)
-            elif len(args_ary) == 1:
-                ftype = args_ary[0].split(".")[-1]
-                res_ary = tp.output(args_ary[0], ftype)
-            elif len(args_ary) == 2:
-                res_ary = tp.output(args_ary[0], args_ary[1])
-            else:
-                print("Usage: topo dst [ftype]")
-                return False
+        self.spp_topo.run(args, self.topo_size)
 
     def complete_topo(self, text, line, begidx, endidx):
-        """Complete topo command
 
-        If no token given, return 'term' and 'http'.
-        On the other hand, complete 'term' or 'http' if token starts
-        from it, or complete file name if is one of supported formats.
-        """
-
-        terms = ['term', 'http']
-        # Supported formats
-        img_exts = ['jpg', 'png', 'bmp']
-        txt_exts = ['dot', 'yml', 'js']
-
-        # Number of given tokens is expected as two. First one is
-        # 'topo'. If it is three or more, this methods returns nothing.
-        tokens = re.sub(r"\s+", " ", line).split(' ')
-        if (len(tokens) == 2):
-            if (text == ''):
-                completions = terms
-            else:
-                completions = []
-                # Check if 2nd token is a part of terms.
-                for t in terms:
-                    if t.startswith(tokens[1]):
-                        completions.append(t)
-                # Not a part of terms, so check for completion for
-                # output file name.
-                if len(completions) == 0:
-                    if tokens[1].endswith('.'):
-                        completions = img_exts + txt_exts
-                    elif ('.' in tokens[1]):
-                        fname = tokens[1].split('.')[0]
-                        token = tokens[1].split('.')[-1]
-                        for ext in img_exts:
-                            if ext.startswith(token):
-                                completions.append(fname + '.' + ext)
-                        for ext in txt_exts:
-                            if ext.startswith(token):
-                                completions.append(fname + '.' + ext)
-            return completions
-        else:  # do nothing for three or more tokens
-            pass
+        return self.spp_topo.complete(text, line, begidx, endidx)
 
     def do_load_cmd(self, args):
         """Load command plugin.
diff --git a/src/controller/spp_common.py b/src/controller/spp_common.py
index 0986918..809bee5 100644
--- a/src/controller/spp_common.py
+++ b/src/controller/spp_common.py
@@ -23,6 +23,3 @@ SECONDARY_LIST = []
 
 # Maximum num of sock queues for secondaries
 MAX_SECONDARY = 16
-
-delim_node = '_'
-delim_label = ':'
-- 
2.13.1

  parent reply	other threads:[~2018-10-18 11:25 UTC|newest]

Thread overview: 10+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2018-10-18 11:25 [spp] [PATCH 0/9] Change SPP controller to use spp-ctl ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 1/9] controller: add spp-ctl client for SPP controller ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 2/9] controller: change controller to use spp-ctl ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 3/9] spp-ctl: add IP address binding ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 4/9] controller: add IP address and port binding ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 5/9] controller: move pri command to SppPrimary ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 6/9] controller: move sec command to SppSecondary ogawa.yasufumi
2018-10-18 11:25 ` ogawa.yasufumi [this message]
2018-10-18 11:25 ` [spp] [PATCH 8/9] controller: move topo_resize command to SppTopo ogawa.yasufumi
2018-10-18 11:25 ` [spp] [PATCH 9/9] controller: move bye command to SppBye ogawa.yasufumi

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=20181018112518.77556-8-ogawa.yasufumi@lab.ntt.co.jp \
    --to=ogawa.yasufumi@lab.ntt.co.jp \
    --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).