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