Fix stale cached links in visual topology
[controller.git] / opendaylight / web / topology / src / main / java / org / opendaylight / controller / topology / web / Topology.java
1
2 /*
3  * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
4  *
5  * This program and the accompanying materials are made available under the
6  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
7  * and is available at http://www.eclipse.org/legal/epl-v10.html
8  */
9
10 package org.opendaylight.controller.topology.web;
11
12 import java.awt.Dimension;
13 import java.nio.ByteBuffer;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.HashMap;
17 import java.util.LinkedList;
18 import java.util.List;
19 import java.util.Map;
20 import java.util.Set;
21
22 import javax.servlet.http.HttpServletRequest;
23
24 import org.opendaylight.controller.sal.authorization.UserLevel;
25 import org.opendaylight.controller.sal.core.Bandwidth;
26 import org.opendaylight.controller.sal.core.Edge;
27 import org.opendaylight.controller.sal.core.Host;
28 import org.opendaylight.controller.sal.core.Node;
29 import org.opendaylight.controller.sal.core.NodeConnector;
30 import org.opendaylight.controller.sal.core.Property;
31 import org.opendaylight.controller.sal.packet.address.EthernetAddress;
32 import org.opendaylight.controller.sal.utils.ServiceHelper;
33 import org.opendaylight.controller.switchmanager.ISwitchManager;
34 import org.opendaylight.controller.switchmanager.Switch;
35 import org.opendaylight.controller.switchmanager.SwitchConfig;
36 import org.opendaylight.controller.topologymanager.ITopologyManager;
37 import org.opendaylight.controller.usermanager.IUserManager;
38 import org.springframework.stereotype.Controller;
39 import org.springframework.web.bind.annotation.PathVariable;
40 import org.springframework.web.bind.annotation.RequestMapping;
41 import org.springframework.web.bind.annotation.RequestMethod;
42 import org.springframework.web.bind.annotation.RequestParam;
43 import org.springframework.web.bind.annotation.ResponseBody;
44
45 import edu.uci.ics.jung.algorithms.layout.CircleLayout;
46 import edu.uci.ics.jung.graph.Graph;
47 import edu.uci.ics.jung.graph.SparseMultigraph;
48
49 @Controller
50 @RequestMapping("/")
51 public class Topology {
52
53     protected Map<String, Map<String, Object>> cache = new HashMap<String, Map<String, Object>>();
54     protected Map<String, Map<String, Object>> stagedNodes;
55     protected Map<String, Map<String, Object>> newNodes;
56     protected Integer nodeHash = null;
57     protected Integer hostHash = null;
58     protected Integer nodeSingleHash = null;
59     protected Integer nodeConfigurationHash = null;
60     
61     /**
62      * Topology of nodes and hosts in the network in JSON format.
63      * 
64      * Mainly intended for consumption by the visual topology.
65      * 
66      * @return - JSON output for visual topology
67      */
68     @RequestMapping(value = "/visual.json", method = RequestMethod.GET)
69     @ResponseBody
70     public Collection<Map<String, Object>> getLinkData() {
71         ITopologyManager topologyManager = (ITopologyManager) ServiceHelper
72                 .getInstance(ITopologyManager.class, "default", this);
73         if (topologyManager == null) return null;
74         ISwitchManager switchManager = (ISwitchManager) ServiceHelper
75                 .getInstance(ISwitchManager.class, "default", this);
76         if (switchManager == null) return null;
77         
78         Map<Node, Set<Edge>> nodeEdges = topologyManager.getNodeEdges();
79         Map<Node, Set<NodeConnector>> hostEdges = topologyManager
80                 .getNodesWithNodeConnectorHost();
81         List<Switch> nodes = switchManager.getNetworkDevices();
82         
83         List<SwitchConfig> switchConfigurations = new ArrayList<SwitchConfig>();
84         for(Switch sw : nodes) {
85                 Node n = sw.getNode();
86                 SwitchConfig config = switchManager.getSwitchConfig(n.getNodeIDString());
87                 switchConfigurations.add(config);
88         }
89         
90         // return cache if topology hasn't changed
91         if (
92                 (nodeHash != null && hostHash != null && nodeSingleHash != null && nodeConfigurationHash != null) &&
93                 nodeHash == nodeEdges.hashCode() && hostHash == hostEdges.hashCode() && nodeSingleHash == nodes.hashCode() && nodeConfigurationHash == switchConfigurations.hashCode()
94         ) {
95                 return cache.values();
96         }
97         
98         // cache has changed, we must assign the new values
99         nodeHash = nodeEdges.hashCode();
100         hostHash = hostEdges.hashCode();
101         nodeSingleHash = nodes.hashCode();
102         nodeConfigurationHash = switchConfigurations.hashCode();
103         
104         stagedNodes = new HashMap<String, Map<String, Object>>();
105         newNodes = new HashMap<String, Map<String, Object>>();
106
107         // nodeEdges addition
108         addNodes(nodeEdges, topologyManager, switchManager);
109
110         // single nodes addition
111         addSingleNodes(nodes, switchManager);
112         
113         // hostNodes addition
114         addHostNodes(hostEdges, topologyManager);
115         
116         repositionTopology();
117         
118         return cache.values();
119     }
120
121     /**
122      * Add regular nodes to main topology
123      *
124      * @param nodeEdges - node-edges mapping derived from topology
125      * @param topology - the topology instance
126      */
127     private void addNodes(Map<Node, Set<Edge>> nodeEdges,
128             ITopologyManager topology, ISwitchManager switchManager) {
129         Bandwidth bandwidth = new Bandwidth(0);
130         Map<Edge, Set<Property>> properties = topology.getEdges();
131         
132         for (Map.Entry<Node, Set<Edge>> e : nodeEdges.entrySet()) {
133             Node n = e.getKey();
134             SwitchConfig config = switchManager.getSwitchConfig(n.getNodeIDString());
135             NodeBean node = createNodeBean(config, n);
136             
137             List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
138             Set<Edge> links = e.getValue();
139             for (Edge link : links) {
140                 for (Property p : properties.get(link)) {
141                     if (p instanceof Bandwidth) {
142                         bandwidth = (Bandwidth) p;
143                         break;
144                     }
145                 }
146                 EdgeBean edge = new EdgeBean(link, bandwidth);
147                 adjacencies.add(edge.out());
148             }
149             
150             node.setLinks(adjacencies);
151             if (cache.containsKey(node.id())) {
152                 // retrieve node from cache
153                 Map<String, Object> nodeEntry = cache.get(node.id());
154                 // update node name when appropriate
155                 if (config != null) {
156                         Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
157                         data.put("$desc", config.getNodeDescription());
158                         nodeEntry.put("data", data);
159                 }
160                 // always update adjacencies
161                 nodeEntry.put("adjacencies", adjacencies);
162                 // stage this cached node (with position)
163                 stagedNodes.put(node.id(), nodeEntry);
164             } else {
165                 newNodes.put(node.id(), node.out());
166             }
167         }
168     }
169     
170     protected NodeBean createNodeBean(SwitchConfig config, Node node) {
171         NodeBean bean = null;
172         if (config != null) {
173                 bean = new NodeBean(node.toString(), config.getNodeDescription(), NodeType.NODE);
174         } else {
175                 bean = new NodeBean(node.toString(), node.toString(), NodeType.NODE);
176         }
177         
178         return bean;
179     }
180     
181     private void addSingleNodes(List<Switch> nodes, ISwitchManager switchManager) {
182         if (nodes == null) return;
183         for (Switch sw : nodes) {
184                 Node n = sw.getNode();
185                 SwitchConfig config = switchManager.getSwitchConfig(n.getNodeIDString());
186                 
187                 if ((stagedNodes.containsKey(n.toString()) && cache.containsKey(n.toString())) || newNodes.containsKey(n.toString())) {
188                         continue;
189                 }
190                 NodeBean node = createNodeBean(config, n);
191                 
192                 // FIXME still doesn't display standalone node when last remaining link is removed
193                 if (cache.containsKey(node.id()) && !stagedNodes.containsKey(node.id())) {
194                         Map<String, Object> nodeEntry = cache.get(node.id());
195                         if (config != null) {
196                                 Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
197                         data.put("$desc", config.getNodeDescription());
198                         nodeEntry.put("data", data);
199                         }
200                 stagedNodes.put(node.id(), nodeEntry);
201             } else {
202                 newNodes.put(node.id(), node.out());
203             }
204         }
205     }
206
207     /**
208      * Add regular hosts to main topology
209      *
210      * @param hostEdges - node-nodeconnectors host-specific mapping from topology
211      * @param topology - topology instance
212      */
213     private void addHostNodes(Map<Node, Set<NodeConnector>> hostEdges,
214             ITopologyManager topology) {
215         for (Map.Entry<Node, Set<NodeConnector>> e : hostEdges.entrySet()) {
216             for (NodeConnector connector : e.getValue()) {
217                 Host host = topology.getHostAttachedToNodeConnector(connector);
218                 EthernetAddress dmac = (EthernetAddress) host.getDataLayerAddress();
219
220                 ByteBuffer addressByteBuffer = ByteBuffer.allocate(8);
221                 addressByteBuffer.putShort((short) 0);
222                 addressByteBuffer.put(dmac.getValue());
223                 addressByteBuffer.rewind();
224                 
225                 long hid = addressByteBuffer.getLong();
226                 String hostId = String.valueOf(hid);
227                 
228                 NodeBean hostBean = new NodeBean(hostId, host.getNetworkAddressAsString(), NodeType.HOST);
229                 List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
230                 EdgeBean edge = new EdgeBean(connector, hid);
231                 adjacencies.add(edge.out());
232                 hostBean.setLinks(adjacencies);
233                 
234                 if (cache.containsKey(hostId)) {
235                         Map<String, Object> hostEntry = cache.get(hostId);
236                         hostEntry.put("adjacencies", adjacencies);
237                         stagedNodes.put(hostId, hostEntry);
238                 } else {
239                         newNodes.put(String.valueOf(hid), hostBean.out());
240                 }
241             }
242         }
243     }
244
245     /**
246      * Re-position nodes in circular layout
247      */
248     private void repositionTopology() {
249         Graph<String, String> graph = new SparseMultigraph<String, String>();
250         cache.clear();
251         cache.putAll(stagedNodes);
252         cache.putAll(newNodes);
253         for (Map<String, Object> on : cache.values()) {
254             graph.addVertex(on.toString());
255
256             List<Map<String, Object>> adjacencies = (List<Map<String, Object>>) on.get("adjacencies");
257             
258             for (Map<String, Object> adj : adjacencies) {
259                 graph.addEdge(
260                         adj.toString(), adj.get("nodeFrom").toString(),
261                         adj.get("nodeTo").toString()
262                 );
263             }
264         }
265         
266         CircleLayout layout = new CircleLayout(graph);
267         layout.setSize(new Dimension(1200, 365));
268         for (Map.Entry<String, Map<String, Object>> v : newNodes.entrySet()) {
269             Double x = layout.transform(v.getKey()).getX();
270             Double y = layout.transform(v.getKey()).getY();
271
272             Map<String, String> nodeData = (HashMap<String, String>) v.getValue().get("data");
273             nodeData.put("$x", (x - 600) + "");
274             nodeData.put("$y", (y - 225) + "");
275
276             newNodes.get(v.getKey()).put("data", nodeData);
277         }
278     }
279
280     /**
281      * Update node position
282      * 
283      * This method is mainly used by the visual topology
284      *
285      * @param nodeId - The node to update
286      * @return The node object
287      */
288     @RequestMapping(value = "/node/{nodeId}", method = RequestMethod.POST)
289     @ResponseBody
290     public Map<String, Object> post(@PathVariable String nodeId, @RequestParam(required = true) String x,
291                 @RequestParam(required = true) String y, HttpServletRequest request) {
292         if (!authorize(UserLevel.NETWORKADMIN, request)) {
293                 return new HashMap<String, Object>(); // silently disregard new node position
294         }
295         
296         String id = new String(nodeId);
297         
298         if (!cache.containsKey(id))
299             return null;
300
301         Map<String, Object> node = cache.get(id);
302         Map<String, String> data = (Map<String, String>) node.get("data");
303
304         data.put("$x", x);
305         data.put("$y", y);
306
307         node.put("data", data);
308         
309         return node;
310     }
311     
312     /**
313      * Node object for visual topology
314      */
315     protected class NodeBean {
316         protected String id;
317         protected String name;
318         protected Map<String, String> data;
319         protected List<Map<String, Object>> links;
320         
321         public NodeBean() {
322                 data = new HashMap<String, String>();
323                 links = new ArrayList<Map<String, Object>>();
324         }
325         
326         public NodeBean(String id, String name, String type) {
327                 this();
328                 this.id = id;
329                 this.name = name;
330                 data.put("$desc", name);
331                 data.put("$type", type);
332         }
333         
334         public void setLinks(List<Map<String, Object>> links) {
335                 this.links = links;
336         }
337         
338         public Map<String, Object> out() {
339                 Map<String, Object> node = new HashMap<String, Object>();
340                 node.put("id", this.id);
341                 node.put("name", this.name);
342                 node.put("data", this.data);
343                 node.put("adjacencies", this.links);
344                 
345                 return node;
346         }
347         
348         public String name() {
349                 return this.name;
350         }
351         
352         public String id() {
353                 return this.id;
354         }
355     }
356     
357     /**
358      * Edge object for visual topology
359      */
360     protected class EdgeBean {
361         protected NodeConnector source;
362         protected NodeConnector destination;
363         protected Map<String, String> data;
364         protected Long hostId;
365         
366         public EdgeBean() {
367                 data = new HashMap<String, String>();
368         }
369         
370         public EdgeBean(Edge link, Bandwidth bandwidth) {
371                 this();
372                 this.source = link.getHeadNodeConnector();
373                 this.destination = link.getTailNodeConnector();
374                 
375                 // data
376                 data.put("$bandwidth", bandwidth.toString());
377                 data.put("$color", bandwidthColor(bandwidth));
378                 data.put("$nodeToPort", destination.getID().toString());
379                 data.put("$nodeFromPort", source.getID().toString());
380                 data.put("$descFrom", source.getNode().toString());
381                 data.put("$descTo", destination.getNode().toString());
382                 data.put("$nodeFromPortName", source.toString());
383                 data.put("$nodeToPortName", destination.toString());
384         }
385         
386         public EdgeBean(NodeConnector connector, Long hostId) {
387                 this();
388                 this.source = null;
389                 this.destination = connector;
390                 this.hostId = hostId;
391                 
392                 data.put("$bandwidth", "N/A");
393                 data.put("$color", bandwidthColor(new Bandwidth(0)));
394                 data.put("$nodeToPort", connector.getNodeConnectorIDString());
395                 data.put("$nodeFromPort", connector.getNodeConnectorIDString());
396                 data.put("$descTo", "");
397                 data.put("$descFrom", "");
398                 data.put("$nodeToPortName", "");
399                 data.put("$nodeFromPortName", "");
400         }
401         
402         public Map<String, Object> out() {
403                 Map<String, Object> edge = new HashMap<String, Object>();
404                 
405                 edge.put("data", data);
406                 if (source == null) {
407                         edge.put("nodeFrom", String.valueOf(this.hostId));
408                 } else {
409                         edge.put("nodeFrom", source.getNode().toString());
410                 }
411                 edge.put("nodeTo", destination.getNode().toString());
412                 
413                 
414                 return edge;
415         }
416         
417         private String bandwidthColor(Bandwidth bandwidth) {
418                 String color = null;
419                 long bandwidthValue = bandwidth.getValue();
420                 
421                 if (bandwidthValue == 0) {
422                 color = "#000";
423             } else if (bandwidthValue < Bandwidth.BW1Kbps) {
424                 color = "#148AC6";
425             } else if (bandwidthValue < Bandwidth.BW1Mbps) {
426                 color = "#2858A0";
427             } else if (bandwidthValue < Bandwidth.BW1Gbps) {
428                 color = "#009393";
429             } else if (bandwidthValue < Bandwidth.BW1Tbps) {
430                 color = "#C6C014";
431             } else if (bandwidthValue < Bandwidth.BW1Pbps) {
432                 color = "#F9F464";
433             }
434                 
435                 return color;
436         }
437     }
438     
439     protected class NodeType {
440         public static final String NODE = "swtch";
441         public static final String HOST = "host";
442     }
443     
444     private boolean authorize(UserLevel level, HttpServletRequest request) {
445         IUserManager userManager = (IUserManager) ServiceHelper
446                 .getGlobalInstance(IUserManager.class, this);
447         if (userManager == null) {
448                 return false;
449         }
450         
451         String username = request.getUserPrincipal().getName();
452         UserLevel userLevel = userManager.getUserLevel(username);
453         if (userLevel.toNumber() <= level.toNumber()) {
454                 return true;
455         }
456         return false;
457     }
458 }