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.containermanager.IContainerAuthorization;
25 import org.opendaylight.controller.sal.authorization.Resource;
26 import org.opendaylight.controller.sal.authorization.UserLevel;
27 import org.opendaylight.controller.sal.core.Bandwidth;
28 import org.opendaylight.controller.sal.core.Edge;
29 import org.opendaylight.controller.sal.core.Host;
30 import org.opendaylight.controller.sal.core.Node;
31 import org.opendaylight.controller.sal.core.Node.NodeIDType;
32 import org.opendaylight.controller.sal.core.NodeConnector;
33 import org.opendaylight.controller.sal.core.Property;
34 import org.opendaylight.controller.sal.packet.address.EthernetAddress;
35 import org.opendaylight.controller.sal.utils.GlobalConstants;
36 import org.opendaylight.controller.sal.utils.ServiceHelper;
37 import org.opendaylight.controller.switchmanager.ISwitchManager;
38 import org.opendaylight.controller.switchmanager.Switch;
39 import org.opendaylight.controller.switchmanager.SwitchConfig;
40 import org.opendaylight.controller.topologymanager.ITopologyManager;
41 import org.opendaylight.controller.usermanager.IUserManager;
42 import org.opendaylight.controller.web.DaylightWebUtil;
43 import org.springframework.stereotype.Controller;
44 import org.springframework.web.bind.annotation.PathVariable;
45 import org.springframework.web.bind.annotation.RequestMapping;
46 import org.springframework.web.bind.annotation.RequestMethod;
47 import org.springframework.web.bind.annotation.RequestParam;
48 import org.springframework.web.bind.annotation.ResponseBody;
50 import edu.uci.ics.jung.algorithms.layout.CircleLayout;
51 import edu.uci.ics.jung.graph.Graph;
52 import edu.uci.ics.jung.graph.SparseMultigraph;
56 public class Topology {
58 protected Map<String, Map<String, Map<String, Object>>> metaCache = new HashMap<String, Map<String, Map<String, Object>>>();
59 protected Map<String, Map<String, Object>> stagedNodes;
60 protected Map<String, Map<String, Object>> newNodes;
62 protected Map<String, Integer> metaNodeHash = new HashMap<String, Integer>();
63 protected Map<String, Integer> metaHostHash = new HashMap<String, Integer>();
64 protected Map<String, Integer> metaNodeSingleHash = new HashMap<String, Integer>();
65 protected Map<String, Integer> metaNodeConfigurationHash = new HashMap<String, Integer>();
68 * Topology of nodes and hosts in the network in JSON format.
70 * Mainly intended for consumption by the visual topology.
72 * @return - JSON output for visual topology
74 @RequestMapping(value = "/visual.json", method = RequestMethod.GET)
76 public Collection<Map<String, Object>> getLinkData(@RequestParam(required = false) String container, HttpServletRequest request) {
77 String containerName = DaylightWebUtil.getAuthorizedContainer(request, container, this);
79 ITopologyManager topologyManager = (ITopologyManager) ServiceHelper
80 .getInstance(ITopologyManager.class, containerName, this);
81 if (topologyManager == null) {
84 ISwitchManager switchManager = (ISwitchManager) ServiceHelper
85 .getInstance(ISwitchManager.class, containerName, this);
86 if (switchManager == null) {
90 Map<Node, Set<Edge>> nodeEdges = topologyManager.getNodeEdges();
91 Map<Node, Set<NodeConnector>> hostEdges = topologyManager
92 .getNodesWithNodeConnectorHost();
93 List<Switch> nodes = switchManager.getNetworkDevices();
95 List<SwitchConfig> switchConfigurations = new ArrayList<SwitchConfig>();
96 for(Switch sw : nodes) {
97 Node n = sw.getNode();
98 SwitchConfig config = switchManager.getSwitchConfig(n.toString());
99 switchConfigurations.add(config);
102 // initialize cache if needed
103 if (!metaCache.containsKey(containerName)) {
104 metaCache.put(containerName, new HashMap<String, Map<String, Object>>());
106 metaNodeHash.put(containerName, null);
107 metaHostHash.put(containerName, null);
108 metaNodeSingleHash.put(containerName, null);
109 metaNodeConfigurationHash.put(containerName, null);
112 // return cache if topology hasn't changed
114 (metaNodeHash.get(containerName) != null && metaHostHash.get(containerName) != null && metaNodeSingleHash.get(containerName) != null && metaNodeConfigurationHash.get(containerName) != null) &&
115 metaNodeHash.get(containerName).equals(nodeEdges.hashCode()) && metaHostHash.get(containerName).equals(hostEdges.hashCode()) && metaNodeSingleHash.get(containerName).equals(nodes.hashCode()) && metaNodeConfigurationHash.get(containerName).equals(switchConfigurations.hashCode())
117 return metaCache.get(containerName).values();
120 // cache has changed, we must assign the new values
121 metaNodeHash.put(containerName, nodeEdges.hashCode());
122 metaHostHash.put(containerName, hostEdges.hashCode());
123 metaNodeSingleHash.put(containerName, nodes.hashCode());
124 metaNodeConfigurationHash.put(containerName, switchConfigurations.hashCode());
126 stagedNodes = new HashMap<String, Map<String, Object>>();
127 newNodes = new HashMap<String, Map<String, Object>>();
129 // nodeEdges addition
130 addNodes(nodeEdges, topologyManager, switchManager, containerName);
132 // single nodes addition
133 addSingleNodes(nodes, switchManager, containerName);
135 // hostNodes addition
136 addHostNodes(hostEdges, topologyManager, containerName);
138 repositionTopology(containerName);
140 return metaCache.get(containerName).values();
144 * Add regular nodes to main topology
146 * @param nodeEdges - node-edges mapping derived from topology
147 * @param topology - the topology instance
149 private void addNodes(Map<Node, Set<Edge>> nodeEdges,
150 ITopologyManager topology, ISwitchManager switchManager, String containerName) {
151 Bandwidth bandwidth = new Bandwidth(0);
152 Map<Edge, Set<Property>> properties = topology.getEdges();
154 for (Map.Entry<Node, Set<Edge>> e : nodeEdges.entrySet()) {
156 String description = switchManager.getNodeDescription(n);
157 NodeBean node = createNodeBean(description, n);
159 // skip production node
164 List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
165 Set<Edge> links = e.getValue();
166 for (Edge link : links) {
167 if (edgeIgnore(link)) {
170 for (Property p : properties.get(link)) {
171 if (p instanceof Bandwidth) {
172 bandwidth = (Bandwidth) p;
176 EdgeBean edge = new EdgeBean(link, bandwidth);
177 adjacencies.add(edge.out());
180 node.setLinks(adjacencies);
181 if (metaCache.get(containerName).containsKey(node.id())) {
182 // retrieve node from cache
183 Map<String, Object> nodeEntry = metaCache.get(containerName).get(node.id());
185 Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
186 data.put("$desc", description);
187 nodeEntry.put("data", data);
189 // always update adjacencies
190 nodeEntry.put("adjacencies", adjacencies);
191 // stage this cached node (with position)
192 stagedNodes.put(node.id(), nodeEntry);
194 newNodes.put(node.id(), node.out());
200 * Check if this node shouldn't appear in the visual topology
205 private boolean nodeIgnore(Node node) {
206 String nodeType = node.getType();
208 // add other node types to ignore later
209 if (nodeType.equals(NodeIDType.PRODUCTION)) {
217 * Check if this edge shouldn't appear in the visual topology
222 private boolean edgeIgnore(Edge edge) {
223 NodeConnector headNodeConnector = edge.getHeadNodeConnector();
224 Node headNode = headNodeConnector.getNode();
225 if (nodeIgnore(headNode)) {
229 NodeConnector tailNodeConnector = edge.getTailNodeConnector();
230 Node tailNode = tailNodeConnector.getNode();
231 if (nodeIgnore(tailNode)) {
238 protected NodeBean createNodeBean(String description, Node node) {
239 String name = (description == null ||
240 description.trim().isEmpty() ||
241 description.equalsIgnoreCase("none"))?
242 node.toString() : description;
243 return new NodeBean(node.toString(), name, NodeType.NODE);
246 @SuppressWarnings("unchecked")
247 private void addSingleNodes(List<Switch> nodes, ISwitchManager switchManager, String containerName) {
251 for (Switch sw : nodes) {
252 Node n = sw.getNode();
254 // skip production node
259 String description = switchManager.getNodeDescription(n);
261 if ((stagedNodes.containsKey(n.toString()) && metaCache.get(containerName).containsKey(n.toString())) || newNodes.containsKey(n.toString())) {
264 NodeBean node = createNodeBean(description, n);
266 // FIXME still doesn't display standalone node when last remaining link is removed
267 if (metaCache.get(containerName).containsKey(node.id()) && !stagedNodes.containsKey(node.id())) {
268 Map<String, Object> nodeEntry = metaCache.get(containerName).get(node.id());
269 Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
270 data.put("$desc", description);
271 nodeEntry.put("data", data);
272 stagedNodes.put(node.id(), nodeEntry);
274 newNodes.put(node.id(), node.out());
280 * Add regular hosts to main topology
282 * @param hostEdges - node-nodeconnectors host-specific mapping from topology
283 * @param topology - topology instance
285 private void addHostNodes(Map<Node, Set<NodeConnector>> hostEdges,
286 ITopologyManager topology, String containerName) {
287 for (Map.Entry<Node, Set<NodeConnector>> e : hostEdges.entrySet()) {
288 for (NodeConnector connector : e.getValue()) {
289 Host host = topology.getHostAttachedToNodeConnector(connector);
290 EthernetAddress dmac = (EthernetAddress) host.getDataLayerAddress();
292 ByteBuffer addressByteBuffer = ByteBuffer.allocate(8);
293 addressByteBuffer.putShort((short) 0);
294 addressByteBuffer.put(dmac.getValue());
295 addressByteBuffer.rewind();
297 long hid = addressByteBuffer.getLong();
298 String hostId = String.valueOf(hid);
300 NodeBean hostBean = new NodeBean(hostId, host.getNetworkAddressAsString(), NodeType.HOST);
301 List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
302 EdgeBean edge = new EdgeBean(connector, hid);
303 adjacencies.add(edge.out());
304 hostBean.setLinks(adjacencies);
306 if (metaCache.get(containerName).containsKey(hostId)) {
307 Map<String, Object> hostEntry = metaCache.get(containerName).get(hostId);
308 hostEntry.put("adjacencies", adjacencies);
309 stagedNodes.put(hostId, hostEntry);
311 newNodes.put(String.valueOf(hid), hostBean.out());
318 * Re-position nodes in circular layout
320 private void repositionTopology(String containerName) {
321 Graph<String, String> graph = new SparseMultigraph<String, String>();
323 metaCache.get(containerName).clear();
324 metaCache.get(containerName).putAll(stagedNodes);
325 metaCache.get(containerName).putAll(newNodes);
327 for (Map<String, Object> on : metaCache.get(containerName).values()) {
328 graph.addVertex(on.toString());
330 List<Map<String, Object>> adjacencies = (List<Map<String, Object>>) on.get("adjacencies");
332 for (Map<String, Object> adj : adjacencies) {
334 adj.toString(), adj.get("nodeFrom").toString(),
335 adj.get("nodeTo").toString()
340 CircleLayout<String, String> layout = new CircleLayout<String, String>(graph);
341 layout.setSize(new Dimension(1200, 365));
342 for (Map.Entry<String, Map<String, Object>> v : newNodes.entrySet()) {
343 Double x = layout.transform(v.getKey()).getX();
344 Double y = layout.transform(v.getKey()).getY();
346 Map<String, String> nodeData = (HashMap<String, String>) v.getValue().get("data");
347 nodeData.put("$x", (x - 600) + "");
348 nodeData.put("$y", (y - 225) + "");
350 newNodes.get(v.getKey()).put("data", nodeData);
355 * Update node position
357 * This method is mainly used by the visual topology
359 * @param nodeId - The node to update
360 * @return The node object
362 @RequestMapping(value = "/node/{nodeId}", method = RequestMethod.POST)
364 public Map<String, Object> post(@PathVariable String nodeId, @RequestParam(required = true) String x,
365 @RequestParam(required = true) String y, @RequestParam(required = false) String container,
366 HttpServletRequest request) {
367 if (!authorize(UserLevel.NETWORKADMIN, request)) {
368 return new HashMap<String, Object>(); // silently disregard new node position
371 String containerName = getAuthorizedContainer(request, container);
373 String id = new String(nodeId);
375 if (!metaCache.get(containerName).containsKey(id)) {
379 Map<String, Object> node = metaCache.get(containerName).get(id);
380 Map<String, String> data = (Map<String, String>) node.get("data");
385 node.put("data", data);
391 * Node object for visual topology
393 protected class NodeBean {
395 protected String name;
396 protected Map<String, String> data;
397 protected List<Map<String, Object>> links;
400 data = new HashMap<String, String>();
401 links = new ArrayList<Map<String, Object>>();
404 public NodeBean(String id, String name, String type) {
408 data.put("$desc", name);
409 data.put("$type", type);
412 public void setLinks(List<Map<String, Object>> links) {
416 public Map<String, Object> out() {
417 Map<String, Object> node = new HashMap<String, Object>();
418 node.put("id", this.id);
419 node.put("name", this.name);
420 node.put("data", this.data);
421 node.put("adjacencies", this.links);
426 public String name() {
436 * Edge object for visual topology
438 protected class EdgeBean {
439 protected NodeConnector source;
440 protected NodeConnector destination;
441 protected Map<String, String> data;
442 protected Long hostId;
445 data = new HashMap<String, String>();
448 public EdgeBean(Edge link, Bandwidth bandwidth) {
450 this.source = link.getHeadNodeConnector();
451 this.destination = link.getTailNodeConnector();
454 data.put("$bandwidth", bandwidth.toString());
455 data.put("$color", bandwidthColor(bandwidth));
456 data.put("$nodeToPort", destination.getID().toString());
457 data.put("$nodeFromPort", source.getID().toString());
458 data.put("$descFrom", source.getNode().toString());
459 data.put("$descTo", destination.getNode().toString());
460 data.put("$nodeFromPortName", source.toString());
461 data.put("$nodeToPortName", destination.toString());
464 public EdgeBean(NodeConnector connector, Long hostId) {
467 this.destination = connector;
468 this.hostId = hostId;
470 data.put("$bandwidth", "N/A");
471 data.put("$color", bandwidthColor(new Bandwidth(0)));
472 data.put("$nodeToPort", connector.getNodeConnectorIDString());
473 data.put("$nodeFromPort", connector.getNodeConnectorIDString());
474 data.put("$descTo", "");
475 data.put("$descFrom", "");
476 data.put("$nodeToPortName", "");
477 data.put("$nodeFromPortName", "");
480 public Map<String, Object> out() {
481 Map<String, Object> edge = new HashMap<String, Object>();
483 edge.put("data", data);
484 if (source == null) {
485 edge.put("nodeFrom", String.valueOf(this.hostId));
487 edge.put("nodeFrom", source.getNode().toString());
489 edge.put("nodeTo", destination.getNode().toString());
495 private String bandwidthColor(Bandwidth bandwidth) {
497 long bandwidthValue = bandwidth.getValue();
499 if (bandwidthValue == 0) {
501 } else if (bandwidthValue < Bandwidth.BW1Kbps) {
503 } else if (bandwidthValue < Bandwidth.BW1Mbps) {
505 } else if (bandwidthValue < Bandwidth.BW1Gbps) {
507 } else if (bandwidthValue < Bandwidth.BW1Tbps) {
509 } else if (bandwidthValue < Bandwidth.BW1Pbps) {
517 protected class NodeType {
518 public static final String NODE = "swtch";
519 public static final String HOST = "host";
522 private boolean authorize(UserLevel level, HttpServletRequest request) {
523 IUserManager userManager = (IUserManager) ServiceHelper
524 .getGlobalInstance(IUserManager.class, this);
525 if (userManager == null) {
529 String username = request.getUserPrincipal().getName();
530 UserLevel userLevel = userManager.getUserLevel(username);
531 if (userLevel.toNumber() <= level.toNumber()) {
537 private String getAuthorizedContainer(HttpServletRequest request, String container) {
538 String username = request.getUserPrincipal().getName();
539 IContainerAuthorization containerAuthorization = (IContainerAuthorization) ServiceHelper.
540 getGlobalInstance(IContainerAuthorization.class, this);
541 if (containerAuthorization != null) {
542 Set<Resource> resources = containerAuthorization.getAllResourcesforUser(username);
543 if (authorizeContainer(container, resources)) {
548 return GlobalConstants.DEFAULT.toString();
551 private boolean authorizeContainer(String container, Set<Resource> resources) {
552 for(Resource resource : resources) {
553 String containerName = (String) resource.getResource();
554 if (containerName.equals(container)) {