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