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