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