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.toString());
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 String description = switchManager.getNodeDescription(n);
135 NodeBean node = createNodeBean(description, 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());
155 Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
156 data.put("$desc", description);
157 nodeEntry.put("data", data);
159 // always update adjacencies
160 nodeEntry.put("adjacencies", adjacencies);
161 // stage this cached node (with position)
162 stagedNodes.put(node.id(), nodeEntry);
164 newNodes.put(node.id(), node.out());
169 protected NodeBean createNodeBean(String description, Node node) {
170 String name = (description == null ||
171 description.trim().isEmpty() ||
172 description.equalsIgnoreCase("none"))?
173 node.toString() : description;
174 return new NodeBean(node.toString(), name, NodeType.NODE);
177 @SuppressWarnings("unchecked")
178 private void addSingleNodes(List<Switch> nodes, ISwitchManager switchManager) {
179 if (nodes == null) return;
180 for (Switch sw : nodes) {
181 Node n = sw.getNode();
183 String description = switchManager.getNodeDescription(n);
185 if ((stagedNodes.containsKey(n.toString()) && cache.containsKey(n.toString())) || newNodes.containsKey(n.toString())) {
188 NodeBean node = createNodeBean(description, n);
190 // FIXME still doesn't display standalone node when last remaining link is removed
191 if (cache.containsKey(node.id()) && !stagedNodes.containsKey(node.id())) {
192 Map<String, Object> nodeEntry = cache.get(node.id());
193 Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
194 data.put("$desc", description);
195 nodeEntry.put("data", data);
196 stagedNodes.put(node.id(), nodeEntry);
198 newNodes.put(node.id(), node.out());
204 * Add regular hosts to main topology
206 * @param hostEdges - node-nodeconnectors host-specific mapping from topology
207 * @param topology - topology instance
209 private void addHostNodes(Map<Node, Set<NodeConnector>> hostEdges,
210 ITopologyManager topology) {
211 for (Map.Entry<Node, Set<NodeConnector>> e : hostEdges.entrySet()) {
212 for (NodeConnector connector : e.getValue()) {
213 Host host = topology.getHostAttachedToNodeConnector(connector);
214 EthernetAddress dmac = (EthernetAddress) host.getDataLayerAddress();
216 ByteBuffer addressByteBuffer = ByteBuffer.allocate(8);
217 addressByteBuffer.putShort((short) 0);
218 addressByteBuffer.put(dmac.getValue());
219 addressByteBuffer.rewind();
221 long hid = addressByteBuffer.getLong();
222 String hostId = String.valueOf(hid);
224 NodeBean hostBean = new NodeBean(hostId, host.getNetworkAddressAsString(), NodeType.HOST);
225 List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
226 EdgeBean edge = new EdgeBean(connector, hid);
227 adjacencies.add(edge.out());
228 hostBean.setLinks(adjacencies);
230 if (cache.containsKey(hostId)) {
231 Map<String, Object> hostEntry = cache.get(hostId);
232 hostEntry.put("adjacencies", adjacencies);
233 stagedNodes.put(hostId, hostEntry);
235 newNodes.put(String.valueOf(hid), hostBean.out());
242 * Re-position nodes in circular layout
244 private void repositionTopology() {
245 Graph<String, String> graph = new SparseMultigraph<String, String>();
247 cache.putAll(stagedNodes);
248 cache.putAll(newNodes);
249 for (Map<String, Object> on : cache.values()) {
250 graph.addVertex(on.toString());
252 List<Map<String, Object>> adjacencies = (List<Map<String, Object>>) on.get("adjacencies");
254 for (Map<String, Object> adj : adjacencies) {
256 adj.toString(), adj.get("nodeFrom").toString(),
257 adj.get("nodeTo").toString()
262 CircleLayout<String,String> layout = new CircleLayout<String,String>(graph);
263 layout.setSize(new Dimension(1200, 365));
264 for (Map.Entry<String, Map<String, Object>> v : newNodes.entrySet()) {
265 Double x = layout.transform(v.getKey()).getX();
266 Double y = layout.transform(v.getKey()).getY();
268 Map<String, String> nodeData = (HashMap<String, String>) v.getValue().get("data");
269 nodeData.put("$x", (x - 600) + "");
270 nodeData.put("$y", (y - 225) + "");
272 newNodes.get(v.getKey()).put("data", nodeData);
277 * Update node position
279 * This method is mainly used by the visual topology
281 * @param nodeId - The node to update
282 * @return The node object
284 @RequestMapping(value = "/node/{nodeId}", method = RequestMethod.POST)
286 public Map<String, Object> post(@PathVariable String nodeId, @RequestParam(required = true) String x,
287 @RequestParam(required = true) String y, HttpServletRequest request) {
288 if (!authorize(UserLevel.NETWORKADMIN, request)) {
289 return new HashMap<String, Object>(); // silently disregard new node position
292 String id = new String(nodeId);
294 if (!cache.containsKey(id))
297 Map<String, Object> node = cache.get(id);
298 Map<String, String> data = (Map<String, String>) node.get("data");
303 node.put("data", data);
309 * Node object for visual topology
311 protected class NodeBean {
313 protected String name;
314 protected Map<String, String> data;
315 protected List<Map<String, Object>> links;
318 data = new HashMap<String, String>();
319 links = new ArrayList<Map<String, Object>>();
322 public NodeBean(String id, String name, String type) {
326 data.put("$desc", name);
327 data.put("$type", type);
330 public void setLinks(List<Map<String, Object>> links) {
334 public Map<String, Object> out() {
335 Map<String, Object> node = new HashMap<String, Object>();
336 node.put("id", this.id);
337 node.put("name", this.name);
338 node.put("data", this.data);
339 node.put("adjacencies", this.links);
344 public String name() {
354 * Edge object for visual topology
356 protected class EdgeBean {
357 protected NodeConnector source;
358 protected NodeConnector destination;
359 protected Map<String, String> data;
360 protected Long hostId;
363 data = new HashMap<String, String>();
366 public EdgeBean(Edge link, Bandwidth bandwidth) {
368 this.source = link.getHeadNodeConnector();
369 this.destination = link.getTailNodeConnector();
372 data.put("$bandwidth", bandwidth.toString());
373 data.put("$color", bandwidthColor(bandwidth));
374 data.put("$nodeToPort", destination.getID().toString());
375 data.put("$nodeFromPort", source.getID().toString());
376 data.put("$descFrom", source.getNode().toString());
377 data.put("$descTo", destination.getNode().toString());
378 data.put("$nodeFromPortName", source.toString());
379 data.put("$nodeToPortName", destination.toString());
382 public EdgeBean(NodeConnector connector, Long hostId) {
385 this.destination = connector;
386 this.hostId = hostId;
388 data.put("$bandwidth", "N/A");
389 data.put("$color", bandwidthColor(new Bandwidth(0)));
390 data.put("$nodeToPort", connector.getNodeConnectorIDString());
391 data.put("$nodeFromPort", connector.getNodeConnectorIDString());
392 data.put("$descTo", "");
393 data.put("$descFrom", "");
394 data.put("$nodeToPortName", "");
395 data.put("$nodeFromPortName", "");
398 public Map<String, Object> out() {
399 Map<String, Object> edge = new HashMap<String, Object>();
401 edge.put("data", data);
402 if (source == null) {
403 edge.put("nodeFrom", String.valueOf(this.hostId));
405 edge.put("nodeFrom", source.getNode().toString());
407 edge.put("nodeTo", destination.getNode().toString());
413 private String bandwidthColor(Bandwidth bandwidth) {
415 long bandwidthValue = bandwidth.getValue();
417 if (bandwidthValue == 0) {
419 } else if (bandwidthValue < Bandwidth.BW1Kbps) {
421 } else if (bandwidthValue < Bandwidth.BW1Mbps) {
423 } else if (bandwidthValue < Bandwidth.BW1Gbps) {
425 } else if (bandwidthValue < Bandwidth.BW1Tbps) {
427 } else if (bandwidthValue < Bandwidth.BW1Pbps) {
435 protected class NodeType {
436 public static final String NODE = "swtch";
437 public static final String HOST = "host";
440 private boolean authorize(UserLevel level, HttpServletRequest request) {
441 IUserManager userManager = (IUserManager) ServiceHelper
442 .getGlobalInstance(IUserManager.class, this);
443 if (userManager == null) {
447 String username = request.getUserPrincipal().getName();
448 UserLevel userLevel = userManager.getUserLevel(username);
449 if (userLevel.toNumber() <= level.toNumber()) {