Primitive support to save the topology coordinates and cleanup of some unused tiering...
[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                 stagedNodes.put(node.id(), nodeEntry);
291             } else {
292                 newNodes.put(node.id(), node.out());
293             }
294         }
295     }
296
297     /**
298      * Add regular hosts to main topology
299      *
300      * @param hostEdges - node-nodeconnectors host-specific mapping from topology
301      * @param topology - topology instance
302      */
303     private void addHostNodes(Map<Node, Set<NodeConnector>> hostEdges,
304             ITopologyManager topology, String containerName) {
305         for (Map.Entry<Node, Set<NodeConnector>> e : hostEdges.entrySet()) {
306             for (NodeConnector connector : e.getValue()) {
307                 Host host = topology.getHostAttachedToNodeConnector(connector);
308                 EthernetAddress dmac = (EthernetAddress) host.getDataLayerAddress();
309
310                 ByteBuffer addressByteBuffer = ByteBuffer.allocate(8);
311                 addressByteBuffer.putShort((short) 0);
312                 addressByteBuffer.put(dmac.getValue());
313                 addressByteBuffer.rewind();
314                 
315                 long hid = addressByteBuffer.getLong();
316                 String hostId = String.valueOf(hid);
317                 
318                 NodeBean hostBean = new NodeBean(hostId, host.getNetworkAddressAsString(), NodeType.HOST);
319                 List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
320                 EdgeBean edge = new EdgeBean(connector, hid);
321                 adjacencies.add(edge.out());
322                 hostBean.setLinks(adjacencies);
323                 
324                 if (metaCache.get(containerName).containsKey(hostId)) {
325                         Map<String, Object> hostEntry = metaCache.get(containerName).get(hostId);
326                         hostEntry.put("adjacencies", adjacencies);
327                         stagedNodes.put(hostId, hostEntry);
328                 } else {
329                         newNodes.put(String.valueOf(hid), hostBean.out());
330                 }
331             }
332         }
333     }
334
335     /**
336      * Re-position nodes in circular layout
337      */
338     private void repositionTopology(String containerName) {
339         Graph<String, String> graph = new SparseMultigraph<String, String>();
340         
341         metaCache.get(containerName).clear();
342         metaCache.get(containerName).putAll(stagedNodes);
343         metaCache.get(containerName).putAll(newNodes);
344         
345         for (Map<String, Object> on : metaCache.get(containerName).values()) {
346             graph.addVertex(on.toString());
347
348             List<Map<String, Object>> adjacencies = (List<Map<String, Object>>) on.get("adjacencies");
349             
350             for (Map<String, Object> adj : adjacencies) {
351                 graph.addEdge(
352                         adj.toString(), adj.get("nodeFrom").toString(),
353                         adj.get("nodeTo").toString()
354                 );
355             }
356         }
357         
358         CircleLayout<String, String> layout = new CircleLayout<String, String>(graph);
359         layout.setSize(new Dimension(1200, 365));
360         for (Map.Entry<String, Map<String, Object>> v : newNodes.entrySet()) {
361             Double x = layout.transform(v.getKey()).getX();
362             Double y = layout.transform(v.getKey()).getY();
363
364             Map<String, String> nodeData = (HashMap<String, String>) v.getValue().get("data");
365             nodeData.put("$x", (x - 600) + "");
366             nodeData.put("$y", (y - 225) + "");
367
368             newNodes.get(v.getKey()).put("data", nodeData);
369         }
370     }
371
372     /**
373      * Update node position
374      * 
375      * This method is mainly used by the visual topology
376      *
377      * @param nodeId - The node to update
378      * @return The node object
379      */
380     @RequestMapping(value = "/node/{nodeId}", method = RequestMethod.POST)
381     @ResponseBody
382     public Map<String, Object> post(@PathVariable String nodeId, @RequestParam(required = true) String x,
383                 @RequestParam(required = true) String y, @RequestParam(required = false) String container, 
384                 HttpServletRequest request) {
385         if (!authorize(UserLevel.NETWORKADMIN, request)) {
386                 return new HashMap<String, Object>(); // silently disregard new node position
387         }
388         
389         String containerName = getAuthorizedContainer(request, container);
390         
391         String id = new String(nodeId);
392         
393         if (!metaCache.get(containerName).containsKey(id)) {
394             return null;
395         }
396
397         Map<String, Object> node = metaCache.get(containerName).get(id);
398         Map<String, String> data = (Map<String, String>) node.get("data");
399
400         data.put("$x", x);
401         data.put("$y", y);
402
403         node.put("data", data);
404         
405         return node;
406     }
407     
408     /**
409      * Node object for visual topology
410      */
411     protected class NodeBean {
412         protected String id;
413         protected String name;
414         protected Map<String, String> data;
415         protected List<Map<String, Object>> links;
416         
417         public NodeBean() {
418                 data = new HashMap<String, String>();
419                 links = new ArrayList<Map<String, Object>>();
420         }
421         
422         public NodeBean(String id, String name, String type) {
423                 this();
424                 this.id = id;
425                 this.name = name;
426                 data.put("$desc", name);
427                 data.put("$type", type);
428         }
429         
430         public void setLinks(List<Map<String, Object>> links) {
431                 this.links = links;
432         }
433         
434         public Map<String, Object> out() {
435                 Map<String, Object> node = new HashMap<String, Object>();
436                 node.put("id", this.id);
437                 node.put("name", this.name);
438                 node.put("data", this.data);
439                 node.put("adjacencies", this.links);
440                 
441                 return node;
442         }
443         
444         public String name() {
445                 return this.name;
446         }
447         
448         public String id() {
449                 return this.id;
450         }
451     }
452     
453     /**
454      * Edge object for visual topology
455      */
456     protected class EdgeBean {
457         protected NodeConnector source;
458         protected NodeConnector destination;
459         protected Map<String, String> data;
460         protected Long hostId;
461         
462         public EdgeBean() {
463                 data = new HashMap<String, String>();
464         }
465         
466         public EdgeBean(Edge link, Bandwidth bandwidth) {
467                 this();
468                 this.source = link.getHeadNodeConnector();
469                 this.destination = link.getTailNodeConnector();
470                 
471                 // data
472                 data.put("$bandwidth", bandwidth.toString());
473                 data.put("$color", bandwidthColor(bandwidth));
474                 data.put("$nodeToPort", destination.getID().toString());
475                 data.put("$nodeFromPort", source.getID().toString());
476                 data.put("$descFrom", source.getNode().toString());
477                 data.put("$descTo", destination.getNode().toString());
478                 data.put("$nodeFromPortName", source.toString());
479                 data.put("$nodeToPortName", destination.toString());
480         }
481         
482         public EdgeBean(NodeConnector connector, Long hostId) {
483                 this();
484                 this.source = null;
485                 this.destination = connector;
486                 this.hostId = hostId;
487                 
488                 data.put("$bandwidth", "N/A");
489                 data.put("$color", bandwidthColor(new Bandwidth(0)));
490                 data.put("$nodeToPort", connector.getNodeConnectorIDString());
491                 data.put("$nodeFromPort", connector.getNodeConnectorIDString());
492                 data.put("$descTo", "");
493                 data.put("$descFrom", "");
494                 data.put("$nodeToPortName", "");
495                 data.put("$nodeFromPortName", "");
496         }
497         
498         public Map<String, Object> out() {
499                 Map<String, Object> edge = new HashMap<String, Object>();
500                 
501                 edge.put("data", data);
502                 if (source == null) {
503                         edge.put("nodeFrom", String.valueOf(this.hostId));
504                 } else {
505                         edge.put("nodeFrom", source.getNode().toString());
506                 }
507                 edge.put("nodeTo", destination.getNode().toString());
508                 
509                 
510                 return edge;
511         }
512         
513         private String bandwidthColor(Bandwidth bandwidth) {
514                 String color = null;
515                 long bandwidthValue = bandwidth.getValue();
516                 
517                 if (bandwidthValue == 0) {
518                 color = "#000";
519             } else if (bandwidthValue < Bandwidth.BW1Kbps) {
520                 color = "#148AC6";
521             } else if (bandwidthValue < Bandwidth.BW1Mbps) {
522                 color = "#2858A0";
523             } else if (bandwidthValue < Bandwidth.BW1Gbps) {
524                 color = "#009393";
525             } else if (bandwidthValue < Bandwidth.BW1Tbps) {
526                 color = "#C6C014";
527             } else if (bandwidthValue < Bandwidth.BW1Pbps) {
528                 color = "#F9F464";
529             }
530                 
531                 return color;
532         }
533     }
534     
535     protected class NodeType {
536         public static final String NODE = "swtch";
537         public static final String HOST = "host";
538     }
539     
540     private boolean authorize(UserLevel level, HttpServletRequest request) {
541         IUserManager userManager = (IUserManager) ServiceHelper
542                 .getGlobalInstance(IUserManager.class, this);
543         if (userManager == null) {
544                 return false;
545         }
546         
547         String username = request.getUserPrincipal().getName();
548         UserLevel userLevel = userManager.getUserLevel(username);
549         if (userLevel.toNumber() <= level.toNumber()) {
550                 return true;
551         }
552         return false;
553     }
554     
555     private String getAuthorizedContainer(HttpServletRequest request, String container) {
556         String username = request.getUserPrincipal().getName();
557         IContainerAuthorization containerAuthorization = (IContainerAuthorization) ServiceHelper.
558                         getGlobalInstance(IContainerAuthorization.class, this);
559         if (containerAuthorization != null) {
560                 Set<Resource> resources = containerAuthorization.getAllResourcesforUser(username);
561                 if (authorizeContainer(container, resources)) {
562                         return container;
563                 }
564         }
565         
566         return GlobalConstants.DEFAULT.toString();
567     }
568     
569     private boolean authorizeContainer(String container, Set<Resource> resources) {
570         for(Resource resource : resources) {
571                 String containerName = (String) resource.getResource();
572                 if (containerName.equals(container)) {
573                         return true;
574                 }
575         }
576         
577         return false;
578     }
579
580     @SuppressWarnings("unchecked")
581         private void loadConfiguration() {
582         ObjectReader objReader = new ObjectReader();
583         metaCache = (Map<String, Map<String, Map<String, Object>>>) objReader.read(this, topologyWebFileName);
584         if (metaCache == null) metaCache = new HashMap<String, Map<String, Map<String, Object>>>();
585     }
586
587     @Override
588     public Status saveConfiguration() {
589         ObjectWriter objWriter = new ObjectWriter();
590         objWriter.write(metaCache, topologyWebFileName);
591         return new Status(StatusCode.SUCCESS, null);
592     }
593
594     @Override
595     public Object readObject(ObjectInputStream ois)
596             throws FileNotFoundException, IOException, ClassNotFoundException {
597         // Perform the class deserialization locally, from inside the package where the class is defined
598         return ois.readObject();
599     }
600 }