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.containermanager.IContainerAuthorization;
29 import org.opendaylight.controller.sal.authorization.Resource;
30 import org.opendaylight.controller.sal.authorization.UserLevel;
31 import org.opendaylight.controller.sal.core.Bandwidth;
32 import org.opendaylight.controller.sal.core.Edge;
33 import org.opendaylight.controller.sal.core.Host;
34 import org.opendaylight.controller.sal.core.Node;
35 import org.opendaylight.controller.sal.core.Node.NodeIDType;
36 import org.opendaylight.controller.sal.core.NodeConnector;
37 import org.opendaylight.controller.sal.core.Property;
38 import org.opendaylight.controller.sal.packet.address.EthernetAddress;
39 import org.opendaylight.controller.sal.utils.GlobalConstants;
40 import org.opendaylight.controller.sal.utils.IObjectReader;
41 import org.opendaylight.controller.sal.utils.ObjectReader;
42 import org.opendaylight.controller.sal.utils.ObjectWriter;
43 import org.opendaylight.controller.sal.utils.ServiceHelper;
44 import org.opendaylight.controller.sal.utils.Status;
45 import org.opendaylight.controller.sal.utils.StatusCode;
46 import org.opendaylight.controller.switchmanager.ISwitchManager;
47 import org.opendaylight.controller.switchmanager.Switch;
48 import org.opendaylight.controller.switchmanager.SwitchConfig;
49 import org.opendaylight.controller.topologymanager.ITopologyManager;
50 import org.opendaylight.controller.usermanager.IUserManager;
51 import org.opendaylight.controller.web.DaylightWebUtil;
52 import org.opendaylight.controller.web.IDaylightWeb;
53 import org.springframework.stereotype.Controller;
54 import org.springframework.web.bind.annotation.PathVariable;
55 import org.springframework.web.bind.annotation.RequestMapping;
56 import org.springframework.web.bind.annotation.RequestMethod;
57 import org.springframework.web.bind.annotation.RequestParam;
58 import org.springframework.web.bind.annotation.ResponseBody;
60 import edu.uci.ics.jung.algorithms.layout.CircleLayout;
61 import edu.uci.ics.jung.graph.Graph;
62 import edu.uci.ics.jung.graph.SparseMultigraph;
66 public class Topology implements IObjectReader, IConfigurationAware {
67 private static String ROOT = GlobalConstants.STARTUPHOME.toString();
68 private String topologyWebFileName = null;
70 protected Map<String, Map<String, Map<String, Object>>> metaCache = new HashMap<String, Map<String, Map<String, Object>>>();
71 protected Map<String, Map<String, Object>> stagedNodes;
72 protected Map<String, Map<String, Object>> newNodes;
74 protected Map<String, Integer> metaNodeHash = new HashMap<String, Integer>();
75 protected Map<String, Integer> metaHostHash = new HashMap<String, Integer>();
76 protected Map<String, Integer> metaNodeSingleHash = new HashMap<String, Integer>();
77 protected Map<String, Integer> metaNodeConfigurationHash = new HashMap<String, Integer>();
80 ServiceHelper.registerGlobalService(IConfigurationAware.class, this, null);
81 topologyWebFileName = ROOT + "topologyCache.sav";
86 * Topology of nodes and hosts in the network in JSON format.
88 * Mainly intended for consumption by the visual topology.
90 * @return - JSON output for visual topology
92 @RequestMapping(value = "/visual.json", method = RequestMethod.GET)
94 public Collection<Map<String, Object>> getLinkData(@RequestParam(required = false) String container, HttpServletRequest request) {
95 String containerName = DaylightWebUtil.getAuthorizedContainer(request, container, this);
97 ITopologyManager topologyManager = (ITopologyManager) ServiceHelper
98 .getInstance(ITopologyManager.class, containerName, this);
99 if (topologyManager == null) {
102 ISwitchManager switchManager = (ISwitchManager) ServiceHelper
103 .getInstance(ISwitchManager.class, containerName, this);
104 if (switchManager == null) {
108 Map<Node, Set<Edge>> nodeEdges = topologyManager.getNodeEdges();
109 Map<Node, Set<NodeConnector>> hostEdges = topologyManager
110 .getNodesWithNodeConnectorHost();
111 List<Switch> nodes = switchManager.getNetworkDevices();
113 List<SwitchConfig> switchConfigurations = new ArrayList<SwitchConfig>();
114 for(Switch sw : nodes) {
115 Node n = sw.getNode();
116 SwitchConfig config = switchManager.getSwitchConfig(n.toString());
117 switchConfigurations.add(config);
120 // initialize cache if needed
121 if (!metaCache.containsKey(containerName)) {
122 metaCache.put(containerName, new HashMap<String, Map<String, Object>>());
124 metaNodeHash.put(containerName, null);
125 metaHostHash.put(containerName, null);
126 metaNodeSingleHash.put(containerName, null);
127 metaNodeConfigurationHash.put(containerName, null);
130 // return cache if topology hasn't changed
132 (metaNodeHash.get(containerName) != null && metaHostHash.get(containerName) != null && metaNodeSingleHash.get(containerName) != null && metaNodeConfigurationHash.get(containerName) != null) &&
133 metaNodeHash.get(containerName).equals(nodeEdges.hashCode()) && metaHostHash.get(containerName).equals(hostEdges.hashCode()) && metaNodeSingleHash.get(containerName).equals(nodes.hashCode()) && metaNodeConfigurationHash.get(containerName).equals(switchConfigurations.hashCode())
135 return metaCache.get(containerName).values();
138 // cache has changed, we must assign the new values
139 metaNodeHash.put(containerName, nodeEdges.hashCode());
140 metaHostHash.put(containerName, hostEdges.hashCode());
141 metaNodeSingleHash.put(containerName, nodes.hashCode());
142 metaNodeConfigurationHash.put(containerName, switchConfigurations.hashCode());
144 stagedNodes = new HashMap<String, Map<String, Object>>();
145 newNodes = new HashMap<String, Map<String, Object>>();
147 // nodeEdges addition
148 addNodes(nodeEdges, topologyManager, switchManager, containerName);
150 // single nodes addition
151 addSingleNodes(nodes, switchManager, containerName);
153 // hostNodes addition
154 addHostNodes(hostEdges, topologyManager, containerName);
156 repositionTopology(containerName);
158 return metaCache.get(containerName).values();
162 * Add regular nodes to main topology
164 * @param nodeEdges - node-edges mapping derived from topology
165 * @param topology - the topology instance
167 private void addNodes(Map<Node, Set<Edge>> nodeEdges,
168 ITopologyManager topology, ISwitchManager switchManager, String containerName) {
169 Bandwidth bandwidth = new Bandwidth(0);
170 Map<Edge, Set<Property>> properties = topology.getEdges();
172 for (Map.Entry<Node, Set<Edge>> e : nodeEdges.entrySet()) {
174 String description = switchManager.getNodeDescription(n);
175 NodeBean node = createNodeBean(description, n);
177 // skip production node
182 List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
183 Set<Edge> links = e.getValue();
184 for (Edge link : links) {
185 if (edgeIgnore(link)) {
188 for (Property p : properties.get(link)) {
189 if (p instanceof Bandwidth) {
190 bandwidth = (Bandwidth) p;
194 EdgeBean edge = new EdgeBean(link, bandwidth);
195 adjacencies.add(edge.out());
198 node.setLinks(adjacencies);
199 if (metaCache.get(containerName).containsKey(node.id())) {
200 // retrieve node from cache
201 Map<String, Object> nodeEntry = metaCache.get(containerName).get(node.id());
203 Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
204 data.put("$desc", description);
205 nodeEntry.put("data", data);
207 // always update adjacencies
208 nodeEntry.put("adjacencies", adjacencies);
209 // stage this cached node (with position)
210 stagedNodes.put(node.id(), nodeEntry);
212 newNodes.put(node.id(), node.out());
218 * Check if this node shouldn't appear in the visual topology
223 private boolean nodeIgnore(Node node) {
224 String nodeType = node.getType();
226 // add other node types to ignore later
227 if (nodeType.equals(NodeIDType.PRODUCTION)) {
235 * Check if this edge shouldn't appear in the visual topology
240 private boolean edgeIgnore(Edge edge) {
241 NodeConnector headNodeConnector = edge.getHeadNodeConnector();
242 Node headNode = headNodeConnector.getNode();
243 if (nodeIgnore(headNode)) {
247 NodeConnector tailNodeConnector = edge.getTailNodeConnector();
248 Node tailNode = tailNodeConnector.getNode();
249 if (nodeIgnore(tailNode)) {
256 protected NodeBean createNodeBean(String description, Node node) {
257 String name = (description == null ||
258 description.trim().isEmpty() ||
259 description.equalsIgnoreCase("none"))?
260 node.toString() : description;
261 return new NodeBean(node.toString(), name, NodeType.NODE);
264 @SuppressWarnings("unchecked")
265 private void addSingleNodes(List<Switch> nodes, ISwitchManager switchManager, String containerName) {
269 for (Switch sw : nodes) {
270 Node n = sw.getNode();
272 // skip production node
277 String description = switchManager.getNodeDescription(n);
279 if ((stagedNodes.containsKey(n.toString()) && metaCache.get(containerName).containsKey(n.toString())) || newNodes.containsKey(n.toString())) {
282 NodeBean node = createNodeBean(description, n);
284 // FIXME still doesn't display standalone node when last remaining link is removed
285 if (metaCache.get(containerName).containsKey(node.id()) && !stagedNodes.containsKey(node.id())) {
286 Map<String, Object> nodeEntry = metaCache.get(containerName).get(node.id());
287 Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
288 data.put("$desc", description);
289 nodeEntry.put("data", data);
290 // clear adjacencies since this is now a single node
291 nodeEntry.put("adjacencies", new LinkedList<Map<String, Object>>());
292 stagedNodes.put(node.id(), nodeEntry);
294 newNodes.put(node.id(), node.out());
300 * Add regular hosts to main topology
302 * @param hostEdges - node-nodeconnectors host-specific mapping from topology
303 * @param topology - topology instance
305 private void addHostNodes(Map<Node, Set<NodeConnector>> hostEdges,
306 ITopologyManager topology, String containerName) {
307 for (Map.Entry<Node, Set<NodeConnector>> e : hostEdges.entrySet()) {
308 for (NodeConnector connector : e.getValue()) {
309 Host host = topology.getHostAttachedToNodeConnector(connector);
310 EthernetAddress dmac = (EthernetAddress) host.getDataLayerAddress();
312 ByteBuffer addressByteBuffer = ByteBuffer.allocate(8);
313 addressByteBuffer.putShort((short) 0);
314 addressByteBuffer.put(dmac.getValue());
315 addressByteBuffer.rewind();
317 long hid = addressByteBuffer.getLong();
318 String hostId = String.valueOf(hid);
320 NodeBean hostBean = new NodeBean(hostId, host.getNetworkAddressAsString(), NodeType.HOST);
321 List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
322 EdgeBean edge = new EdgeBean(connector, hid);
323 adjacencies.add(edge.out());
324 hostBean.setLinks(adjacencies);
326 if (metaCache.get(containerName).containsKey(hostId)) {
327 Map<String, Object> hostEntry = metaCache.get(containerName).get(hostId);
328 hostEntry.put("adjacencies", adjacencies);
329 stagedNodes.put(hostId, hostEntry);
331 newNodes.put(String.valueOf(hid), hostBean.out());
338 * Re-position nodes in circular layout
340 private void repositionTopology(String containerName) {
341 Graph<String, String> graph = new SparseMultigraph<String, String>();
343 metaCache.get(containerName).clear();
344 metaCache.get(containerName).putAll(stagedNodes);
345 metaCache.get(containerName).putAll(newNodes);
347 for (Map<String, Object> on : metaCache.get(containerName).values()) {
348 graph.addVertex(on.toString());
350 List<Map<String, Object>> adjacencies = (List<Map<String, Object>>) on.get("adjacencies");
352 for (Map<String, Object> adj : adjacencies) {
354 adj.toString(), adj.get("nodeFrom").toString(),
355 adj.get("nodeTo").toString()
360 CircleLayout<String, String> layout = new CircleLayout<String, String>(graph);
361 layout.setSize(new Dimension(1200, 365));
362 for (Map.Entry<String, Map<String, Object>> v : newNodes.entrySet()) {
363 Double x = layout.transform(v.getKey()).getX();
364 Double y = layout.transform(v.getKey()).getY();
366 Map<String, String> nodeData = (HashMap<String, String>) v.getValue().get("data");
367 nodeData.put("$x", (x - 600) + "");
368 nodeData.put("$y", (y - 225) + "");
370 newNodes.get(v.getKey()).put("data", nodeData);
375 * Update node position
377 * This method is mainly used by the visual topology
379 * @param nodeId - The node to update
380 * @return The node object
382 @RequestMapping(value = "/node/{nodeId}", method = RequestMethod.POST)
384 public Map<String, Object> post(@PathVariable String nodeId, @RequestParam(required = true) String x,
385 @RequestParam(required = true) String y, @RequestParam(required = false) String container,
386 HttpServletRequest request) {
387 if (!authorize(UserLevel.NETWORKADMIN, request)) {
388 return new HashMap<String, Object>(); // silently disregard new node position
391 String containerName = getAuthorizedContainer(request, container);
393 String id = new String(nodeId);
395 if (!metaCache.get(containerName).containsKey(id)) {
399 Map<String, Object> node = metaCache.get(containerName).get(id);
400 Map<String, String> data = (Map<String, String>) node.get("data");
405 node.put("data", data);
411 * Node object for visual topology
413 protected class NodeBean {
415 protected String name;
416 protected Map<String, String> data;
417 protected List<Map<String, Object>> links;
420 data = new HashMap<String, String>();
421 links = new ArrayList<Map<String, Object>>();
424 public NodeBean(String id, String name, String type) {
428 data.put("$desc", name);
429 data.put("$type", type);
432 public void setLinks(List<Map<String, Object>> links) {
436 public Map<String, Object> out() {
437 Map<String, Object> node = new HashMap<String, Object>();
438 node.put("id", this.id);
439 node.put("name", this.name);
440 node.put("data", this.data);
441 node.put("adjacencies", this.links);
446 public String name() {
456 * Edge object for visual topology
458 protected class EdgeBean {
459 protected NodeConnector source;
460 protected NodeConnector destination;
461 protected Map<String, String> data;
462 protected Long hostId;
465 data = new HashMap<String, String>();
468 public EdgeBean(Edge link, Bandwidth bandwidth) {
470 this.source = link.getHeadNodeConnector();
471 this.destination = link.getTailNodeConnector();
474 data.put("$bandwidth", bandwidth.toString());
475 data.put("$color", bandwidthColor(bandwidth));
476 data.put("$nodeToPort", destination.getID().toString());
477 data.put("$nodeFromPort", source.getID().toString());
478 data.put("$descFrom", source.getNode().toString());
479 data.put("$descTo", destination.getNode().toString());
480 data.put("$nodeFromPortName", source.toString());
481 data.put("$nodeToPortName", destination.toString());
484 public EdgeBean(NodeConnector connector, Long hostId) {
487 this.destination = connector;
488 this.hostId = hostId;
490 data.put("$bandwidth", "N/A");
491 data.put("$color", bandwidthColor(new Bandwidth(0)));
492 data.put("$nodeToPort", connector.getNodeConnectorIDString());
493 data.put("$nodeFromPort", connector.getNodeConnectorIDString());
494 data.put("$descTo", "");
495 data.put("$descFrom", "");
496 data.put("$nodeToPortName", "");
497 data.put("$nodeFromPortName", "");
500 public Map<String, Object> out() {
501 Map<String, Object> edge = new HashMap<String, Object>();
503 edge.put("data", data);
504 if (source == null) {
505 edge.put("nodeFrom", String.valueOf(this.hostId));
507 edge.put("nodeFrom", source.getNode().toString());
509 edge.put("nodeTo", destination.getNode().toString());
515 private String bandwidthColor(Bandwidth bandwidth) {
517 long bandwidthValue = bandwidth.getValue();
519 if (bandwidthValue == 0) {
521 } else if (bandwidthValue < Bandwidth.BW1Kbps) {
523 } else if (bandwidthValue < Bandwidth.BW1Mbps) {
525 } else if (bandwidthValue < Bandwidth.BW1Gbps) {
527 } else if (bandwidthValue < Bandwidth.BW1Tbps) {
529 } else if (bandwidthValue < Bandwidth.BW1Pbps) {
537 protected class NodeType {
538 public static final String NODE = "swtch";
539 public static final String HOST = "host";
542 private boolean authorize(UserLevel level, HttpServletRequest request) {
543 IUserManager userManager = (IUserManager) ServiceHelper
544 .getGlobalInstance(IUserManager.class, this);
545 if (userManager == null) {
549 String username = request.getUserPrincipal().getName();
550 UserLevel userLevel = userManager.getUserLevel(username);
551 if (userLevel.toNumber() <= level.toNumber()) {
557 private String getAuthorizedContainer(HttpServletRequest request, String container) {
558 String username = request.getUserPrincipal().getName();
559 IContainerAuthorization containerAuthorization = (IContainerAuthorization) ServiceHelper.
560 getGlobalInstance(IContainerAuthorization.class, this);
561 if (containerAuthorization != null) {
562 Set<Resource> resources = containerAuthorization.getAllResourcesforUser(username);
563 if (authorizeContainer(container, resources)) {
568 return GlobalConstants.DEFAULT.toString();
571 private boolean authorizeContainer(String container, Set<Resource> resources) {
572 for(Resource resource : resources) {
573 String containerName = (String) resource.getResource();
574 if (containerName.equals(container)) {
582 @SuppressWarnings("unchecked")
583 private void loadConfiguration() {
584 ObjectReader objReader = new ObjectReader();
585 metaCache = (Map<String, Map<String, Map<String, Object>>>) objReader.read(this, topologyWebFileName);
586 if (metaCache == null) metaCache = new HashMap<String, Map<String, Map<String, Object>>>();
590 public Status saveConfiguration() {
591 ObjectWriter objWriter = new ObjectWriter();
592 objWriter.write(metaCache, topologyWebFileName);
593 return new Status(StatusCode.SUCCESS, null);
597 public Object readObject(ObjectInputStream ois)
598 throws FileNotFoundException, IOException, ClassNotFoundException {
599 // Perform the class deserialization locally, from inside the package where the class is defined
600 return ois.readObject();