Merge branch 'master' of https://github.com/OPENNETWORKINGLAB/ONOS
diff --git a/web/js/onos-topology.js b/web/js/onos-topology.js
new file mode 100644
index 0000000..7bee4c0
--- /dev/null
+++ b/web/js/onos-topology.js
@@ -0,0 +1,243 @@
+function gui(data_source){
+ var width = 960,
+ height = 500;
+ var color = d3.scale.category20();
+
+ var svg = d3.select("body").append("svg:svg")
+ .attr("width", width)
+ .attr("height", height);
+
+ var force = d3.layout.force()
+ .charge(-500)
+ .linkDistance(100)
+ .size([width, height]);
+
+ var path = svg.selectAll("path");
+ var circle = svg.selectAll("circle");
+ var text = svg.selectAll("g");
+
+
+ d3.json(data_source,init);
+
+ function init(json){
+ nodes = force.nodes();
+ links = force.links();
+
+ json.nodes.forEach(function(item) {
+ nodes.push(item);
+ });
+ json.links.forEach(function(item) {
+ links.push(item);
+ });
+ draw(nodes, links);
+ }
+
+ function update(json) {
+ Array.prototype.diff2 = function(arr) {
+ return this.filter(function(i) {
+ for (var j = 0; j < arr.length ; j++) {
+ if (arr[j].source === i.source.index &&
+ arr[j].target === i.target.index)
+ return false;
+ }
+ return true;
+ });
+ };
+
+ Array.prototype.diff = function(arr) {
+ return this.filter(function(i) {
+ for (var j = 0; j < arr.length ; j++) {
+ if (arr[j].source.index === i.source &&
+ arr[j].target.index === i.target)
+ return false;
+ }
+ return true;
+ });
+ };
+
+ Array.prototype.node_diff = function(arr) {
+ return this.filter(function(i) {
+ for (var j = 0; j < arr.length ; j++) {
+ if (arr[j].name === i.name)
+ return false;
+ }
+ return true;
+ });
+ };
+
+
+// links.sort(function(a,b) {
+// if (a.source > b.source) {return 1;}
+// else if (a.source < b.source) {return -1;}
+// else {
+// if (a.target > b.target) {return 1;}
+// if (a.target < b.target) {return -1;}
+// else {return 0;}
+// }
+// });
+// for (var i=0; i<links.length; i++) {
+// if (i != 0 &&
+// links[i].source == links[i-1].source &&
+// links[i].target == links[i-1].target) {
+// links[i].linknum = links[i-1].linknum + 1;
+// }
+// else {links[i].linknum = 1;};
+// };
+
+
+
+ function cdiff(topo) {
+ var changed = false;
+ var l_adds = topo.links.diff(links);
+ var l_rems = links.diff2(topo.links);
+
+ var n_adds = topo.nodes.node_diff(nodes);
+ var n_rems = nodes.node_diff(topo.nodes);
+
+ for (var i = 0; i < l_rems.length ; i++) {
+ for (var j = 0; j < links.length; j++) {
+ if (links[j].source.index == l_rems[i].source.index &&
+ links[j].target.index == l_rems[i].target.index) {
+ links.splice(j,1);
+ changed = true;
+ break;
+ }
+ }
+ }
+ for (var i = 0; i < l_adds.length; i++) {
+ links.push(l_adds[i]);
+ changed = true;
+ }
+ for (var i = 0; i < n_rems.length; i++) {
+ for (var j = 0; j < nodes.length; j++) {
+ if ( nodes[j].name == n_rems[i].name ){
+ nodes.splice(j,1);
+ changed = true;
+ break;
+ }
+ }
+ }
+ for (var i = 0; i < n_adds.length; i++) {
+ nodes.push(n_adds[i]);
+ changed = true;
+ }
+ return changed
+ }
+
+
+
+ var changed = cdiff(json);
+ for (var i = 0; i < json.nodes.length; i++) {
+ nodes[i].group = json.nodes[i].group
+ }
+
+ console.log(circle);
+
+ console.log("changed?");
+ console.log(changed);
+
+
+ if (changed){
+ path = svg.selectAll("path").data(links)
+ circle = svg.selectAll("circle").data(nodes);
+ text = svg.selectAll("text").data(nodes);
+
+ force.stop();
+
+ path.enter().append("svg:path")
+ .attr("class", function(d) { return "link"; })
+ .attr("marker-end", "url(#Triangle)");
+
+ circle.enter().append("svg:circle")
+ .attr("r", 6)
+ .call(force.drag);
+
+/* text.enter().append("svg:text")
+ .attr("x", 8)
+ .attr("y", ".31em")
+ .attr("class", "shadow")
+ .text(function(d) { return d.name.split(":")[7]; }); */
+
+ text.enter().append("svg:text")
+ .attr("x", 8)
+ .attr("y", ".31em")
+ .text(function(d) { return d.name.split(":")[7]; });
+
+ circle.append("title")
+ .text(function(d) { return d.name; });
+
+ path.exit().remove();
+ circle.exit().remove();
+ text.exit().remove();
+
+ force.on("tick", tick);
+ force.start();
+ }
+ }
+ function draw(nodes, links){
+ path = svg.append("svg:g").selectAll("path").data(links)
+ circle = svg.append("svg:g").selectAll("circle").data(nodes);
+ text = svg.append("svg:g").selectAll("text").data(nodes);
+
+ path.enter().append("svg:path")
+ .attr("class", function(d) { return "link"; })
+ .attr("marker-end", "url(#Triangle)");
+
+ circle.enter().append("svg:circle")
+ .attr("r", 8)
+ .call(force.drag);
+
+/* text.enter().append("svg:text")
+ .attr("x", 8)
+ .attr("y", ".31em")
+ .attr("class", "shadow")
+ .text(function(d) { return d.name.split(":")[7]; }); */
+
+ text.enter().append("svg:text")
+ .attr("x", 8)
+ .attr("y", ".31em")
+ .text(function(d) { return d.name.split(":")[7]; });
+
+ circle.append("title")
+ .text(function(d) { return d.name; });
+
+ circle.attr("fill", function(d) { if (d.group == 0){return "blue";}else{ return "gray"; }})
+
+ force.on("tick", tick);
+ path.exit().remove();
+ circle.exit().remove();
+// text.exit().remove();
+
+ force.start();
+
+ setInterval(function() {
+ $.ajax({
+// url: 'http://onosnat.onlab.us:8080/topology',
+ url: data_source,
+ success: function(json) {
+ update(json)
+ },
+ dataType: "json"
+ });
+ }, 3000);
+ }
+ function tick() {
+ path.attr("d", function(d) {
+ var dx = d.target.x - d.source.x,
+ dy = d.target.y - d.source.y,
+ dr = 1/d.linknum; //linknum is defined above
+ dr = 300;
+ return "M" + d.source.x + "," + d.source.y + "A" + dr + "," + dr + " 0 0,1 " + d.target.x + "," + d.target.y;
+ });
+// circle.attr("cx", function(d) { return d.x; }).attr("cy", function(d) { return d.y; });
+ circle.attr("transform", function(d) {
+ return "translate(" + d.x + "," + d.y + ")";
+ })
+ circle.attr("fill", function(d) { if (d.group == 0){return "blue";}else{ return "gray"; }});
+// text.attr("x", function(d) { return d.x; }).attr("y", function(d) { return d.y; });
+// text.attr("x", function(d) { return d.x; }).attr("y", function(d) { return d.y; });
+ text.attr("transform", function(d) {
+ return "translate(" + d.x + "," + d.y + ")";
+ });
+ }
+}
diff --git a/web/onos-topology.html b/web/onos-topology.html
new file mode 100644
index 0000000..1edbaee
--- /dev/null
+++ b/web/onos-topology.html
@@ -0,0 +1,45 @@
+<!DOCTYPE html>
+<html>
+<meta charset="utf-8">
+<style>
+path.link {
+ fill: none;
+ stroke: #666;
+ stroke-width: 1.5px;
+}
+circle {
+ stroke: #333;
+ stroke-width: 1.5px;
+}
+
+text {
+ font: 20px sans-serif;
+ pointer-events: none;
+}
+
+</style>
+<head>
+<title>ONOS GUI</title>
+<script src="http://d3js.org/d3.v3.min.js"></script>
+<script type="text/javascript" src="js/onos-topology.js"></script>
+<script type="text/javascript" src="js/jquery-1.7.2.min.js"></script>
+</head>
+<body>
+<svg width="4in" height="2in"
+ viewBox="0 0 4000 2000" version="1.1"
+ xmlns="http://www.w3.org/2000/svg">
+ <defs>
+ <marker id="Triangle"
+ viewBox="0 -5 10 10" refX="15" refY="-1.5"
+ markerUnits="strokeWidth"
+ markerWidth="6" markerHeight="6"
+ orient="auto">
+ <path d="M0,-5L10,0L0,5"/>
+ </marker>
+ </defs>
+<script type="text/javascript">
+gui("http://onosnat.onlab.us:8080/topology");
+</script>
+</svg>
+</body>
+</html>
diff --git a/web/topology_rest.py b/web/topology_rest.py
new file mode 100755
index 0000000..cbb2300
--- /dev/null
+++ b/web/topology_rest.py
@@ -0,0 +1,313 @@
+#! /usr/bin/env python
+import pprint
+import os
+import sys
+import subprocess
+import json
+import argparse
+import io
+import time
+
+from flask import Flask, json, Response, render_template, make_response, request
+
+## Global Var ##
+RestIP="onos1vpc"
+RestPort=8080
+#DBName="onos-network-map"
+
+DEBUG=1
+pp = pprint.PrettyPrinter(indent=4)
+
+app = Flask(__name__)
+
+## Worker Functions ##
+def log_error(txt):
+ print '%s' % (txt)
+
+def debug(txt):
+ if DEBUG:
+ print '%s' % (txt)
+
+## Rest APIs ##
+### File Fetch ###
+@app.route('/ui/img/<filename>', methods=['GET'])
+@app.route('/img/<filename>', methods=['GET'])
+@app.route('/css/<filename>', methods=['GET'])
+@app.route('/js/models/<filename>', methods=['GET'])
+@app.route('/js/views/<filename>', methods=['GET'])
+@app.route('/js/<filename>', methods=['GET'])
+@app.route('/lib/<filename>', methods=['GET'])
+@app.route('/', methods=['GET'])
+@app.route('/<filename>', methods=['GET'])
+@app.route('/tpl/<filename>', methods=['GET'])
+def return_file(filename="index.html"):
+ if request.path == "/":
+ fullpath = "./index.html"
+ else:
+ fullpath = str(request.path)[1:]
+
+ response = make_response(open(fullpath).read())
+ suffix = fullpath.split(".")[-1]
+
+ if suffix == "html" or suffix == "htm":
+ response.headers["Content-type"] = "text/html"
+ elif suffix == "js":
+ response.headers["Content-type"] = "application/javascript"
+ elif suffix == "css":
+ response.headers["Content-type"] = "text/css"
+ elif suffix == "png":
+ response.headers["Content-type"] = "image/png"
+
+ return response
+
+init_topo1 = {
+ "nodes" : [
+ {"name" : "sw0", "group" : 0},
+ {"name" : "sw1", "group" : 0},
+ {"name" : "sw2", "group" : 0},
+ {"name" : "sw3", "group" : 0},
+ {"name" : "sw4", "group" : 0},
+ {"name" : "sw5", "group" : 0},
+ {"name" : "host0", "group" : 1}
+ ],
+ "links" : [
+ {"source" :0, "target": 1},
+ {"source" :1, "target": 0},
+ {"source" :0, "target": 2},
+ {"source" :2, "target": 0},
+ {"source" :1, "target": 3},
+ {"source" :3, "target": 1},
+ {"source" :2, "target": 3},
+ {"source" :3, "target": 2},
+ {"source" :2, "target": 4},
+ {"source" :4, "target": 2},
+ {"source" :3, "target": 5},
+ {"source" :5, "target": 3},
+ {"source" :4, "target": 5},
+ {"source" :5, "target": 4},
+ {"source" :6, "target": 0},
+ {"source" :0, "target": 6}
+ ]
+}
+
+def node_id(switch_array, dpid):
+ id = -1
+ for i, val in enumerate(switch_array):
+ if val['name'] == dpid:
+ id = i
+ break
+
+ return id
+
+@app.route("/topology")
+def topology_for_gui():
+ try:
+ command = "curl -s \'http://%s:%s/wm/core/topology/switches/all/json\'" % (RestIP, RestPort)
+ result = os.popen(command).read()
+ parsedResult = json.loads(result)
+ except:
+ log_error("REST IF has issue: %s" % command)
+ log_error("%s" % result)
+ sys.exit(0)
+
+ topo = {}
+ switches = []
+ links = []
+
+ for v in parsedResult:
+ if v.has_key('dpid'):
+# if v.has_key('dpid') and str(v['state']) == "ACTIVE":#;if you want only ACTIVE nodes
+ dpid = str(v['dpid'])
+ state = str(v['state'])
+ sw = {}
+ sw['name']=dpid
+ if str(v['state']) == "ACTIVE":
+ sw['group']=0
+ if str(v['state']) == "INACTIVE":
+ sw['group']=1
+
+ switches.append(sw)
+
+ try:
+ command = "curl -s \'http://%s:%s/wm/core/topology/links/json\'" % (RestIP, RestPort)
+ result = os.popen(command).read()
+ parsedResult = json.loads(result)
+ except:
+ log_error("REST IF has issue: %s" % command)
+ log_error("%s" % result)
+ sys.exit(0)
+
+ for v in parsedResult:
+ link = {}
+ if v.has_key('dst-switch'):
+ dst_dpid = str(v['dst-switch'])
+ dst_id = node_id(switches, dst_dpid)
+ if v.has_key('src-switch'):
+ src_dpid = str(v['src-switch'])
+ src_id = node_id(switches, src_dpid)
+ link['source'] = src_id
+ link['target'] = dst_id
+ links.append(link)
+
+ topo['nodes'] = switches
+ topo['links'] = links
+
+ pp.pprint(topo)
+ js = json.dumps(topo)
+ resp = Response(js, status=200, mimetype='application/json')
+ return resp
+
+
+@app.route("/wm/core/controller/switches/json")
+def query_switch():
+ try:
+ command = "curl -s \'http://%s:%s/wm/core/topology/switches/all/json\'" % (RestIP, RestPort)
+# http://localhost:8080/wm/core/topology/switches/active/json
+ result = os.popen(command).read()
+ parsedResult = json.loads(result)
+ except:
+ log_error("REST IF has issue: %s" % command)
+ log_error("%s" % result)
+ sys.exit(0)
+
+# print command
+# print result
+ switches_ = []
+ for v in parsedResult:
+ if v.has_key('dpid'):
+ if v.has_key('dpid') and str(v['state']) == "ACTIVE":#;if you want only ACTIVE nodes
+ dpid = str(v['dpid'])
+ state = str(v['state'])
+ sw = {}
+ sw['dpid']=dpid
+ sw['active']=state
+ switches_.append(sw)
+
+ pp.pprint(switches_)
+ js = json.dumps(switches_)
+ resp = Response(js, status=200, mimetype='application/json')
+ return resp
+
+@app.route("/wm/device/")
+def devices():
+ try:
+ command = "curl -s http://%s:%s/graphs/%s/vertices\?key=type\&value=device" % (RestIP, RestPort, DBName)
+ result = os.popen(command).read()
+ parsedResult = json.loads(result)['results']
+ except:
+ log_error("REST IF has issue: %s" % command)
+ log_error("%s" % result)
+ sys.exit(0)
+
+ devices = []
+ for v in parsedResult:
+ dl_addr = v['dl_addr']
+ nw_addr = v['nw_addr']
+ vertex = v['_id']
+ mac = []
+ mac.append(dl_addr)
+ ip = []
+ ip.append(nw_addr)
+ device = {}
+ device['entryClass']="DefaultEntryClass"
+ device['mac']=mac
+ device['ipv4']=ip
+ device['vlan']=[]
+ device['lastSeen']=0
+ attachpoints =[]
+
+ port, dpid = deviceV_to_attachpoint(vertex)
+ attachpoint = {}
+ attachpoint['port']=port
+ attachpoint['switchDPID']=dpid
+ attachpoints.append(attachpoint)
+ device['attachmentPoint']=attachpoints
+ devices.append(device)
+
+ print devices
+ js = json.dumps(devices)
+ resp = Response(js, status=200, mimetype='application/json')
+ return resp
+
+#{"entityClass":"DefaultEntityClass","mac":["7c:d1:c3:e0:8c:a3"],"ipv4":["192.168.2.102","10.1.10.35"],"vlan":[],"attachmentPoint":[{"port":13,"switchDPID":"00:01:00:12:e2:78:32:44","errorStatus":null}],"lastSeen":1357333593496}
+
+
+## return fake stat for now
+@app.route("/wm/core/switch/<switchId>/<statType>/json")
+def switch_stat(switchId, statType):
+ if statType == "desc":
+ desc=[{"length":1056,"serialNumber":"None","manufacturerDescription":"Nicira Networks, Inc.","hardwareDescription":"Open vSwitch","softwareDescription":"1.4.0+build0","datapathDescription":"None"}]
+ ret = {}
+ ret[switchId]=desc
+ elif statType == "aggregate":
+ aggr = {"packetCount":0,"byteCount":0,"flowCount":0}
+ ret = {}
+ ret[switchId]=aggr
+ else:
+ ret = {}
+
+ js = json.dumps(ret)
+ resp = Response(js, status=200, mimetype='application/json')
+ return resp
+
+
+@app.route("/wm/topology/links/json")
+def query_links():
+ try:
+ command = 'curl -s http://%s:%s/graphs/%s/vertices?key=type\&value=port' % (RestIP, RestPort, DBName)
+ result = os.popen(command).read()
+ parsedResult = json.loads(result)['results']
+ except:
+ log_error("REST IF has issue: %s" % command)
+ log_error("%s" % result)
+ sys.exit(0)
+
+ debug("query_links %s" % command)
+ pp.pprint(parsedResult)
+ sport = []
+ links = []
+ for v in parsedResult:
+ srcport = v['_id']
+ try:
+ command = "curl -s http://%s:%s/graphs/%s/vertices/%d/out?_label=link" % (RestIP, RestPort, DBName, srcport)
+ print command
+ result = os.popen(command).read()
+ linkResults = json.loads(result)['results']
+ except:
+ log_error("REST IF has issue: %s" % command)
+ log_error("%s" % result)
+ sys.exit(0)
+
+ for p in linkResults:
+ if p.has_key('type') and p['type'] == "port":
+ dstport = p['_id']
+ (sport, sdpid) = portV_to_port_dpid(srcport)
+ (dport, ddpid) = portV_to_port_dpid(dstport)
+ link = {}
+ link["src-switch"]=sdpid
+ link["src-port"]=sport
+ link["src-port-state"]=0
+ link["dst-switch"]=ddpid
+ link["dst-port"]=dport
+ link["dst-port-state"]=0
+ link["type"]="internal"
+ links.append(link)
+
+ pp.pprint(links)
+ js = json.dumps(links)
+ resp = Response(js, status=200, mimetype='application/json')
+ return resp
+
+if __name__ == "__main__":
+ if len(sys.argv) > 1 and sys.argv[1] == "-d":
+ print "-- query all switches --"
+ query_switch()
+ print "-- query topo --"
+ topology_for_gui()
+# print "-- query all links --"
+# query_links()
+# print "-- query all devices --"
+# devices()
+ else:
+ app.debug = True
+ app.run(host="0.0.0.0", port=9000)