3 * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved.
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
10 package org.opendaylight.controller.topology.web;
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;
25 import javax.servlet.http.HttpServletRequest;
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.Node;
33 import org.opendaylight.controller.sal.core.Node.NodeIDType;
34 import org.opendaylight.controller.sal.core.NodeConnector;
35 import org.opendaylight.controller.sal.core.Property;
36 import org.opendaylight.controller.sal.packet.address.EthernetAddress;
37 import org.opendaylight.controller.sal.utils.GlobalConstants;
38 import org.opendaylight.controller.sal.utils.IObjectReader;
39 import org.opendaylight.controller.sal.utils.ObjectReader;
40 import org.opendaylight.controller.sal.utils.ObjectWriter;
41 import org.opendaylight.controller.sal.utils.ServiceHelper;
42 import org.opendaylight.controller.sal.utils.Status;
43 import org.opendaylight.controller.sal.utils.StatusCode;
44 import org.opendaylight.controller.switchmanager.ISwitchManager;
45 import org.opendaylight.controller.switchmanager.Switch;
46 import org.opendaylight.controller.switchmanager.SwitchConfig;
47 import org.opendaylight.controller.topologymanager.ITopologyManager;
48 import org.opendaylight.controller.web.DaylightWebUtil;
49 import org.springframework.stereotype.Controller;
50 import org.springframework.web.bind.annotation.PathVariable;
51 import org.springframework.web.bind.annotation.RequestMapping;
52 import org.springframework.web.bind.annotation.RequestMethod;
53 import org.springframework.web.bind.annotation.RequestParam;
54 import org.springframework.web.bind.annotation.ResponseBody;
56 import edu.uci.ics.jung.algorithms.layout.CircleLayout;
57 import edu.uci.ics.jung.graph.Graph;
58 import edu.uci.ics.jung.graph.SparseMultigraph;
62 public class Topology implements IObjectReader, IConfigurationAware {
63 private static String ROOT = GlobalConstants.STARTUPHOME.toString();
64 private String topologyWebFileName = null;
66 protected Map<String, Map<String, Map<String, Object>>> metaCache = new HashMap<String, Map<String, Map<String, Object>>>();
67 protected Map<String, Map<String, Object>> stagedNodes;
68 protected Map<String, Map<String, Object>> newNodes;
70 protected Map<String, Integer> metaNodeHash = new HashMap<String, Integer>();
71 protected Map<String, Integer> metaHostHash = new HashMap<String, Integer>();
72 protected Map<String, Integer> metaNodeSingleHash = new HashMap<String, Integer>();
73 protected Map<String, Integer> metaNodeConfigurationHash = new HashMap<String, Integer>();
76 ServiceHelper.registerGlobalService(IConfigurationAware.class, this, null);
77 topologyWebFileName = ROOT + "topologyCache.sav";
82 * Topology of nodes and hosts in the network in JSON format.
84 * Mainly intended for consumption by the visual topology.
86 * @return - JSON output for visual topology
88 @RequestMapping(value = "/visual.json", method = RequestMethod.GET)
90 public Collection<Map<String, Object>> getLinkData(@RequestParam(required = false) String container, HttpServletRequest request) {
91 String containerName = (container == null) ? GlobalConstants.DEFAULT.toString() : container;
93 // Derive the privilege this user has on the current container
94 String userName = request.getUserPrincipal().getName();
95 Privilege privilege = DaylightWebUtil.getContainerPrivilege(userName, containerName, this);
97 if (privilege == Privilege.NONE) {
101 ITopologyManager topologyManager = (ITopologyManager) ServiceHelper
102 .getInstance(ITopologyManager.class, containerName, this);
103 if (topologyManager == null) {
106 ISwitchManager switchManager = (ISwitchManager) ServiceHelper
107 .getInstance(ISwitchManager.class, containerName, this);
108 if (switchManager == null) {
112 Map<Node, Set<Edge>> nodeEdges = topologyManager.getNodeEdges();
113 Map<Node, Set<NodeConnector>> hostEdges = topologyManager
114 .getNodesWithNodeConnectorHost();
115 List<Switch> nodes = switchManager.getNetworkDevices();
117 List<SwitchConfig> switchConfigurations = new ArrayList<SwitchConfig>();
118 for(Switch sw : nodes) {
119 Node n = sw.getNode();
120 SwitchConfig config = switchManager.getSwitchConfig(n.toString());
121 switchConfigurations.add(config);
124 // initialize cache if needed
125 if (!metaCache.containsKey(containerName)) {
126 metaCache.put(containerName, new HashMap<String, Map<String, Object>>());
128 metaNodeHash.put(containerName, null);
129 metaHostHash.put(containerName, null);
130 metaNodeSingleHash.put(containerName, null);
131 metaNodeConfigurationHash.put(containerName, null);
134 // return cache if topology hasn't changed
136 (metaNodeHash.get(containerName) != null && metaHostHash.get(containerName) != null && metaNodeSingleHash.get(containerName) != null && metaNodeConfigurationHash.get(containerName) != null) &&
137 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 return metaCache.get(containerName).values();
142 // cache has changed, we must assign the new values
143 metaNodeHash.put(containerName, nodeEdges.hashCode());
144 metaHostHash.put(containerName, hostEdges.hashCode());
145 metaNodeSingleHash.put(containerName, nodes.hashCode());
146 metaNodeConfigurationHash.put(containerName, switchConfigurations.hashCode());
148 stagedNodes = new HashMap<String, Map<String, Object>>();
149 newNodes = new HashMap<String, Map<String, Object>>();
151 // nodeEdges addition
152 addNodes(nodeEdges, topologyManager, switchManager, containerName);
154 // single nodes addition
155 addSingleNodes(nodes, switchManager, containerName);
157 // hostNodes addition
158 addHostNodes(hostEdges, topologyManager, containerName);
160 repositionTopology(containerName);
162 return metaCache.get(containerName).values();
166 * Add regular nodes to main topology
168 * @param nodeEdges - node-edges mapping derived from topology
169 * @param topology - the topology instance
171 private void addNodes(Map<Node, Set<Edge>> nodeEdges,
172 ITopologyManager topology, ISwitchManager switchManager, String containerName) {
173 Bandwidth bandwidth = new Bandwidth(0);
174 Map<Edge, Set<Property>> properties = topology.getEdges();
176 for (Map.Entry<Node, Set<Edge>> e : nodeEdges.entrySet()) {
178 String description = switchManager.getNodeDescription(n);
179 NodeBean node = createNodeBean(description, n);
181 // skip production node
186 List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
187 Set<Edge> links = e.getValue();
188 for (Edge link : links) {
189 if (edgeIgnore(link)) {
192 for (Property p : properties.get(link)) {
193 if (p instanceof Bandwidth) {
194 bandwidth = (Bandwidth) p;
198 EdgeBean edge = new EdgeBean(link, bandwidth);
199 adjacencies.add(edge.out());
202 node.setLinks(adjacencies);
203 if (metaCache.get(containerName).containsKey(node.id())) {
204 // retrieve node from cache
205 Map<String, Object> nodeEntry = metaCache.get(containerName).get(node.id());
207 Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
208 data.put("$desc", description);
209 nodeEntry.put("data", data);
211 // always update adjacencies
212 nodeEntry.put("adjacencies", adjacencies);
213 // stage this cached node (with position)
214 stagedNodes.put(node.id(), nodeEntry);
216 newNodes.put(node.id(), node.out());
222 * Check if this node shouldn't appear in the visual topology
227 private boolean nodeIgnore(Node node) {
228 String nodeType = node.getType();
230 // add other node types to ignore later
231 if (nodeType.equals(NodeIDType.PRODUCTION)) {
239 * Check if this edge shouldn't appear in the visual topology
244 private boolean edgeIgnore(Edge edge) {
245 NodeConnector headNodeConnector = edge.getHeadNodeConnector();
246 Node headNode = headNodeConnector.getNode();
247 if (nodeIgnore(headNode)) {
251 NodeConnector tailNodeConnector = edge.getTailNodeConnector();
252 Node tailNode = tailNodeConnector.getNode();
253 if (nodeIgnore(tailNode)) {
260 protected NodeBean createNodeBean(String description, Node node) {
261 String name = (description == null ||
262 description.trim().isEmpty() ||
263 description.equalsIgnoreCase("none"))?
264 node.toString() : description;
265 return new NodeBean(node.toString(), name, NodeType.NODE);
268 @SuppressWarnings("unchecked")
269 private void addSingleNodes(List<Switch> nodes, ISwitchManager switchManager, String containerName) {
273 for (Switch sw : nodes) {
274 Node n = sw.getNode();
276 // skip production node
281 String description = switchManager.getNodeDescription(n);
283 if ((stagedNodes.containsKey(n.toString()) && metaCache.get(containerName).containsKey(n.toString())) || newNodes.containsKey(n.toString())) {
286 NodeBean node = createNodeBean(description, n);
288 // FIXME still doesn't display standalone node when last remaining link is removed
289 if (metaCache.get(containerName).containsKey(node.id()) && !stagedNodes.containsKey(node.id())) {
290 Map<String, Object> nodeEntry = metaCache.get(containerName).get(node.id());
291 Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
292 data.put("$desc", description);
293 nodeEntry.put("data", data);
294 // clear adjacencies since this is now a single node
295 nodeEntry.put("adjacencies", new LinkedList<Map<String, Object>>());
296 stagedNodes.put(node.id(), nodeEntry);
298 newNodes.put(node.id(), node.out());
304 * Add regular hosts to main topology
306 * @param hostEdges - node-nodeconnectors host-specific mapping from topology
307 * @param topology - topology instance
309 private void addHostNodes(Map<Node, Set<NodeConnector>> hostEdges,
310 ITopologyManager topology, String containerName) {
311 for (Map.Entry<Node, Set<NodeConnector>> e : hostEdges.entrySet()) {
312 for (NodeConnector connector : e.getValue()) {
313 Host host = topology.getHostAttachedToNodeConnector(connector);
314 EthernetAddress dmac = (EthernetAddress) host.getDataLayerAddress();
316 ByteBuffer addressByteBuffer = ByteBuffer.allocate(8);
317 addressByteBuffer.putShort((short) 0);
318 addressByteBuffer.put(dmac.getValue());
319 addressByteBuffer.rewind();
321 long hid = addressByteBuffer.getLong();
322 String hostId = String.valueOf(hid);
324 NodeBean hostBean = new NodeBean(hostId, host.getNetworkAddressAsString(), NodeType.HOST);
325 List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
326 EdgeBean edge = new EdgeBean(connector, hid);
327 adjacencies.add(edge.out());
328 hostBean.setLinks(adjacencies);
330 if (metaCache.get(containerName).containsKey(hostId)) {
331 Map<String, Object> hostEntry = metaCache.get(containerName).get(hostId);
332 hostEntry.put("adjacencies", adjacencies);
333 stagedNodes.put(hostId, hostEntry);
335 newNodes.put(String.valueOf(hid), hostBean.out());
342 * Re-position nodes in circular layout
344 private void repositionTopology(String containerName) {
345 Graph<String, String> graph = new SparseMultigraph<String, String>();
347 metaCache.get(containerName).clear();
348 metaCache.get(containerName).putAll(stagedNodes);
349 metaCache.get(containerName).putAll(newNodes);
351 for (Map<String, Object> on : metaCache.get(containerName).values()) {
352 graph.addVertex(on.toString());
354 List<Map<String, Object>> adjacencies = (List<Map<String, Object>>) on.get("adjacencies");
356 for (Map<String, Object> adj : adjacencies) {
358 adj.toString(), adj.get("nodeFrom").toString(),
359 adj.get("nodeTo").toString()
364 CircleLayout<String, String> layout = new CircleLayout<String, String>(graph);
365 layout.setSize(new Dimension(1200, 365));
366 for (Map.Entry<String, Map<String, Object>> v : newNodes.entrySet()) {
367 Double x = layout.transform(v.getKey()).getX();
368 Double y = layout.transform(v.getKey()).getY();
370 Map<String, String> nodeData = (HashMap<String, String>) v.getValue().get("data");
371 nodeData.put("$x", (x - 600) + "");
372 nodeData.put("$y", (y - 225) + "");
374 newNodes.get(v.getKey()).put("data", nodeData);
379 * Update node position
381 * This method is mainly used by the visual topology
383 * @param nodeId - The node to update
384 * @return The node object
386 @RequestMapping(value = "/node/{nodeId}", method = RequestMethod.POST)
388 public Map<String, Object> post(@PathVariable String nodeId, @RequestParam(required = true) String x,
389 @RequestParam(required = true) String y, @RequestParam(required = false) String container,
390 HttpServletRequest request) {
391 String containerName = (container == null) ? GlobalConstants.DEFAULT.toString() : container;
393 // Derive the privilege this user has on the current container
394 String userName = request.getUserPrincipal().getName();
395 Privilege privilege = DaylightWebUtil.getContainerPrivilege(userName, containerName, this);
397 if (privilege != Privilege.WRITE) {
398 return new HashMap<String, Object>(); // silently disregard new node position
401 String id = new String(nodeId);
403 if (!metaCache.get(containerName).containsKey(id)) {
407 Map<String, Object> node = metaCache.get(containerName).get(id);
408 Map<String, String> data = (Map<String, String>) node.get("data");
413 node.put("data", data);
419 * Node object for visual topology
421 protected class NodeBean {
423 protected String name;
424 protected Map<String, String> data;
425 protected List<Map<String, Object>> links;
428 data = new HashMap<String, String>();
429 links = new ArrayList<Map<String, Object>>();
432 public NodeBean(String id, String name, String type) {
436 data.put("$desc", name);
437 data.put("$type", type);
440 public void setLinks(List<Map<String, Object>> links) {
444 public Map<String, Object> out() {
445 Map<String, Object> node = new HashMap<String, Object>();
446 node.put("id", this.id);
447 node.put("name", this.name);
448 node.put("data", this.data);
449 node.put("adjacencies", this.links);
454 public String name() {
464 * Edge object for visual topology
466 protected class EdgeBean {
467 protected NodeConnector source;
468 protected NodeConnector destination;
469 protected Map<String, String> data;
470 protected Long hostId;
473 data = new HashMap<String, String>();
476 public EdgeBean(Edge link, Bandwidth bandwidth) {
478 this.source = link.getHeadNodeConnector();
479 this.destination = link.getTailNodeConnector();
482 data.put("$bandwidth", bandwidth.toString());
483 data.put("$color", bandwidthColor(bandwidth));
484 data.put("$nodeToPort", destination.getID().toString());
485 data.put("$nodeFromPort", source.getID().toString());
486 data.put("$descFrom", source.getNode().toString());
487 data.put("$descTo", destination.getNode().toString());
488 data.put("$nodeFromPortName", source.toString());
489 data.put("$nodeToPortName", destination.toString());
492 public EdgeBean(NodeConnector connector, Long hostId) {
495 this.destination = connector;
496 this.hostId = hostId;
498 data.put("$bandwidth", "N/A");
499 data.put("$color", bandwidthColor(new Bandwidth(0)));
500 data.put("$nodeToPort", connector.getNodeConnectorIDString());
501 data.put("$nodeFromPort", connector.getNodeConnectorIDString());
502 data.put("$descTo", "");
503 data.put("$descFrom", "");
504 data.put("$nodeToPortName", "");
505 data.put("$nodeFromPortName", "");
508 public Map<String, Object> out() {
509 Map<String, Object> edge = new HashMap<String, Object>();
511 edge.put("data", data);
512 if (source == null) {
513 edge.put("nodeFrom", String.valueOf(this.hostId));
515 edge.put("nodeFrom", source.getNode().toString());
517 edge.put("nodeTo", destination.getNode().toString());
523 private String bandwidthColor(Bandwidth bandwidth) {
525 long bandwidthValue = bandwidth.getValue();
527 if (bandwidthValue == 0) {
529 } else if (bandwidthValue < Bandwidth.BW1Kbps) {
531 } else if (bandwidthValue < Bandwidth.BW1Mbps) {
533 } else if (bandwidthValue < Bandwidth.BW1Gbps) {
535 } else if (bandwidthValue < Bandwidth.BW1Tbps) {
537 } else if (bandwidthValue < Bandwidth.BW1Pbps) {
545 protected class NodeType {
546 public static final String NODE = "swtch";
547 public static final String HOST = "host";
550 @SuppressWarnings("unchecked")
551 private void loadConfiguration() {
552 ObjectReader objReader = new ObjectReader();
553 metaCache = (Map<String, Map<String, Map<String, Object>>>) objReader.read(this, topologyWebFileName);
554 if (metaCache == null) {
555 metaCache = new HashMap<String, Map<String, Map<String, Object>>>();
560 public Status saveConfiguration() {
561 ObjectWriter objWriter = new ObjectWriter();
562 objWriter.write(metaCache, topologyWebFileName);
563 return new Status(StatusCode.SUCCESS, null);
567 public Object readObject(ObjectInputStream ois)
568 throws FileNotFoundException, IOException, ClassNotFoundException {
569 // Perform the class deserialization locally, from inside the package where the class is defined
570 return ois.readObject();