org.opendaylight.controller.topology.web.Topology.java Source code

Java tutorial

Introduction

Here is the source code for org.opendaylight.controller.topology.web.Topology.java

Source

/*
 * 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();
    }

}