--- /dev/null
+/target-ide
--- /dev/null
+Open Daylight learning switch module based on SDN Hub's learning switch,
+ ported to Opendaylight Tookit
+
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+ <parent>
+ <groupId>org.opendaylight.toolkit</groupId>
+ <artifactId>common</artifactId>
+ <version>0.0.1-SNAPSHOT</version>
+ <relativePath>../common</relativePath>
+ </parent>
+
+
+ <groupId>org.sdnhub.odl</groupId>
+ <artifactId>learningswitch</artifactId>
+ <version>0.1.0-SNAPSHOT</version>
+
+ <packaging>bundle</packaging>
+
+ <build>
+ <plugins>
+ <plugin>
+ <groupId>org.apache.felix</groupId>
+ <artifactId>maven-bundle-plugin</artifactId>
+ <version>${bundle.plugin.version}</version>
+ <extensions>true</extensions>
+ <configuration>
+ <instructions>
+ <Import-Package>
+ org.opendaylight.controller.forwardingrulesmanager.*,
+ org.opendaylight.toolkit.web,
+ org.opendaylight.controller.sal.core,
+ org.opendaylight.controller.sal.reader.*,
+ org.opendaylight.controller.sal.utils,
+ org.opendaylight.controller.sal.packet,
+ org.opendaylight.controller.sal.action,
+ org.opendaylight.controller.sal.flowprogrammer,
+ org.opendaylight.controller.sal.match,
+ org.opendaylight.controller.sal.authorization,
+ org.opendaylight.controller.switchmanager.*,
+ javax.annotation,
+ javax.naming,
+ javax.servlet,
+ javax.servlet.annotation,
+ javax.servlet.http,
+ javax.servlet.jsp,
+ javax.servlet.jsp.el,
+ javax.servlet.jsp.jstl.core,
+ javax.servlet.jsp.jstl.fmt,
+ javax.servlet.jsp.jstl.tlv,
+ javax.servlet.jsp.tagext,
+ javax.servlet.resources,
+ javax.xml.parsers,
+ javax.xml.transform,
+ org.apache.commons.logging,
+ org.apache.taglibs.standard.functions,
+ org.apache.taglibs.standard.resources,
+ org.apache.taglibs.standard.tag.common.core,
+ org.apache.taglibs.standard.tag.common.fmt,
+ org.apache.taglibs.standard.tag.rt.core,
+ org.apache.taglibs.standard.tag.rt.fmt,
+ org.apache.taglibs.standard.tei,
+ org.apache.taglibs.standard.tlv,
+ org.osgi.framework,
+ org.slf4j,
+ org.springframework.beans,
+ org.springframework.beans.factory.xml,
+ org.springframework.context.config,
+ org.springframework.stereotype,
+ org.springframework.ui,
+ org.springframework.web,
+ org.springframework.web.bind.annotation,
+ org.springframework.web.servlet,
+ org.springframework.web.servlet.config,
+ org.springframework.web.servlet.view,
+ org.springframework.web.filter,
+ org.springframework.web.context,
+
+ org.apache.felix.dm,
+
+ org.opendaylight.controller.northbound.commons,
+ org.opendaylight.controller.northbound.commons.exception,
+ org.opendaylight.controller.northbound.commons.utils,
+ com.sun.jersey.spi.container.servlet,
+ com.fasterxml.jackson.annotation,
+ javax.ws.rs,
+ javax.ws.rs.core,
+ javax.xml.bind,
+ javax.xml.bind.annotation,
+ org.apache.catalina.filters,
+ com.fasterxml.jackson.jaxrs.base,
+ com.fasterxml.jackson.jaxrs.json,
+ !org.codehaus.enunciate.jaxrs
+ </Import-Package>
+ <Export-Package></Export-Package>
+ <Web-ContextPath>/learningswitch</Web-ContextPath>
+ <Jaxrs-Resources>,${classes;ANNOTATION;javax.ws.rs.Path}</Jaxrs-Resources>
+ <Bundle-Activator>
+ org.sdnhub.odl.learningswitch.internal.Activator
+ </Bundle-Activator>
+ </instructions>
+ <manifestLocation>${project.basedir}/src/main/resources/META-INF</manifestLocation>
+ <buildDirectory>../../main/target/main-osgipackage/opendaylight/plugins/</buildDirectory> <!-- TODO use pom var -->
+ </configuration>
+ </plugin>
+ </plugins>
+ </build>
+ <dependencies>
+ <dependency>
+ <groupId>org.opendaylight.toolkit</groupId>
+ <artifactId>web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>sal</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.springframework</groupId>
+ <artifactId>spring-web</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.codehaus.enunciate</groupId>
+ <artifactId>enunciate-core-annotations</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>commons.northbound</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>switchmanager</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.opendaylight.controller</groupId>
+ <artifactId>forwardingrulesmanager</artifactId>
+ </dependency>
+
+ </dependencies>
+</project>
--- /dev/null
+
+package org.sdnhub.odl.learningswitch;
+
+import java.util.List;
+import java.util.Map;
+import java.util.UUID;
+
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.utils.Status;
+import org.sdnhub.odl.learningswitch.MacTable.MacPortTableElem;
+
+public interface ILearningSwitch {
+ public Table getData();
+ public void deleteData();
+ public String getFunction();
+ public Boolean setFunction(String function);
+}
--- /dev/null
+package org.sdnhub.odl.learningswitch;
+
+public class LearningSwitchOptions {
+
+ public static final int NUM_OPTIONS = 7;
+
+ public static final Long SRC_MAC = 1L;
+ public static final Long DST_MAC = 2L;
+ public static final Long SRC_IPv4 = 4L;
+ public static final Long DST_IPv4 = 8L;
+ public static final Long IPv4_PROT = 16L;
+ public static final Long SRC_PORT = 32L;
+ public static final Long DST_PORT = 64L;
+
+
+
+ public Long options;
+
+ public LearningSwitchOptions() {
+ options = 0L;
+ }
+}
--- /dev/null
+
+package org.sdnhub.odl.learningswitch;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.Map;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+import org.opendaylight.controller.sal.utils.HexEncode;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+
+@XmlRootElement(name="MacToPortTable")
+@XmlAccessorType(XmlAccessType.NONE)
+public class MacTable {
+
+ // For each switch port, track list of learned MACs
+ private Map<NodeConnector, Set<Long>> table;
+
+ public NodeConnector getNodeConnector(Node n, Long mac) {
+ for (Map.Entry<NodeConnector, Set<Long>> entry : this.table.entrySet()) {
+ if (((NodeConnector)entry.getKey()).getNode().equals(n))
+ if (((Set<Long>)entry.getValue()).contains(mac))
+ return (NodeConnector)entry.getKey();
+ }
+ return null;
+ }
+
+ public void setNodeConnector(NodeConnector nc, Long mac) {
+ if (table.containsKey(nc)) {
+ table.get(nc).add(mac);
+ }
+ else
+ table.put(nc, new HashSet<Long>(Collections.singleton(mac)));
+ }
+
+ public void clearNodeConnector(NodeConnector nc) {
+ table.remove(nc);
+ }
+
+ public void initNode(Node n) {
+ //Nothing to do right now
+ }
+
+ public void clear() {
+ table.clear();
+ }
+ public void clearNode(Node n) {
+ Set<NodeConnector> nodeConnectors = new LinkedHashSet<NodeConnector>();
+
+ for (Map.Entry<NodeConnector, Set<Long>> entry : this.table.entrySet()) {
+ if (((NodeConnector)entry.getKey()).getNode().equals(n)) {
+ nodeConnectors.add((NodeConnector)entry.getKey());
+ }
+ }
+
+ for (NodeConnector nc: nodeConnectors) {
+ clearNodeConnector(nc);
+ }
+ }
+
+ public MacTable() {
+ super();
+ table = new HashMap<NodeConnector, Set<Long> >();
+ }
+
+ public class MacPortTableElem {
+ @XmlElement
+ String mac;
+ @XmlElement
+ String connector;
+ public MacPortTableElem() {
+ // TODO Auto-generated constructor stub
+ super();
+ }
+
+ public MacPortTableElem(String mac, String connector) {
+ super();
+ this.mac = mac;
+ this.connector = connector;
+ }
+ }
+
+ @XmlElement(name="entries")
+ public List< MacPortTableElem > getMap() {
+ NodeConnector nc;
+ List< MacPortTableElem > entries = new ArrayList<MacPortTableElem>();
+
+ for (Map.Entry<NodeConnector, Set<Long> > entry : this.table.entrySet()) {
+ MacPortTableElem elem = new MacPortTableElem();
+ nc = (NodeConnector)entry.getKey();
+
+ for (Long mac: (Set<Long>)entry.getValue()) {
+ elem.mac = HexEncode.longToHexString(mac);
+ elem.connector = nc.toString();
+ entries.add(elem);
+ }
+ }
+ return entries;
+ }
+
+ public String toString() {
+ NodeConnector nc;
+ String str = "MAC Table: " + table.size() + " entries\n";
+ for (Map.Entry<NodeConnector, Set<Long> > entry : table.entrySet()) {
+ nc = (NodeConnector)entry.getKey();
+ for (Long mac: (Set<Long>)entry.getValue())
+ str += HexEncode.longToHexString(mac) + " - " +
+ nc.toString() + "\n";
+ }
+ return str;
+ }
+}
+
--- /dev/null
+package org.sdnhub.odl.learningswitch;
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.NodeConnector;
+
+import java.util.Date;
+import java.util.List;
+import java.util.ArrayList;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+class Pair<F, S> {
+ @XmlElement(name="first")
+ private F first;
+ @XmlElement(name="second")
+ private S second;
+ public Pair(F f, S s) {
+ first = f;
+ second = s;
+ }
+}
+
+@XmlRootElement(name="NodeConnector")
+public class NodeConnectorPlus {
+
+ @XmlElement(name="nodeconnector")
+ NodeConnector nc;
+ // History of all MACs / IPs / whatever seen on this node conncetor
+ List<Pair<String, Date> > history;
+ // Add an
+ public NodeConnectorPlus(NodeConnector nc) {
+ this.nc = nc;
+ history = new ArrayList<Pair<String, Date>>();
+ }
+
+ public String toString() {
+ return nc.toString();
+ }
+
+}
--- /dev/null
+package org.sdnhub.odl.learningswitch;
+
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Date;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.xml.bind.annotation.XmlRootElement;
+
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.packet.Ethernet;
+import org.opendaylight.controller.sal.packet.IPv4;
+import org.opendaylight.controller.sal.packet.Packet;
+import org.opendaylight.controller.sal.packet.RawPacket;
+import org.opendaylight.controller.sal.packet.BitBufferHelper;
+import org.opendaylight.controller.sal.utils.HexEncode;
+
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+
+@XmlRootElement(name="NodeTable")
+public class NodeTable {
+ Set <NodeConnectorPlus> nodeConnectors;
+
+ Map <String, NodeConnectorPlus> table;
+ @XmlElement(name="node")
+ Node n;
+
+ public NodeTable(Node n) {
+ this.n = n;
+ table = new ConcurrentHashMap<String, NodeConnectorPlus>();
+ nodeConnectors = new HashSet<NodeConnectorPlus>();
+ }
+
+ private String createSourceKey(Packet pkt, LearningSwitchOptions lo) {
+ String key = "";
+
+ if ( pkt instanceof Ethernet &&
+ (lo.options & LearningSwitchOptions.SRC_MAC) > 0) {
+
+ key += HexEncode.bytesToHexString(((Ethernet)pkt).getSourceMACAddress());
+ }
+ if ((lo.options & LearningSwitchOptions.SRC_IPv4) > 0) {
+ if ((Packet)pkt instanceof IPv4) {
+ key += String.valueOf(((IPv4)pkt).getSourceAddress());
+ }
+ }
+ // Remaining checks not implemented
+
+ return key;
+ }
+
+ private String createDestKey(Packet pkt, LearningSwitchOptions lo) {
+ String key = "";
+
+ if ( pkt instanceof Ethernet &&
+ (lo.options & LearningSwitchOptions.DST_MAC) > 0) {
+ key += HexEncode.bytesToHexString(((Ethernet)pkt).getDestinationMACAddress());
+ }
+ if ((lo.options & LearningSwitchOptions.DST_IPv4) > 0) {
+ if ((Packet)pkt instanceof IPv4) {
+ key += String.valueOf(((IPv4)pkt).getDestinationAddress());
+ }
+ }
+ // Remaining checks not implemented
+
+ return key;
+ }
+
+
+ public void learnSourceNodeConnector(Packet pkt, NodeConnector incomingNodeConnector, LearningSwitchOptions lo)
+ {
+ String key = createSourceKey(pkt, lo);
+
+ NodeConnectorPlus ncplus = table.get(key);
+ if (ncplus == null) {
+ ncplus = new NodeConnectorPlus(incomingNodeConnector);
+ ncplus.history.add(new Pair<String, Date>(key, new Date()));
+ table.put(key, ncplus);
+ } else {
+ if (!ncplus.nc.equals(incomingNodeConnector)) {
+ ncplus.nc = incomingNodeConnector;
+ }
+ ncplus.history.add(new Pair<String, Date>(key, new Date()));
+ }
+ }
+
+ public NodeConnector findDestinationNodeConnector(Packet pkt, LearningSwitchOptions lo) {
+ String key = createDestKey(pkt, lo);
+ NodeConnectorPlus ncplus = table.get(key);
+ if (ncplus != null) {
+ return ncplus.nc;
+ } else {
+ return null;
+ }
+ }
+
+ @XmlElement(name="table")
+ public List<Pair<String, NodeConnectorPlus>> getNodeTable()
+ {
+ List<Pair<String, NodeConnectorPlus>> t = new ArrayList<Pair<String, NodeConnectorPlus>>();
+ for (Map.Entry<String, NodeConnectorPlus> entry : table.entrySet()) {
+ Pair<String, NodeConnectorPlus> p = new Pair<String, NodeConnectorPlus>(entry.getKey(), entry.getValue());
+ t.add(p);
+ }
+ return t;
+ }
+
+
+ public String toString() {
+ return table.toString();
+ }
+
+ public List<NodeConnector> getNodeConnectors()
+ {
+ List<NodeConnector> list_nc = new ArrayList<NodeConnector>();
+ for (NodeConnectorPlus ncp : nodeConnectors) {
+ list_nc.add(ncp.nc);
+ }
+ return list_nc;
+ }
+
+}
--- /dev/null
+package org.sdnhub.odl.learningswitch;
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Map;
+import java.util.Set;
+import java.io.Serializable;
+import java.util.concurrent.ConcurrentHashMap;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.packet.Ethernet;
+import org.opendaylight.controller.sal.packet.Packet;
+import org.sdnhub.odl.learningswitch.NodeTable;
+import org.sdnhub.odl.learningswitch.LearningSwitchOptions;
+
+@XmlRootElement(name="LearningTable")
+@XmlAccessorType(XmlAccessType.NONE)
+public class Table {
+
+ Map <Node, NodeTable> table;
+
+ public Table()
+ {
+ table = new ConcurrentHashMap<Node, NodeTable>();
+ }
+
+ public void addNode(Node n)
+ {
+ NodeTable nt = (NodeTable) table.get(n);
+ if (nt == null) {
+ NodeTable new_nt = new NodeTable(n);
+ table.put(n, new_nt);
+ }
+ }
+
+ public void deleteNode(Node n)
+ {
+ NodeTable nt = (NodeTable) table.get(n);
+ if (nt != null) {
+ table.remove(n);
+ }
+ }
+
+ public void learnSourceFields(Packet pkt, NodeConnector incomingNodeConnector, LearningSwitchOptions lo) throws NoSuchFieldException
+ {
+ NodeTable nt = (NodeTable) table.get(incomingNodeConnector.getNode());
+ if (nt == null) {
+ throw new NoSuchFieldException("This node was not found in the node table");
+ }
+
+ nt.learnSourceNodeConnector(pkt, incomingNodeConnector, lo);
+ }
+
+ public NodeConnector getDestinationNodeConnector(Packet pkt, Node incomingNode, LearningSwitchOptions lo) throws NoSuchFieldException
+ {
+ NodeTable nt = (NodeTable) table.get(incomingNode);
+ if (nt == null) {
+ throw new NoSuchFieldException("This node was not found in the node table");
+ }
+
+ return nt.findDestinationNodeConnector(pkt, lo);
+ }
+ public String toString() {
+ return this.table.toString();
+ }
+
+ public void clear()
+ {
+ table.clear();
+ }
+
+ @XmlElement(name="table")
+ public List<NodeTable> getTable()
+ {
+ List<NodeTable> l = new ArrayList<NodeTable>();
+ for (Map.Entry<Node, NodeTable> e : table.entrySet()) {
+ l.add(e.getValue());
+ }
+ return l;
+ }
+
+}
--- /dev/null
+
+package org.sdnhub.odl.learningswitch.internal;
+
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Set;
+
+import org.apache.felix.dm.Component;
+import org.sdnhub.odl.learningswitch.ILearningSwitch;
+import org.opendaylight.controller.sal.core.ComponentActivatorAbstractBase;
+import org.opendaylight.controller.sal.flowprogrammer.IFlowProgrammerService;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.opendaylight.controller.sal.packet.IListenDataPacket;
+import org.opendaylight.controller.sal.packet.IDataPacketService;
+import org.opendaylight.controller.switchmanager.IInventoryListener;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+
+
+
+public class Activator extends ComponentActivatorAbstractBase {
+ protected static final Logger log = LoggerFactory.getLogger(Activator.class);
+
+ /**
+ * Function called when the activator starts just after some initializations
+ * are done by the ComponentActivatorAbstractBase.
+ *
+ */
+ /*
+ @Override
+ public void init() {
+ }
+ */
+
+ /**
+ * Function called when the activator stops just before the cleanup done by
+ * ComponentActivatorAbstractBase
+ *
+ */
+ /*
+ @Override
+ public void destroy() {
+ }
+*/
+ /**
+ * Function that is used to communicate to dependency manager the list of
+ * known implementations for services inside a container
+ *
+ *
+ * @return An array containing all the CLASS objects that will be
+ * instantiated in order to get an fully working implementation
+ * Object
+ */
+ /* @Override
+ public Object[] getGlobalImplementations() {
+ Object[] res = { LearningSwitch.class };
+ return res;
+ }*/
+ public Object[] getImplementations() {
+ // TODO: Call your Class.class
+
+ Object[] res = { LearningSwitch.class };
+ return res;
+ }
+
+
+ /**
+ * Function that is called when configuration of the dependencies is
+ * required.
+ *
+ * @param c
+ * dependency manager Component object, used for configuring the
+ * dependencies exported and imported
+ * @param imp
+ * Implementation class that is being configured, needed as long
+ * as the same routine can configure multiple implementations
+ * @param containerName
+ * The containerName being configured, this allow also optional
+ * per-container different behavior if needed, usually should not
+ * be the case though.
+ */
+/*
+ @Override
+ public void configureGlobalInstance(Component c, Object imp) {
+ if (imp.equals(LearningSwitch.class)) {
+ Dictionary<String, Set<String>> props = new Hashtable<String, Set<String>>();
+ String interfaces[] = null;
+ interfaces = new String[] { ILearningSwitch.class.getName() };
+ c.setInterface(interfaces, props);
+ }
+ }
+*/
+ public void configureInstance(Component c, Object imp, String containerName) {
+
+ // TODO: configure instance
+ log.info("Learningswithc activator - container name: {}", containerName);
+
+ if (imp.equals(LearningSwitch.class)) {
+ // export the services
+ Dictionary<String, String> props = new Hashtable<String, String>();
+ props.put("salListenerName", "LearningSwitch");
+ c.setInterface(new String[] { IListenDataPacket.class.getName(),
+ ILearningSwitch.class.getName(),
+ IInventoryListener.class.getName()}, props);
+
+ // register dependent modules
+ c.add(createContainerServiceDependency(containerName).setService(
+ ISwitchManager.class).setCallbacks("setSwitchManager",
+ "unsetSwitchManager").setRequired(true));
+
+ c.add(createContainerServiceDependency(containerName).setService(
+ IDataPacketService.class).setCallbacks(
+ "setDataPacketService", "unsetDataPacketService")
+ .setRequired(true));
+
+ c.add(createContainerServiceDependency(containerName).setService(
+ IFlowProgrammerService.class).setCallbacks(
+ "setFlowProgrammerService", "unsetFlowProgrammerService")
+ .setRequired(true));
+
+ }
+ }
+}
--- /dev/null
+
+package org.sdnhub.odl.learningswitch.internal;
+
+import org.sdnhub.odl.learningswitch.ILearningSwitch;
+import org.sdnhub.odl.learningswitch.LearningSwitchOptions;
+import org.sdnhub.odl.learningswitch.MacTable;
+import org.sdnhub.odl.learningswitch.MacTable.MacPortTableElem;
+import org.sdnhub.odl.learningswitch.Table;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Set;
+import java.util.UUID;
+import java.util.Map;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+
+import org.opendaylight.controller.sal.utils.EtherTypes;
+import org.opendaylight.controller.sal.utils.HexEncode;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.sal.action.Action;
+import org.opendaylight.controller.sal.action.Output;
+import org.opendaylight.controller.sal.core.ConstructionException;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.core.Property;
+import org.opendaylight.controller.sal.core.UpdateType;
+import org.opendaylight.controller.sal.flowprogrammer.Flow;
+import org.opendaylight.controller.sal.flowprogrammer.IFlowProgrammerService;
+import org.opendaylight.controller.sal.match.Match;
+import org.opendaylight.controller.sal.match.MatchField;
+import org.opendaylight.controller.sal.match.MatchType;
+import org.opendaylight.controller.sal.packet.BitBufferHelper;
+import org.opendaylight.controller.sal.packet.Ethernet;
+import org.opendaylight.controller.sal.packet.IDataPacketService;
+import org.opendaylight.controller.sal.packet.IListenDataPacket;
+import org.opendaylight.controller.sal.packet.IPv4;
+import org.opendaylight.controller.sal.packet.TCP;
+import org.opendaylight.controller.sal.packet.UDP;
+import org.opendaylight.controller.sal.packet.Packet;
+import org.opendaylight.controller.sal.packet.PacketResult;
+import org.opendaylight.controller.sal.packet.RawPacket;
+import org.opendaylight.controller.switchmanager.IInventoryListener;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+
+public class LearningSwitch implements IListenDataPacket, ILearningSwitch, IInventoryListener {
+ protected static final Logger logger = LoggerFactory.getLogger(LearningSwitch.class);
+ private IDataPacketService dataPacketService = null;
+ private ISwitchManager switchManager = null;
+ private IFlowProgrammerService programmer = null;
+
+ private Table table = null;
+ private LearningSwitchOptions src_opts = null, dst_opts = null;
+
+ private String function = "switch";
+
+ void init() {
+ logger.info("Initializing Simple application");
+ table = new Table();
+ src_opts = new LearningSwitchOptions();
+ src_opts.options |= LearningSwitchOptions.SRC_MAC;
+ dst_opts = new LearningSwitchOptions();
+ dst_opts.options |= LearningSwitchOptions.DST_MAC;
+ }
+ void start() {
+ logger.info("Simple application starting");
+ }
+
+ void stop() {
+ logger.info("Simple application stopping");
+ }
+
+ void setDataPacketService(IDataPacketService s) {
+ this.dataPacketService = s;
+ }
+
+ void unsetDataPacketService(IDataPacketService s) {
+ if (this.dataPacketService == s) {
+ this.dataPacketService = null;
+ }
+ }
+
+ public void setFlowProgrammerService(IFlowProgrammerService s)
+ {
+ this.programmer = s;
+ }
+
+ public void unsetFlowProgrammerService(IFlowProgrammerService s) {
+ if (this.programmer == s) {
+ this.programmer = null;
+ }
+ }
+
+ public void notifyNode(Node node, UpdateType type,
+ Map<String, Property> propMap) {
+ if (type == UpdateType.ADDED)
+ this.table.addNode(node);
+ else if (type == UpdateType.REMOVED)
+ this.table.deleteNode(node);
+
+ }
+
+ public void notifyNodeConnector(NodeConnector nodeConnector,
+ UpdateType type, Map<String, Property> propMap) {
+
+// if (type == UpdateType.ADDED)
+// this.macToPortTable.initNodeConnector(nodeConnector);
+// else if (type == UpdateType.REMOVED)
+// this.macToPortTable.clearNodeConnector(nodeConnector);
+
+ }
+
+ void setSwitchManager(ISwitchManager s) {
+ logger.debug("SwitchManager set");
+ this.switchManager = s;
+ }
+
+ void unsetSwitchManager(ISwitchManager s) {
+ if (this.switchManager == s) {
+ logger.debug("SwitchManager removed!");
+ this.switchManager = null;
+ }
+ }
+
+ private void floodPacket(RawPacket inPkt) {
+ NodeConnector incoming_connector = inPkt.getIncomingNodeConnector();
+ Node incoming_node = incoming_connector.getNode();
+
+ Set<NodeConnector> nodeConnectors =
+ this.switchManager.getUpNodeConnectors(incoming_node);
+
+ for (NodeConnector nc : nodeConnectors) {
+ if (!nc.equals(incoming_connector) &&
+ (nc.getType() != NodeConnector.NodeConnectorIDType.SWSTACK)) {
+ try {
+ RawPacket destPkt = new RawPacket(inPkt);
+ destPkt.setOutgoingNodeConnector(nc);
+ this.dataPacketService.transmitDataPacket(destPkt);
+ } catch (ConstructionException e2) {
+ continue;
+ }
+ }
+ }
+ }
+
+ @Override
+ public PacketResult receiveDataPacket(RawPacket inPkt) {
+ if (inPkt == null) {
+ return PacketResult.IGNORED;
+ }
+
+ Packet formattedPak = this.dataPacketService.decodeDataPacket(inPkt);
+ if (!(formattedPak instanceof Ethernet)) {
+ return PacketResult.IGNORED;
+ }
+
+ //Ignore LLDP packets. They should never be flooded out
+ Ethernet etherPak = (Ethernet)formattedPak;
+ if (etherPak.getEtherType() == EtherTypes.LLDP.intValue())
+ return PacketResult.IGNORED;
+
+ // Hub implementation
+ if (function.equals("hub")) {
+ floodPacket(inPkt);
+ } else {
+ NodeConnector incoming_connector = inPkt.getIncomingNodeConnector();
+
+ try {
+ this.table.learnSourceFields((Packet)etherPak, incoming_connector, src_opts);
+// logger.info("Table entries: {}" + this.table.toString());
+ } catch (NoSuchFieldException e) {
+ // TODO Auto-generated catch block
+ logger.error("Error learning source fields: {}", e.toString());
+ }
+
+ NodeConnector outgoing_connector = null;
+ try {
+ outgoing_connector = this.table.getDestinationNodeConnector((Packet)etherPak, incoming_connector.getNode(), dst_opts);
+ } catch (NoSuchFieldException e) {
+ // TODO Auto-generated catch block
+ logger.error("Error learning source fields: {}", e.toString());
+ }
+
+ if (outgoing_connector == null) {
+ floodPacket(inPkt);
+ } else {
+ if (!programFlow(etherPak, incoming_connector,
+ outgoing_connector)) {
+ return PacketResult.IGNORED;
+ }
+ }
+ }
+ return PacketResult.CONSUME;
+ }
+
+ private boolean programFlow(Ethernet etherPak,
+ NodeConnector incoming_connector,
+ NodeConnector outgoing_connector) {
+ byte[] dstMAC = etherPak.getDestinationMACAddress();
+
+ Match match = new Match();
+ match.setField(new MatchField(MatchType.IN_PORT, incoming_connector) );
+ match.setField(new MatchField(MatchType.DL_DST, dstMAC.clone()));
+
+ List<Action> actions = new ArrayList<Action>();
+ actions.add(new Output(outgoing_connector));
+
+ Flow f = new Flow(match, actions);
+ f.setIdleTimeout((short)5);
+
+ // Modify the flow on the network node
+ Node incoming_node = incoming_connector.getNode();
+ Status status = programmer.addFlow(incoming_node, f);
+
+ if (!status.isSuccess()) {
+ logger.warn("SDN Plugin failed to program the flow: {}. The failure is: {}",
+ f, status.getDescription());
+ return false;
+ } else {
+ return true;
+ }
+ }
+
+ @Override
+ public Table getData()
+ {
+ //return macToPortTable.getMap();
+ return this.table;
+ }
+
+ @Override
+ public void deleteData()
+ {
+ this.table.clear();
+ }
+
+
+ @Override
+ public String getFunction() {
+ return this.function;
+ }
+
+ @Override
+ public Boolean setFunction(String fn) {
+ Boolean ret = Boolean.TRUE;
+ if (fn.equals("hub")) {
+ function = "hub";
+ } else if (fn.equals("switch")) {
+ function = "switch";
+ } else {
+ ret = Boolean.FALSE;
+ }
+ return ret;
+ }
+}
--- /dev/null
+
+package org.sdnhub.odl.learningswitch.northbound;
+
+import org.sdnhub.odl.learningswitch.ILearningSwitch;
+import org.sdnhub.odl.learningswitch.Table;
+import org.sdnhub.odl.learningswitch.MacTable.MacPortTableElem;
+import org.sdnhub.odl.learningswitch.internal.LearningSwitch;
+import org.codehaus.enunciate.jaxrs.StatusCodes;
+import org.codehaus.enunciate.jaxrs.TypeHint;
+import org.opendaylight.controller.sal.reader.IReadService;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.controller.sal.utils.HexEncode;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.UUID;
+
+import javax.ws.rs.Consumes;
+import javax.ws.rs.DELETE;
+import javax.ws.rs.GET;
+import javax.ws.rs.POST;
+import javax.ws.rs.PUT;
+import javax.ws.rs.Path;
+import javax.ws.rs.PathParam;
+import javax.ws.rs.Produces;
+import javax.ws.rs.core.Context;
+import javax.ws.rs.core.MediaType;
+import javax.ws.rs.core.Response;
+import javax.ws.rs.core.SecurityContext;
+import javax.ws.rs.core.UriInfo;
+
+import javax.xml.bind.annotation.XmlAccessType;
+import javax.xml.bind.annotation.XmlAccessorType;
+import javax.xml.bind.annotation.XmlElement;
+import javax.xml.bind.annotation.XmlRootElement;
+import javax.xml.bind.annotation.adapters.XmlAdapter;
+import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter;
+
+import org.codehaus.enunciate.jaxrs.ResponseCode;
+import org.opendaylight.controller.northbound.commons.RestMessages;
+import org.opendaylight.controller.northbound.commons.exception.ServiceUnavailableException;
+import org.opendaylight.controller.northbound.commons.exception.UnauthorizedException;
+import org.opendaylight.controller.northbound.commons.utils.NorthboundUtils;
+import org.opendaylight.controller.sal.authorization.Privilege;
+import org.opendaylight.controller.sal.core.NodeConnector;
+import org.opendaylight.controller.sal.utils.Status;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.forwardingrulesmanager.FlowConfig;
+import org.opendaylight.controller.forwardingrulesmanager.IForwardingRulesManager;
+import org.opendaylight.controller.switchmanager.ISwitchManager;
+import org.opendaylight.controller.switchmanager.Switch;
+import org.opendaylight.controller.sal.core.Node;
+import org.opendaylight.controller.sal.core.Description;
+import org.opendaylight.controller.sal.core.Name;
+import org.opendaylight.controller.switchmanager.SwitchConfig;
+import org.opendaylight.controller.sal.reader.FlowOnNode;
+
+
+
+
+/**
+ * Northbound REST API
+ *
+ * This entire web class can be accessed via /northbound prefix as specified in
+ * web.xml
+ *
+ * <br>
+ * <br>
+ * Authentication scheme : <b>HTTP Basic</b><br>
+ * Authentication realm : <b>opendaylight</b><br>
+ * Transport : <b>HTTP and HTTPS</b><br>
+ * <br>
+ * HTTPS Authentication is disabled by default.
+ */
+@Path("/")
+public class AppNorthbound {
+ @Context
+ private UriInfo _uriInfo;
+ private String username;
+
+ @Context
+ public void setSecurityContext(SecurityContext context) {
+ if (context != null && context.getUserPrincipal() != null) {
+ username = context.getUserPrincipal().getName();
+ }
+ }
+
+ protected String getUserName() {
+ return username;
+ }
+
+
+
+ @XmlRootElement(name="SwitchFunction")
+ class SwitchFunction {
+ @XmlElement(name="function")
+ String function;
+ public SwitchFunction(String fn) {
+ function = fn;
+ }
+ }
+
+ /**
+ *
+ * Get current function (hub or switch) - GET REST API call
+ *
+ * @return A response string
+ *
+ * <pre>
+ * Example:
+ *
+ * Request URL:
+ * http://localhost:8080/app/northbound/learningswitch/function/
+ *
+ * Response body in JSON:
+ * { "function" : "hub"}
+ * </pre>
+ */
+ @Path("/learningswitch/function/")
+ @GET
+ @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+ @StatusCodes()
+ public SwitchFunction getFunction() {
+ if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+ throw new UnauthorizedException("User is not authorized to perform this operation");
+ }
+ //LearningSwitch simple = (LearningSwitch) ServiceHelper.getInstance(LearningSwitch.class, "default", this);
+ ILearningSwitch simple = (ILearningSwitch) ServiceHelper.getInstance(ILearningSwitch.class, "default", this);
+ if (simple == null) {
+ throw new ServiceUnavailableException("LearningSwitch Service " + RestMessages.SERVICEUNAVAILABLE.toString());
+ }
+
+ SwitchFunction sf = new SwitchFunction(simple.getFunction());
+ return sf;
+ }
+
+ /**
+ *
+ * Set current function (hub or switch) - PUT REST API call
+ *
+ * @return Response
+ *
+ * <pre>
+ * Example:
+ *
+ * Request URL:
+ * http://localhost:8080/app/northbound/learningswitch/function
+ *
+ * </pre>
+ */
+ @Path("/learningswitch/function/")
+ @PUT
+ @Consumes({ MediaType.APPLICATION_JSON})
+ @StatusCodes()
+ public Response setFunction(@TypeHint(SwitchFunction.class) SwitchFunction fn) {
+ if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+ throw new UnauthorizedException("User is not authorized to perform this operation");
+ }
+ ILearningSwitch simple = (ILearningSwitch) ServiceHelper.getInstance(ILearningSwitch.class, "default", this);
+ if (simple == null) {
+ throw new ServiceUnavailableException("LearningSwitch Service " + RestMessages.SERVICEUNAVAILABLE.toString());
+ }
+
+ if (simple.setFunction(fn.function) == true) {
+ return Response.status(Response.Status.OK).build();
+ } else {
+ return Response.status(Response.Status.NOT_FOUND).build();
+ }
+ }
+
+
+
+ /**
+ *
+ * Sample GET REST API call
+ *
+ * @return A response string
+ *
+ * <pre>
+ * Example:
+ *
+ * Request URL:
+ * http://localhost:8080/app/northbound/learningswitch/mactable
+ *
+ * Response body in XML:
+ * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ * Sample Northbound API
+ *
+ * Response body in JSON:
+ * Sample Northbound API
+ * </pre>
+ */
+ @Path("/learningswitch/table")
+ @GET
+ @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+ @StatusCodes()
+ public Table getTableEntries() {
+ if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+ throw new UnauthorizedException("User is not authorized to perform this operation");
+ }
+ //LearningSwitch simple = (LearningSwitch) ServiceHelper.getInstance(LearningSwitch.class, "default", this);
+ ILearningSwitch simple = (ILearningSwitch) ServiceHelper.getInstance(ILearningSwitch.class, "default", this);
+ if (simple == null) {
+ throw new ServiceUnavailableException("Simple Service " + RestMessages.SERVICEUNAVAILABLE.toString());
+ }
+
+ return simple.getData();
+ }
+
+
+ /**
+ *
+ * Sample Delete REST API call
+ *
+ * @return A response string
+ *
+ * <pre>
+ * Example:
+ *
+ * Request URL:
+ * http://localhost:8080/app/northbound/learningswitch/{uuid}
+ *
+ * Response body in XML:
+ * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ * Sample Northbound API
+ *
+ * Response body in JSON:
+ * Sample Northbound API
+ * </pre>
+ */
+ @Path("/learningswitch/table")
+ @DELETE
+ @StatusCodes({ @ResponseCode(code = 200, condition = "Data Deleted successfully"),
+ @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
+ @ResponseCode(code = 500, condition = "Error deleting data"),
+ @ResponseCode(code = 503, condition = "One or more of service is unavailable")})
+ @Consumes({ MediaType.APPLICATION_JSON})
+ public Response deleteTableEntries() {
+ if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+ throw new UnauthorizedException("User is not authorized to perform this operation");
+ }
+ ILearningSwitch simple = (ILearningSwitch) ServiceHelper.getGlobalInstance(ILearningSwitch.class, this);
+ if (simple == null) {
+ throw new ServiceUnavailableException("Simple Service " + RestMessages.SERVICEUNAVAILABLE.toString());
+ }
+
+ simple.deleteData();
+ return Response.status(Response.Status.OK).build();
+ }
+
+
+ @XmlRootElement(name="NodeToFlowEntries")
+ class NodeToFlowEntries {
+ @XmlElement(name="node")
+ String node;
+ @XmlElement(name="flows")
+ List <String> flows;
+ public NodeToFlowEntries() {
+ flows = new ArrayList<String>();
+ }
+ }
+
+ @Path("/learningswitch/flowtable")
+ @GET
+ @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+ @StatusCodes()
+ public List<NodeToFlowEntries> getNodeFlows() {
+ if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+ throw new UnauthorizedException("User is not authorized to perform this operation");
+ }
+ ISwitchManager switchManager = (ISwitchManager) ServiceHelper.getInstance(ISwitchManager.class, "default",
+ this);
+ if (switchManager == null) {
+ return null;
+ }
+ IForwardingRulesManager frm = (IForwardingRulesManager) ServiceHelper.getInstance(
+ IForwardingRulesManager.class, "default", this);
+ if (frm == null) {
+ return null;
+ }
+
+ IReadService rds = (IReadService) ServiceHelper.getInstance(IReadService.class, "default", this);
+
+ List< NodeToFlowEntries > output = new ArrayList<NodeToFlowEntries>();
+ //Map<String, Object> output = new HashMap<String, Object>(2);
+
+ for (Switch sw : switchManager.getNetworkDevices()) {
+ Node node = sw.getNode();
+ NodeToFlowEntries nfentries = new NodeToFlowEntries();
+
+ nfentries.node = node.toString();
+
+ //List<FlowConfig> staticFlowList = frm.getStaticFlows(node);
+ List<FlowOnNode> flowList = rds.readAllFlows(node);
+ for (FlowOnNode flow : flowList) {
+ nfentries.flows.add(flow.getFlow().toString());
+ }
+ output.add(nfentries);
+ }
+ return output;
+ }
+
+ private String getNodeDesc(Node node, ISwitchManager switchManager) {
+ Description desc = (Description) switchManager.getNodeProp(node, Description.propertyName);
+ String description = (desc == null) ? "" : desc.getValue();
+ return (description.isEmpty() || description.equalsIgnoreCase("none")) ? node.toString() : description;
+ }
+
+
+
+ // @Path("/learningswitch/{uuid}")
+ // @GET
+ // @Produces({ MediaType.APPLICATION_JSON, MediaType.APPLICATION_XML })
+ // @TypeHint(MacToPortTable.class)
+ // @StatusCodes()
+ // public MacToPortTable getData(@PathParam("uuid") String uuid) {
+ // if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+ // throw new UnauthorizedException("User is not authorized to perform this operation");
+ // }
+ // ILearningSwitch simple = (ILearningSwitch) ServiceHelper.getGlobalInstance(ILearningSwitch.class, this);
+ // if (simple == null) {
+ // throw new ServiceUnavailableException("Simple Service " + RestMessages.SERVICEUNAVAILABLE.toString());
+ // }
+ //
+ // return simple.readData(UUID.fromString(uuid));
+ // }
+
+ /**
+ *
+ * Sample POST REST API call
+ *
+ * @return A response string
+ *
+ * <pre>
+ * Example:
+ *
+ * Request URL:
+ * http://localhost:8080/app/northbound/learningswitch
+ *
+ * Response body in XML:
+ * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ * Sample Northbound API
+ *
+ * Response body in JSON:
+ * Sample Northbound API
+ * </pre>
+ */
+ // @Path("/learningswitch")
+ // @POST
+ // @StatusCodes({ @ResponseCode(code = 201, condition = "Data Inserted successfully"),
+ // @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
+ // @ResponseCode(code = 500, condition = "Error inserting data"),
+ // @ResponseCode(code = 503, condition = "One or more of service is unavailable")})
+ // @Consumes({ MediaType.APPLICATION_JSON})
+ // public Response createData(@TypeHint(MacToPortTable.class) MacToPortTable data) {
+ // if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+ // throw new UnauthorizedException("User is not authorized to perform this operation");
+ // }
+ // ILearningSwitch simple = (ILearningSwitch) ServiceHelper.getGlobalInstance(ILearningSwitch.class, this);
+ // if (simple == null) {
+ // throw new ServiceUnavailableException("Simple Service " + RestMessages.SERVICEUNAVAILABLE.toString());
+ // }
+ //
+ // UUID uuid = simple.createData(data);
+ // if (uuid == null) {
+ // return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+ // }
+ // return Response.status(Response.Status.CREATED)
+ // .header("Location", String.format("%s/%s", _uriInfo.getAbsolutePath().toString(),
+ // uuid.toString()))
+ // .entity(uuid.toString())
+ // .build();
+ // }
+
+ /**
+ *
+ * Sample PUT REST API call
+ *
+ * @return A response string
+ *
+ * <pre>
+ * Example:
+ *
+ * Request URL:
+ * http://localhost:8080/app/northbound/learningswitch/{uuid}
+ *
+ * Response body in XML:
+ * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ * Sample Northbound API
+ *
+ * Response body in JSON:
+ * Sample Northbound API
+ * </pre>
+ */
+ // @Path("/learningswitch/{uuid}")
+ // @PUT
+ // @StatusCodes({ @ResponseCode(code = 200, condition = "Data Updated successfully"),
+ // @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
+ // @ResponseCode(code = 500, condition = "Error updating data"),
+ // @ResponseCode(code = 503, condition = "One or more of service is unavailable")})
+ // @Consumes({ MediaType.APPLICATION_JSON})
+ // public Response updateData(@PathParam("uuid") String uuid, @TypeHint(MacToPortTable.class) MacToPortTable data) {
+ // if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+ // throw new UnauthorizedException("User is not authorized to perform this operation");
+ // }
+ // ILearningSwitch simple = (ILearningSwitch) ServiceHelper.getGlobalInstance(ILearningSwitch.class, this);
+ // if (simple == null) {
+ // throw new ServiceUnavailableException("Simple Service " + RestMessages.SERVICEUNAVAILABLE.toString());
+ // }
+ //
+ // Status status = simple.updateData(UUID.fromString(uuid), data);
+ // if (!status.isSuccess()) {
+ // return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+ // }
+ // return Response.status(Response.Status.OK).build();
+ // }
+
+ /**
+ *
+ * Sample Delete REST API call
+ *
+ * @return A response string
+ *
+ * <pre>
+ * Example:
+ *
+ * Request URL:
+ * http://localhost:8080/app/northbound/learningswitch/{uuid}
+ *
+ * Response body in XML:
+ * <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
+ * Sample Northbound API
+ *
+ * Response body in JSON:
+ * Sample Northbound API
+ * </pre>
+ */
+ // @Path("/learningswitch/{uuid}")
+ // @DELETE
+ // @StatusCodes({ @ResponseCode(code = 200, condition = "Data Deleted successfully"),
+ // @ResponseCode(code = 401, condition = "User not authorized to perform this operation"),
+ // @ResponseCode(code = 500, condition = "Error deleting data"),
+ // @ResponseCode(code = 503, condition = "One or more of service is unavailable")})
+ // @Consumes({ MediaType.APPLICATION_JSON})
+ // public Response updateData(@PathParam("uuid") String uuid) {
+ // if (!NorthboundUtils.isAuthorized(getUserName(), "default", Privilege.WRITE, this)) {
+ // throw new UnauthorizedException("User is not authorized to perform this operation");
+ // }
+ // ILearningSwitch simple = (ILearningSwitch) ServiceHelper.getGlobalInstance(ILearningSwitch.class, this);
+ // if (simple == null) {
+ // throw new ServiceUnavailableException("Simple Service " + RestMessages.SERVICEUNAVAILABLE.toString());
+ // }
+ //
+ // Status status = simple.deleteData(UUID.fromString(uuid));
+ // if (!status.isSuccess()) {
+ // return Response.status(Response.Status.INTERNAL_SERVER_ERROR).build();
+ // }
+ // return Response.status(Response.Status.OK).build();
+ // }
+
+}
--- /dev/null
+package org.sdnhub.odl.learningswitch.web;
+
+import javax.servlet.http.HttpServletRequest;
+import javax.servlet.http.HttpServletResponse;
+
+import org.opendaylight.controller.sal.authorization.UserLevel;
+import org.opendaylight.controller.sal.utils.ServiceHelper;
+import org.opendaylight.toolkit.web.IDaylightWeb;
+import org.springframework.stereotype.Controller;
+import org.springframework.ui.Model;
+import org.springframework.web.bind.annotation.RequestMapping;
+
+/**
+ * This entire web class can be accessed via /web prefix as specified in web.xml
+ */
+@Controller
+@RequestMapping("/")
+public class AppWeb implements IDaylightWeb {
+ private static final String WEB_NAME = "learningswitch App";
+ private static final String WEB_ID = "learningswitch";
+ private static final short WEB_ORDER = 1;
+ private static final UserLevel AUTH_LEVEL = UserLevel.CONTAINERUSER;
+
+ public AppWeb() {
+ ServiceHelper.registerGlobalService(IDaylightWeb.class, this, null);
+ }
+
+ @RequestMapping(value = "")
+ public String index(Model model, HttpServletRequest request) {
+ return "main";
+ }
+
+ @Override
+ public String getWebName() {
+ return WEB_NAME;
+ }
+
+ @Override
+ public String getWebId() {
+ return WEB_ID;
+ }
+
+ @Override
+ public short getWebOrder() {
+ return WEB_ORDER;
+ }
+
+ @Override
+ public boolean isAuthorized(UserLevel userLevel) {
+ return userLevel.ordinal() <= AUTH_LEVEL.ordinal();
+ }
+
+ @RequestMapping(value = "login")
+ public String login(final HttpServletRequest request, final HttpServletResponse response) {
+ return "forward:" + "/";
+ }
+
+}
--- /dev/null
+org.springframework.beans.BeanInfoFactory=org.springframework.beans.ExtendedBeanInfoFactory
--- /dev/null
+http\://www.springframework.org/schema/context=org.springframework.context.config.ContextNamespaceHandler
+http\://www.springframework.org/schema/jee=org.springframework.ejb.config.JeeNamespaceHandler
+http\://www.springframework.org/schema/lang=org.springframework.scripting.config.LangNamespaceHandler
+http\://www.springframework.org/schema/task=org.springframework.scheduling.config.TaskNamespaceHandler
+http\://www.springframework.org/schema/cache=org.springframework.cache.config.CacheNamespaceHandler
+http\://www.springframework.org/schema/c=org.springframework.beans.factory.xml.SimpleConstructorNamespaceHandler
+http\://www.springframework.org/schema/p=org.springframework.beans.factory.xml.SimplePropertyNamespaceHandler
+http\://www.springframework.org/schema/util=org.springframework.beans.factory.xml.UtilNamespaceHandler
+http\://www.springframework.org/schema/mvc=org.springframework.web.servlet.config.MvcNamespaceHandler
+http\://www.springframework.org/schema/security=org.springframework.security.config.SecurityNamespaceHandler
--- /dev/null
+http\://www.springframework.org/schema/beans/spring-beans-2.0.xsd=org/springframework/beans/factory/xml/spring-beans-2.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-2.5.xsd=org/springframework/beans/factory/xml/spring-beans-2.5.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.0.xsd=org/springframework/beans/factory/xml/spring-beans-3.0.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.1.xsd=org/springframework/beans/factory/xml/spring-beans-3.1.xsd
+http\://www.springframework.org/schema/beans/spring-beans-3.2.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/beans/spring-beans.xsd=org/springframework/beans/factory/xml/spring-beans-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.0.xsd=org/springframework/beans/factory/xml/spring-tool-2.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-2.5.xsd=org/springframework/beans/factory/xml/spring-tool-2.5.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.0.xsd=org/springframework/beans/factory/xml/spring-tool-3.0.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.1.xsd=org/springframework/beans/factory/xml/spring-tool-3.1.xsd
+http\://www.springframework.org/schema/tool/spring-tool-3.2.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/tool/spring-tool.xsd=org/springframework/beans/factory/xml/spring-tool-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util-2.0.xsd=org/springframework/beans/factory/xml/spring-util-2.0.xsd
+http\://www.springframework.org/schema/util/spring-util-2.5.xsd=org/springframework/beans/factory/xml/spring-util-2.5.xsd
+http\://www.springframework.org/schema/util/spring-util-3.0.xsd=org/springframework/beans/factory/xml/spring-util-3.0.xsd
+http\://www.springframework.org/schema/util/spring-util-3.1.xsd=org/springframework/beans/factory/xml/spring-util-3.1.xsd
+http\://www.springframework.org/schema/util/spring-util-3.2.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/util/spring-util.xsd=org/springframework/beans/factory/xml/spring-util-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context-2.5.xsd=org/springframework/context/config/spring-context-2.5.xsd
+http\://www.springframework.org/schema/context/spring-context-3.0.xsd=org/springframework/context/config/spring-context-3.0.xsd
+http\://www.springframework.org/schema/context/spring-context-3.1.xsd=org/springframework/context/config/spring-context-3.1.xsd
+http\://www.springframework.org/schema/context/spring-context-3.2.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/context/spring-context.xsd=org/springframework/context/config/spring-context-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.0.xsd=org/springframework/ejb/config/spring-jee-2.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-2.5.xsd=org/springframework/ejb/config/spring-jee-2.5.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.0.xsd=org/springframework/ejb/config/spring-jee-3.0.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.1.xsd=org/springframework/ejb/config/spring-jee-3.1.xsd
+http\://www.springframework.org/schema/jee/spring-jee-3.2.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/jee/spring-jee.xsd=org/springframework/ejb/config/spring-jee-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.0.xsd=org/springframework/scripting/config/spring-lang-2.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-2.5.xsd=org/springframework/scripting/config/spring-lang-2.5.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.0.xsd=org/springframework/scripting/config/spring-lang-3.0.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.1.xsd=org/springframework/scripting/config/spring-lang-3.1.xsd
+http\://www.springframework.org/schema/lang/spring-lang-3.2.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/lang/spring-lang.xsd=org/springframework/scripting/config/spring-lang-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task-3.0.xsd=org/springframework/scheduling/config/spring-task-3.0.xsd
+http\://www.springframework.org/schema/task/spring-task-3.1.xsd=org/springframework/scheduling/config/spring-task-3.1.xsd
+http\://www.springframework.org/schema/task/spring-task-3.2.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/task/spring-task.xsd=org/springframework/scheduling/config/spring-task-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.1.xsd=org/springframework/cache/config/spring-cache-3.1.xsd
+http\://www.springframework.org/schema/cache/spring-cache-3.2.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/cache/spring-cache.xsd=org/springframework/cache/config/spring-cache-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd=org/springframework/web/servlet/config/spring-mvc-3.0.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.1.xsd=org/springframework/web/servlet/config/spring-mvc-3.1.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc-3.2.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/mvc/spring-mvc.xsd=org/springframework/web/servlet/config/spring-mvc-3.2.xsd
+http\://www.springframework.org/schema/security/spring-security-3.1.xsd=org/springframework/security/config/spring-security-3.1.xsd
+http\://www.springframework.org/schema/security/spring-security-3.2.xsd=org/springframework/security/config/spring-security-3.2.xsd
+
--- /dev/null
+# Tooling related information for the beans namespace
+http\://www.springframework.org/schema/beans@name=beans Namespace
+http\://www.springframework.org/schema/beans@prefix=beans
+http\://www.springframework.org/schema/beans@icon=org/springframework/beans/factory/xml/spring-beans.gif
+
+# Tooling related information for the util namespace
+http\://www.springframework.org/schema/util@name=util Namespace
+http\://www.springframework.org/schema/util@prefix=util
+http\://www.springframework.org/schema/util@icon=org/springframework/beans/factory/xml/spring-util.gif
+
+# Tooling related information for the context namespace
+http\://www.springframework.org/schema/context@name=context Namespace
+http\://www.springframework.org/schema/context@prefix=context
+http\://www.springframework.org/schema/context@icon=org/springframework/context/config/spring-context.gif
+
+# Tooling related information for the jee namespace
+http\://www.springframework.org/schema/jee@name=jee Namespace
+http\://www.springframework.org/schema/jee@prefix=jee
+http\://www.springframework.org/schema/jee@icon=org/springframework/ejb/config/spring-jee.gif
+
+# Tooling related information for the scheduling namespace
+http\://www.springframework.org/schema/task@name=task Namespace
+http\://www.springframework.org/schema/task@prefix=task
+http\://www.springframework.org/schema/task@icon=org/springframework/scheduling/config/spring-task.gif
+
+# Tooling related information for the lang namespace
+http\://www.springframework.org/schema/lang@name=lang Namespace
+http\://www.springframework.org/schema/lang@prefix=lang
+http\://www.springframework.org/schema/lang@icon=org/springframework/scripting/config/spring-lang.gif
+
+# Tooling related information for the cache namespace
+http\://www.springframework.org/schema/cache@name=cache Namespace
+http\://www.springframework.org/schema/cache@prefix=cache
+http\://www.springframework.org/schema/cache@icon=org/springframework/cache/config/spring-cache.gif
+
+# Tooling related information for the mvc namespace
+http\://www.springframework.org/schema/mvc@name=mvc Namespace
+http\://www.springframework.org/schema/mvc@prefix=mvc
+http\://www.springframework.org/schema/mvc@icon=org/springframework/web/servlet/config/spring-mvc.gif
--- /dev/null
+<?xml version="1.0" encoding="UTF-8"?>
+<beans xmlns="http://www.springframework.org/schema/beans"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xmlns:context="http://www.springframework.org/schema/context"
+ xmlns:mvc="http://www.springframework.org/schema/mvc"
+ xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
+ http://www.springframework.org/schema/mvc http://www.springframework.org/schema/mvc/spring-mvc-3.0.xsd
+ http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">
+
+ <context:component-scan base-package="org.sdnhub.odl.learningswitch"/>
+
+ <mvc:resources mapping="/js/**" location="/js/" />
+ <mvc:resources mapping="/css/**" location="/css/" />
+ <mvc:resources mapping="/img/**" location="/img/" />
+ <mvc:annotation-driven/>
+
+ <bean id="viewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
+ <property name="prefix" value="/WEB-INF/jsp/"/>
+ <property name="suffix" value=".jsp"/>
+ </bean>
+</beans>
--- /dev/null
+<!DOCTYPE html>
+<html>
+ <head>
+ <meta charset="utf-8">
+ <meta http-equiv="X-UA-Compatible" content="IE=edge">
+ <title>App</title>
+ <meta name="description" content="">
+ <meta name="viewport" content="width=device-width, initial-scale=1">
+
+ <!-- style -->
+ <link rel="stylesheet" href="/css/ext/pure/pure.css"/>
+ <link rel="stylesheet" href="/css/phoenix.css"/>
+
+ <!-- style app -->
+ <link rel="stylesheet" href="/learningswitch/web/css/simple.css"/>
+
+ <!-- scripts -->
+ <script data-main="/learningswitch/web/js/main" src="/js/ext/requirejs/require.js"></script>
+
+ </head>
+
+ <body>
+
+
+ <h2> Learning Switch </h2>
+
+ <h4> Currently functioning as <div id="mode"></div> </h4>
+ <button id="togglemode" class="pure-button
+ button-success">Toggle</button>
+
+ <h3> Select node </h3>
+ <select id="nodeselect"> </select>
+ <button id="refreshNodeList" class="pure-button
+ button-success">Refresh Node List</button>
+
+ <!-- full-width outer box -->
+
+ <h3> Switch Mac Table </h3>
+
+ <div class="pure-g">
+ <div class="pure-u-1 pure-u-md-1-1">
+ <!-- split-width inner box for heaer -->
+ <table class="pure-table" id="mactable" style="">
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>MAC</th>
+ <th>Nodeconnector</th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+
+ </div>
+ </div> <!-- outer box ends -->
+ <p />
+ <button id="refreshMacTable" class="pure-button button-success">Refresh</button>
+ <button id="clearMacTable" class="pure-button button-error">Clear</button>
+
+
+ <p /><p />
+
+
+
+ <h3>Flow tables</h3>
+ <div class="pure-g">
+ <div class="pure-u-1 pure-u-md-1-1">
+ <table class="pure-table" id="flowtable" style="">
+ <thead>
+ <tr>
+ <th>#</th>
+ <th>Flow Details</th>
+ </tr>
+ </thead>
+ <tbody>
+ </tbody>
+ </table>
+ </div>
+ </div>
+ <p />
+ <button id="refreshFlowTable" class="pure-button button-success">Refresh</button>
+
+
+
+
+ </body>
+</html>
--- /dev/null
+<?xml version="1.0" encoding="ISO-8859-1"?>
+<web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
+ version="3.0">
+
+ <security-constraint>
+ <display-name>App</display-name>
+ <web-resource-collection>
+ <web-resource-name>AppWeb</web-resource-name>
+ <url-pattern>/web/js/*</url-pattern>
+ <url-pattern>/web/images/*</url-pattern>
+ <url-pattern>/web/css/*</url-pattern>
+ <url-pattern>/web/favicon.ico</url-pattern>
+ </web-resource-collection>
+ <web-resource-collection>
+ <web-resource-name>App</web-resource-name>
+ <url-pattern>/*</url-pattern>
+ </web-resource-collection>
+ <web-resource-collection>
+ <web-resource-name>AppNorthbound</web-resource-name>
+ <url-pattern>/northbound/*</url-pattern>
+ <http-method>POST</http-method>
+ <http-method>GET</http-method>
+ <http-method>PUT</http-method>
+ <http-method>PATCH</http-method>
+ <http-method>DELETE</http-method>
+ <http-method>HEAD</http-method>
+ </web-resource-collection>
+ <auth-constraint>
+ <role-name>System-Admin</role-name>
+ <role-name>Network-Admin</role-name>
+ <role-name>Network-Operator</role-name>
+ <role-name>Container-User</role-name>
+ </auth-constraint>
+ </security-constraint>
+
+ <security-role>
+ <role-name>System-Admin</role-name>
+ </security-role>
+ <security-role>
+ <role-name>Network-Admin</role-name>
+ </security-role>
+ <security-role>
+ <role-name>Network-Operator</role-name>
+ </security-role>
+ <security-role>
+ <role-name>Container-User</role-name>
+ </security-role>
+
+ <!-- <login-config> // enabling this auto directs to login page, considering removing this
+ <auth-method>FORM</auth-method>
+ <form-login-config>
+ <form-login-page>/WEB-INF/jsp/login.jsp</form-login-page>
+ <form-error-page>/WEB-INF/jsp/error.jsp</form-error-page>
+ </form-login-config>
+ </login-config>-->
+
+ <login-config>
+ <auth-method>BASIC</auth-method>
+ <realm-name>opendaylight</realm-name>
+ </login-config>
+
+ <!-- <error-page>
+ <error-code>403</error-code>
+ <location>/WEB-INF/jsp/autherror.jsp</location>
+ </error-page> -->
+
+ <!-- web -->
+ <servlet>
+ <servlet-name>AppWeb</servlet-name>
+ <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>AppWeb</servlet-name>
+ <url-pattern>/web/*</url-pattern>
+ </servlet-mapping>
+
+ <listener>
+ <listener-class>org.opendaylight.toolkit.web.ControllerUISessionManager</listener-class>
+ </listener>
+
+ <!-- <session-config> // needs further testing
+ <cookie-config>
+ <path>/</path>
+ </cookie-config>
+ </session-config>-->
+
+ <!-- northbound -->
+ <servlet>
+ <servlet-name>AppNorthbound</servlet-name>
+ <servlet-class>com.sun.jersey.spi.container.servlet.ServletContainer</servlet-class>
+ <init-param>
+ <param-name>javax.ws.rs.Application</param-name>
+ <param-value>org.opendaylight.controller.northbound.commons.NorthboundApplication</param-value>
+ </init-param>
+ <load-on-startup>1</load-on-startup>
+ </servlet>
+ <servlet-mapping>
+ <servlet-name>AppNorthbound</servlet-name>
+ <url-pattern>/northbound/*</url-pattern>
+ </servlet-mapping>
+
+ <filter>
+ <filter-name>CorsFilter</filter-name>
+ <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
+ <init-param>
+ <param-name>cors.allowed.origins</param-name>
+ <param-value>*</param-value>
+ </init-param>
+ <init-param>
+ <param-name>cors.allowed.methods</param-name>
+ <param-value>GET,POST,HEAD,OPTIONS,PUT,DELETE</param-value>
+ </init-param>
+ <init-param>
+ <param-name>cors.allowed.headers</param-name>
+ <param-value>Content-Type,X-Requested-With,accept,authorization, origin,Origin,Access-Control-Request-Method,Access-Control-Request-Headers</param-value>
+ </init-param>
+ <init-param>
+ <param-name>cors.exposed.headers</param-name>
+ <param-value>Access-Control-Allow-Origin,Access-Control-Allow-Credentials</param-value>
+ </init-param>
+ <init-param>
+ <param-name>cors.support.credentials</param-name>
+ <param-value>true</param-value>
+ </init-param>
+ <init-param>
+ <param-name>cors.preflight.maxage</param-name>
+ <param-value>10</param-value>
+ </init-param>
+ </filter>
+
+ <filter-mapping>
+ <filter-name>CorsFilter</filter-name>
+ <url-pattern>/northbound/*</url-pattern>
+ </filter-mapping>
+
+</web-app>
--- /dev/null
+{
+ "name" : "OpenDaylight Phoenix External CSS",
+ "version" : "0.0.1",
+ "dependencies" : {
+ "pure" : "v0.4.2"
+ }
+}
--- /dev/null
+/* pure */
+.selected {
+ background: #F3F781
+}
+
+.button-success,
+.button-error {
+ color: white;
+ border-radius: 4px;
+ text-shadow: 0 1px 1px rgba(0, 0, 0, 0.2);
+}
+
+.button-success {
+ background: rgb(28, 184, 65); /* this is a green */
+}
+
+.button-error {
+ background: rgb(202, 60, 60); /* this is a maroon */
+}
+
+
+fieldset {
+}
+
+fieldset > label {
+ display: block;
+}
+
+fieldset > input {
+ width: 100%;
+}
+
+#simpleContainer {
+ margin-top: 15px;
+}
+body {
+ background: #fff;
+ padding-top:20px;
+ padding-left:30px;
+ padding-right:50px;
+ padding-bottom:10px;
+ background-color:#efefef;
+}
+
+h2 {
+ color: #646464;
+}
+
+h3 {
+ color: #464646;
+}
+
+ol > li {
+ padding: 2px 0;
+}
+
+code {
+ background: #F3F781;
+ padding: 2px 4px;
+ font-weight: bold;
+ color: #000;
+ border-radius: 2px;
+}
--- /dev/null
+//Filename: app.js
+
+define
+([
+ // These are path alias that we configured in our bootstrap
+ 'jquery',
+ 'jquery-ui',
+ 'underscore',
+ ], function($, _)
+ {
+ // ajax settings
+ $.ajaxSetup({
+ data: {},
+ dataType: 'json',
+ xhrFields: {
+ withCredentials: true
+ },
+ crossDomain: true,
+ success: 'callback',
+ headers: {
+ 'Accept': 'application/json',
+ 'Content-Type': 'application/json'
+ }
+ });
+
+ var controller_uri = "http://127.0.0.1:8080";
+
+ var switchfunction = "";
+
+
+ var nodelist = [];
+
+ var mactable = {
+ findNode: function (nodename)
+ {
+ var ret = [];
+ if (!mactable.obj) {
+ return undefined;
+ }
+
+ nodelist = [];
+ $.each(mactable.obj.table, function(i, entry) {
+ if (entry.node) {
+ var nodenm = entry.node.type + "|" + entry.node.id;
+ nodelist.push(nodenm);
+ if (nodenm == nodename) {
+ $.each(entry.table, function(ii, elem) {
+ var x = {};
+ x.mac = elem.first;
+ x.nodeconnector = elem.second.nodeconnector.type + "|" + elem.second.nodeconnector.id;
+ ret.push(x);
+ });
+ }
+ }
+ });
+ return ret;
+ },
+
+ obj: undefined
+ };
+
+ var flowtable = {
+ findNode: function(nodename)
+ {
+ var ret = [];
+ if (!flowtable.obj) {
+ return undefined;
+ }
+
+ nodelist = [];
+ $.each(flowtable.obj, function(i, entry) {
+ if (entry.node) {
+
+ nodelist.push(entry.node);
+
+ if (entry.node == nodename) {
+ $.each(entry.flows, function(ii, flow) {
+ ret.push(flow);
+ });
+ }
+ }
+ });
+ return ret;
+ },
+ obj: undefined
+ };
+
+ function populateFlowTable(data) {
+
+ var tbody = $('#flowtable tbody');
+ var rows = $('#flowtable tbody > tr');
+ rows.remove();
+
+ var selectednode = $("#nodeselect option:selected").val();
+ var data = flowtable.findNode(selectednode);
+ console.log("flow table data: " + JSON.stringify(data));
+
+ $.each(data, function(i, elem) {
+ var tr = $(document.createElement('tr'));
+ tr.append($(document.createElement('td')).append(i));
+ tr.append($(document.createElement('td')).append(elem));
+ tbody.append(tr);
+ console.log("appending : " + tr);
+ });
+ }
+
+ function populateMacTable(data) {
+ var tbody = $('#mactable tbody');
+ var rows = $('#mactable tbody > tr');
+ rows.remove();
+
+ var selectednode = $("#nodeselect option:selected").val();
+ var data = mactable.findNode(selectednode);
+ console.log("mac table data: " + JSON.stringify(data));
+
+ $.each(data, function(i, elem) {
+ var tr = $(document.createElement('tr'));
+
+ tr.append($(document.createElement('td')).append(i));
+ tr.append($(document.createElement('td')).append(elem.mac));
+ tr.append($(document.createElement('td')).append(elem.nodeconnector));
+
+ tbody.append(tr);
+ console.log("appending : " + tr);
+ });
+ }
+
+ // bind form submit
+ // $('button').click(function() {
+ // var simple = {};
+ // simple.foo = $('#foo').val();
+ // simple.bar = $('#bar').val();
+ // $.post('http://127.0.0.1:8080/simple/northbound/simple', JSON.stringify(simple), function(result) {
+ // console.log(result);
+ // });
+ // });
+
+ function refreshNodeList() {
+ var foo = flowtable.findNode("asdf"); // dummy to get node list to refresh
+ var select = $("#nodeselect");
+ var selectoptions = $("#nodeselect option");
+ selectoptions.remove();
+ $.each(nodelist, function(i, node) {
+ select.append('<option value="' + node + '">' + node + '</option>');
+ });
+ }
+
+ function updateMode() {
+ $.getJSON(controller_uri + '/learningswitch/northbound/learningswitch/function', function(result) {
+ console.log("Got data " + JSON.stringify(result));
+ switchfunction = result.function;
+ $("#mode").html(result.function);
+ });
+ }
+
+ $('#refreshNodeList').click(function() {
+ // populate table
+ $.getJSON(controller_uri + '/learningswitch/northbound/learningswitch/flowtable', function(result) {
+ flowtable.obj = result;
+ refreshNodeList();
+ });
+ });
+
+ $('#refreshMacTable').click(function() {
+ // populate table
+ $.getJSON(controller_uri + '/learningswitch/northbound/learningswitch/table', function(result) {
+ console.log("Got result: " + JSON.stringify(result));
+ mactable.obj = result;
+ populateMacTable();
+ });
+ });
+
+
+ $('#refreshFlowTable').click(function() {
+ // populate table
+ $.getJSON(controller_uri + '/learningswitch/northbound/learningswitch/flowtable', function(result) {
+ console.log("Got result: " + JSON.stringify(result));
+ flowtable.obj = result;
+ populateFlowTable();
+ });
+ });
+
+ $('#clearFlowTable').click(function() {
+ // populate table
+ $.getJSON(controller_uri + '/learningswitch/northbound/learningswitch/flowtable', function(result) {
+ console.log("Got result: " + JSON.stringify(result));
+ flowtable.obj = result;
+ populateFlowTable();
+ });
+ });
+
+ $("#nodeselect").change(function() {
+ populateMacTable();
+ populateFlowTable();
+ });
+
+ $('#clearMacTable').click(function() {
+ // populate table
+ $.ajax({
+ url: controller_uri + '/learningswitch/northbound/learningswitch/table',
+ type: 'DELETE',
+ });
+
+ updateMode();
+ });
+
+ $('#togglemode').click(function() {
+ $.ajax({
+ type: 'PUT',
+ url: controller_uri + '/learningswitch/northbound/learningswitch/function',
+ contentType: 'application/json',
+ data: {"function": (switchfunction.function == "hub" ? "switch" : "hub") }
+ });
+
+ updateMode();
+ });
+
+ $(updateMode());
+
+});
--- /dev/null
+define(['backbone','underscore','/learningswitch/web/js/models/FlowModel.js'],
+ function(Backbone, _, FlowModel) {
+ var FlowCollection = Backbone.Collection.extend({
+ model : FlowModel,
+ url : '/learningswitch/northbound/learningswitch/flowtable',
+ parse: function(data) {
+ console.log("Received data" + data);
+
+ }
+
+ });
+ return FlowCollection;
+});
--- /dev/null
+define(
+ [
+ 'backbone',
+ 'underscore',
+ '/learningswitch/web/js/models/SimpleModel.js'
+ ], function(Backbone, _, SimpleModel) {
+ var SimpleCollection = Backbone.Collection.extend({
+ model : SimpleModel,
+ url : '/learningswitch/northbound/learningswitch/mactable'
+ });
+ return SimpleCollection;
+ });
--- /dev/null
+// Filename: main.js
+
+require.config({
+ paths: {
+ 'jquery': '/js/ext/jquery/dist/jquery',
+ 'jquery-ui': '/js/ext/jquery-ui/ui/minified/jquery-ui.min',
+ 'underscore': '/js/ext/underscore/underscore'
+ },
+ shim: {
+ 'jquery-ui' : {
+ exports: '$',
+ deps: ['jquery']
+ }
+ }
+});
+
+require([
+ 'app', '/js/phoenix.js'
+], function(App, Phoenix) {
+ new Phoenix.initialize();
+
+});
--- /dev/null
+define(['backbone', 'underscore'], function(Backbone, _) {
+ var FlowModel = Backbone.Model.extend({
+ idAttribute : 'node',
+ defaults : {
+ node : '',
+ flows : new Array()
+ },
+ initialize : function() {
+ },
+ setUrlRoot: function() {
+ this.urlRoot = '/learningswitch/northbound/learningswitch/flowtable';
+ }
+ });
+ return FlowModel;
+});
+
+
--- /dev/null
+define(['backbone', 'underscore'], function(Backbone, _) {
+ var SimpleModel = Backbone.Model.extend({
+ idAttribute : 'mac',
+ defaults : {
+ mac : '',
+ nodeconnector : ''
+ },
+ initialize : function() {
+ },
+ setUrlRoot: function() {
+ this.urlRoot = '/learningswitch/northbound/learningswitch/mactable';
+ }
+ });
+ return SimpleModel;
+});
--- /dev/null
+<script type="text/template" id="flowContainer">
+ <div id="flowDiv" style="margin-left:20px; margin-right: 100px; float: left;">
+
+<h4>Programmed flows</h4>
+
+ <% _.each(flowdata, function(flowdata1) { %>
+<h5>Flows for <%= flowdata1.attributes.node %> </h5>
+<table id="programmedFlowTablefor<%= flowdata1.attributes.node %>" class="pure-table pure-table-bordered">
+ <thead>
+ <tr>
+ <th>Flow</th>
+ </tr>
+ </thead>
+ <tbody>
+ <% _.each(flowdata1.attributes.flows, function(flow) { %>
+ <tr>
+ <td><%= flow %></td>
+ </tr>
+ <% }); %>
+ </tbody>
+</table>
+<% }); %>
+</div>
+</script>
\ No newline at end of file
--- /dev/null
+<script type="text/template" id="simpleContainer">
+ <div id="simpleDiv" style="margin-left:20px; margin-right:100px; float: left;">
+ <h3>Learning Switch </h3>
+
+ <h4>Mac -> Port Table</h4>
+ <table id="simpleTable" class="pure-table pure-table-bordered">
+ <thead>
+ <tr>
+ <th>MAC Address</th>
+ <th>Port ID</th>
+ <th>Actions</th>
+ </tr>
+ </thead>
+ <tbody>
+ <% _.each(simple, function(simpleton) { %>
+ <tr data-id="<%= simpleton.mac %>">
+ <td><%= simpleton.attributes.mac %></td>
+ <td><%= simpleton.attributes.connector %></td>
+ <td><button id="removeMacPortEntry" class="pure-button button-error" onclick="return false;">Remove</button></td>
+ </tr>
+ <% }); %>
+ <tr>
+ <td><input type="text" id="macInput" placeholder="Mac addr, like 00:00:72:41:4c:e6:c2:01" /></td>
+ <td><input type="text" id="ncInput" placeholder="Port info, like OF|1@OF|00:00:00:00:00:00:00:01" /></td>
+ <td><button id="addMacPortEntry" class="pure-button button-success" onclick="return false;">Add</button></td>
+ </tr>
+
+ </tbody>
+ </table>
+
+ </div>
+
+
+
+
+</script>
--- /dev/null
+\r
+ <div id="main">\r
+ <div id="chart1"></div> \r
+ </div>\r
+ \r
+ <script class="code" language="javascript" type="text/javascript">\r
+ \r
+ alert("page loaded! ");\r
+ \r
+ \r
+\r
+ </script>\r
--- /dev/null
+define(
+ [
+ 'jquery',
+ 'backbone',
+ 'underscore',
+ '/learningswitch/web/js/models/FlowModel.js',
+ '/learningswitch/web/js/collections/FlowCollection.js',
+ '/js/ext/text/text.js!/learningswitch/web/js/templates/flowtemplate.html'
+ ], function($, Backbone, _, FlowCollection, FlowModel, Template) {
+ var FlowTableView = Backbone.View.extend({
+ el: $("#flowtablediv"),
+ initialize: function() {
+ var self = this;
+
+ this.flowcollection = new FlowCollection();
+ this.flowcollection.url = '/learningswitch/northbound/learningswitch/flowtable';
+ this.flowcollection.fetch({
+ success : function(call, response) {
+ self.renderflowtable();
+ }
+ });
+ },
+
+ renderflowtable: function() {
+ var that = this;
+ var compiledTemplate = _.template(Template,
+ {
+ flowdata : that.flowcollection.models
+ });
+ $(this.el).append($(compiledTemplate).html());
+ },
+
+ events : {
+ 'click #simpleContainer button' : 'handleSimpleButton',
+ 'click #simpleTable tbody tr' : 'tableRowClicked'
+ },
+
+ handleSwitchHubToggle : function(evt) {
+ debugger;
+ var self = this;
+ var $button = $(evt.currentTarget);
+ if ($button.attr('id') == 'toggleButton') {
+ $.get( "/learningswitch/northbound/learningswitch/toggle", function( data ) {
+ $("#toggleButton").val(data);
+ });
+ }
+ },
+
+ updateView : function() {
+ $('#flowContainer').remove();
+ this.initialize();
+ }
+ });
+ return FlowTableView;
+ });
--- /dev/null
+define(
+ [
+ 'jquery',
+ 'backbone',
+ 'underscore',
+ '/learningswitch/web/js/collections/SimpleCollection.js',
+ '/learningswitch/web/js/models/SimpleModel.js',
+ '/js/ext/text/text.js!/learningswitch/web/js/templates/simple.html'
+ ], function($, Backbone, _, SimpleCollection, SimpleModel, Template) {
+ var View = Backbone.View.extend({
+ el: $("#mactablediv"),
+ initialize: function() {
+ var self = this;
+ this.maccollection = new SimpleCollection();
+ this.maccollection.url = '/learningswitch/northbound/learningswitch/mactable';
+ this.maccollection.fetch({
+ success : function(call, response) {
+ self.rendermactable();
+ }
+ });
+
+
+
+ },
+
+ rendermactable: function() {
+ var that = this;
+ var compiledTemplate = _.template(Template,
+ {
+ simple : that.maccollection.models
+ });
+ $(this.el).append($(compiledTemplate).html());
+ },
+
+ events : {
+ 'click #simpleContainer button' : 'handleSimpleButton',
+ 'click #simpleTable tbody tr' : 'tableRowClicked'
+ },
+
+ handleSimpleButton : function(evt) {
+ var self = this;
+ var $button = $(evt.currentTarget);
+ if ($button.attr('id') == 'simpleButton') {
+ var simpleModel = new SimpleModel({
+ foo : $('#simpleFooInput').val(),
+ bar : $('#simpleBarInput').val()
+ });
+ simpleModel.urlRoot = '/learningswitch/northbound/learningswitch';
+ simpleModel.save(null, {
+ dataType: 'text',
+ success: function(model, response) {
+ $('#main').empty();
+ self.updateView();
+ }
+ });
+ } else if ($button.attr('id') == 'simpleRemoveButton') {
+ var id = $('#simpleTable tbody tr.selected').attr('data-id');
+ var simpleModel = self.collection.get(id);
+ simpleModel.setUrlRoot();
+ simpleModel.destroy({
+ dataType: 'text',
+ success: function() {
+ $('#main').empty();
+ self.updateView();
+ },
+ error: function() {
+ $('#main').empty();
+ self.updateView();
+ }
+ });
+ } else {
+ // cancel button
+ $('#simpleInput').val('');
+ }
+ },
+ tableRowClicked : function(evt) {
+ $('#simpleTable tbody tr.selected').removeClass('selected');
+ var $tr = $(evt.currentTarget);
+ $tr.addClass('selected');
+
+
+ },
+ updateView : function() {
+ $('#simpleContainer').remove();
+ this.initialize();
+ }
+ });
+ return View;
+ });