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