Java tutorial
/* * Copyright (c) 2013 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.controller.topology.web; import java.awt.Dimension; import java.io.FileNotFoundException; import java.io.IOException; import java.io.ObjectInputStream; import java.nio.ByteBuffer; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import javax.servlet.http.HttpServletRequest; import org.opendaylight.controller.configuration.IConfigurationAware; import org.opendaylight.controller.sal.authorization.Privilege; import org.opendaylight.controller.sal.core.Bandwidth; import org.opendaylight.controller.sal.core.Description; import org.opendaylight.controller.sal.core.Edge; import org.opendaylight.controller.sal.core.Host; import org.opendaylight.controller.sal.core.Name; import org.opendaylight.controller.sal.core.Node; import org.opendaylight.controller.sal.core.Node.NodeIDType; import org.opendaylight.controller.sal.core.NodeConnector; import org.opendaylight.controller.sal.core.Property; import org.opendaylight.controller.sal.packet.address.EthernetAddress; import org.opendaylight.controller.sal.utils.GlobalConstants; import org.opendaylight.controller.sal.utils.IObjectReader; import org.opendaylight.controller.sal.utils.ObjectReader; import org.opendaylight.controller.sal.utils.ObjectWriter; import org.opendaylight.controller.sal.utils.ServiceHelper; import org.opendaylight.controller.sal.utils.Status; import org.opendaylight.controller.sal.utils.StatusCode; import org.opendaylight.controller.switchmanager.ISwitchManager; import org.opendaylight.controller.switchmanager.Switch; import org.opendaylight.controller.switchmanager.SwitchConfig; import org.opendaylight.controller.topologymanager.ITopologyManager; import org.opendaylight.controller.web.DaylightWebUtil; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import edu.uci.ics.jung.algorithms.layout.CircleLayout; import edu.uci.ics.jung.graph.Graph; import edu.uci.ics.jung.graph.SparseMultigraph; @Controller @RequestMapping("/") public class Topology implements IObjectReader, IConfigurationAware { private static String ROOT = GlobalConstants.STARTUPHOME.toString(); private String topologyWebFileName = null; protected Map<String, Map<String, Map<String, Object>>> metaCache = new HashMap<String, Map<String, Map<String, Object>>>(); protected Map<String, Map<String, Object>> stagedNodes; protected Map<String, Map<String, Object>> newNodes; protected Map<String, Integer> metaNodeHash = new HashMap<String, Integer>(); protected Map<String, Integer> metaHostHash = new HashMap<String, Integer>(); protected Map<String, Integer> metaNodeSingleHash = new HashMap<String, Integer>(); protected Map<String, Integer> metaNodeConfigurationHash = new HashMap<String, Integer>(); public Topology() { ServiceHelper.registerGlobalService(IConfigurationAware.class, this, null); topologyWebFileName = ROOT + "topologyCache.sav"; loadConfiguration(); } /** * Topology of nodes and hosts in the network in JSON format. * * Mainly intended for consumption by the visual topology. * * @return - JSON output for visual topology */ @RequestMapping(value = "/visual.json", method = RequestMethod.GET) @ResponseBody public Collection<Map<String, Object>> getLinkData(@RequestParam(required = false) String container, HttpServletRequest request) { String containerName = (container == null) ? GlobalConstants.DEFAULT.toString() : container; // Derive the privilege this user has on the current container String userName = request.getUserPrincipal().getName(); Privilege privilege = DaylightWebUtil.getContainerPrivilege(userName, containerName, this); if (privilege == Privilege.NONE) { return null; } ITopologyManager topologyManager = (ITopologyManager) ServiceHelper.getInstance(ITopologyManager.class, containerName, this); if (topologyManager == null) { return null; } ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, containerName, this); if (switchManager == null) { return null; } Map<Node, Set<Edge>> nodeEdges = topologyManager.getNodeEdges(); Map<Node, Set<NodeConnector>> hostEdges = topologyManager.getNodesWithNodeConnectorHost(); int hostEdgesHashCode = getHostHashCode(hostEdges, topologyManager); List<Switch> nodes = switchManager.getNetworkDevices(); List<SwitchConfig> switchConfigurations = new ArrayList<SwitchConfig>(); for (Switch sw : nodes) { Node n = sw.getNode(); SwitchConfig config = switchManager.getSwitchConfig(n.toString()); switchConfigurations.add(config); } // initialize cache if needed if (!metaCache.containsKey(containerName)) { metaCache.put(containerName, new HashMap<String, Map<String, Object>>()); // initialize hashes metaNodeHash.put(containerName, null); metaHostHash.put(containerName, null); metaNodeSingleHash.put(containerName, null); metaNodeConfigurationHash.put(containerName, null); } // return cache if topology hasn't changed if ((metaNodeHash.get(containerName) != null && metaHostHash.get(containerName) != null && metaNodeSingleHash.get(containerName) != null && metaNodeConfigurationHash.get(containerName) != null) && metaNodeHash.get(containerName).equals(nodeEdges.hashCode()) && metaHostHash.get(containerName).equals(hostEdgesHashCode) && metaNodeSingleHash.get(containerName).equals(nodes.hashCode()) && metaNodeConfigurationHash.get(containerName).equals(switchConfigurations.hashCode())) { return metaCache.get(containerName).values(); } // cache has changed, we must assign the new values metaNodeHash.put(containerName, nodeEdges.hashCode()); metaHostHash.put(containerName, hostEdgesHashCode); metaNodeSingleHash.put(containerName, nodes.hashCode()); metaNodeConfigurationHash.put(containerName, switchConfigurations.hashCode()); stagedNodes = new HashMap<String, Map<String, Object>>(); newNodes = new HashMap<String, Map<String, Object>>(); // nodeEdges addition addNodes(nodeEdges, topologyManager, switchManager, containerName); // single nodes addition addSingleNodes(nodes, switchManager, containerName); // hostNodes addition addHostNodes(hostEdges, topologyManager, containerName); repositionTopology(containerName); return metaCache.get(containerName).values(); } /** * Add regular nodes to main topology * * @param nodeEdges - node-edges mapping derived from topology * @param topology - the topology instance */ private void addNodes(Map<Node, Set<Edge>> nodeEdges, ITopologyManager topology, ISwitchManager switchManager, String containerName) { Bandwidth bandwidth = new Bandwidth(0); Map<Edge, Set<Property>> properties = topology.getEdges(); for (Map.Entry<Node, Set<Edge>> e : nodeEdges.entrySet()) { Node n = e.getKey(); String description = getNodeDesc(n, switchManager); NodeBean node = createNodeBean(description, n); // skip production node if (nodeIgnore(n)) { continue; } List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>(); Set<Edge> links = e.getValue(); for (Edge link : links) { if (edgeIgnore(link)) { continue; } Set<Property> props = properties.get(link); if (props == null) { continue; } for (Property p : props) { if (p instanceof Bandwidth) { bandwidth = (Bandwidth) p; break; } } NodeConnector headNodeConnector = link.getHeadNodeConnector(); NodeConnector tailNodeConnector = link.getTailNodeConnector(); String headDescription = this.getNodeConnectorDescription(headNodeConnector, switchManager); String tailDescription = this.getNodeConnectorDescription(tailNodeConnector, switchManager); String headPortDescription = this.getNodeConnectorPortDescription(headNodeConnector, switchManager); String tailPortDescription = this.getNodeConnectorPortDescription(tailNodeConnector, switchManager); EdgeBean edge = new EdgeBean(link, bandwidth, headDescription, tailDescription, headPortDescription, tailPortDescription); adjacencies.add(edge.out()); } node.setLinks(adjacencies); if (metaCache.get(containerName).containsKey(node.id())) { // retrieve node from cache Map<String, Object> nodeEntry = metaCache.get(containerName).get(node.id()); Map<String, String> data = (Map<String, String>) nodeEntry.get("data"); data.put("$desc", description); nodeEntry.put("data", data); // always update adjacencies nodeEntry.put("adjacencies", adjacencies); // stage this cached node (with position) stagedNodes.put(node.id(), nodeEntry); } else { newNodes.put(node.id(), node.out()); } } } /** * Check if this node shouldn't appear in the visual topology * * @param node * @return */ private boolean nodeIgnore(Node node) { String nodeType = node.getType(); // add other node types to ignore later if (nodeType.equals(NodeIDType.PRODUCTION)) { return true; } return false; } /** * Check if this edge shouldn't appear in the visual topology * * @param edge * @return */ private boolean edgeIgnore(Edge edge) { NodeConnector headNodeConnector = edge.getHeadNodeConnector(); Node headNode = headNodeConnector.getNode(); if (nodeIgnore(headNode)) { return true; } NodeConnector tailNodeConnector = edge.getTailNodeConnector(); Node tailNode = tailNodeConnector.getNode(); if (nodeIgnore(tailNode)) { return true; } return false; } protected NodeBean createNodeBean(String description, Node node) { String name = this.getDescription(description, node); return new NodeBean(node.toString(), name, NodeType.NODE); } private String getDescription(String description, Node node) { String name = (description == null || description.trim().isEmpty() || description.equalsIgnoreCase("none")) ? node.toString() : description; return name; } private String getNodeConnectorDescription(NodeConnector nodeConnector, ISwitchManager switchManager) { Node node = nodeConnector.getNode(); String name = this.getDescription(getNodeDesc(node, switchManager), node); return name; } private String getNodeConnectorPortDescription(NodeConnector nodeConnector, ISwitchManager switchManager) { Name ncName = (Name) switchManager.getNodeConnectorProp(nodeConnector, Name.NamePropName); String nodeConnectorName = nodeConnector.getNodeConnectorIDString(); if (ncName != null) { nodeConnectorName = ncName.getValue(); } return nodeConnectorName; } @SuppressWarnings("unchecked") private void addSingleNodes(List<Switch> nodes, ISwitchManager switchManager, String containerName) { if (nodes == null) { return; } for (Switch sw : nodes) { Node n = sw.getNode(); // skip production node if (nodeIgnore(n)) { continue; } String description = getNodeDesc(n, switchManager); if ((stagedNodes.containsKey(n.toString()) && metaCache.get(containerName).containsKey(n.toString())) || newNodes.containsKey(n.toString())) { continue; } NodeBean node = createNodeBean(description, n); // FIXME still doesn't display standalone node when last remaining link is removed if (metaCache.get(containerName).containsKey(node.id()) && !stagedNodes.containsKey(node.id())) { Map<String, Object> nodeEntry = metaCache.get(containerName).get(node.id()); Map<String, String> data = (Map<String, String>) nodeEntry.get("data"); data.put("$desc", description); nodeEntry.put("data", data); // clear adjacencies since this is now a single node nodeEntry.put("adjacencies", new LinkedList<Map<String, Object>>()); stagedNodes.put(node.id(), nodeEntry); } else { newNodes.put(node.id(), node.out()); } } } /** * Calculate the total host hashcode * * This is to handle cases where there are multiple hosts per NodeConnector * * @param hostEdges * - hostEdges data structure * @param topology * - topology bundle * @return this topology's host hashcode */ private int getHostHashCode(Map<Node, Set<NodeConnector>> hostEdges, ITopologyManager topology) { List<Host> hosts = new ArrayList<Host>(); for (Set<NodeConnector> nodeConnectors : hostEdges.values()) { for (NodeConnector nodeConnector : nodeConnectors) { List<Host> theseHosts = topology.getHostsAttachedToNodeConnector(nodeConnector); hosts.addAll(theseHosts); } } return hosts.hashCode(); } /** * Add regular hosts to main topology * * @param hostEdges - node-nodeconnectors host-specific mapping from topology * @param topology - topology instance */ private void addHostNodes(Map<Node, Set<NodeConnector>> hostEdges, ITopologyManager topology, String containerName) { for (Map.Entry<Node, Set<NodeConnector>> e : hostEdges.entrySet()) { for (NodeConnector connector : e.getValue()) { List<Host> hosts = topology.getHostsAttachedToNodeConnector(connector); for (Host host : hosts) { EthernetAddress dmac = (EthernetAddress) host.getDataLayerAddress(); ByteBuffer addressByteBuffer = ByteBuffer.allocate(8); addressByteBuffer.putShort((short) 0); addressByteBuffer.put(dmac.getValue()); addressByteBuffer.rewind(); long hid = addressByteBuffer.getLong(); String hostId = String.valueOf(hid); NodeBean hostBean = new NodeBean(hostId, host.getNetworkAddressAsString(), NodeType.HOST); List<Map<String, Object>> adjacencies = new LinkedList<Map<String, Object>>(); EdgeBean edge = new EdgeBean(connector, hid); adjacencies.add(edge.out()); hostBean.setLinks(adjacencies); if (metaCache.get(containerName).containsKey(hostId)) { Map<String, Object> hostEntry = metaCache.get(containerName).get(hostId); hostEntry.put("adjacencies", adjacencies); stagedNodes.put(hostId, hostEntry); } else { newNodes.put(String.valueOf(hid), hostBean.out()); } } } } } /** * Re-position nodes in circular layout */ private void repositionTopology(String containerName) { Graph<String, String> graph = new SparseMultigraph<String, String>(); metaCache.get(containerName).clear(); metaCache.get(containerName).putAll(stagedNodes); metaCache.get(containerName).putAll(newNodes); for (Map<String, Object> on : metaCache.get(containerName).values()) { graph.addVertex(on.toString()); List<Map<String, Object>> adjacencies = (List<Map<String, Object>>) on.get("adjacencies"); for (Map<String, Object> adj : adjacencies) { graph.addEdge(adj.toString(), adj.get("nodeFrom").toString(), adj.get("nodeTo").toString()); } } CircleLayout<String, String> layout = new CircleLayout<String, String>(graph); layout.setSize(new Dimension(1200, 365)); for (Map.Entry<String, Map<String, Object>> v : newNodes.entrySet()) { Double x = layout.transform(v.getKey()).getX(); Double y = layout.transform(v.getKey()).getY(); Map<String, String> nodeData = (HashMap<String, String>) v.getValue().get("data"); nodeData.put("$x", (x - 600) + ""); nodeData.put("$y", (y - 225) + ""); newNodes.get(v.getKey()).put("data", nodeData); } } /** * Update node position * * This method is mainly used by the visual topology * * @param nodeId - The node to update * @return The node object */ @RequestMapping(value = "/node/{nodeId}", method = RequestMethod.POST) @ResponseBody public Map<String, Object> post(@PathVariable String nodeId, @RequestParam(required = true) String x, @RequestParam(required = true) String y, @RequestParam(required = false) String container, HttpServletRequest request) { String containerName = (container == null) ? GlobalConstants.DEFAULT.toString() : container; // Derive the privilege this user has on the current container String userName = request.getUserPrincipal().getName(); Privilege privilege = DaylightWebUtil.getContainerPrivilege(userName, containerName, this); if (privilege != Privilege.WRITE) { return new HashMap<String, Object>(); // silently disregard new node position } String id = new String(nodeId); if (!metaCache.get(containerName).containsKey(id)) { return null; } Map<String, Object> node = metaCache.get(containerName).get(id); Map<String, String> data = (Map<String, String>) node.get("data"); data.put("$x", x); data.put("$y", y); node.put("data", data); return node; } /** * Node object for visual topology */ protected class NodeBean { protected String id; protected String name; protected Map<String, String> data; protected List<Map<String, Object>> links; public NodeBean() { data = new HashMap<String, String>(); links = new ArrayList<Map<String, Object>>(); } public NodeBean(String id, String name, String type) { this(); this.id = id; this.name = name; data.put("$desc", name); data.put("$type", type); } public void setLinks(List<Map<String, Object>> links) { this.links = links; } public Map<String, Object> out() { Map<String, Object> node = new HashMap<String, Object>(); node.put("id", this.id); node.put("name", this.name); node.put("data", this.data); node.put("adjacencies", this.links); return node; } public String name() { return this.name; } public String id() { return this.id; } } /** * Edge object for visual topology */ protected class EdgeBean { protected NodeConnector source; protected NodeConnector destination; protected Map<String, String> data; protected Long hostId; public EdgeBean() { data = new HashMap<String, String>(); } /** * EdgeBean object that includes complete node description * * @param link * @param bandwidth * @param headDescription * @param tailDescription */ public EdgeBean(Edge link, Bandwidth bandwidth, String headDescription, String tailDescription, String headPortDescription, String tailPortDescription) { this(); this.source = link.getHeadNodeConnector(); this.destination = link.getTailNodeConnector(); // data data.put("$bandwidth", bandwidth.toString()); data.put("$color", bandwidthColor(bandwidth)); data.put("$nodeToPort", destination.getID().toString()); data.put("$nodeFromPort", source.getID().toString()); data.put("$descFrom", headDescription); data.put("$descTo", tailDescription); data.put("$nodeFromPortName", source.toString()); data.put("$nodeToPortName", destination.toString()); data.put("$nodeFromPortDescription", headPortDescription); data.put("$nodeToPortDescription", tailPortDescription); } public EdgeBean(NodeConnector connector, Long hostId) { this(); this.source = null; this.destination = connector; this.hostId = hostId; data.put("$bandwidth", "N/A"); data.put("$color", bandwidthColor(new Bandwidth(0))); data.put("$nodeToPort", connector.getNodeConnectorIDString()); data.put("$nodeFromPort", connector.getNodeConnectorIDString()); data.put("$descTo", ""); data.put("$descFrom", ""); data.put("$nodeToPortName", ""); data.put("$nodeFromPortName", ""); } public Map<String, Object> out() { Map<String, Object> edge = new HashMap<String, Object>(); edge.put("data", data); if (source == null) { edge.put("nodeFrom", String.valueOf(this.hostId)); } else { edge.put("nodeFrom", source.getNode().toString()); } edge.put("nodeTo", destination.getNode().toString()); return edge; } private String bandwidthColor(Bandwidth bandwidth) { String color = null; long bandwidthValue = bandwidth.getValue(); if (bandwidthValue == 0) { color = "#000"; } else if (bandwidthValue < Bandwidth.BW1Kbps) { color = "#148AC6"; } else if (bandwidthValue < Bandwidth.BW1Mbps) { color = "#2858A0"; } else if (bandwidthValue < Bandwidth.BW1Gbps) { color = "#009393"; } else if (bandwidthValue < Bandwidth.BW1Tbps) { color = "#C6C014"; } else if (bandwidthValue < Bandwidth.BW1Pbps) { color = "#F9F464"; } return color; } } protected class NodeType { public static final String NODE = "switch"; public static final String HOST = "host"; } @SuppressWarnings("unchecked") private void loadConfiguration() { ObjectReader objReader = new ObjectReader(); metaCache = (Map<String, Map<String, Map<String, Object>>>) objReader.read(this, topologyWebFileName); if (metaCache == null) { metaCache = new HashMap<String, Map<String, Map<String, Object>>>(); } } @Override public Status saveConfiguration() { ObjectWriter objWriter = new ObjectWriter(); objWriter.write(metaCache, topologyWebFileName); return new Status(StatusCode.SUCCESS, null); } @Override public Object readObject(ObjectInputStream ois) throws FileNotFoundException, IOException, ClassNotFoundException { // Perform the class deserialization locally, from inside the package where the class is defined return ois.readObject(); } private String getNodeDesc(Node node, ISwitchManager switchManager) { Description desc = (Description) switchManager.getNodeProp(node, Description.propertyName); return (desc == null) ? "" : desc.getValue(); } }