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.sal.authorization.Privilege;
29 import org.opendaylight.controller.sal.core.Bandwidth;
30 import org.opendaylight.controller.sal.core.Description;
31 import org.opendaylight.controller.sal.core.Edge;
32 import org.opendaylight.controller.sal.core.Host;
33 import org.opendaylight.controller.sal.core.Name;
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.web.DaylightWebUtil;
51 import org.springframework.stereotype.Controller;
52 import org.springframework.web.bind.annotation.PathVariable;
53 import org.springframework.web.bind.annotation.RequestMapping;
54 import org.springframework.web.bind.annotation.RequestMethod;
55 import org.springframework.web.bind.annotation.RequestParam;
56 import org.springframework.web.bind.annotation.ResponseBody;
58 import edu.uci.ics.jung.algorithms.layout.CircleLayout;
59 import edu.uci.ics.jung.graph.Graph;
60 import edu.uci.ics.jung.graph.SparseMultigraph;
64 public class Topology implements IObjectReader, IConfigurationAware {
65 private static String ROOT = GlobalConstants.STARTUPHOME.toString();
66 private String topologyWebFileName = null;
68 protected Map<String, Map<String, Map<String, Object>>> metaCache = new HashMap<String, Map<String, Map<String, Object>>>();
69 protected Map<String, Map<String, Object>> stagedNodes;
70 protected Map<String, Map<String, Object>> newNodes;
72 protected Map<String, Integer> metaNodeHash = new HashMap<String, Integer>();
73 protected Map<String, Integer> metaHostHash = new HashMap<String, Integer>();
74 protected Map<String, Integer> metaNodeSingleHash = new HashMap<String, Integer>();
75 protected Map<String, Integer> metaNodeConfigurationHash = new HashMap<String, Integer>();
78 ServiceHelper.registerGlobalService(IConfigurationAware.class, this, null);
79 topologyWebFileName = ROOT + "topologyCache.sav";
84 * Topology of nodes and hosts in the network in JSON format.
86 * Mainly intended for consumption by the visual topology.
88 * @return - JSON output for visual topology
90 @RequestMapping(value = "/visual.json", method = RequestMethod.GET)
92 public Collection<Map<String, Object>> getLinkData(@RequestParam(required = false) String container, HttpServletRequest request) {
93 String containerName = (container == null) ? GlobalConstants.DEFAULT.toString() : container;
95 // Derive the privilege this user has on the current container
96 String userName = request.getUserPrincipal().getName();
97 Privilege privilege = DaylightWebUtil.getContainerPrivilege(userName, containerName, this);
99 if (privilege == Privilege.NONE) {
103 ITopologyManager topologyManager = (ITopologyManager) ServiceHelper
104 .getInstance(ITopologyManager.class, containerName, this);
105 if (topologyManager == null) {
108 ISwitchManager switchManager = (ISwitchManager) ServiceHelper
109 .getInstance(ISwitchManager.class, containerName, this);
110 if (switchManager == null) {
114 Map<Node, Set<Edge>> nodeEdges = topologyManager.getNodeEdges();
115 Map<Node, Set<NodeConnector>> hostEdges = topologyManager
116 .getNodesWithNodeConnectorHost();
117 int hostEdgesHashCode = getHostHashCode(hostEdges, topologyManager);
118 List<Switch> nodes = switchManager.getNetworkDevices();
120 List<SwitchConfig> switchConfigurations = new ArrayList<SwitchConfig>();
121 for(Switch sw : nodes) {
122 Node n = sw.getNode();
123 SwitchConfig config = switchManager.getSwitchConfig(n.toString());
124 switchConfigurations.add(config);
127 // initialize cache if needed
128 if (!metaCache.containsKey(containerName)) {
129 metaCache.put(containerName, new HashMap<String, Map<String, Object>>());
131 metaNodeHash.put(containerName, null);
132 metaHostHash.put(containerName, null);
133 metaNodeSingleHash.put(containerName, null);
134 metaNodeConfigurationHash.put(containerName, null);
137 // return cache if topology hasn't changed
139 (metaNodeHash.get(containerName) != null && metaHostHash.get(containerName) != null && metaNodeSingleHash.get(containerName) != null && metaNodeConfigurationHash.get(containerName) != null) &&
140 metaNodeHash.get(containerName).equals(nodeEdges.hashCode()) && metaHostHash.get(containerName).equals(hostEdgesHashCode) && metaNodeSingleHash.get(containerName).equals(nodes.hashCode()) && metaNodeConfigurationHash.get(containerName).equals(switchConfigurations.hashCode())
142 return metaCache.get(containerName).values();
145 // cache has changed, we must assign the new values
146 metaNodeHash.put(containerName, nodeEdges.hashCode());
147 metaHostHash.put(containerName, hostEdgesHashCode);
148 metaNodeSingleHash.put(containerName, nodes.hashCode());
149 metaNodeConfigurationHash.put(containerName, switchConfigurations.hashCode());
151 stagedNodes = new HashMap<String, Map<String, Object>>();
152 newNodes = new HashMap<String, Map<String, Object>>();
154 // nodeEdges addition
155 addNodes(nodeEdges, topologyManager, switchManager, containerName);
157 // single nodes addition
158 addSingleNodes(nodes, switchManager, containerName);
160 // hostNodes addition
161 addHostNodes(hostEdges, topologyManager, containerName);
163 repositionTopology(containerName);
165 return metaCache.get(containerName).values();
169 * Add regular nodes to main topology
171 * @param nodeEdges - node-edges mapping derived from topology
172 * @param topology - the topology instance
174 private void addNodes(Map<Node, Set<Edge>> nodeEdges,
175 ITopologyManager topology, ISwitchManager switchManager, String containerName) {
176 Bandwidth bandwidth = new Bandwidth(0);
177 Map<Edge, Set<Property>> properties = topology.getEdges();
179 for (Map.Entry<Node, Set<Edge>> e : nodeEdges.entrySet()) {
181 String description = getNodeDesc(n, switchManager);
183 NodeBean node = createNodeBean(description, n);
185 // skip production node
190 List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
191 Set<Edge> links = e.getValue();
192 for (Edge link : links) {
193 if (edgeIgnore(link)) {
196 Set<Property> props = properties.get(link);
200 for (Property p : props) {
201 if (p instanceof Bandwidth) {
202 bandwidth = (Bandwidth) p;
206 NodeConnector headNodeConnector = link.getHeadNodeConnector();
207 NodeConnector tailNodeConnector = link.getTailNodeConnector();
209 String headDescription = this.getNodeConnectorDescription(headNodeConnector, switchManager);
210 String tailDescription = this.getNodeConnectorDescription(tailNodeConnector, switchManager);
211 String headPortDescription = this.getNodeConnectorPortDescription(headNodeConnector, switchManager);
212 String tailPortDescription = this.getNodeConnectorPortDescription(tailNodeConnector, switchManager);
213 EdgeBean edge = new EdgeBean(link, bandwidth, headDescription, tailDescription, headPortDescription, tailPortDescription);
214 adjacencies.add(edge.out());
217 node.setLinks(adjacencies);
218 if (metaCache.get(containerName).containsKey(node.id())) {
219 // retrieve node from cache
220 Map<String, Object> nodeEntry = metaCache.get(containerName).get(node.id());
222 Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
223 data.put("$desc", description);
224 nodeEntry.put("data", data);
226 // always update adjacencies
227 nodeEntry.put("adjacencies", adjacencies);
228 // stage this cached node (with position)
229 stagedNodes.put(node.id(), nodeEntry);
231 newNodes.put(node.id(), node.out());
237 * Check if this node shouldn't appear in the visual topology
242 private boolean nodeIgnore(Node node) {
243 String nodeType = node.getType();
245 // add other node types to ignore later
246 if (nodeType.equals(NodeIDType.PRODUCTION)) {
254 * Check if this edge shouldn't appear in the visual topology
259 private boolean edgeIgnore(Edge edge) {
260 NodeConnector headNodeConnector = edge.getHeadNodeConnector();
261 Node headNode = headNodeConnector.getNode();
262 if (nodeIgnore(headNode)) {
266 NodeConnector tailNodeConnector = edge.getTailNodeConnector();
267 Node tailNode = tailNodeConnector.getNode();
268 if (nodeIgnore(tailNode)) {
275 protected NodeBean createNodeBean(String description, Node node) {
276 String name = this.getDescription(description, node);
277 return new NodeBean(node.toString(), name, NodeType.NODE);
280 private String getDescription(String description, Node node) {
281 String name = (description == null ||
282 description.trim().isEmpty() ||
283 description.equalsIgnoreCase("none"))?
284 node.toString() : description;
288 private String getNodeConnectorDescription(NodeConnector nodeConnector, ISwitchManager switchManager) {
289 Node node = nodeConnector.getNode();
290 String name = this.getDescription(getNodeDesc(node, switchManager), node);
294 private String getNodeConnectorPortDescription(NodeConnector nodeConnector, ISwitchManager switchManager) {
295 Name ncName = (Name) switchManager.getNodeConnectorProp(nodeConnector, Name.NamePropName);
296 String nodeConnectorName = nodeConnector.getNodeConnectorIDString();
297 if (ncName != null) {
298 nodeConnectorName = ncName.getValue();
300 return nodeConnectorName;
303 @SuppressWarnings("unchecked")
304 private void addSingleNodes(List<Switch> nodes, ISwitchManager switchManager, String containerName) {
308 for (Switch sw : nodes) {
309 Node n = sw.getNode();
311 // skip production node
316 String description = getNodeDesc(n, switchManager);
318 if ((stagedNodes.containsKey(n.toString()) && metaCache.get(containerName).containsKey(n.toString())) || newNodes.containsKey(n.toString())) {
321 NodeBean node = createNodeBean(description, n);
323 // FIXME still doesn't display standalone node when last remaining link is removed
324 if (metaCache.get(containerName).containsKey(node.id()) && !stagedNodes.containsKey(node.id())) {
325 Map<String, Object> nodeEntry = metaCache.get(containerName).get(node.id());
326 Map<String, String> data = (Map<String, String>) nodeEntry.get("data");
327 data.put("$desc", description);
328 nodeEntry.put("data", data);
329 // clear adjacencies since this is now a single node
330 nodeEntry.put("adjacencies", new LinkedList<Map<String, Object>>());
331 stagedNodes.put(node.id(), nodeEntry);
333 newNodes.put(node.id(), node.out());
339 * Calculate the total host hashcode
341 * This is to handle cases where there are multiple hosts per NodeConnector
344 * - hostEdges data structure
347 * @return this topology's host hashcode
349 private int getHostHashCode(Map<Node, Set<NodeConnector>> hostEdges, ITopologyManager topology) {
350 List<Host> hosts = new ArrayList<Host>();
351 for (Set<NodeConnector> nodeConnectors : hostEdges.values()) {
352 for (NodeConnector nodeConnector : nodeConnectors) {
353 List<Host> theseHosts = topology.getHostsAttachedToNodeConnector(nodeConnector);
354 hosts.addAll(theseHosts);
358 return hosts.hashCode();
362 * Add regular hosts to main topology
364 * @param hostEdges - node-nodeconnectors host-specific mapping from topology
365 * @param topology - topology instance
367 private void addHostNodes(Map<Node, Set<NodeConnector>> hostEdges,
368 ITopologyManager topology, String containerName) {
369 for (Map.Entry<Node, Set<NodeConnector>> e : hostEdges.entrySet()) {
370 for (NodeConnector connector : e.getValue()) {
371 List<Host> hosts = topology.getHostsAttachedToNodeConnector(connector);
372 for (Host host : hosts) {
373 EthernetAddress dmac = (EthernetAddress) host.getDataLayerAddress();
375 ByteBuffer addressByteBuffer = ByteBuffer.allocate(8);
376 addressByteBuffer.putShort((short) 0);
377 addressByteBuffer.put(dmac.getValue());
378 addressByteBuffer.rewind();
380 long hid = addressByteBuffer.getLong();
381 String hostId = String.valueOf(hid);
383 NodeBean hostBean = new NodeBean(hostId, host.getNetworkAddressAsString(), NodeType.HOST);
384 List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>();
385 EdgeBean edge = new EdgeBean(connector, hid);
386 adjacencies.add(edge.out());
387 hostBean.setLinks(adjacencies);
389 if (metaCache.get(containerName).containsKey(hostId)) {
390 Map<String, Object> hostEntry = metaCache.get(containerName).get(hostId);
391 hostEntry.put("adjacencies", adjacencies);
392 stagedNodes.put(hostId, hostEntry);
394 newNodes.put(String.valueOf(hid), hostBean.out());
402 * Re-position nodes in circular layout
404 private void repositionTopology(String containerName) {
405 Graph<String, String> graph = new SparseMultigraph<String, String>();
407 metaCache.get(containerName).clear();
408 metaCache.get(containerName).putAll(stagedNodes);
409 metaCache.get(containerName).putAll(newNodes);
411 for (Map<String, Object> on : metaCache.get(containerName).values()) {
412 graph.addVertex(on.toString());
414 List<Map<String, Object>> adjacencies = (List<Map<String, Object>>) on.get("adjacencies");
416 for (Map<String, Object> adj : adjacencies) {
418 adj.toString(), adj.get("nodeFrom").toString(),
419 adj.get("nodeTo").toString()
424 CircleLayout<String, String> layout = new CircleLayout<String, String>(graph);
425 layout.setSize(new Dimension(1200, 365));
426 for (Map.Entry<String, Map<String, Object>> v : newNodes.entrySet()) {
427 Double x = layout.transform(v.getKey()).getX();
428 Double y = layout.transform(v.getKey()).getY();
430 Map<String, String> nodeData = (HashMap<String, String>) v.getValue().get("data");
431 nodeData.put("$x", (x - 600) + "");
432 nodeData.put("$y", (y - 225) + "");
434 newNodes.get(v.getKey()).put("data", nodeData);
439 * Update node position
441 * This method is mainly used by the visual topology
443 * @param nodeId - The node to update
444 * @return The node object
446 @RequestMapping(value = "/node/{nodeId}", method = RequestMethod.POST)
448 public Map<String, Object> post(@PathVariable String nodeId, @RequestParam(required = true) String x,
449 @RequestParam(required = true) String y, @RequestParam(required = false) String container,
450 HttpServletRequest request) {
451 String containerName = (container == null) ? GlobalConstants.DEFAULT.toString() : container;
453 // Derive the privilege this user has on the current container
454 String userName = request.getUserPrincipal().getName();
455 Privilege privilege = DaylightWebUtil.getContainerPrivilege(userName, containerName, this);
457 if (privilege != Privilege.WRITE) {
458 return new HashMap<String, Object>(); // silently disregard new node position
461 String id = new String(nodeId);
463 if (!metaCache.get(containerName).containsKey(id)) {
467 Map<String, Object> node = metaCache.get(containerName).get(id);
468 Map<String, String> data = (Map<String, String>) node.get("data");
473 node.put("data", data);
479 * Node object for visual topology
481 protected class NodeBean {
483 protected String name;
484 protected Map<String, String> data;
485 protected List<Map<String, Object>> links;
488 data = new HashMap<String, String>();
489 links = new ArrayList<Map<String, Object>>();
492 public NodeBean(String id, String name, String type) {
496 data.put("$desc", name);
497 data.put("$type", type);
500 public void setLinks(List<Map<String, Object>> links) {
504 public Map<String, Object> out() {
505 Map<String, Object> node = new HashMap<String, Object>();
506 node.put("id", this.id);
507 node.put("name", this.name);
508 node.put("data", this.data);
509 node.put("adjacencies", this.links);
514 public String name() {
524 * Edge object for visual topology
526 protected class EdgeBean {
527 protected NodeConnector source;
528 protected NodeConnector destination;
529 protected Map<String, String> data;
530 protected Long hostId;
533 data = new HashMap<String, String>();
537 * EdgeBean object that includes complete node description
541 * @param headDescription
542 * @param tailDescription
544 public EdgeBean(Edge link, Bandwidth bandwidth, String headDescription,
545 String tailDescription, String headPortDescription, String tailPortDescription) {
547 this.source = link.getHeadNodeConnector();
548 this.destination = link.getTailNodeConnector();
551 data.put("$bandwidth", bandwidth.toString());
552 data.put("$color", bandwidthColor(bandwidth));
553 data.put("$nodeToPort", destination.getID().toString());
554 data.put("$nodeFromPort", source.getID().toString());
555 data.put("$descFrom", headDescription);
556 data.put("$descTo", tailDescription);
557 data.put("$nodeFromPortName", source.toString());
558 data.put("$nodeToPortName", destination.toString());
559 data.put("$nodeFromPortDescription", headPortDescription);
560 data.put("$nodeToPortDescription", tailPortDescription);
563 public EdgeBean(NodeConnector connector, Long hostId) {
566 this.destination = connector;
567 this.hostId = hostId;
569 data.put("$bandwidth", "N/A");
570 data.put("$color", bandwidthColor(new Bandwidth(0)));
571 data.put("$nodeToPort", connector.getNodeConnectorIDString());
572 data.put("$nodeFromPort", connector.getNodeConnectorIDString());
573 data.put("$descTo", "");
574 data.put("$descFrom", "");
575 data.put("$nodeToPortName", "");
576 data.put("$nodeFromPortName", "");
579 public Map<String, Object> out() {
580 Map<String, Object> edge = new HashMap<String, Object>();
582 edge.put("data", data);
583 if (source == null) {
584 edge.put("nodeFrom", String.valueOf(this.hostId));
586 edge.put("nodeFrom", source.getNode().toString());
588 edge.put("nodeTo", destination.getNode().toString());
594 private String bandwidthColor(Bandwidth bandwidth) {
596 long bandwidthValue = bandwidth.getValue();
598 if (bandwidthValue == 0) {
600 } else if (bandwidthValue < Bandwidth.BW1Kbps) {
602 } else if (bandwidthValue < Bandwidth.BW1Mbps) {
604 } else if (bandwidthValue < Bandwidth.BW1Gbps) {
606 } else if (bandwidthValue < Bandwidth.BW1Tbps) {
608 } else if (bandwidthValue < Bandwidth.BW1Pbps) {
616 protected class NodeType {
617 public static final String NODE = "switch";
618 public static final String HOST = "host";
621 @SuppressWarnings("unchecked")
622 private void loadConfiguration() {
623 ObjectReader objReader = new ObjectReader();
624 metaCache = (Map<String, Map<String, Map<String, Object>>>) objReader.read(this, topologyWebFileName);
625 if (metaCache == null) {
626 metaCache = new HashMap<String, Map<String, Map<String, Object>>>();
631 public Status saveConfiguration() {
632 ObjectWriter objWriter = new ObjectWriter();
633 objWriter.write(metaCache, topologyWebFileName);
634 return new Status(StatusCode.SUCCESS, null);
638 public Object readObject(ObjectInputStream ois)
639 throws FileNotFoundException, IOException, ClassNotFoundException {
640 // Perform the class deserialization locally, from inside the package where the class is defined
641 return ois.readObject();
644 private String getNodeDesc(Node node, ISwitchManager switchManager) {
645 Description desc = (Description) switchManager.getNodeProp(node, Description.propertyName);
646 return (desc == null) ? "" : desc.getValue();