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.packet.address.EthernetAddress;
32 import org.opendaylight.controller.sal.utils.ServiceHelper;
33 import org.opendaylight.controller.switchmanager.ISwitchManager;
34 import org.opendaylight.controller.switchmanager.Switch;
35 import org.opendaylight.controller.switchmanager.SwitchConfig;
36 import org.opendaylight.controller.topologymanager.ITopologyManager;
37 import org.opendaylight.controller.usermanager.IUserManager;
38 import org.springframework.stereotype.Controller;
39 import org.springframework.web.bind.annotation.PathVariable;
40 import org.springframework.web.bind.annotation.RequestMapping;
41 import org.springframework.web.bind.annotation.RequestMethod;
42 import org.springframework.web.bind.annotation.RequestParam;
43 import org.springframework.web.bind.annotation.ResponseBody;
45 import edu.uci.ics.jung.algorithms.layout.CircleLayout;
46 import edu.uci.ics.jung.graph.Graph;
47 import edu.uci.ics.jung.graph.SparseMultigraph;
51 public class Topology {
53 protected Map<String, Map<String, Object>> cache = new HashMap<String, Map<String, Object>>();
54 protected Map<String, Map<String, Object>> stagedNodes;
55 protected Map<String, Map<String, Object>> newNodes;
56 protected Integer nodeHash = null;
57 protected Integer hostHash = null;
58 protected Integer nodeSingleHash = null;
59 protected Integer nodeConfigurationHash = null;
62 * Topology of nodes and hosts in the network in JSON format.
64 * Mainly intended for consumption by the visual topology.
66 * @return - JSON output for visual topology
68 @RequestMapping(value = "/visual.json", method = RequestMethod.GET)
70 public Collection<Map<String, Object>> getLinkData() {
71 ITopologyManager topologyManager = (ITopologyManager) ServiceHelper
72 .getInstance(ITopologyManager.class, "default", this);
73 if (topologyManager == null) return null;
74 ISwitchManager switchManager = (ISwitchManager) ServiceHelper
75 .getInstance(ISwitchManager.class, "default", this);
76 if (switchManager == null) return null;
78 Map<Node, Set<Edge>> nodeEdges = topologyManager.getNodeEdges();
79 Map<Node, Set<NodeConnector>> hostEdges = topologyManager
80 .getNodesWithNodeConnectorHost();
81 List<Switch> nodes = switchManager.getNetworkDevices();
83 List<SwitchConfig> switchConfigurations = new ArrayList<SwitchConfig>();
84 for(Switch sw : nodes) {
85 Node n = sw.getNode();
86 SwitchConfig config = switchManager.getSwitchConfig(n.getNodeIDString());
87 switchConfigurations.add(config);
90 // return cache if topology hasn't changed
92 (nodeHash != null && hostHash != null && nodeSingleHash != null && nodeConfigurationHash != null) &&
93 nodeHash == nodeEdges.hashCode() && hostHash == hostEdges.hashCode() && nodeSingleHash == nodes.hashCode() && nodeConfigurationHash == switchConfigurations.hashCode()
95 return cache.values();
98 // cache has changed, we must assign the new values
99 nodeHash = nodeEdges.hashCode();
100 hostHash = hostEdges.hashCode();
101 nodeSingleHash = nodes.hashCode();
102 nodeConfigurationHash = switchConfigurations.hashCode();
104 stagedNodes = new HashMap<String, Map<String, Object>>();
105 newNodes = new HashMap<String, Map<String, Object>>();
107 // nodeEdges addition
108 addNodes(nodeEdges, topologyManager, switchManager);
110 // single nodes addition
111 addSingleNodes(nodes, switchManager);
113 // hostNodes addition
114 addHostNodes(hostEdges, topologyManager);
116 repositionTopology();
118 return cache.values();
122 * Add regular nodes to main topology
124 * @param nodeEdges - node-edges mapping derived from topology
125 * @param topology - the topology instance
127 private void addNodes(Map<Node, Set<Edge>> nodeEdges,
128 ITopologyManager topology, ISwitchManager switchManager) {
129 Bandwidth bandwidth = new Bandwidth(0);
130 Map<Edge, Set<Property>> properties = topology.getEdges();
132 for (Map.Entry<Node, Set<Edge>> e : nodeEdges.entrySet()) {
134 SwitchConfig config = switchManager.getSwitchConfig(n.getNodeIDString());
135 NodeBean node = createNodeBean(config, n);
137 List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
138 Set<Edge> links = e.getValue();
139 for (Edge link : links) {
140 for (Property p : properties.get(link)) {
141 if (p instanceof Bandwidth) {
142 bandwidth = (Bandwidth) p;
146 EdgeBean edge = new EdgeBean(link, bandwidth);
147 adjacencies.add(edge.out());
150 node.setLinks(adjacencies);
151 if (cache.containsKey(node.id())) {
152 // retrieve node from cache
153 Map<String, Object> nodeEntry = cache.get(node.id());
154 // update node name when appropriate
155 if (config != null) {
156 Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
157 data.put("$desc", config.getNodeDescription());
158 nodeEntry.put("data", data);
160 // always update adjacencies
161 nodeEntry.put("adjacencies", adjacencies);
162 // stage this cached node (with position)
163 stagedNodes.put(node.id(), nodeEntry);
165 newNodes.put(node.id(), node.out());
170 protected NodeBean createNodeBean(SwitchConfig config, Node node) {
171 NodeBean bean = null;
172 if (config != null) {
173 bean = new NodeBean(node.toString(), config.getNodeDescription(), NodeType.NODE);
175 bean = new NodeBean(node.toString(), node.toString(), NodeType.NODE);
181 private void addSingleNodes(List<Switch> nodes, ISwitchManager switchManager) {
182 if (nodes == null) return;
183 for (Switch sw : nodes) {
184 Node n = sw.getNode();
185 SwitchConfig config = switchManager.getSwitchConfig(n.getNodeIDString());
187 if ((stagedNodes.containsKey(n.toString()) && cache.containsKey(n.toString())) || newNodes.containsKey(n.toString())) {
190 NodeBean node = createNodeBean(config, n);
192 // FIXME still doesn't display standalone node when last remaining link is removed
193 if (cache.containsKey(node.id()) && !stagedNodes.containsKey(node.id())) {
194 Map<String, Object> nodeEntry = cache.get(node.id());
195 if (config != null) {
196 Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
197 data.put("$desc", config.getNodeDescription());
198 nodeEntry.put("data", data);
200 stagedNodes.put(node.id(), nodeEntry);
202 newNodes.put(node.id(), node.out());
208 * Add regular hosts to main topology
210 * @param hostEdges - node-nodeconnectors host-specific mapping from topology
211 * @param topology - topology instance
213 private void addHostNodes(Map<Node, Set<NodeConnector>> hostEdges,
214 ITopologyManager topology) {
215 for (Map.Entry<Node, Set<NodeConnector>> e : hostEdges.entrySet()) {
216 for (NodeConnector connector : e.getValue()) {
217 Host host = topology.getHostAttachedToNodeConnector(connector);
218 EthernetAddress dmac = (EthernetAddress) host.getDataLayerAddress();
220 ByteBuffer addressByteBuffer = ByteBuffer.allocate(8);
221 addressByteBuffer.putShort((short) 0);
222 addressByteBuffer.put(dmac.getValue());
223 addressByteBuffer.rewind();
225 long hid = addressByteBuffer.getLong();
226 String hostId = String.valueOf(hid);
228 NodeBean hostBean = new NodeBean(hostId, host.getNetworkAddressAsString(), NodeType.HOST);
229 List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
230 EdgeBean edge = new EdgeBean(connector, hid);
231 adjacencies.add(edge.out());
232 hostBean.setLinks(adjacencies);
234 if (cache.containsKey(hostId)) {
235 Map<String, Object> hostEntry = cache.get(hostId);
236 hostEntry.put("adjacencies", adjacencies);
237 stagedNodes.put(hostId, hostEntry);
239 newNodes.put(String.valueOf(hid), hostBean.out());
246 * Re-position nodes in circular layout
248 private void repositionTopology() {
249 Graph<String, String> graph = new SparseMultigraph<String, String>();
251 cache.putAll(stagedNodes);
252 cache.putAll(newNodes);
253 for (Map<String, Object> on : cache.values()) {
254 graph.addVertex(on.toString());
256 List<Map<String, Object>> adjacencies = (List<Map<String, Object>>) on.get("adjacencies");
258 for (Map<String, Object> adj : adjacencies) {
260 adj.toString(), adj.get("nodeFrom").toString(),
261 adj.get("nodeTo").toString()
266 CircleLayout layout = new CircleLayout(graph);
267 layout.setSize(new Dimension(1200, 365));
268 for (Map.Entry<String, Map<String, Object>> v : newNodes.entrySet()) {
269 Double x = layout.transform(v.getKey()).getX();
270 Double y = layout.transform(v.getKey()).getY();
272 Map<String, String> nodeData = (HashMap<String, String>) v.getValue().get("data");
273 nodeData.put("$x", (x - 600) + "");
274 nodeData.put("$y", (y - 225) + "");
276 newNodes.get(v.getKey()).put("data", nodeData);
281 * Update node position
283 * This method is mainly used by the visual topology
285 * @param nodeId - The node to update
286 * @return The node object
288 @RequestMapping(value = "/node/{nodeId}", method = RequestMethod.POST)
290 public Map<String, Object> post(@PathVariable String nodeId, @RequestParam(required = true) String x,
291 @RequestParam(required = true) String y, HttpServletRequest request) {
292 if (!authorize(UserLevel.NETWORKADMIN, request)) {
293 return new HashMap<String, Object>(); // silently disregard new node position
296 String id = new String(nodeId);
298 if (!cache.containsKey(id))
301 Map<String, Object> node = cache.get(id);
302 Map<String, String> data = (Map<String, String>) node.get("data");
307 node.put("data", data);
313 * Node object for visual topology
315 protected class NodeBean {
317 protected String name;
318 protected Map<String, String> data;
319 protected List<Map<String, Object>> links;
322 data = new HashMap<String, String>();
323 links = new ArrayList<Map<String, Object>>();
326 public NodeBean(String id, String name, String type) {
330 data.put("$desc", name);
331 data.put("$type", type);
334 public void setLinks(List<Map<String, Object>> links) {
338 public Map<String, Object> out() {
339 Map<String, Object> node = new HashMap<String, Object>();
340 node.put("id", this.id);
341 node.put("name", this.name);
342 node.put("data", this.data);
343 node.put("adjacencies", this.links);
348 public String name() {
358 * Edge object for visual topology
360 protected class EdgeBean {
361 protected NodeConnector source;
362 protected NodeConnector destination;
363 protected Map<String, String> data;
364 protected Long hostId;
367 data = new HashMap<String, String>();
370 public EdgeBean(Edge link, Bandwidth bandwidth) {
372 this.source = link.getHeadNodeConnector();
373 this.destination = link.getTailNodeConnector();
376 data.put("$bandwidth", bandwidth.toString());
377 data.put("$color", bandwidthColor(bandwidth));
378 data.put("$nodeToPort", destination.getID().toString());
379 data.put("$nodeFromPort", source.getID().toString());
380 data.put("$descFrom", source.getNode().toString());
381 data.put("$descTo", destination.getNode().toString());
382 data.put("$nodeFromPortName", source.toString());
383 data.put("$nodeToPortName", destination.toString());
386 public EdgeBean(NodeConnector connector, Long hostId) {
389 this.destination = connector;
390 this.hostId = hostId;
392 data.put("$bandwidth", "N/A");
393 data.put("$color", bandwidthColor(new Bandwidth(0)));
394 data.put("$nodeToPort", connector.getNodeConnectorIDString());
395 data.put("$nodeFromPort", connector.getNodeConnectorIDString());
396 data.put("$descTo", "");
397 data.put("$descFrom", "");
398 data.put("$nodeToPortName", "");
399 data.put("$nodeFromPortName", "");
402 public Map<String, Object> out() {
403 Map<String, Object> edge = new HashMap<String, Object>();
405 edge.put("data", data);
406 if (source == null) {
407 edge.put("nodeFrom", String.valueOf(this.hostId));
409 edge.put("nodeFrom", source.getNode().toString());
411 edge.put("nodeTo", destination.getNode().toString());
417 private String bandwidthColor(Bandwidth bandwidth) {
419 long bandwidthValue = bandwidth.getValue();
421 if (bandwidthValue == 0) {
423 } else if (bandwidthValue < Bandwidth.BW1Kbps) {
425 } else if (bandwidthValue < Bandwidth.BW1Mbps) {
427 } else if (bandwidthValue < Bandwidth.BW1Gbps) {
429 } else if (bandwidthValue < Bandwidth.BW1Tbps) {
431 } else if (bandwidthValue < Bandwidth.BW1Pbps) {
439 protected class NodeType {
440 public static final String NODE = "swtch";
441 public static final String HOST = "host";
444 private boolean authorize(UserLevel level, HttpServletRequest request) {
445 IUserManager userManager = (IUserManager) ServiceHelper
446 .getGlobalInstance(IUserManager.class, this);
447 if (userManager == null) {
451 String username = request.getUserPrincipal().getName();
452 UserLevel userLevel = userManager.getUserLevel(username);
453 if (userLevel.toNumber() <= level.toNumber()) {