Giant patch of changes to support OpenFlow 1.3
The following people have contributed to this patch:
- Ali Al-Shabibi <alshabibi.ali@gmail.com>
- Ayaka Koshibe <ayaka@onlab.us>
- Brian O'Connor <bocon@onlab.us>
- Jonathan Hart <jono@onlab.us>
- Matteo Gerola <mgerola@create-net.org>
- Michele Santuari <michele.santuari@create-net.org>
- Pavlin Radoslavov <pavlin@onlab.us>
- Saurav Das <sauravdas@alumni.stanford.edu>
- Toshio Koide <t-koide@onlab.us>
- Yuta HIGUCHI <y-higuchi@onlab.us>
The patch includes the following changes:
- New Floodlight I/O loop / state machine
- New switch/port handling
- New role management (incl. Role.EQUAL)
- Added Floodlight debug framework
- Updates to Controller.java
- Move to Loxigen's OpenflowJ library
- Added OF1.3 support
- Added support for different switches (via DriverManager)
- Updated ONOS modules to use new APIs
- Added and updated unit tests
Change-Id: Ic70a8d50f7136946193d2ba2e4dc0b4bfac5f599
diff --git a/src/main/java/net/floodlightcontroller/debugevent/CircularBuffer.java b/src/main/java/net/floodlightcontroller/debugevent/CircularBuffer.java
new file mode 100644
index 0000000..f1a6db3
--- /dev/null
+++ b/src/main/java/net/floodlightcontroller/debugevent/CircularBuffer.java
@@ -0,0 +1,95 @@
+package net.floodlightcontroller.debugevent;
+
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.concurrent.LinkedBlockingDeque;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class CircularBuffer<T> implements Iterable<T>{
+ protected static Logger log = LoggerFactory.getLogger(CircularBuffer.class);
+ private final LinkedBlockingDeque<T> buffer;
+
+ public CircularBuffer(int capacity) {
+ this.buffer = new LinkedBlockingDeque<T>(capacity);
+ }
+
+ /**
+ * Adding an element to the circular buffer implies adding the element to
+ * the tail of the deque. In doing so, if the capacity of the buffer has
+ * been exhausted, then the deque's head should be removed to preserve the
+ * circular nature of the buffer. A LinkedBlockingDeque is used because of its
+ * concurrent nature and the fact that adding to tail and removing from head are
+ * both O(1) operations. The removed head is returned to the caller for reuse.
+ *
+ * @param e the element to be added
+ * @return removed element (for reuse) or null
+ */
+ public T add(T e) {
+ T oldE = null;
+ while (!buffer.offerLast(e)) {
+ oldE = buffer.poll();
+ }
+ return oldE;
+ }
+
+ /**
+ * The basic idea here is that an ArrayList has been passed in, which may or may not
+ * have a size bigger that the actual number of elements that are meant to
+ * be flushed to the Circular Buffer. Thus the 'uptoIndex' parameter specifies
+ * the number of elements that need to be flushed starting from index 0.
+ * Note that after flushing, the circular buffer may return a memory unit (of type T)
+ * for reuse in the list, if the circular buffer popped off memory to preserve
+ * its circular nature. Or it may just return null if nothing was popped off.
+ * Either way, the list that is returned by this method, is of the SAME SIZE
+ * as the list passed in, as ArrayLists can hold null elements. The only difference
+ * is that the list returned will have elements that reference old popped-off memory
+ * from the circular-buffer or null.
+ *
+ * @param elist the ArrayList to flush into the circular buffer.
+ * @param uptoIndex flush starting from index 0 upto but not including
+ * index 'uptoIndex'.
+ * @return the 'elist' passed in with members now pointing to
+ * to null or old-memory for reuse. The returned list
+ * if of the same size as 'elist'.
+ */
+ public ArrayList<T> addAll(ArrayList<T> elist, int uptoIndex) {
+ if (uptoIndex > elist.size()) {
+ log.error("uptoIndex is greater than elist size .. aborting addAll");
+ return elist;
+ }
+ for (int index=0; index < uptoIndex; index++) {
+ T e = elist.get(index);
+ if (e != null) {
+ elist.set(index, add(e));
+ }
+ }
+ return elist;
+ }
+
+ /**
+ * Returns an iterator over the elements in the circular buffer in proper sequence.
+ * The elements will be returned in order from most-recent to oldest.
+ * The returned Iterator is a "weakly consistent" iterator that will never
+ * throw ConcurrentModificationException, and guarantees to traverse elements
+ * as they existed upon construction of the iterator, and may (but is not
+ * guaranteed to) reflect any modifications subsequent to construction.
+ */
+ @Override
+ public Iterator<T> iterator() {
+ return buffer.descendingIterator();
+ }
+
+ public int size() {
+ return buffer.size();
+ }
+
+ /**
+ * Atomically removes all elements in the circular buffer
+ */
+ public void clear() {
+ buffer.clear();
+ }
+}
diff --git a/src/main/java/net/floodlightcontroller/debugevent/DebugEvent.java b/src/main/java/net/floodlightcontroller/debugevent/DebugEvent.java
new file mode 100644
index 0000000..e167b26
--- /dev/null
+++ b/src/main/java/net/floodlightcontroller/debugevent/DebugEvent.java
@@ -0,0 +1,506 @@
+package net.floodlightcontroller.debugevent;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.debugevent.web.DebugEventRoutable;
+import net.floodlightcontroller.restserver.IRestApiService;
+
+import com.google.common.collect.Sets;
+/**
+ * This class implements a central store for all events used for debugging the
+ * system. The basic idea is that given the functionality provided by this class,
+ * it should be unnecessary to resort to scraping through system DEBUG/TRACE logs
+ * to understand behavior in a running system.
+ *
+ * @author saurav
+ */
+public class DebugEvent implements IFloodlightModule, IDebugEventService {
+ protected static Logger log = LoggerFactory.getLogger(DebugEvent.class);
+
+ /**
+ * Every registered event type gets an event id, the value for which is obtained
+ * while holding the lock.
+ */
+ protected int eventIdCounter = 0;
+ protected Object eventIdLock = new Object();
+
+ private static final int PCT_LOCAL_CAP = 10; // % of global capacity
+ private static final int MIN_LOCAL_CAPACITY = 10; //elements
+
+ /**
+ * Event Information
+ */
+ public class EventInfo {
+ int eventId;
+ boolean enabled;
+ int bufferCapacity;
+ EventType etype;
+ String eventDesc;
+ String eventName;
+ String moduleName;
+ String moduleEventName;
+ Class<?> eventClass;
+ String[] metaData;
+
+ public EventInfo(int eventId, boolean enabled, int bufferCapacity,
+ EventType etype, Class<?> eventClass, String eventDesc,
+ String eventName, String moduleName, String... metaData) {
+ this.enabled = enabled;
+ this.eventId = eventId;
+ this.bufferCapacity = bufferCapacity;
+ this.etype = etype;
+ this.eventClass = eventClass;
+ this.eventDesc = eventDesc;
+ this.eventName = eventName;
+ this.moduleName = moduleName;
+ this.moduleEventName = moduleName + "/" + eventName;
+ this.metaData = metaData;
+ }
+
+ public int getEventId() { return eventId; }
+ public boolean isEnabled() { return enabled; }
+ public int getBufferCapacity() { return bufferCapacity; }
+ public EventType getEtype() { return etype; }
+ public String getEventDesc() { return eventDesc; }
+ public String getEventName() { return eventName; }
+ public String getModuleName() { return moduleName; }
+ public String getModuleEventName() { return moduleEventName; }
+ public String[] getMetaData() { return metaData; }
+ }
+
+ //******************
+ // Global stores
+ //******************
+
+ /**
+ * Event history for a particular event-id is stored in a circular buffer
+ */
+ protected class DebugEventHistory {
+ EventInfo einfo;
+ CircularBuffer<Event> eventBuffer;
+
+ public DebugEventHistory(EventInfo einfo, int capacity) {
+ this.einfo = einfo;
+ this.eventBuffer = new CircularBuffer<Event>(capacity);
+ }
+ }
+
+ /**
+ * Global storage for all event types and their corresponding event buffers.
+ * A particular event type is accessed by directly indexing into the array
+ * with the corresponding event-id.
+ */
+ protected DebugEventHistory[] allEvents =
+ new DebugEventHistory[MAX_EVENTS];
+
+ /**
+ * Global storage for all event ids registered for a module. The map is indexed
+ * by the module name and event name and returns the event-ids that correspond to the
+ * event types registered by that module (for example module 'linkdiscovery'
+ * may register events that have ids 0 and 1 that correspond to link up/down
+ * events, and receiving malformed LLDP packets, respectively).
+ */
+ protected ConcurrentHashMap<String, ConcurrentHashMap<String, Integer>>
+ moduleEvents = new ConcurrentHashMap<String,
+ ConcurrentHashMap<String, Integer>>();
+
+ /**
+ * A collection of event ids that are currently enabled for logging
+ */
+ protected Set<Integer> currentEvents = Collections.newSetFromMap(
+ new ConcurrentHashMap<Integer,Boolean>());
+
+ //******************
+ // Thread local stores
+ //******************
+
+ /**
+ * Thread local storage for events
+ */
+ protected class LocalEventHistory {
+ int nextIndex;
+ int maxCapacity;
+ boolean enabled;
+ ArrayList<Event> eventList;
+
+ public LocalEventHistory(boolean enabled, int maxCapacity) {
+ this.nextIndex = 0;
+ this.maxCapacity = maxCapacity;
+ this.enabled = enabled;
+ this.eventList = new ArrayList<Event>();
+ }
+ }
+
+ /**
+ * Thread local event buffers used for maintaining event history local to
+ * a thread. Eventually this locally maintained information is flushed
+ * into the global event buffers.
+ */
+ protected final ThreadLocal<LocalEventHistory[]> threadlocalEvents =
+ new ThreadLocal<LocalEventHistory[]>() {
+ @Override
+ protected LocalEventHistory[] initialValue() {
+ return new LocalEventHistory[MAX_EVENTS];
+ }
+ };
+
+ /**
+ * Thread local cache for event-ids that are currently active.
+ */
+ protected final ThreadLocal<Set<Integer>> threadlocalCurrentEvents =
+ new ThreadLocal<Set<Integer>>() {
+ @Override
+ protected Set<Integer> initialValue() {
+ return new HashSet<Integer>();
+ }
+ };
+
+ //*******************************
+ // IEventUpdater
+ //*******************************
+
+ protected class EventUpdaterImpl<T> implements IEventUpdater<T> {
+ private final int eventId;
+
+ public EventUpdaterImpl(int evId) {
+ this.eventId = evId;
+ }
+
+ @Override
+ public void updateEventNoFlush(Object event) {
+ if (!validEventId()) return;
+ updateEvent(eventId, false, event);
+ }
+
+ @Override
+ public void updateEventWithFlush(Object event) {
+ if (!validEventId()) return;
+ updateEvent(eventId, true, event);
+ }
+
+ private boolean validEventId() {
+ if (eventId < 0 || eventId >= MAX_EVENTS) {
+ throw new IllegalStateException();
+ }
+ return true;
+ }
+
+ }
+
+ //*******************************
+ // IDebugEventService
+ //*******************************
+
+ @Override
+ public <T> IEventUpdater<T> registerEvent(String moduleName, String eventName,
+ String eventDescription, EventType et,
+ Class<T> eventClass, int bufferCapacity,
+ String... metaData) throws MaxEventsRegistered {
+ int eventId = -1;
+ synchronized (eventIdLock) {
+ eventId = Integer.valueOf(eventIdCounter++);
+ }
+ if (eventId > MAX_EVENTS-1) {
+ throw new MaxEventsRegistered();
+ }
+
+ // register event id for moduleName
+ if (!moduleEvents.containsKey(moduleName)) {
+ moduleEvents.put(moduleName, new ConcurrentHashMap<String, Integer>());
+ }
+ if (!moduleEvents.get(moduleName).containsKey(eventName)) {
+ moduleEvents.get(moduleName).put(eventName, new Integer(eventId));
+ } else {
+ int existingEventId = moduleEvents.get(moduleName).get(eventName);
+ log.error("Duplicate event registration for moduleName {} eventName {}",
+ moduleName, eventName);
+ return new EventUpdaterImpl<T>(existingEventId);
+ }
+
+ // create storage for event-type
+ boolean enabled = (et == EventType.ALWAYS_LOG) ? true : false;
+ EventInfo ei = new EventInfo(eventId, enabled, bufferCapacity,
+ et, eventClass, eventDescription, eventName,
+ moduleName, metaData);
+ allEvents[eventId] = new DebugEventHistory(ei, bufferCapacity);
+ if (enabled) {
+ currentEvents.add(eventId);
+ }
+
+ return new EventUpdaterImpl<T>(eventId);
+ }
+
+ private void updateEvent(int eventId, boolean flushNow, Object eventData) {
+ if (eventId < 0 || eventId > MAX_EVENTS-1) return;
+
+ LocalEventHistory[] thishist = this.threadlocalEvents.get();
+ if (thishist[eventId] == null) {
+ // seeing this event for the first time in this thread - create local
+ // store by consulting global store
+ DebugEventHistory de = allEvents[eventId];
+ if (de != null) {
+ boolean enabled = de.einfo.enabled;
+ int localCapacity = de.einfo.bufferCapacity * PCT_LOCAL_CAP/ 100;
+ if (localCapacity < 10) localCapacity = MIN_LOCAL_CAPACITY;
+ thishist[eventId] = new LocalEventHistory(enabled, localCapacity);
+ if (enabled) {
+ Set<Integer> thisset = this.threadlocalCurrentEvents.get();
+ thisset.add(eventId);
+ }
+ } else {
+ log.error("updateEvent seen locally for event {} but no global"
+ + "storage exists for it yet .. not updating", eventId);
+ return;
+ }
+ }
+
+ // update local store if enabled locally for updating
+ LocalEventHistory le = thishist[eventId];
+ if (le.enabled) {
+ long timestamp = System.currentTimeMillis();
+ long thisthread = Thread.currentThread().getId();
+ String thisthreadname = Thread.currentThread().getName();
+ if (le.nextIndex < le.eventList.size()) {
+ if (le.eventList.get(le.nextIndex) == null) {
+ le.eventList.set(le.nextIndex, new Event(timestamp, thisthread,
+ thisthreadname,
+ eventData));
+ } else {
+ Event e = le.eventList.get(le.nextIndex);
+ e.timestamp = timestamp;
+ e.threadId = thisthread;
+ e.threadName = thisthreadname;
+ e.eventData = eventData;
+ e.nullifyCachedFormattedEvent();
+ }
+ } else {
+ le.eventList.add(new Event(timestamp, thisthread, thisthreadname, eventData));
+ }
+ le.nextIndex++;
+
+ if (le.nextIndex >= le.maxCapacity || flushNow) {
+ // flush this buffer now
+ DebugEventHistory de = allEvents[eventId];
+ if (de.einfo.enabled) {
+ le.eventList = de.eventBuffer.addAll(le.eventList, le.nextIndex);
+ } else {
+ // global buffer is disabled - don't flush, disable locally
+ le.enabled = false;
+ Set<Integer> thisset = this.threadlocalCurrentEvents.get();
+ thisset.remove(eventId);
+ }
+ le.nextIndex = 0;
+ }
+ }
+ }
+
+ @Override
+ public void flushEvents() {
+ LocalEventHistory[] thishist = this.threadlocalEvents.get();
+ Set<Integer> thisset = this.threadlocalCurrentEvents.get();
+ ArrayList<Integer> temp = new ArrayList<Integer>();
+
+ for (int eventId : thisset) {
+ LocalEventHistory le = thishist[eventId];
+ if (le != null && le.nextIndex > 0) {
+ // flush this buffer now
+ DebugEventHistory de = allEvents[eventId];
+ if (de.einfo.enabled) {
+ le.eventList = de.eventBuffer.addAll(le.eventList, le.nextIndex);
+ } else {
+ // global buffer is disabled - don't flush, disable locally
+ le.enabled = false;
+ temp.add(eventId);
+ }
+ le.nextIndex = 0;
+ }
+ }
+ for (int eId : temp)
+ thisset.remove(eId);
+
+ // sync thread local currently enabled set of eventIds with global set.
+ Sets.SetView<Integer> sv = Sets.difference(currentEvents, thisset);
+ for (int eventId : sv) {
+ if (thishist[eventId] != null) {
+ thishist[eventId].enabled = true;
+ thisset.add(eventId);
+ }
+ }
+
+ }
+
+ @Override
+ public boolean containsModuleEventName(String moduleName, String eventName) {
+ if (!moduleEvents.containsKey(moduleName)) return false;
+ if (moduleEvents.get(moduleName).containsKey(eventName)) return true;
+ return false;
+ }
+
+ @Override
+ public boolean containsModuleName(String moduleName) {
+ return moduleEvents.containsKey(moduleName);
+ }
+
+ @Override
+ public List<DebugEventInfo> getAllEventHistory() {
+ List<DebugEventInfo> moduleEventList = new ArrayList<DebugEventInfo>();
+ for (Map<String, Integer> modev : moduleEvents.values()) {
+ for (int eventId : modev.values()) {
+ DebugEventHistory de = allEvents[eventId];
+ if (de != null) {
+ List<Map<String,String>> ret = new ArrayList<Map<String,String>>();
+ for (Event e : de.eventBuffer) {
+ ret.add(e.getFormattedEvent(de.einfo.eventClass,
+ de.einfo.moduleEventName));
+ }
+ moduleEventList.add(new DebugEventInfo(de.einfo, ret));
+ }
+ }
+ }
+ return moduleEventList;
+ }
+
+ @Override
+ public List<DebugEventInfo> getModuleEventHistory(String moduleName) {
+ if (!moduleEvents.containsKey(moduleName)) return Collections.emptyList();
+ List<DebugEventInfo> moduleEventList = new ArrayList<DebugEventInfo>();
+ for (int eventId : moduleEvents.get(moduleName).values()) {
+ DebugEventHistory de = allEvents[eventId];
+ if (de != null) {
+ List<Map<String,String>> ret = new ArrayList<Map<String,String>>();
+ for (Event e : de.eventBuffer) {
+ ret.add(e.getFormattedEvent(de.einfo.eventClass,
+ de.einfo.moduleEventName));
+ }
+ moduleEventList.add(new DebugEventInfo(de.einfo, ret));
+ }
+ }
+ return moduleEventList;
+ }
+
+ @Override
+ public DebugEventInfo getSingleEventHistory(String moduleName, String eventName,
+ int last) {
+ if (!moduleEvents.containsKey(moduleName)) return null;
+ Integer eventId = moduleEvents.get(moduleName).get(eventName);
+ if (eventId == null) return null;
+ DebugEventHistory de = allEvents[eventId];
+ if (de != null) {
+ int num = 1;
+ List<Map<String,String>> ret = new ArrayList<Map<String,String>>();
+ for (Event e : de.eventBuffer) {
+ if (num > last)
+ break;
+ Map<String, String> temp = e.getFormattedEvent(de.einfo.eventClass,
+ de.einfo.moduleEventName);
+ temp.put("#", String.valueOf(num++));
+ ret.add(temp);
+ }
+ return new DebugEventInfo(de.einfo, ret);
+ }
+ return null;
+ }
+
+ @Override
+ public void resetAllEvents() {
+ for (Map<String, Integer> eventMap : moduleEvents.values()) {
+ for (Integer evId : eventMap.values()) {
+ allEvents[evId].eventBuffer.clear();
+ }
+ }
+ }
+
+ @Override
+ public void resetAllModuleEvents(String moduleName) {
+ if (!moduleEvents.containsKey(moduleName)) return;
+ Map<String, Integer> modEvents = moduleEvents.get(moduleName);
+ for (Integer evId : modEvents.values()) {
+ allEvents[evId].eventBuffer.clear();
+ }
+ }
+
+ @Override
+ public void resetSingleEvent(String moduleName, String eventName) {
+ if (!moduleEvents.containsKey(moduleName)) return;
+ Integer eventId = moduleEvents.get(moduleName).get(eventName);
+ if (eventId == null) return;
+ DebugEventHistory de = allEvents[eventId];
+ if (de != null) {
+ de.eventBuffer.clear();
+ }
+ }
+
+ @Override
+ public List<String> getModuleList() {
+ List<String> el = new ArrayList<String>();
+ el.addAll(moduleEvents.keySet());
+ return el;
+ }
+
+ @Override
+ public List<String> getModuleEventList(String moduleName) {
+ if (!moduleEvents.containsKey(moduleName))
+ return Collections.emptyList();
+ List<String> el = new ArrayList<String>();
+ el.addAll(moduleEvents.get(moduleName).keySet());
+ return el;
+ }
+
+ //*******************************
+ // IFloodlightModule
+ //*******************************
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleServices() {
+ Collection<Class<? extends IFloodlightService>> l =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ l.add(IDebugEventService.class);
+ return l;
+ }
+
+ @Override
+ public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() {
+ Map<Class<? extends IFloodlightService>, IFloodlightService> m =
+ new HashMap<Class<? extends IFloodlightService>, IFloodlightService>();
+ m.put(IDebugEventService.class, this);
+ return m;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>> getModuleDependencies() {
+ ArrayList<Class<? extends IFloodlightService>> deps =
+ new ArrayList<Class<? extends IFloodlightService>>();
+ deps.add(IRestApiService.class);
+ return deps;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+ IRestApiService restService =
+ context.getServiceImpl(IRestApiService.class);
+ restService.addRestletRoutable(new DebugEventRoutable());
+ DebugEventAppender.setDebugEventServiceImpl(this);
+ }
+
+}
diff --git a/src/main/java/net/floodlightcontroller/debugevent/DebugEventAppender.java b/src/main/java/net/floodlightcontroller/debugevent/DebugEventAppender.java
new file mode 100644
index 0000000..3429675
--- /dev/null
+++ b/src/main/java/net/floodlightcontroller/debugevent/DebugEventAppender.java
@@ -0,0 +1,96 @@
+package net.floodlightcontroller.debugevent;
+
+import net.floodlightcontroller.debugevent.IDebugEventService.EventColumn;
+import net.floodlightcontroller.debugevent.IDebugEventService.EventFieldType;
+import net.floodlightcontroller.debugevent.IDebugEventService.EventType;
+import net.floodlightcontroller.debugevent.IDebugEventService.MaxEventsRegistered;
+import ch.qos.logback.classic.Level;
+import ch.qos.logback.classic.spi.ILoggingEvent;
+import ch.qos.logback.core.UnsynchronizedAppenderBase;
+
+public class DebugEventAppender<E> extends UnsynchronizedAppenderBase<E> {
+ static IDebugEventService debugEvent;
+ static IEventUpdater<WarnErrorEvent> evWarnError;
+ static Thread debugEventRegistryTask = new Thread() {
+ @Override
+ public void run() {
+ while(DebugEventAppender.debugEvent == null) {
+ try {
+ Thread.sleep(100);
+ } catch (InterruptedException e) {
+ return;
+ }
+ }
+ //safe to register debugEvent
+ registerDebugEventQueue();
+ }
+ };
+
+ @Override
+ public void start() {
+ DebugEventAppender.debugEventRegistryTask.start();
+ super.start();
+ }
+
+ public static void setDebugEventServiceImpl(IDebugEventService debugEvent) {
+ DebugEventAppender.debugEvent = debugEvent;
+ // It is now ok to register an event Q - but letting this thread go
+ // since it was called from a startUp() routine
+ }
+
+ /**
+ * The logging system calls append for every log message. This method filters
+ * out the WARN and ERROR message and adds to a debug event queue that can
+ * be accessed via cli or rest-api or gui.
+ */
+ @Override
+ protected void append(E eventObject) {
+ if (!isStarted()) {
+ return;
+ }
+ if (evWarnError != null) {
+ ILoggingEvent ev = ((ILoggingEvent) eventObject);
+ if (ev.getLevel().equals(Level.ERROR) || ev.getLevel().equals(Level.WARN)) {
+ evWarnError.updateEventWithFlush(
+ new WarnErrorEvent(ev.getFormattedMessage(), ev.getLevel(),
+ ev.getThreadName(), ev.getLoggerName()));
+ }
+ }
+ }
+
+ private static void registerDebugEventQueue() {
+ try {
+ evWarnError = debugEvent.registerEvent("net.floodlightcontroller.core",
+ "warn-error-queue",
+ "all WARN and ERROR logs",
+ EventType.ALWAYS_LOG, WarnErrorEvent.class,
+ 100);
+ } catch (MaxEventsRegistered e) {
+ e.printStackTrace();
+ }
+
+ }
+
+ public static class WarnErrorEvent {
+ @EventColumn(name = "message", description = EventFieldType.STRING)
+ String message;
+
+ @EventColumn(name = "level", description = EventFieldType.OBJECT)
+ Level level;
+
+ @EventColumn(name = "threadName", description = EventFieldType.STRING)
+ String threadName;
+
+ @EventColumn(name = "logger", description = EventFieldType.OBJECT)
+ String logger;
+
+ public WarnErrorEvent(String message, Level level, String threadName,
+ String logger) {
+ this.message = message;
+ this.level = level;
+ this.threadName = threadName;
+ this.logger = logger;
+ }
+ }
+
+}
diff --git a/src/main/java/net/floodlightcontroller/debugevent/Event.java b/src/main/java/net/floodlightcontroller/debugevent/Event.java
new file mode 100644
index 0000000..b3e284e
--- /dev/null
+++ b/src/main/java/net/floodlightcontroller/debugevent/Event.java
@@ -0,0 +1,233 @@
+package net.floodlightcontroller.debugevent;
+
+import java.lang.ref.SoftReference;
+import java.lang.reflect.Field;
+import java.text.SimpleDateFormat;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.debugevent.IDebugEventService.EventColumn;
+import net.onrc.onos.core.packet.IPv4;
+import net.onrc.onos.core.util.SwitchPort;
+
+import org.openflow.protocol.OFFlowMod;
+import org.openflow.util.HexString;
+
+public class Event {
+ long timestamp;
+ long threadId;
+ String threadName;
+ Object eventData;
+ private Map<String, String> returnMap;
+
+ public Event(long timestamp, long threadId, String threadName, Object eventData) {
+ super();
+ this.timestamp = timestamp;
+ this.threadId = threadId;
+ this.threadName = threadName;
+ this.eventData = eventData;
+ }
+
+ public long getTimestamp() {
+ return timestamp;
+ }
+
+ public void setTimestamp(long timestamp) {
+ this.timestamp = timestamp;
+ }
+
+ public long getThreadId() {
+ return threadId;
+ }
+
+ public void setThreadId(long threadId) {
+ this.threadId = threadId;
+ }
+
+ public String getThreadName() {
+ return threadName;
+ }
+
+ public void setThreadName(String threadName) {
+ this.threadName = threadName;
+ }
+
+ public Object geteventData() {
+ return eventData;
+ }
+
+ public void seteventData(Object eventData) {
+ this.eventData = eventData;
+ }
+
+ /**
+ * If an old event (eg. popped from a circular buffer) is being re-used for
+ * storing a new event, it is very important to clear the cached formatted
+ * event, so that the formatting can be redone with the new event data.
+ * Otherwise it will appear as if the circular buffer is not getting updated
+ * at all as old (cached) formatted event is delivered to the user.
+ */
+ public void nullifyCachedFormattedEvent() {
+ this.returnMap = null;
+ }
+
+ @Override
+ public String toString() {
+ return "Event [timestamp=" + timestamp + ", threadId=" + threadId
+ + ", eventData=" + eventData.toString() + "]";
+ }
+
+ public Map<String, String> getFormattedEvent(Class<?> eventClass, String moduleEventName) {
+ if (eventClass == null || !eventClass.equals(eventData.getClass())) {
+ returnMap = new HashMap<String, String>();
+ returnMap.put("Error", "null event data or event-class does not match event-data");
+ return returnMap;
+ }
+ // return cached value if there is one
+ if (returnMap != null)
+ return returnMap;
+
+ returnMap = new HashMap<String, String>();
+ returnMap.put("Timestamp", new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss.SSSZ")
+ .format(timestamp));
+ returnMap.put("Thread Id", String.valueOf(threadId));
+ returnMap.put("Thread Name", String.valueOf(threadName));
+ customFormat(eventClass, eventData, returnMap);
+ return returnMap;
+ }
+
+ private void customFormat(Class<?> clazz, Object eventData,
+ Map<String, String> retMap) {
+ for (Field f : clazz.getDeclaredFields()) {
+ EventColumn ec = f.getAnnotation(EventColumn.class);
+ if (ec == null) continue;
+ f.setAccessible(true);
+ try {
+ Object obj = f.get(eventData);
+
+ switch(ec.description()) {
+ case DPID:
+ retMap.put(ec.name(), HexString.toHexString((Long) obj));
+ break;
+ case MAC:
+ retMap.put(ec.name(), HexString.toHexString((Long) obj, 6));
+ break;
+ case IPv4:
+ retMap.put(ec.name(), net.onrc.onos.core.packet.IPv4.fromIPv4Address((Integer) obj));
+ break;
+ case FLOW_MOD_FLAGS:
+ int flags = (Integer)obj;
+ StringBuilder builder = new StringBuilder();
+ if (flags == 0) {
+ builder.append("None");
+ }
+ else {
+ if ((flags & OFFlowMod.OFPFF_SEND_FLOW_REM) != 0) {
+ builder.append("SEND_FLOW_REM ");
+ }
+ if ((flags & OFFlowMod.OFPFF_CHECK_OVERLAP) != 0) {
+ builder.append("CHECK_OVERLAP ");
+ }
+ if ((flags & OFFlowMod.OFPFF_EMERG) != 0) {
+ builder.append("EMERG ");
+ }
+ }
+ retMap.put(ec.name(), builder.toString());
+ break;
+ case LIST_IPV4:
+ @SuppressWarnings("unchecked")
+ List<Integer> ipv4Addresses = (List<Integer>)obj;
+ StringBuilder ipv4AddressesStr = new StringBuilder();
+ if (ipv4Addresses.size() == 0) {
+ ipv4AddressesStr.append("--");
+ } else {
+ for (Integer ipv4Addr : ipv4Addresses) {
+ ipv4AddressesStr.append(IPv4.fromIPv4Address(ipv4Addr.intValue()));
+ ipv4AddressesStr.append(" ");
+ }
+ }
+ retMap.put(ec.name(), ipv4AddressesStr.toString());
+ break;
+ case LIST_ATTACHMENT_POINT:
+ @SuppressWarnings("unchecked")
+ List<SwitchPort> aps = (List<SwitchPort>)obj;
+ StringBuilder apsStr = new StringBuilder();
+ if (aps.size() == 0) {
+ apsStr.append("--");
+ } else {
+ for (SwitchPort ap : aps) {
+ apsStr.append(HexString.toHexString(ap.dpid().value()));
+ apsStr.append("/");
+ apsStr.append(ap.port().value());
+ apsStr.append(" ");
+ }
+ }
+ retMap.put(ec.name(), apsStr.toString());
+ break;
+ case LIST_OBJECT:
+ @SuppressWarnings("unchecked")
+ List<Object> obl = (List<Object>)obj;
+ StringBuilder sbldr = new StringBuilder();
+ if (obl.size() == 0) {
+ sbldr.append("--");
+ } else {
+ for (Object o : obl) {
+ sbldr.append(o.toString());
+ sbldr.append(" ");
+ }
+ }
+ retMap.put(ec.name(), sbldr.toString());
+ break;
+ case SREF_LIST_OBJECT:
+ @SuppressWarnings("unchecked")
+ SoftReference<List<Object>> srefListObj =
+ (SoftReference<List<Object>>)obj;
+ List<Object> ol = srefListObj.get();
+ if (ol != null) {
+ StringBuilder sb = new StringBuilder();
+ if (ol.size() == 0) {
+ sb.append("--");
+ } else {
+ for (Object o : ol) {
+ sb.append(o.toString());
+ sb.append(" ");
+ }
+ }
+ retMap.put(ec.name(), sb.toString());
+ } else {
+ retMap.put(ec.name(), "-- reference not available --");
+ }
+ break;
+ case SREF_OBJECT:
+ @SuppressWarnings("unchecked")
+ SoftReference<Object> srefObj = (SoftReference<Object>)obj;
+ if (srefObj == null) {
+ retMap.put(ec.name(), "--");
+ } else {
+ Object o = srefObj.get();
+ if (o != null) {
+ retMap.put(ec.name(), o.toString());
+ } else {
+ retMap.put(ec.name(),
+ "-- reference not available --");
+ }
+ }
+ break;
+ case STRING:
+ case OBJECT:
+ case PRIMITIVE:
+ default:
+ retMap.put(ec.name(), obj.toString());
+ }
+ } catch (ClassCastException e) {
+ retMap.put("Error", e.getMessage());
+ } catch (IllegalArgumentException e) {
+ retMap.put("Error", e.getMessage());
+ } catch (IllegalAccessException e) {
+ retMap.put("Error", e.getMessage());
+ }
+ }
+ }
+
+}
diff --git a/src/main/java/net/floodlightcontroller/debugevent/IDebugEventService.java b/src/main/java/net/floodlightcontroller/debugevent/IDebugEventService.java
new file mode 100644
index 0000000..d54e9f6
--- /dev/null
+++ b/src/main/java/net/floodlightcontroller/debugevent/IDebugEventService.java
@@ -0,0 +1,189 @@
+package net.floodlightcontroller.debugevent;
+
+import java.lang.annotation.ElementType;
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+import java.lang.annotation.Target;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.core.module.IFloodlightService;
+import net.floodlightcontroller.debugevent.DebugEvent.EventInfo;
+
+public interface IDebugEventService extends IFloodlightService {
+
+ /**
+ * Different event types. Events that are meant to be logged on demand
+ * need to be separately enabled/disabled.
+ */
+ public enum EventType {
+ ALWAYS_LOG,
+ LOG_ON_DEMAND
+ }
+
+ /**
+ * Describes the type of field obtained from reflection
+ */
+ enum EventFieldType {
+ DPID, IPv4, MAC, STRING, OBJECT, PRIMITIVE, LIST_IPV4,
+ LIST_ATTACHMENT_POINT, LIST_OBJECT, SREF_LIST_OBJECT, SREF_OBJECT,
+ FLOW_MOD_FLAGS
+ }
+
+ /**
+ * EventColumn is the only annotation given to the fields of the event
+ * when updating an event.
+ */
+ @Target(ElementType.FIELD)
+ @Retention(RetentionPolicy.RUNTIME)
+ public @interface EventColumn {
+ String name() default "param";
+ EventFieldType description() default EventFieldType.PRIMITIVE;
+ }
+
+ /**
+ * Debug Event Qualifiers
+ */
+ public static final String EV_MDATA_WARN = "warn";
+ public static final String EV_MDATA_ERROR = "error";
+
+ /**
+ * A limit on the maximum number of events that can be created
+ */
+ public static final int MAX_EVENTS = 2000;
+
+ /**
+ * Public class for information returned in response to rest API calls.
+ */
+ public class DebugEventInfo {
+ EventInfo eventInfo;
+ List<Map<String,String>> events;
+
+ public DebugEventInfo(EventInfo eventInfo,
+ List<Map<String, String>> eventHistory) {
+ this.eventInfo = eventInfo;
+ this.events = eventHistory;
+ }
+
+ public EventInfo getEventInfo() {
+ return eventInfo;
+ }
+
+ public List<Map<String,String>> getEvents() {
+ return events;
+ }
+ }
+
+ /**
+ * exception thrown when MAX_EVENTS have been registered
+ */
+ public class MaxEventsRegistered extends Exception {
+ private static final long serialVersionUID = 2609587082227510262L;
+ }
+
+ /**
+ * Register an event for debugging.
+ *
+ * @param moduleName module registering event eg. linkdiscovery, virtualrouting.
+ * @param eventName name given to event.
+ * @param eventDescription A descriptive string describing the event.
+ * @param eventType EventType for this event. On-demand events have to
+ * be explicitly enabled using other methods in this API
+ * @param eventClass A user defined class that annotates the fields
+ * with @EventColumn. This class specifies the
+ * fields/columns for this event.
+ * @param bufferCapacity Number of events to store for this event in a circular
+ * buffer. Older events will be discarded once the
+ * buffer is full.
+ * @param metaData variable arguments that qualify an event
+ * eg. EV_MDATA_WARN, EV_MDATA_ERROR etc. See Debug Event Qualifiers
+ * @return IEventUpdater with update methods that can be used to
+ * update an event of the given eventClass
+ * @throws MaxEventsRegistered
+ */
+ public <T> IEventUpdater<T> registerEvent(String moduleName, String eventName,
+ String eventDescription,
+ EventType eventType,
+ Class<T> eventClass,
+ int bufferCapacity,
+ String... metaData)
+ throws MaxEventsRegistered;
+
+ /**
+ * Update the global event stores with values from the thread local stores. This
+ * method is not typically intended for use by any module. It's typical usage is from
+ * floodlight core for events that happen in the packet processing pipeline.
+ * For other rare events, flushEvents should be called.
+ */
+ public void flushEvents();
+
+ /**
+ * Determine if eventName is a registered event for a given moduleName
+ */
+ public boolean containsModuleEventName(String moduleName, String eventName);
+
+ /**
+ * Determine if any events have been registered for module of name moduleName
+ */
+ public boolean containsModuleName(String moduleName);
+
+ /**
+ * Get event history for all events. This call can be expensive as it
+ * formats the event histories for all events.
+ *
+ * @return a list of all event histories or an empty list if no events have
+ * been registered
+ */
+ public List<DebugEventInfo> getAllEventHistory();
+
+ /**
+ * Get event history for all events registered for a given moduleName
+ *
+ * @return a list of all event histories for all events registered for the
+ * the module or an empty list if there are no events for this module
+ */
+ public List<DebugEventInfo> getModuleEventHistory(String moduleName);
+
+ /**
+ * Get event history for a single event
+ *
+ * @param moduleName registered module name
+ * @param eventName registered event name for moduleName
+ * @param last last X events
+ * @return DebugEventInfo for that event, or null if the moduleEventName
+ * does not correspond to a registered event.
+ */
+ public DebugEventInfo getSingleEventHistory(String moduleName, String eventName, int last);
+
+ /**
+ * Wipe out all event history for all registered events
+ */
+ public void resetAllEvents();
+
+ /**
+ * Wipe out all event history for all events registered for a specific module
+ *
+ * @param moduleName registered module name
+ */
+ public void resetAllModuleEvents(String moduleName);
+
+ /**
+ * Wipe out event history for a single event
+ * @param moduleName registered module name
+ * @param eventName registered event name for moduleName
+ */
+ public void resetSingleEvent(String moduleName, String eventName);
+
+ /**
+ * Retrieve a list of moduleNames registered for debug events or an empty
+ * list if no events have been registered in the system
+ */
+ public List<String> getModuleList();
+
+ /**
+ * Returns a list of all events registered for a specific moduleName
+ * or a empty list
+ */
+ public List<String> getModuleEventList(String moduleName);
+
+}
diff --git a/src/main/java/net/floodlightcontroller/debugevent/IEventUpdater.java b/src/main/java/net/floodlightcontroller/debugevent/IEventUpdater.java
new file mode 100644
index 0000000..7aec38f
--- /dev/null
+++ b/src/main/java/net/floodlightcontroller/debugevent/IEventUpdater.java
@@ -0,0 +1,30 @@
+package net.floodlightcontroller.debugevent;
+
+/**
+ * eventUPdater is used to log events for pre-registered events.
+ */
+public interface IEventUpdater<T> {
+
+ /**
+ * Logs the instance of the event thread-locally. Flushing to the global
+ * circular buffer for this event is delayed resulting in better performance.
+ * This method should typically be used by those events that happen in the
+ * packet processing pipeline
+ *
+ * @param event an instance of the user-defined event of type T
+ */
+ public void updateEventNoFlush(T event);
+
+ /**
+ * Logs the instance of the event thread-locally and immediated flushes
+ * to the global circular buffer for this event.
+ * This method should typically be used by those events that happen
+ * outside the packet processing pipeline
+ *
+ * @param event an instance of the user-defined event of type T
+ */
+ public void updateEventWithFlush(T event);
+
+
+
+}
diff --git a/src/main/java/net/floodlightcontroller/debugevent/NullDebugEvent.java b/src/main/java/net/floodlightcontroller/debugevent/NullDebugEvent.java
new file mode 100644
index 0000000..7d479ea
--- /dev/null
+++ b/src/main/java/net/floodlightcontroller/debugevent/NullDebugEvent.java
@@ -0,0 +1,135 @@
+package net.floodlightcontroller.debugevent;
+
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.core.module.FloodlightModuleContext;
+import net.floodlightcontroller.core.module.FloodlightModuleException;
+import net.floodlightcontroller.core.module.IFloodlightModule;
+import net.floodlightcontroller.core.module.IFloodlightService;
+
+public class NullDebugEvent implements IFloodlightModule, IDebugEventService {
+
+
+ @Override
+ public void flushEvents() {
+
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>>
+ getModuleServices() {
+ Collection<Class<? extends IFloodlightService>> services =
+ new ArrayList<Class<? extends IFloodlightService>>(1);
+ services.add(IDebugEventService.class);
+ return services;
+ }
+
+ @Override
+ public Map<Class<? extends IFloodlightService>, IFloodlightService>
+ getServiceImpls() {
+ Map<Class<? extends IFloodlightService>,
+ IFloodlightService> m =
+ new HashMap<Class<? extends IFloodlightService>,
+ IFloodlightService>();
+ m.put(IDebugEventService.class, this);
+ return m;
+ }
+
+ @Override
+ public Collection<Class<? extends IFloodlightService>>
+ getModuleDependencies() {
+ return null;
+ }
+
+ @Override
+ public void init(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+
+ }
+
+ @Override
+ public void startUp(FloodlightModuleContext context)
+ throws FloodlightModuleException {
+
+ }
+
+ @Override
+ public boolean containsModuleEventName(String moduleName, String eventName) {
+ return false;
+ }
+
+ @Override
+ public boolean containsModuleName(String moduleName) {
+ return false;
+ }
+
+ @Override
+ public List<DebugEventInfo> getAllEventHistory() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<DebugEventInfo> getModuleEventHistory(String param) {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public DebugEventInfo getSingleEventHistory(String moduleName, String eventName,
+ int last) {
+ return null;
+ }
+
+ @Override
+ public void resetAllEvents() {
+
+ }
+
+ @Override
+ public void resetAllModuleEvents(String moduleName) {
+
+ }
+
+ @Override
+ public void resetSingleEvent(String moduleName, String eventName) {
+
+ }
+
+ @Override
+ public <T> IEventUpdater<T>
+ registerEvent(String moduleName, String eventName,
+ String eventDescription, EventType eventType,
+ Class<T> eventClass, int bufferCapacity,
+ String... metaData) throws MaxEventsRegistered {
+ return new NullEventImpl<T>();
+ }
+
+ public class NullEventImpl<T> implements IEventUpdater<T> {
+
+ @Override
+ public void updateEventNoFlush(Object event) {
+
+ }
+
+ @Override
+ public void updateEventWithFlush(Object event) {
+
+ }
+
+ }
+
+ @Override
+ public List<String> getModuleList() {
+ return Collections.emptyList();
+ }
+
+ @Override
+ public List<String> getModuleEventList(String moduleName) {
+ return Collections.emptyList();
+ }
+
+}
diff --git a/src/main/java/net/floodlightcontroller/debugevent/web/DebugEventResource.java b/src/main/java/net/floodlightcontroller/debugevent/web/DebugEventResource.java
new file mode 100644
index 0000000..a3f06ce
--- /dev/null
+++ b/src/main/java/net/floodlightcontroller/debugevent/web/DebugEventResource.java
@@ -0,0 +1,322 @@
+package net.floodlightcontroller.debugevent.web;
+
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import net.floodlightcontroller.debugevent.IDebugEventService.DebugEventInfo;
+import net.floodlightcontroller.debugevent.IDebugEventService.EventType;
+
+import org.restlet.resource.Get;
+import org.restlet.resource.Post;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Web interface for Debug Events
+ *
+ * @author Saurav
+ */
+public class DebugEventResource extends DebugEventResourceBase {
+ protected static Logger logger =
+ LoggerFactory.getLogger(DebugEventResource.class);
+
+ /**
+ * The output JSON model that contains the counter information
+ */
+ public static class DebugEventInfoOutput {
+ protected class DEInfo {
+ private final boolean enabled;
+ private final int bufferCapacity;
+ private final EventType eventType;
+ private final String eventDesc;
+ private final String eventName;
+ private final String moduleName;
+ private final String[] metaData;
+ private final List<Map<String,String>> eventHistory;
+
+ DEInfo(DebugEventInfo dei) {
+ this.moduleName = dei.getEventInfo().getModuleName();
+ this.eventName = dei.getEventInfo().getEventName();
+ this.eventDesc = dei.getEventInfo().getEventDesc();
+ this.metaData = dei.getEventInfo().getMetaData();
+ this.enabled = dei.getEventInfo().isEnabled();
+ this.eventType = dei.getEventInfo().getEtype();
+ this.bufferCapacity = dei.getEventInfo().getBufferCapacity();
+ this.eventHistory = dei.getEvents();
+ }
+ public boolean isEnabled() {
+ return enabled;
+ }
+ public int getBufferCapacity() {
+ return bufferCapacity;
+ }
+ public String getEventDesc() {
+ return eventDesc;
+ }
+ public String getEventName() {
+ return eventName;
+ }
+ public String getModuleName() {
+ return moduleName;
+ }
+ public String[] getMetaData() {
+ return metaData;
+ }
+ public EventType getEventType() {
+ return eventType;
+ }
+ public List<Map<String,String>> getEventHistory() {
+ return eventHistory;
+ }
+
+ }
+
+ public Map<String, DEInfo> eventMap = null;
+ public List<String> names = null;
+ public String error = null;
+
+ DebugEventInfoOutput(boolean getList) {
+ if (!getList) {
+ eventMap = new HashMap<String, DEInfo>();
+ }
+ }
+ public Map<String, DEInfo> getEventMap() {
+ return eventMap;
+ }
+ public List<String> getNames() {
+ return names;
+ }
+ public String getError() {
+ return error;
+ }
+
+ }
+
+ public enum Option {
+ ALL, ONE_MODULE, ONE_MODULE_EVENT, ERROR_BAD_MODULE_NAME, ERROR_BAD_PARAM,
+ ERROR_BAD_MODULE_EVENT_NAME
+ }
+
+ public static class DebugEventPost {
+ public Boolean reset;
+
+ public Boolean getReset() {
+ return reset;
+ }
+ public void setReset(Boolean reset) {
+ this.reset = reset;
+ }
+ }
+
+ public static class ResetOutput {
+ String error = null;
+
+ public String getError() {
+ return error;
+ }
+ public void setError(String error) {
+ this.error = error;
+ }
+ }
+
+ /**
+ * Reset events
+ *
+ * If using curl:
+ * curl -X POST -d {\"reset\":true} -H "Content-Type: application/json" URL
+ * where URL must be in one of the following forms for resetting registered events:
+ * "http://{controller-hostname}:8080/wm/debugevent/
+ * "http://{controller-hostname}:8080/wm/debugevent/{param1}
+ * "http://{controller-hostname}:8080/wm/debugevent/{param1}/{param2}
+ *
+ * Not giving {param1} will reset all events
+ * {param1} can be 'all' or the name of a module. The former case will reset
+ * all events, while the latter will reset all events for the moduleName (if
+ * param2 is null).{param2} must be an eventName for the given moduleName to
+ * reset a specific event.
+ */
+ @Post
+ public ResetOutput postHandler(DebugEventPost postData) {
+ ResetOutput output = new ResetOutput();
+ String param1 = (String)getRequestAttributes().get("param1");
+ String param2 = (String)getRequestAttributes().get("param2");
+
+ if (postData.getReset() != null && postData.getReset()) {
+ Option choice = Option.ERROR_BAD_PARAM;
+
+ if (param1 == null) {
+ param1 = "all";
+ choice = Option.ALL;
+ } else if (param1.equals("all")) {
+ choice = Option.ALL;
+ } else if (param2 == null) {
+ boolean isRegistered = debugEvent.containsModuleName(param1);
+ if (isRegistered) {
+ choice = Option.ONE_MODULE;
+ } else {
+ choice = Option.ERROR_BAD_MODULE_NAME;
+ }
+ } else {
+ // differentiate between disabled and non-existing events
+ boolean isRegistered = debugEvent.containsModuleEventName(param1, param2);
+ if (isRegistered) {
+ choice = Option.ONE_MODULE_EVENT;
+ } else {
+ choice = Option.ERROR_BAD_MODULE_EVENT_NAME;
+ }
+ }
+
+ switch (choice) {
+ case ALL:
+ debugEvent.resetAllEvents();
+ break;
+ case ONE_MODULE:
+ debugEvent.resetAllModuleEvents(param1);
+ break;
+ case ONE_MODULE_EVENT:
+ debugEvent.resetSingleEvent(param1, param2);
+ break;
+ case ERROR_BAD_MODULE_NAME:
+ output.error = "Module name has no corresponding registered events";
+ break;
+ case ERROR_BAD_MODULE_EVENT_NAME:
+ output.error = "Event not registered";
+ break;
+ case ERROR_BAD_PARAM:
+ output.error = "Bad param";
+ }
+ }
+
+ return output;
+
+ }
+
+ /**
+ * Return the debug event data for the get rest-api call
+ *
+ * URL must be in one of the following forms for retrieving a list
+ * moduleNames "http://{controller-hostname}:8080/wm/debugevent/
+ * counterNames "http://{controller-hostname}:8080/wm/debugevent/{moduleName}
+ *
+ * URL must be in one of the following forms for retrieving event data:
+ * "http://{controller-hostname}:8080/wm/debugevent/{param1}
+ * "http://{controller-hostname}:8080/wm/debugevent/{param1}/{param2}
+ *
+ * where {param1} must be one of (no quotes):
+ * null if nothing is given then by default the list
+ * of all moduleNames is returned for which
+ * events have been registered
+ * "all" can return value/info on all active events
+ * but is currently disallowed
+ * "{moduleName}" returns value/info on events for the specified module
+ * depending on the value of param2
+ * and {param2} must be one of (no quotes):
+ * null returns all eventNames registered for the
+ * given moduleName (in param1)
+ * "{eventName}" returns value/info for specific event if it is active.
+ *
+ */
+ @Get("json")
+ public DebugEventInfoOutput handleEventInfoQuery() {
+ Option choice = Option.ERROR_BAD_PARAM;
+ DebugEventInfoOutput output;
+ String laststr = getQueryValue("last");
+ int last = Integer.MAX_VALUE;
+ try {
+ if (laststr != null)
+ last = Integer.valueOf(laststr);
+ if (last < 1) last = Integer.MAX_VALUE;
+ } catch (NumberFormatException e) {
+ output = new DebugEventInfoOutput(false);
+ output.error = "Expected an integer requesting last X events;" +
+ " received " + laststr;
+ return output;
+ }
+ String param1 = (String)getRequestAttributes().get("param1");
+ String param2 = (String)getRequestAttributes().get("param2");
+
+ if (param1 == null) {
+ output = new DebugEventInfoOutput(true);
+ return listEvents(output);
+ } else if (param1.equals("all")) {
+ output = new DebugEventInfoOutput(false);
+ //populateEvents(debugEvent.getAllEventHistory(), output);
+ output.error = "Cannot retrieve all events - please select a specific event";
+ return output;
+ }
+
+ if (param2 == null) {
+ output = new DebugEventInfoOutput(true);
+ boolean isRegistered = debugEvent.containsModuleName(param1);
+ if (isRegistered) {
+ return listEvents(param1, output);
+ } else {
+ choice = Option.ERROR_BAD_MODULE_NAME;
+ }
+ } else if (param2.equals("all")) {
+ output = new DebugEventInfoOutput(false);
+ //choice = Option.ONE_MODULE;
+ output.error = "Cannot retrieve all events - please select a specific event";
+ return output;
+ } else {
+ // differentiate between disabled and non-existing events
+ boolean isRegistered = debugEvent.containsModuleEventName(param1, param2);
+ if (isRegistered) {
+ choice = Option.ONE_MODULE_EVENT;
+ } else {
+ choice = Option.ERROR_BAD_MODULE_EVENT_NAME;
+ }
+ }
+
+ output = new DebugEventInfoOutput(false);
+ switch (choice) {
+ case ONE_MODULE:
+ populateEvents(debugEvent.getModuleEventHistory(param1), output);
+ break;
+ case ONE_MODULE_EVENT:
+ populateSingleEvent(debugEvent.getSingleEventHistory(param1, param2, last),
+ output);
+ break;
+ case ERROR_BAD_MODULE_NAME:
+ output.error = "Module name has no corresponding registered events";
+ break;
+ case ERROR_BAD_MODULE_EVENT_NAME:
+ output.error = "Event not registered";
+ break;
+ case ERROR_BAD_PARAM:
+ default:
+ output.error = "Bad param";
+ }
+
+ return output;
+ }
+
+ private DebugEventInfoOutput listEvents(DebugEventInfoOutput output) {
+ output.names = debugEvent.getModuleList();
+ return output;
+ }
+
+ private DebugEventInfoOutput listEvents(String moduleName,
+ DebugEventInfoOutput output) {
+ output.names = debugEvent.getModuleEventList(moduleName);
+ return output;
+ }
+
+ private void populateSingleEvent(DebugEventInfo singleEventHistory,
+ DebugEventInfoOutput output) {
+ if (singleEventHistory != null) {
+ output.eventMap.put(singleEventHistory.getEventInfo().getModuleEventName(),
+ output.new DEInfo(singleEventHistory));
+ }
+ }
+
+ private void populateEvents(List<DebugEventInfo> eventHistory,
+ DebugEventInfoOutput output) {
+ if (eventHistory != null) {
+ for (DebugEventInfo de : eventHistory)
+ populateSingleEvent(de, output);
+ }
+ }
+}
diff --git a/src/main/java/net/floodlightcontroller/debugevent/web/DebugEventResourceBase.java b/src/main/java/net/floodlightcontroller/debugevent/web/DebugEventResourceBase.java
new file mode 100644
index 0000000..964deeb
--- /dev/null
+++ b/src/main/java/net/floodlightcontroller/debugevent/web/DebugEventResourceBase.java
@@ -0,0 +1,18 @@
+package net.floodlightcontroller.debugevent.web;
+
+
+import net.floodlightcontroller.debugevent.IDebugEventService;
+
+import org.restlet.resource.ResourceException;
+import org.restlet.resource.ServerResource;
+
+public class DebugEventResourceBase extends ServerResource{
+ protected IDebugEventService debugEvent;
+
+ @Override
+ protected void doInit() throws ResourceException {
+ super.doInit();
+ debugEvent = (IDebugEventService)getContext().getAttributes().
+ get(IDebugEventService.class.getCanonicalName());
+ }
+}
diff --git a/src/main/java/net/floodlightcontroller/debugevent/web/DebugEventRoutable.java b/src/main/java/net/floodlightcontroller/debugevent/web/DebugEventRoutable.java
new file mode 100644
index 0000000..d4ee7c6
--- /dev/null
+++ b/src/main/java/net/floodlightcontroller/debugevent/web/DebugEventRoutable.java
@@ -0,0 +1,27 @@
+package net.floodlightcontroller.debugevent.web;
+
+import org.restlet.Context;
+import org.restlet.Restlet;
+import org.restlet.routing.Router;
+
+import net.floodlightcontroller.restserver.RestletRoutable;
+
+public class DebugEventRoutable implements RestletRoutable {
+
+ @Override
+ public Restlet getRestlet(Context context) {
+ Router router = new Router(context);
+ router.attach("/{param1}/{param2}/", DebugEventResource.class);
+ router.attach("/{param1}/{param2}", DebugEventResource.class);
+ router.attach("/{param1}/", DebugEventResource.class);
+ router.attach("/{param1}", DebugEventResource.class);
+ router.attach("/", DebugEventResource.class);
+ return router;
+ }
+
+ @Override
+ public String basePath() {
+ return "/wm/debugevent";
+ }
+
+}