3e25110e58358a77be386bc2ddf1fa5148cea08c
[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.containermanager.IContainerAuthorization;
25 import org.opendaylight.controller.sal.authorization.Resource;
26 import org.opendaylight.controller.sal.authorization.UserLevel;
27 import org.opendaylight.controller.sal.core.Bandwidth;
28 import org.opendaylight.controller.sal.core.Edge;
29 import org.opendaylight.controller.sal.core.Host;
30 import org.opendaylight.controller.sal.core.Node;
31 import org.opendaylight.controller.sal.core.Node.NodeIDType;
32 import org.opendaylight.controller.sal.core.NodeConnector;
33 import org.opendaylight.controller.sal.core.Property;
34 import org.opendaylight.controller.sal.packet.address.EthernetAddress;
35 import org.opendaylight.controller.sal.utils.GlobalConstants;
36 import org.opendaylight.controller.sal.utils.ServiceHelper;
37 import org.opendaylight.controller.switchmanager.ISwitchManager;
38 import org.opendaylight.controller.switchmanager.Switch;
39 import org.opendaylight.controller.switchmanager.SwitchConfig;
40 import org.opendaylight.controller.topologymanager.ITopologyManager;
41 import org.opendaylight.controller.usermanager.IUserManager;
42 import org.opendaylight.controller.web.DaylightWebUtil;
43 import org.springframework.stereotype.Controller;
44 import org.springframework.web.bind.annotation.PathVariable;
45 import org.springframework.web.bind.annotation.RequestMapping;
46 import org.springframework.web.bind.annotation.RequestMethod;
47 import org.springframework.web.bind.annotation.RequestParam;
48 import org.springframework.web.bind.annotation.ResponseBody;
49
50 import edu.uci.ics.jung.algorithms.layout.CircleLayout;
51 import edu.uci.ics.jung.graph.Graph;
52 import edu.uci.ics.jung.graph.SparseMultigraph;
53
54 @Controller
55 @RequestMapping("/")
56 public class Topology {
57
58     protected Map<String, Map<String, Map<String, Object>>> metaCache = new HashMap<String, Map<String, Map<String, Object>>>();
59     protected Map<String, Map<String, Object>> stagedNodes;
60     protected Map<String, Map<String, Object>> newNodes;
61     
62     protected Map<String, Integer> metaNodeHash = new HashMap<String, Integer>();
63     protected Map<String, Integer> metaHostHash = new HashMap<String, Integer>();
64     protected Map<String, Integer> metaNodeSingleHash = new HashMap<String, Integer>();
65     protected Map<String, Integer> metaNodeConfigurationHash = new HashMap<String, Integer>();
66     
67     /**
68      * Topology of nodes and hosts in the network in JSON format.
69      * 
70      * Mainly intended for consumption by the visual topology.
71      * 
72      * @return - JSON output for visual topology
73      */
74     @RequestMapping(value = "/visual.json", method = RequestMethod.GET)
75     @ResponseBody
76     public Collection<Map<String, Object>> getLinkData(@RequestParam(required = false) String container, HttpServletRequest request) {
77         String containerName = DaylightWebUtil.getAuthorizedContainer(request, container, this);
78         
79         ITopologyManager topologyManager = (ITopologyManager) ServiceHelper
80                 .getInstance(ITopologyManager.class, containerName, this);
81         if (topologyManager == null) {
82                 return null;
83         }
84         ISwitchManager switchManager = (ISwitchManager) ServiceHelper
85                 .getInstance(ISwitchManager.class, containerName, this);
86         if (switchManager == null) {
87                 return null;
88         }
89         
90         Map<Node, Set<Edge>> nodeEdges = topologyManager.getNodeEdges();
91         Map<Node, Set<NodeConnector>> hostEdges = topologyManager
92                 .getNodesWithNodeConnectorHost();
93         List<Switch> nodes = switchManager.getNetworkDevices();
94         
95         List<SwitchConfig> switchConfigurations = new ArrayList<SwitchConfig>();
96         for(Switch sw : nodes) {
97                 Node n = sw.getNode();
98                 SwitchConfig config = switchManager.getSwitchConfig(n.toString());
99                 switchConfigurations.add(config);
100         }
101         
102         // initialize cache if needed
103         if (!metaCache.containsKey(containerName)) {
104                 metaCache.put(containerName, new HashMap<String, Map<String, Object>>());
105                 // initialize hashes
106                 metaNodeHash.put(containerName, null);
107                 metaHostHash.put(containerName, null);
108                 metaNodeSingleHash.put(containerName, null);
109                 metaNodeConfigurationHash.put(containerName, null);
110         }
111         
112         // return cache if topology hasn't changed
113         if (
114                 (metaNodeHash.get(containerName) != null && metaHostHash.get(containerName) != null && metaNodeSingleHash.get(containerName) != null && metaNodeConfigurationHash.get(containerName) != null) &&
115                 metaNodeHash.get(containerName).equals(nodeEdges.hashCode()) && metaHostHash.get(containerName).equals(hostEdges.hashCode()) && metaNodeSingleHash.get(containerName).equals(nodes.hashCode()) && metaNodeConfigurationHash.get(containerName).equals(switchConfigurations.hashCode())
116         ) {
117                 return metaCache.get(containerName).values();
118         }
119         
120         // cache has changed, we must assign the new values
121         metaNodeHash.put(containerName, nodeEdges.hashCode());
122         metaHostHash.put(containerName, hostEdges.hashCode());
123         metaNodeSingleHash.put(containerName, nodes.hashCode());
124         metaNodeConfigurationHash.put(containerName, switchConfigurations.hashCode());
125         
126         stagedNodes = new HashMap<String, Map<String, Object>>();
127         newNodes = new HashMap<String, Map<String, Object>>();
128
129         // nodeEdges addition
130         addNodes(nodeEdges, topologyManager, switchManager, containerName);
131
132         // single nodes addition
133         addSingleNodes(nodes, switchManager, containerName);
134         
135         // hostNodes addition
136         addHostNodes(hostEdges, topologyManager, containerName);
137         
138         repositionTopology(containerName);
139         
140         return metaCache.get(containerName).values();
141     }
142
143     /**
144      * Add regular nodes to main topology
145      *
146      * @param nodeEdges - node-edges mapping derived from topology
147      * @param topology - the topology instance
148      */
149     private void addNodes(Map<Node, Set<Edge>> nodeEdges,
150             ITopologyManager topology, ISwitchManager switchManager, String containerName) {            
151         Bandwidth bandwidth = new Bandwidth(0);
152         Map<Edge, Set<Property>> properties = topology.getEdges();
153         
154         for (Map.Entry<Node, Set<Edge>> e : nodeEdges.entrySet()) {
155             Node n = e.getKey();
156             String description = switchManager.getNodeDescription(n);
157             NodeBean node = createNodeBean(description, n);
158             
159             // skip production node
160             if (nodeIgnore(n)) {
161                 continue;
162             }
163             
164             List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
165             Set<Edge> links = e.getValue();
166             for (Edge link : links) {
167                 if (edgeIgnore(link)) {
168                     continue;
169                 }
170                 for (Property p : properties.get(link)) {
171                     if (p instanceof Bandwidth) {
172                         bandwidth = (Bandwidth) p;
173                         break;
174                     }
175                 }
176                 EdgeBean edge = new EdgeBean(link, bandwidth);
177                 adjacencies.add(edge.out());
178             }
179             
180             node.setLinks(adjacencies);
181             if (metaCache.get(containerName).containsKey(node.id())) {
182                 // retrieve node from cache
183                 Map<String, Object> nodeEntry = metaCache.get(containerName).get(node.id());
184
185                         Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
186                         data.put("$desc", description);
187                         nodeEntry.put("data", data);
188                         
189                 // always update adjacencies
190                 nodeEntry.put("adjacencies", adjacencies);
191                 // stage this cached node (with position)
192                 stagedNodes.put(node.id(), nodeEntry);
193             } else {
194                 newNodes.put(node.id(), node.out());
195             }
196         }
197     }
198     
199     /**
200      * Check if this node shouldn't appear in the visual topology
201      * 
202      * @param node
203      * @return
204      */
205     private boolean nodeIgnore(Node node) {
206         String nodeType = node.getType();
207         
208         // add other node types to ignore later
209         if (nodeType.equals(NodeIDType.PRODUCTION)) {
210             return true;
211         }
212         
213         return false;
214     }
215     
216     /**
217      * Check if this edge shouldn't appear in the visual topology
218      * 
219      * @param edge
220      * @return
221      */
222     private boolean edgeIgnore(Edge edge) {
223         NodeConnector headNodeConnector = edge.getHeadNodeConnector();
224         Node headNode = headNodeConnector.getNode();
225         if (nodeIgnore(headNode)) {
226             return true;
227         }
228         
229         NodeConnector tailNodeConnector = edge.getTailNodeConnector();
230         Node tailNode = tailNodeConnector.getNode();
231         if (nodeIgnore(tailNode)) {
232             return true;
233         }
234         
235         return false;
236     }
237     
238     protected NodeBean createNodeBean(String description, Node node) {
239         String name = (description == null || 
240                         description.trim().isEmpty() ||
241                         description.equalsIgnoreCase("none"))?
242                                         node.toString() : description;
243                 return  new NodeBean(node.toString(), name, NodeType.NODE);
244     }
245     
246     @SuppressWarnings("unchecked")
247         private void addSingleNodes(List<Switch> nodes, ISwitchManager switchManager, String containerName) {
248         if (nodes == null) {
249                 return;
250         }
251         for (Switch sw : nodes) {
252                 Node n = sw.getNode();
253                 
254                 // skip production node
255                 if (nodeIgnore(n)) {
256                     continue;
257                 }
258
259                 String description = switchManager.getNodeDescription(n);
260                 
261                 if ((stagedNodes.containsKey(n.toString()) && metaCache.get(containerName).containsKey(n.toString())) || newNodes.containsKey(n.toString())) {
262                         continue;
263                 }
264                 NodeBean node = createNodeBean(description, n);
265                 
266                 // FIXME still doesn't display standalone node when last remaining link is removed
267                 if (metaCache.get(containerName).containsKey(node.id()) && !stagedNodes.containsKey(node.id())) {
268                         Map<String, Object> nodeEntry = metaCache.get(containerName).get(node.id());
269                                 Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
270                         data.put("$desc", description);
271                         nodeEntry.put("data", data);
272                 stagedNodes.put(node.id(), nodeEntry);
273             } else {
274                 newNodes.put(node.id(), node.out());
275             }
276         }
277     }
278
279     /**
280      * Add regular hosts to main topology
281      *
282      * @param hostEdges - node-nodeconnectors host-specific mapping from topology
283      * @param topology - topology instance
284      */
285     private void addHostNodes(Map<Node, Set<NodeConnector>> hostEdges,
286             ITopologyManager topology, String containerName) {
287         for (Map.Entry<Node, Set<NodeConnector>> e : hostEdges.entrySet()) {
288             for (NodeConnector connector : e.getValue()) {
289                 Host host = topology.getHostAttachedToNodeConnector(connector);
290                 EthernetAddress dmac = (EthernetAddress) host.getDataLayerAddress();
291
292                 ByteBuffer addressByteBuffer = ByteBuffer.allocate(8);
293                 addressByteBuffer.putShort((short) 0);
294                 addressByteBuffer.put(dmac.getValue());
295                 addressByteBuffer.rewind();
296                 
297                 long hid = addressByteBuffer.getLong();
298                 String hostId = String.valueOf(hid);
299                 
300                 NodeBean hostBean = new NodeBean(hostId, host.getNetworkAddressAsString(), NodeType.HOST);
301                 List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
302                 EdgeBean edge = new EdgeBean(connector, hid);
303                 adjacencies.add(edge.out());
304                 hostBean.setLinks(adjacencies);
305                 
306                 if (metaCache.get(containerName).containsKey(hostId)) {
307                         Map<String, Object> hostEntry = metaCache.get(containerName).get(hostId);
308                         hostEntry.put("adjacencies", adjacencies);
309                         stagedNodes.put(hostId, hostEntry);
310                 } else {
311                         newNodes.put(String.valueOf(hid), hostBean.out());
312                 }
313             }
314         }
315     }
316
317     /**
318      * Re-position nodes in circular layout
319      */
320     private void repositionTopology(String containerName) {
321         Graph<String, String> graph = new SparseMultigraph<String, String>();
322         
323         metaCache.get(containerName).clear();
324         metaCache.get(containerName).putAll(stagedNodes);
325         metaCache.get(containerName).putAll(newNodes);
326         
327         for (Map<String, Object> on : metaCache.get(containerName).values()) {
328             graph.addVertex(on.toString());
329
330             List<Map<String, Object>> adjacencies = (List<Map<String, Object>>) on.get("adjacencies");
331             
332             for (Map<String, Object> adj : adjacencies) {
333                 graph.addEdge(
334                         adj.toString(), adj.get("nodeFrom").toString(),
335                         adj.get("nodeTo").toString()
336                 );
337             }
338         }
339         
340         CircleLayout<String, String> layout = new CircleLayout<String, String>(graph);
341         layout.setSize(new Dimension(1200, 365));
342         for (Map.Entry<String, Map<String, Object>> v : newNodes.entrySet()) {
343             Double x = layout.transform(v.getKey()).getX();
344             Double y = layout.transform(v.getKey()).getY();
345
346             Map<String, String> nodeData = (HashMap<String, String>) v.getValue().get("data");
347             nodeData.put("$x", (x - 600) + "");
348             nodeData.put("$y", (y - 225) + "");
349
350             newNodes.get(v.getKey()).put("data", nodeData);
351         }
352     }
353
354     /**
355      * Update node position
356      * 
357      * This method is mainly used by the visual topology
358      *
359      * @param nodeId - The node to update
360      * @return The node object
361      */
362     @RequestMapping(value = "/node/{nodeId}", method = RequestMethod.POST)
363     @ResponseBody
364     public Map<String, Object> post(@PathVariable String nodeId, @RequestParam(required = true) String x,
365                 @RequestParam(required = true) String y, @RequestParam(required = false) String container, 
366                 HttpServletRequest request) {
367         if (!authorize(UserLevel.NETWORKADMIN, request)) {
368                 return new HashMap<String, Object>(); // silently disregard new node position
369         }
370         
371         String containerName = getAuthorizedContainer(request, container);
372         
373         String id = new String(nodeId);
374         
375         if (!metaCache.get(containerName).containsKey(id)) {
376             return null;
377         }
378
379         Map<String, Object> node = metaCache.get(containerName).get(id);
380         Map<String, String> data = (Map<String, String>) node.get("data");
381
382         data.put("$x", x);
383         data.put("$y", y);
384
385         node.put("data", data);
386         
387         return node;
388     }
389     
390     /**
391      * Node object for visual topology
392      */
393     protected class NodeBean {
394         protected String id;
395         protected String name;
396         protected Map<String, String> data;
397         protected List<Map<String, Object>> links;
398         
399         public NodeBean() {
400                 data = new HashMap<String, String>();
401                 links = new ArrayList<Map<String, Object>>();
402         }
403         
404         public NodeBean(String id, String name, String type) {
405                 this();
406                 this.id = id;
407                 this.name = name;
408                 data.put("$desc", name);
409                 data.put("$type", type);
410         }
411         
412         public void setLinks(List<Map<String, Object>> links) {
413                 this.links = links;
414         }
415         
416         public Map<String, Object> out() {
417                 Map<String, Object> node = new HashMap<String, Object>();
418                 node.put("id", this.id);
419                 node.put("name", this.name);
420                 node.put("data", this.data);
421                 node.put("adjacencies", this.links);
422                 
423                 return node;
424         }
425         
426         public String name() {
427                 return this.name;
428         }
429         
430         public String id() {
431                 return this.id;
432         }
433     }
434     
435     /**
436      * Edge object for visual topology
437      */
438     protected class EdgeBean {
439         protected NodeConnector source;
440         protected NodeConnector destination;
441         protected Map<String, String> data;
442         protected Long hostId;
443         
444         public EdgeBean() {
445                 data = new HashMap<String, String>();
446         }
447         
448         public EdgeBean(Edge link, Bandwidth bandwidth) {
449                 this();
450                 this.source = link.getHeadNodeConnector();
451                 this.destination = link.getTailNodeConnector();
452                 
453                 // data
454                 data.put("$bandwidth", bandwidth.toString());
455                 data.put("$color", bandwidthColor(bandwidth));
456                 data.put("$nodeToPort", destination.getID().toString());
457                 data.put("$nodeFromPort", source.getID().toString());
458                 data.put("$descFrom", source.getNode().toString());
459                 data.put("$descTo", destination.getNode().toString());
460                 data.put("$nodeFromPortName", source.toString());
461                 data.put("$nodeToPortName", destination.toString());
462         }
463         
464         public EdgeBean(NodeConnector connector, Long hostId) {
465                 this();
466                 this.source = null;
467                 this.destination = connector;
468                 this.hostId = hostId;
469                 
470                 data.put("$bandwidth", "N/A");
471                 data.put("$color", bandwidthColor(new Bandwidth(0)));
472                 data.put("$nodeToPort", connector.getNodeConnectorIDString());
473                 data.put("$nodeFromPort", connector.getNodeConnectorIDString());
474                 data.put("$descTo", "");
475                 data.put("$descFrom", "");
476                 data.put("$nodeToPortName", "");
477                 data.put("$nodeFromPortName", "");
478         }
479         
480         public Map<String, Object> out() {
481                 Map<String, Object> edge = new HashMap<String, Object>();
482                 
483                 edge.put("data", data);
484                 if (source == null) {
485                         edge.put("nodeFrom", String.valueOf(this.hostId));
486                 } else {
487                         edge.put("nodeFrom", source.getNode().toString());
488                 }
489                 edge.put("nodeTo", destination.getNode().toString());
490                 
491                 
492                 return edge;
493         }
494         
495         private String bandwidthColor(Bandwidth bandwidth) {
496                 String color = null;
497                 long bandwidthValue = bandwidth.getValue();
498                 
499                 if (bandwidthValue == 0) {
500                 color = "#000";
501             } else if (bandwidthValue < Bandwidth.BW1Kbps) {
502                 color = "#148AC6";
503             } else if (bandwidthValue < Bandwidth.BW1Mbps) {
504                 color = "#2858A0";
505             } else if (bandwidthValue < Bandwidth.BW1Gbps) {
506                 color = "#009393";
507             } else if (bandwidthValue < Bandwidth.BW1Tbps) {
508                 color = "#C6C014";
509             } else if (bandwidthValue < Bandwidth.BW1Pbps) {
510                 color = "#F9F464";
511             }
512                 
513                 return color;
514         }
515     }
516     
517     protected class NodeType {
518         public static final String NODE = "swtch";
519         public static final String HOST = "host";
520     }
521     
522     private boolean authorize(UserLevel level, HttpServletRequest request) {
523         IUserManager userManager = (IUserManager) ServiceHelper
524                 .getGlobalInstance(IUserManager.class, this);
525         if (userManager == null) {
526                 return false;
527         }
528         
529         String username = request.getUserPrincipal().getName();
530         UserLevel userLevel = userManager.getUserLevel(username);
531         if (userLevel.toNumber() <= level.toNumber()) {
532                 return true;
533         }
534         return false;
535     }
536     
537     private String getAuthorizedContainer(HttpServletRequest request, String container) {
538         String username = request.getUserPrincipal().getName();
539         IContainerAuthorization containerAuthorization = (IContainerAuthorization) ServiceHelper.
540                         getGlobalInstance(IContainerAuthorization.class, this);
541         if (containerAuthorization != null) {
542                 Set<Resource> resources = containerAuthorization.getAllResourcesforUser(username);
543                 if (authorizeContainer(container, resources)) {
544                         return container;
545                 }
546         }
547         
548         return GlobalConstants.DEFAULT.toString();
549     }
550     
551     private boolean authorizeContainer(String container, Set<Resource> resources) {
552         for(Resource resource : resources) {
553                 String containerName = (String) resource.getResource();
554                 if (containerName.equals(container)) {
555                         return true;
556                 }
557         }
558         
559         return false;
560     }
561 }