[spp] [PATCH 1/2] controller: add websocket server for topo cmd

ogawa.yasufumi at lab.ntt.co.jp ogawa.yasufumi at lab.ntt.co.jp
Tue Jun 12 09:03:42 CEST 2018


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

Topo command is for showing SPP network configuration graphically.
This update is to add the feature to output in browser. Network
topology is shown on a browser by runnign 'topo http' from SPP
controller.

It is implemented with 'tornado'[1] for websocket and 'vis.js'[2] for
parsing dot language and depicting on browser. Using websocket means
that you can display the topology immediately if it is updated.

Expression of dot of 'vis.js' is poor comparing with graphviz actually.
However, you can see the topology on remote node with 'topo http', and
'topo term' cannot do it.

To activate this 'topo http' command, you should install requried
packages with pip (or pip3).

  * tornado
  * websocket-client

[1] http://www.tornadoweb.org/en/stable/
[2] http://visjs.org/

Signed-off-by: Yasufumi Ogawa <ogawa.yasufumi at lab.ntt.co.jp>
---
 src/controller/websocket/spp_ws.py            | 98 +++++++++++++++++++
 src/controller/websocket/static/main.css      | 67 +++++++++++++
 src/controller/websocket/static/spp_ws.js     | 70 +++++++++++++
 src/controller/websocket/templates/index.html | 40 ++++++++
 4 files changed, 275 insertions(+)
 create mode 100755 src/controller/websocket/spp_ws.py
 create mode 100644 src/controller/websocket/static/main.css
 create mode 100644 src/controller/websocket/static/spp_ws.js
 create mode 100644 src/controller/websocket/templates/index.html

diff --git a/src/controller/websocket/spp_ws.py b/src/controller/websocket/spp_ws.py
new file mode 100755
index 0000000..848945a
--- /dev/null
+++ b/src/controller/websocket/spp_ws.py
@@ -0,0 +1,98 @@
+#!/usr/bin/env python
+# SPDX-License-Identifier: BSD-3-Clause
+# Copyright(c) 2017-2018 Nippon Telegraph and Telephone Corporation
+
+import logging
+import os
+import tornado.escape
+import tornado.ioloop
+import tornado.options
+import tornado.web
+import tornado.websocket
+
+from tornado.options import define
+from tornado.options import options
+
+curdir = os.path.abspath(os.path.dirname(__file__))
+logging.basicConfig(
+    filename='%s/server.log' % curdir, level=logging.DEBUG)
+
+# Server address
+ipaddr = '127.0.0.1'  # used only for start message
+port = 8989
+
+define("port", default=port, help="Wait for 'topo http' command", type=int)
+
+
+class Application(tornado.web.Application):
+    def __init__(self):
+        handlers = [
+            (r"/", MainHandler),
+            (r"/spp_ws", SppWsHandler),
+        ]
+        settings = dict(
+            template_path=os.path.join(
+                os.path.dirname(__file__), "templates"),
+            static_path=os.path.join(
+                os.path.dirname(__file__), "static"),
+            cookie_secret="yasupyasup",
+            xsrf_cookies=True,
+        )
+        super(Application, self).__init__(handlers, **settings)
+
+
+class MainHandler(tornado.web.RequestHandler):
+    def get(self):
+        logging.info("Render to browser\n%s", SppWsHandler.dot)
+        self.render(
+            "index.html",
+            dotData=SppWsHandler.dot.replace('\n', ''))
+
+
+class SppWsHandler(tornado.websocket.WebSocketHandler):
+    waiters = set()
+    dot = ""
+
+    def get_compression_options(self):
+        # Non-None enables compression with default options.
+        return {}
+
+    def open(self):
+        SppWsHandler.waiters.add(self)
+
+    def on_close(self):
+        SppWsHandler.waiters.remove(self)
+
+    @classmethod
+    def update_dot(cls, msg):
+        cls.dot = msg
+
+    @classmethod
+    def send_updates(cls, msg):
+        logging.info(
+            "Send message to %d clients", len(cls.waiters))
+        for waiter in cls.waiters:
+            try:
+                waiter.write_message(msg)
+            except Exception:
+                pass
+                logging.error("Error sending message", exc_info=True)
+
+    def on_message(self, message):
+        logging.info("Received from client\n%r", message)
+        self.dot = message
+        SppWsHandler.update_dot(message)
+        SppWsHandler.send_updates(message)
+
+
+def main():
+    tornado.options.parse_command_line()
+    app = Application()
+    app.listen(options.port)
+    print('Running SPP topo server on http://%s:%d ...' % (
+        ipaddr, port))
+    tornado.ioloop.IOLoop.current().start()
+
+
+if __name__ == "__main__":
+    main()
diff --git a/src/controller/websocket/static/main.css b/src/controller/websocket/static/main.css
new file mode 100644
index 0000000..d2fdfa9
--- /dev/null
+++ b/src/controller/websocket/static/main.css
@@ -0,0 +1,67 @@
+body, html {
+  font: 10pt sans;
+  line-height: 1.5em;;
+  width: 100%;
+  height: 100%;
+  padding: 0;
+  margin: 0;
+  color: #4d4d4d;
+  box-sizing: border-box;
+  overflow: hidden;
+}
+
+#header {
+  margin: 0;
+  padding: 10px;
+  box-sizing: border-box;
+}
+
+#contents {
+  height: 100%;
+  margin: 0;
+  padding: 0;
+  box-sizing: border-box;
+  position: relative;
+}
+
+#right {
+  position: absolute;
+  width: 100%;
+  height: 100%;
+  margin: 0;
+  padding: 10px;
+  box-sizing: border-box;
+  display: inline-block;
+}
+
+#right {
+  top: 0;
+  right: 0;
+}
+
+#error {
+  color: red;
+}
+
+#data {
+  width: 100%;
+  height: 100%;
+  border: 1px solid #d3d3d3;
+  box-sizing: border-box;
+  resize: none;
+}
+
+#draw, #reset {
+  padding: 5px 15px;
+}
+
+#spp_topo_network{
+  width: 100%;
+  height: 100%;
+  border: 1px solid #d3d3d3;
+  box-sizing: border-box;
+}
+
+a:hover {
+  color: red;
+}
diff --git a/src/controller/websocket/static/spp_ws.js b/src/controller/websocket/static/spp_ws.js
new file mode 100644
index 0000000..80b70b6
--- /dev/null
+++ b/src/controller/websocket/static/spp_ws.js
@@ -0,0 +1,70 @@
+/**
+ * @license
+ * SPDX-License-Identifier: BSD-3-Clause
+ * Copyright(c) 2017-2018 Nippon Telegraph and Telephone Corporation
+ */
+
+$(document).ready(function() {
+  if (!window.console) window.console = {};
+  if (!window.console.log) window.console.log = function() {};
+
+  updater.start();
+});
+
+var updater = {
+  socket: null,
+
+  start: function() {
+    var url = "ws://" + location.host + "/spp_ws";
+    updater.socket = new WebSocket(url);
+    updater.socket.onmessage = function(event) {
+      updater.showDot(event.data);
+    }
+  },
+
+  showDot: function(dot) {
+    console.log(dot);
+    dotData = dot
+    draw();
+  }
+};
+
+
+// create a network
+var container = document.getElementById('spp_topo_network');
+var options = {
+  physics: {
+    stabilization: false,
+    barnesHut: {
+      springLength: 200
+    }
+  }
+};
+var data = {};
+var network = new vis.Network(container, data, options);
+
+$(window).resize(resize);
+$(window).load(draw);
+
+function resize() {
+  $('#contents').height($('body').height() - $('#header').height() - 30);
+}
+
+function draw() {
+  try {
+    resize();
+    $('#error').html('');
+
+    data = vis.network.convertDot(dotData);
+
+    network.setData(data);
+  }
+  catch (err) {
+    // show an error message
+    $('#error').html(err.toString());
+  }
+}
+
+window.onload = function() {
+  draw();
+}
diff --git a/src/controller/websocket/templates/index.html b/src/controller/websocket/templates/index.html
new file mode 100644
index 0000000..ceedc21
--- /dev/null
+++ b/src/controller/websocket/templates/index.html
@@ -0,0 +1,40 @@
+<!doctype html>
+<html>
+  <head>
+    <title>SPP Topo Viewer</title>
+
+    <script src="https://ajax.googleapis.com/ajax/libs/jquery/3.3.1/jquery.min.js"
+      type="text/javascript"></script>
+    <script src="http://visjs.org/dist/vis.js" type="text/javascript"></script>
+    <link rel="stylesheet" href="{{ static_url("main.css") }}" type="text/css" />
+    <link rel="stylesheet" href="http://visjs.org/dist/vis-network.min.css"
+    type="text/css" />
+  </head>
+  <body>
+
+    <div id="header">
+      <h1>SPP Topo Viewer</h1>
+      <div>
+        Show topology of
+        <a href="https://spp.readthedocs.io/en/latest/index.html">SPP</a> network
+        from '<i>topo http</i>' command.
+        Please refer
+        <a href="https://spp.readthedocs.io/en/latest/commands/experimental.html#topo">
+          topo
+        </a> command reference.
+      </div>
+    </div>
+
+    <div id="contents">
+      <div id="right">
+        <div id="spp_topo_network"></div>
+      </div>
+    </div>
+
+    <script type="text/javascript">
+      {% autoescape None %}
+      var dotData = '{{ dotData }}';
+    </script>
+    <script src="{{ static_url("spp_ws.js") }}" type="text/javascript"></script>
+  </body>
+</html>
-- 
2.17.1



More information about the spp mailing list