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