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.nio.ByteBuffer;
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.HashMap;
17 import java.util.LinkedList;
18 import java.util.List;
22 import javax.servlet.http.HttpServletRequest;
24 import org.opendaylight.controller.sal.authorization.UserLevel;
25 import org.opendaylight.controller.sal.core.Bandwidth;
26 import org.opendaylight.controller.sal.core.Edge;
27 import org.opendaylight.controller.sal.core.Host;
28 import org.opendaylight.controller.sal.core.Node;
29 import org.opendaylight.controller.sal.core.NodeConnector;
30 import org.opendaylight.controller.sal.core.Property;
31 import org.opendaylight.controller.sal.core.Node.NodeIDType;
32 import org.opendaylight.controller.sal.packet.address.EthernetAddress;
33 import org.opendaylight.controller.sal.utils.ServiceHelper;
34 import org.opendaylight.controller.switchmanager.ISwitchManager;
35 import org.opendaylight.controller.switchmanager.Switch;
36 import org.opendaylight.controller.switchmanager.SwitchConfig;
37 import org.opendaylight.controller.topologymanager.ITopologyManager;
38 import org.opendaylight.controller.usermanager.IUserManager;
39 import org.springframework.stereotype.Controller;
40 import org.springframework.web.bind.annotation.PathVariable;
41 import org.springframework.web.bind.annotation.RequestMapping;
42 import org.springframework.web.bind.annotation.RequestMethod;
43 import org.springframework.web.bind.annotation.RequestParam;
44 import org.springframework.web.bind.annotation.ResponseBody;
46 import edu.uci.ics.jung.algorithms.layout.CircleLayout;
47 import edu.uci.ics.jung.graph.Graph;
48 import edu.uci.ics.jung.graph.SparseMultigraph;
52 public class Topology {
54 protected Map<String, Map<String, Object>> cache = new HashMap<String, Map<String, Object>>();
55 protected Map<String, Map<String, Object>> stagedNodes;
56 protected Map<String, Map<String, Object>> newNodes;
57 protected Integer nodeHash = null;
58 protected Integer hostHash = null;
59 protected Integer nodeSingleHash = null;
60 protected Integer nodeConfigurationHash = null;
63 * Topology of nodes and hosts in the network in JSON format.
65 * Mainly intended for consumption by the visual topology.
67 * @return - JSON output for visual topology
69 @RequestMapping(value = "/visual.json", method = RequestMethod.GET)
71 public Collection<Map<String, Object>> getLinkData() {
72 ITopologyManager topologyManager = (ITopologyManager) ServiceHelper
73 .getInstance(ITopologyManager.class, "default", this);
74 if (topologyManager == null) return null;
75 ISwitchManager switchManager = (ISwitchManager) ServiceHelper
76 .getInstance(ISwitchManager.class, "default", this);
77 if (switchManager == null) return null;
79 Map<Node, Set<Edge>> nodeEdges = topologyManager.getNodeEdges();
80 Map<Node, Set<NodeConnector>> hostEdges = topologyManager
81 .getNodesWithNodeConnectorHost();
82 List<Switch> nodes = switchManager.getNetworkDevices();
84 List<SwitchConfig> switchConfigurations = new ArrayList<SwitchConfig>();
85 for(Switch sw : nodes) {
86 Node n = sw.getNode();
87 SwitchConfig config = switchManager.getSwitchConfig(n.toString());
88 switchConfigurations.add(config);
91 // return cache if topology hasn't changed
93 (nodeHash != null && hostHash != null && nodeSingleHash != null && nodeConfigurationHash != null) &&
94 nodeHash == nodeEdges.hashCode() && hostHash == hostEdges.hashCode() && nodeSingleHash == nodes.hashCode() && nodeConfigurationHash == switchConfigurations.hashCode()
96 return cache.values();
99 // cache has changed, we must assign the new values
100 nodeHash = nodeEdges.hashCode();
101 hostHash = hostEdges.hashCode();
102 nodeSingleHash = nodes.hashCode();
103 nodeConfigurationHash = switchConfigurations.hashCode();
105 stagedNodes = new HashMap<String, Map<String, Object>>();
106 newNodes = new HashMap<String, Map<String, Object>>();
108 // nodeEdges addition
109 addNodes(nodeEdges, topologyManager, switchManager);
111 // single nodes addition
112 addSingleNodes(nodes, switchManager);
114 // hostNodes addition
115 addHostNodes(hostEdges, topologyManager);
117 repositionTopology();
119 return cache.values();
123 * Add regular nodes to main topology
125 * @param nodeEdges - node-edges mapping derived from topology
126 * @param topology - the topology instance
128 private void addNodes(Map<Node, Set<Edge>> nodeEdges,
129 ITopologyManager topology, ISwitchManager switchManager) {
130 Bandwidth bandwidth = new Bandwidth(0);
131 Map<Edge, Set<Property>> properties = topology.getEdges();
133 for (Map.Entry<Node, Set<Edge>> e : nodeEdges.entrySet()) {
136 // skip production node
141 String description = switchManager.getNodeDescription(n);
142 NodeBean node = createNodeBean(description, n);
144 List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
145 Set<Edge> links = e.getValue();
146 for (Edge link : links) {
147 if (edgeIgnore(link)) {
150 for (Property p : properties.get(link)) {
151 if (p instanceof Bandwidth) {
152 bandwidth = (Bandwidth) p;
156 EdgeBean edge = new EdgeBean(link, bandwidth);
157 adjacencies.add(edge.out());
160 node.setLinks(adjacencies);
161 if (cache.containsKey(node.id())) {
162 // retrieve node from cache
163 Map<String, Object> nodeEntry = cache.get(node.id());
165 Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
166 data.put("$desc", description);
167 nodeEntry.put("data", data);
169 // always update adjacencies
170 nodeEntry.put("adjacencies", adjacencies);
171 // stage this cached node (with position)
172 stagedNodes.put(node.id(), nodeEntry);
174 newNodes.put(node.id(), node.out());
180 * Check if this node shouldn't appear in the visual topology
185 private boolean nodeIgnore(Node node) {
186 String nodeType = node.getType();
188 // add other node types to ignore later
189 if (nodeType.equals(NodeIDType.PRODUCTION)) {
197 * Check if this edge shouldn't appear in the visual topology
202 private boolean edgeIgnore(Edge edge) {
203 NodeConnector headNodeConnector = edge.getHeadNodeConnector();
204 Node headNode = headNodeConnector.getNode();
205 if (nodeIgnore(headNode)) {
209 NodeConnector tailNodeConnector = edge.getTailNodeConnector();
210 Node tailNode = tailNodeConnector.getNode();
211 if (nodeIgnore(tailNode)) {
218 protected NodeBean createNodeBean(String description, Node node) {
219 String name = (description == null ||
220 description.trim().isEmpty() ||
221 description.equalsIgnoreCase("none"))?
222 node.toString() : description;
223 return new NodeBean(node.toString(), name, NodeType.NODE);
226 @SuppressWarnings("unchecked")
227 private void addSingleNodes(List<Switch> nodes, ISwitchManager switchManager) {
228 if (nodes == null) return;
229 for (Switch sw : nodes) {
230 Node n = sw.getNode();
232 // skip production node
237 String description = switchManager.getNodeDescription(n);
239 if ((stagedNodes.containsKey(n.toString()) && cache.containsKey(n.toString())) || newNodes.containsKey(n.toString())) {
242 NodeBean node = createNodeBean(description, n);
244 // FIXME still doesn't display standalone node when last remaining link is removed
245 if (cache.containsKey(node.id()) && !stagedNodes.containsKey(node.id())) {
246 Map<String, Object> nodeEntry = cache.get(node.id());
247 Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
248 data.put("$desc", description);
249 nodeEntry.put("data", data);
250 stagedNodes.put(node.id(), nodeEntry);
252 newNodes.put(node.id(), node.out());
258 * Add regular hosts to main topology
260 * @param hostEdges - node-nodeconnectors host-specific mapping from topology
261 * @param topology - topology instance
263 private void addHostNodes(Map<Node, Set<NodeConnector>> hostEdges,
264 ITopologyManager topology) {
265 for (Map.Entry<Node, Set<NodeConnector>> e : hostEdges.entrySet()) {
266 for (NodeConnector connector : e.getValue()) {
267 Host host = topology.getHostAttachedToNodeConnector(connector);
268 EthernetAddress dmac = (EthernetAddress) host.getDataLayerAddress();
270 ByteBuffer addressByteBuffer = ByteBuffer.allocate(8);
271 addressByteBuffer.putShort((short) 0);
272 addressByteBuffer.put(dmac.getValue());
273 addressByteBuffer.rewind();
275 long hid = addressByteBuffer.getLong();
276 String hostId = String.valueOf(hid);
278 NodeBean hostBean = new NodeBean(hostId, host.getNetworkAddressAsString(), NodeType.HOST);
279 List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
280 EdgeBean edge = new EdgeBean(connector, hid);
281 adjacencies.add(edge.out());
282 hostBean.setLinks(adjacencies);
284 if (cache.containsKey(hostId)) {
285 Map<String, Object> hostEntry = cache.get(hostId);
286 hostEntry.put("adjacencies", adjacencies);
287 stagedNodes.put(hostId, hostEntry);
289 newNodes.put(String.valueOf(hid), hostBean.out());
296 * Re-position nodes in circular layout
298 private void repositionTopology() {
299 Graph<String, String> graph = new SparseMultigraph<String, String>();
301 cache.putAll(stagedNodes);
302 cache.putAll(newNodes);
303 for (Map<String, Object> on : cache.values()) {
304 graph.addVertex(on.toString());
306 List<Map<String, Object>> adjacencies = (List<Map<String, Object>>) on.get("adjacencies");
308 for (Map<String, Object> adj : adjacencies) {
310 adj.toString(), adj.get("nodeFrom").toString(),
311 adj.get("nodeTo").toString()
316 CircleLayout<String,String> layout = new CircleLayout<String,String>(graph);
317 layout.setSize(new Dimension(1200, 365));
318 for (Map.Entry<String, Map<String, Object>> v : newNodes.entrySet()) {
319 Double x = layout.transform(v.getKey()).getX();
320 Double y = layout.transform(v.getKey()).getY();
322 Map<String, String> nodeData = (HashMap<String, String>) v.getValue().get("data");
323 nodeData.put("$x", (x - 600) + "");
324 nodeData.put("$y", (y - 225) + "");
326 newNodes.get(v.getKey()).put("data", nodeData);
331 * Update node position
333 * This method is mainly used by the visual topology
335 * @param nodeId - The node to update
336 * @return The node object
338 @RequestMapping(value = "/node/{nodeId}", method = RequestMethod.POST)
340 public Map<String, Object> post(@PathVariable String nodeId, @RequestParam(required = true) String x,
341 @RequestParam(required = true) String y, HttpServletRequest request) {
342 if (!authorize(UserLevel.NETWORKADMIN, request)) {
343 return new HashMap<String, Object>(); // silently disregard new node position
346 String id = new String(nodeId);
348 if (!cache.containsKey(id))
351 Map<String, Object> node = cache.get(id);
352 Map<String, String> data = (Map<String, String>) node.get("data");
357 node.put("data", data);
363 * Node object for visual topology
365 protected class NodeBean {
367 protected String name;
368 protected Map<String, String> data;
369 protected List<Map<String, Object>> links;
372 data = new HashMap<String, String>();
373 links = new ArrayList<Map<String, Object>>();
376 public NodeBean(String id, String name, String type) {
380 data.put("$desc", name);
381 data.put("$type", type);
384 public void setLinks(List<Map<String, Object>> links) {
388 public Map<String, Object> out() {
389 Map<String, Object> node = new HashMap<String, Object>();
390 node.put("id", this.id);
391 node.put("name", this.name);
392 node.put("data", this.data);
393 node.put("adjacencies", this.links);
398 public String name() {
408 * Edge object for visual topology
410 protected class EdgeBean {
411 protected NodeConnector source;
412 protected NodeConnector destination;
413 protected Map<String, String> data;
414 protected Long hostId;
417 data = new HashMap<String, String>();
420 public EdgeBean(Edge link, Bandwidth bandwidth) {
422 this.source = link.getHeadNodeConnector();
423 this.destination = link.getTailNodeConnector();
426 data.put("$bandwidth", bandwidth.toString());
427 data.put("$color", bandwidthColor(bandwidth));
428 data.put("$nodeToPort", destination.getID().toString());
429 data.put("$nodeFromPort", source.getID().toString());
430 data.put("$descFrom", source.getNode().toString());
431 data.put("$descTo", destination.getNode().toString());
432 data.put("$nodeFromPortName", source.toString());
433 data.put("$nodeToPortName", destination.toString());
436 public EdgeBean(NodeConnector connector, Long hostId) {
439 this.destination = connector;
440 this.hostId = hostId;
442 data.put("$bandwidth", "N/A");
443 data.put("$color", bandwidthColor(new Bandwidth(0)));
444 data.put("$nodeToPort", connector.getNodeConnectorIDString());
445 data.put("$nodeFromPort", connector.getNodeConnectorIDString());
446 data.put("$descTo", "");
447 data.put("$descFrom", "");
448 data.put("$nodeToPortName", "");
449 data.put("$nodeFromPortName", "");
452 public Map<String, Object> out() {
453 Map<String, Object> edge = new HashMap<String, Object>();
455 edge.put("data", data);
456 if (source == null) {
457 edge.put("nodeFrom", String.valueOf(this.hostId));
459 edge.put("nodeFrom", source.getNode().toString());
461 edge.put("nodeTo", destination.getNode().toString());
467 private String bandwidthColor(Bandwidth bandwidth) {
469 long bandwidthValue = bandwidth.getValue();
471 if (bandwidthValue == 0) {
473 } else if (bandwidthValue < Bandwidth.BW1Kbps) {
475 } else if (bandwidthValue < Bandwidth.BW1Mbps) {
477 } else if (bandwidthValue < Bandwidth.BW1Gbps) {
479 } else if (bandwidthValue < Bandwidth.BW1Tbps) {
481 } else if (bandwidthValue < Bandwidth.BW1Pbps) {
489 protected class NodeType {
490 public static final String NODE = "swtch";
491 public static final String HOST = "host";
494 private boolean authorize(UserLevel level, HttpServletRequest request) {
495 IUserManager userManager = (IUserManager) ServiceHelper
496 .getGlobalInstance(IUserManager.class, this);
497 if (userManager == null) {
501 String username = request.getUserPrincipal().getName();
502 UserLevel userLevel = userManager.getUserLevel(username);
503 if (userLevel.toNumber() <= level.toNumber()) {