diff --git a/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java b/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java index be61662c5e190993b8b0a63efd79faa4df1a6413..ba898d2d852d7d256e330f0592978498fec7527c 100644 --- a/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java +++ b/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java @@ -113,13 +113,13 @@ public interface IFloodlightProviderService extends IFloodlightService { * Adds a listener for HA role events * @param listener The module that wants to listen for events */ - public void addHAListener(IHARoleListener listener); + public void addHAListener(IHAListener listener); /** * Removes a listener for HA role events * @param listener The module that no longer wants to listen for events */ - public void removeHAListener(IHARoleListener listener); + public void removeHAListener(IHAListener listener); /** * Terminate the process diff --git a/src/main/java/net/floodlightcontroller/core/IHAListener.java b/src/main/java/net/floodlightcontroller/core/IHAListener.java new file mode 100644 index 0000000000000000000000000000000000000000..c76f46a5fde13d5e28bfe9fdb9fe5dbd1507e1e0 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/IHAListener.java @@ -0,0 +1,30 @@ +package net.floodlightcontroller.core; + +import java.util.Map; + +import net.floodlightcontroller.core.IFloodlightProviderService.Role; + +public interface IHAListener { + /** + * Gets called when the controller changes role (i.e. Master -> Slave). + * Note that oldRole CAN be null. + * @param oldRole The controller's old role + * @param newRole The controller's new role + */ + public void roleChanged(Role oldRole, Role newRole); + + /** + * Gets called when the IP addresses of the controller nodes in the + * controller cluster change. All parameters map controller ID to + * the controller's IP. + * + * @param curControllerNodeIPs The current mapping of controller IDs to IP + * @param addedControllerNodeIPs These IPs were added since the last update + * @param removedControllerNodeIPs These IPs were removed since the last update + */ + public void controllerNodeIPsChanged( + Map<String, String> curControllerNodeIPs, + Map<String, String> addedControllerNodeIPs, + Map<String, String> removedControllerNodeIPs + ); +} diff --git a/src/main/java/net/floodlightcontroller/core/IHARoleListener.java b/src/main/java/net/floodlightcontroller/core/IHARoleListener.java deleted file mode 100644 index 83749caed0d7e33fb2d177d4837d43b2d95f5d97..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/core/IHARoleListener.java +++ /dev/null @@ -1,13 +0,0 @@ -package net.floodlightcontroller.core; - -import net.floodlightcontroller.core.IFloodlightProviderService.Role; - -public interface IHARoleListener { - /** - * Gets called when the controller changes role (i.e. Master -> Slave). - * Note that oldRole CAN be null. - * @param oldRole The controller's old role - * @param newRole The controller's new role - */ - public void roleChanged(Role oldRole, Role newRole); -} diff --git a/src/main/java/net/floodlightcontroller/core/internal/Controller.java b/src/main/java/net/floodlightcontroller/core/internal/Controller.java index c087bf217c052cbdafcbce314947c2683c7a2685..78db8ee1120a97752b4be48baceb889eb6ece15e 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/Controller.java +++ b/src/main/java/net/floodlightcontroller/core/internal/Controller.java @@ -48,7 +48,7 @@ import java.util.concurrent.TimeoutException; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; -import net.floodlightcontroller.core.IHARoleListener; +import net.floodlightcontroller.core.IHAListener; import net.floodlightcontroller.core.IInfoProvider; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFMessageListener.Command; @@ -63,6 +63,7 @@ import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.perfmon.IPktInProcessingTimeService; import net.floodlightcontroller.restserver.IRestApiService; import net.floodlightcontroller.storage.IResultSet; +import net.floodlightcontroller.storage.IStorageSourceListener; import net.floodlightcontroller.storage.IStorageSourceService; import net.floodlightcontroller.storage.OperatorPredicate; import net.floodlightcontroller.storage.StorageException; @@ -129,7 +130,8 @@ import org.slf4j.LoggerFactory; /** * The main controller class. Handles all setup and network listeners */ -public class Controller implements IFloodlightProviderService { +public class Controller implements IFloodlightProviderService, + IStorageSourceListener { protected static Logger log = LoggerFactory.getLogger(Controller.class); @@ -146,10 +148,14 @@ public class Controller implements IFloodlightProviderService { // send role request messages to the switches when our role changes to master protected ConcurrentHashMap<Long, IOFSwitch> connectedSwitches; + // The controllerNodeIPsCache maps Controller IDs to their IP address. + // It's only used by handleControllerNodeIPsChanged + protected HashMap<String, String> controllerNodeIPsCache; + protected Set<IOFSwitchListener> switchListeners; - protected Set<IHARoleListener> haListeners; + protected Set<IHAListener> haListeners; protected Map<String, List<IInfoProvider>> providerMap; - protected BlockingQueue<Update> updates; + protected BlockingQueue<IUpdate> updates; // Module dependencies protected IRestApiService restApi; @@ -197,31 +203,107 @@ public class Controller implements IFloodlightProviderService { protected static final String PORT_SUPPORTED_FEATURES = "supported_features"; protected static final String PORT_PEER_FEATURES = "peer_features"; + protected static final String CONTROLLER_INTERFACE_TABLE_NAME = "controller_controllerinterface"; + protected static final String CONTROLLER_INTERFACE_ID = "id"; + protected static final String CONTROLLER_INTERFACE_CONTROLLER_ID = "controller_id"; + protected static final String CONTROLLER_INTERFACE_TYPE = "type"; + protected static final String CONTROLLER_INTERFACE_NUMBER = "number"; + protected static final String CONTROLLER_INTERFACE_DISCOVERED_IP = "discovered_ip"; + + + // Perf. related configuration protected static final int SEND_BUFFER_SIZE = 4 * 1024 * 1024; protected static final int BATCH_MAX_SIZE = 100; protected static final boolean ALWAYS_DECODE_ETH = true; - protected enum UpdateType { - SWITCH, HA + /** + * Updates handled by the main loop + */ + protected interface IUpdate { + /** + * Calls the appropriate listeners + */ + public void dispatch(); } - protected class Update { - public UpdateType type; + /** + * A switch was added or removed + */ + protected class SwitchUpdate implements IUpdate { public IOFSwitch sw; public boolean added; - public Role oldRole; - public Role newRole; - - public Update(IOFSwitch sw, boolean added) { + public SwitchUpdate(IOFSwitch sw, boolean added) { this.sw = sw; this.added = added; - this.type = UpdateType.SWITCH; } - - public Update(Role newRole, Role oldRole) { + public void dispatch() { + if (log.isDebugEnabled()) { + log.debug("Dispatching switch update {} {}", + sw, added); + } + if (switchListeners != null) { + for (IOFSwitchListener listener : switchListeners) { + if (added) + listener.addedSwitch(sw); + else + listener.removedSwitch(sw); + } + } + } + } + + /** + * Controller's role has changed + */ + protected class HARoleUpdate implements IUpdate { + public Role oldRole; + public Role newRole; + public HARoleUpdate(Role newRole, Role oldRole) { this.oldRole = oldRole; this.newRole = newRole; - this.type = UpdateType.HA; + } + public void dispatch() { + if (log.isDebugEnabled()) { + log.debug("Dispatching HA Role update newRole = {}, oldRole = {}", + newRole, oldRole); + } + if (haListeners != null) { + for (IHAListener listener : haListeners) { + listener.roleChanged(oldRole, newRole); + } + } + } + } + + /** + * IPs of controllers in controller cluster have changed. + */ + protected class HAControllerNodeIPUpdate implements IUpdate { + public Map<String,String> curControllerNodeIPs; + public Map<String,String> addedControllerNodeIPs; + public Map<String,String> removedControllerNodeIPs; + public HAControllerNodeIPUpdate( + HashMap<String,String> curControllerNodeIPs, + HashMap<String,String> addedControllerNodeIPs, + HashMap<String,String> removedControllerNodeIPs) { + this.curControllerNodeIPs = curControllerNodeIPs; + this.addedControllerNodeIPs = addedControllerNodeIPs; + this.removedControllerNodeIPs = removedControllerNodeIPs; + } + public void dispatch() { + if (log.isDebugEnabled()) { + log.debug("Dispatching HA Controller Node IP update " + + "curIPs = {}, addedIPs = {}, removedIPs = {}", + new Object[] { curControllerNodeIPs, addedControllerNodeIPs, + removedControllerNodeIPs } + ); + } + if (haListeners != null) { + for (IHAListener listener: haListeners) { + listener.controllerNodeIPsChanged(curControllerNodeIPs, + addedControllerNodeIPs, removedControllerNodeIPs); + } + } } } @@ -275,7 +357,11 @@ public class Controller implements IFloodlightProviderService { } // Enqueue an update for our listeners. - this.updates.add(new Update(role, oldRole)); + try { + this.updates.put(new HARoleUpdate(role, oldRole)); + } catch (InterruptedException e) { + log.error("Failure adding update to queue", e); + } } /** @@ -907,39 +993,39 @@ public class Controller implements IFloodlightProviderService { /** * flcontext_cache - Keep a thread local stack of contexts */ - protected static final ThreadLocal<Stack<FloodlightContext>> flcontext_cache = - new ThreadLocal <Stack<FloodlightContext>> () { - @Override - protected Stack<FloodlightContext> initialValue() { - return new Stack<FloodlightContext>(); - } - }; + protected static final ThreadLocal<Stack<FloodlightContext>> flcontext_cache = + new ThreadLocal <Stack<FloodlightContext>> () { + @Override + protected Stack<FloodlightContext> initialValue() { + return new Stack<FloodlightContext>(); + } + }; - /** - * flcontext_alloc - pop a context off the stack, if required create a new one - * @return FloodlightContext - */ - protected static FloodlightContext flcontext_alloc() { - FloodlightContext flcontext = null; + /** + * flcontext_alloc - pop a context off the stack, if required create a new one + * @return FloodlightContext + */ + protected static FloodlightContext flcontext_alloc() { + FloodlightContext flcontext = null; - if (flcontext_cache.get().empty()) { - flcontext = new FloodlightContext(); - } - else { - flcontext = flcontext_cache.get().pop(); - } + if (flcontext_cache.get().empty()) { + flcontext = new FloodlightContext(); + } + else { + flcontext = flcontext_cache.get().pop(); + } - return flcontext; - } + return flcontext; + } - /** - * flcontext_free - Free the context to the current thread - * @param flcontext - */ - protected void flcontext_free(FloodlightContext flcontext) { - flcontext.getStorage().clear(); - flcontext_cache.get().push(flcontext); - } + /** + * flcontext_free - Free the context to the current thread + * @param flcontext + */ + protected void flcontext_free(FloodlightContext flcontext) { + flcontext.getStorage().clear(); + flcontext_cache.get().push(flcontext); + } /** * Handle replies to certain OFMessages, and pass others off to listeners @@ -1200,7 +1286,7 @@ public class Controller implements IFloodlightProviderService { } updateActiveSwitchInfo(sw); - Update update = new Update(sw, true); + SwitchUpdate update = new SwitchUpdate(sw, true); try { this.updates.put(update); } catch (InterruptedException e) { @@ -1233,7 +1319,7 @@ public class Controller implements IFloodlightProviderService { // of the switch state that's written to storage. updateInactiveSwitchInfo(sw); - Update update = new Update(sw, false); + SwitchUpdate update = new SwitchUpdate(sw, false); try { this.updates.put(update); } catch (InterruptedException e) { @@ -1257,7 +1343,7 @@ public class Controller implements IFloodlightProviderService { ldd.addListener(type, listener); if (log.isDebugEnabled()) { - logListeners(type, ldd); + logListeners(type, ldd); } } @@ -1269,7 +1355,7 @@ public class Controller implements IFloodlightProviderService { if (ldd != null) { ldd.removeListener(listener); if (log.isDebugEnabled()) { - logListeners(type, ldd); + logListeners(type, ldd); } } } @@ -1564,10 +1650,10 @@ public class Controller implements IFloodlightProviderService { Role role = null; String roleString = configParams.get("role"); if (roleString == null) { - String rolePath = configParams.get("rolepath"); - if (rolePath != null) { - Properties properties = new Properties(); - try { + String rolePath = configParams.get("rolepath"); + if (rolePath != null) { + Properties properties = new Properties(); + try { properties.load(new FileInputStream(rolePath)); roleString = properties.getProperty("floodlight.role"); } @@ -1621,37 +1707,8 @@ public class Controller implements IFloodlightProviderService { // main loop while (true) { try { - Update update = updates.take(); - switch (update.type) { - case SWITCH: - if (log.isDebugEnabled()) { - log.debug("Dispatching switch update {} {}", - update.sw, update.added); - } - if (switchListeners != null) { - for (IOFSwitchListener listener : switchListeners) { - if (update.added) - listener.addedSwitch(update.sw); - else - listener.removedSwitch(update.sw); - } - } - break; - case HA: - if (log.isDebugEnabled()) { - log.debug("Dispatching HA update newRole = {}, oldRole = {}", - update.newRole, update.oldRole); - } - if (haListeners != null) { - for (IHARoleListener listener : haListeners) { - listener.roleChanged(update.oldRole, update.newRole); - } - } - break; - default: - log.error("Unreognized update type " + update.type); - break; - } + IUpdate update = updates.take(); + update.dispatch(); } catch (InterruptedException e) { return; } catch (StorageException e) { @@ -1727,10 +1784,11 @@ public class Controller implements IFloodlightProviderService { ListenerDispatcher<OFType, IOFMessageListener>>(); this.switchListeners = new CopyOnWriteArraySet<IOFSwitchListener>(); - this.haListeners = new CopyOnWriteArraySet<IHARoleListener>(); + this.haListeners = new CopyOnWriteArraySet<IHAListener>(); this.activeSwitches = new ConcurrentHashMap<Long, IOFSwitch>(); this.connectedSwitches = new ConcurrentHashMap<Long, IOFSwitch>(); - this.updates = new LinkedBlockingQueue<Update>(); + this.controllerNodeIPsCache = new HashMap<String, String>(); + this.updates = new LinkedBlockingQueue<IUpdate>(); this.factory = new BasicFactory(); this.providerMap = new HashMap<String, List<IInfoProvider>>(); setConfigParams(configParams); @@ -1747,11 +1805,15 @@ public class Controller implements IFloodlightProviderService { storageSource.createTable(CONTROLLER_TABLE_NAME, null); storageSource.createTable(SWITCH_TABLE_NAME, null); storageSource.createTable(PORT_TABLE_NAME, null); + storageSource.createTable(CONTROLLER_INTERFACE_TABLE_NAME, null); storageSource.setTablePrimaryKeyName(CONTROLLER_TABLE_NAME, CONTROLLER_ID); storageSource.setTablePrimaryKeyName(SWITCH_TABLE_NAME, SWITCH_DATAPATH_ID); storageSource.setTablePrimaryKeyName(PORT_TABLE_NAME, PORT_ID); + storageSource.setTablePrimaryKeyName(CONTROLLER_INTERFACE_TABLE_NAME, + CONTROLLER_INTERFACE_ID); + storageSource.addListener(CONTROLLER_INTERFACE_TABLE_NAME, this); while (true) { try { @@ -1772,42 +1834,120 @@ public class Controller implements IFloodlightProviderService { restApi.addRestletRoutable(new CoreWebRoutable()); } - @Override - public void addInfoProvider(String type, IInfoProvider provider) { - if (!providerMap.containsKey(type)) { - providerMap.put(type, new ArrayList<IInfoProvider>()); - } - providerMap.get(type).add(provider); - } + @Override + public void addInfoProvider(String type, IInfoProvider provider) { + if (!providerMap.containsKey(type)) { + providerMap.put(type, new ArrayList<IInfoProvider>()); + } + providerMap.get(type).add(provider); + } - @Override - public void removeInfoProvider(String type, IInfoProvider provider) { - if (!providerMap.containsKey(type)) { - log.debug("Provider type {} doesn't exist.", type); - return; - } - - providerMap.get(type).remove(provider); - } - - public Map<String, Object> getControllerInfo(String type) { - if (!providerMap.containsKey(type)) return null; - - Map<String, Object> result = new LinkedHashMap<String, Object>(); - for (IInfoProvider provider : providerMap.get(type)) { - result.putAll(provider.getInfo(type)); - } - - return result; - } + @Override + public void removeInfoProvider(String type, IInfoProvider provider) { + if (!providerMap.containsKey(type)) { + log.debug("Provider type {} doesn't exist.", type); + return; + } + + providerMap.get(type).remove(provider); + } + + public Map<String, Object> getControllerInfo(String type) { + if (!providerMap.containsKey(type)) return null; + + Map<String, Object> result = new LinkedHashMap<String, Object>(); + for (IInfoProvider provider : providerMap.get(type)) { + result.putAll(provider.getInfo(type)); + } + + return result; + } @Override - public void addHAListener(IHARoleListener listener) { + public void addHAListener(IHAListener listener) { this.haListeners.add(listener); } @Override - public void removeHAListener(IHARoleListener listener) { + public void removeHAListener(IHAListener listener) { this.haListeners.remove(listener); } + + + /** + * Handle changes to the controller nodes IPs and dispatch update. + */ + @SuppressWarnings("unchecked") + protected void handleControllerNodeIPChanges() { + HashMap<String,String> curControllerNodeIPs = new HashMap<String,String>(); + HashMap<String,String> addedControllerNodeIPs = new HashMap<String,String>(); + HashMap<String,String> removedControllerNodeIPs =new HashMap<String,String>(); + String[] colNames = { CONTROLLER_INTERFACE_CONTROLLER_ID, + CONTROLLER_INTERFACE_TYPE, + CONTROLLER_INTERFACE_NUMBER, + CONTROLLER_INTERFACE_DISCOVERED_IP }; + synchronized(curControllerNodeIPs) { + // We currently assume that interface Ethernet0 is the relevant + // controller interface. Might change. + // We could (should?) implement this using + // predicates, but creating the individual and compound predicate + // seems more overhead then just checking every row. Particularly, + // since the number of rows is small and changes infrequent + IResultSet res = storageSource.executeQuery(CONTROLLER_INTERFACE_TABLE_NAME, + colNames,null, null); + while (res.next()) { + if (res.getString(CONTROLLER_INTERFACE_TYPE).equals("Ethernet") && + res.getInt(CONTROLLER_INTERFACE_NUMBER) == 0) { + String controllerID = res.getString(CONTROLLER_INTERFACE_CONTROLLER_ID); + String discoveredIP = res.getString(CONTROLLER_INTERFACE_DISCOVERED_IP); + String curIP = controllerNodeIPsCache.get(controllerID); + + curControllerNodeIPs.put(controllerID, discoveredIP); + if (curIP == null) { + // new controller node IP + addedControllerNodeIPs.put(controllerID, discoveredIP); + } + else if (curIP != discoveredIP) { + // IP changed + removedControllerNodeIPs.put(controllerID, curIP); + addedControllerNodeIPs.put(controllerID, discoveredIP); + } + } + } + // Now figure out if rows have been deleted. We can't use the + // rowKeys from rowsDeleted directly, since the tables primary + // key is a compound that we can't disassemble + Set<String> curEntries = curControllerNodeIPs.keySet(); + Set<String> removedEntries = controllerNodeIPsCache.keySet(); + removedEntries.removeAll(curEntries); + for (String removedControllerID : removedEntries) + removedControllerNodeIPs.put(removedControllerID, controllerNodeIPsCache.get(removedControllerID)); + controllerNodeIPsCache = (HashMap<String, String>) curControllerNodeIPs.clone(); + HAControllerNodeIPUpdate update = new HAControllerNodeIPUpdate( + curControllerNodeIPs, addedControllerNodeIPs, + removedControllerNodeIPs); + if (!removedControllerNodeIPs.isEmpty() || !addedControllerNodeIPs.isEmpty()) { + try { + this.updates.put(update); + } catch (InterruptedException e) { + log.error("Failure adding update to queue", e); + } + } + } + } + + @Override + public void rowsModified(String tableName, Set<Object> rowKeys) { + if (tableName.equals(CONTROLLER_INTERFACE_TABLE_NAME)) { + handleControllerNodeIPChanges(); + } + + } + + @Override + public void rowsDeleted(String tableName, Set<Object> rowKeys) { + if (tableName.equals(CONTROLLER_INTERFACE_TABLE_NAME)) { + handleControllerNodeIPChanges(); + } + } } diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java index b1f06633cc409039cb87510b07dcb747d86e71c7..81fc0037a64ad662ef51d6a620c83a81c9d79d8e 100644 --- a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java +++ b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java @@ -42,7 +42,7 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IFloodlightProviderService.Role; -import net.floodlightcontroller.core.IHARoleListener; +import net.floodlightcontroller.core.IHAListener; import net.floodlightcontroller.core.IInfoProvider; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; @@ -120,7 +120,7 @@ import org.slf4j.LoggerFactory; public class LinkDiscoveryManager implements IOFMessageListener, IOFSwitchListener, IStorageSourceListener, ILinkDiscoveryService, - IFloodlightModule, IInfoProvider, IHARoleListener { + IFloodlightModule, IInfoProvider, IHAListener { protected static Logger log = LoggerFactory.getLogger(LinkDiscoveryManager.class); // Names of table/fields for links in the storage API @@ -1537,4 +1537,13 @@ public class LinkDiscoveryManager break; } } + + @Override + public void controllerNodeIPsChanged( + Map<String, String> curControllerNodeIPs, + Map<String, String> addedControllerNodeIPs, + Map<String, String> removedControllerNodeIPs) { + // ignore + } + } diff --git a/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java b/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java index bff3a422fa9795bb15efb2892b0c15612adb3848..e2cc5ec2c38ecf738cecfcca846ba0701576148a 100644 --- a/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java +++ b/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java @@ -16,7 +16,7 @@ import java.util.concurrent.ConcurrentHashMap; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IFloodlightProviderService.Role; -import net.floodlightcontroller.core.IHARoleListener; +import net.floodlightcontroller.core.IHAListener; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.IOFSwitchListener; @@ -46,7 +46,7 @@ import org.slf4j.LoggerFactory; public class StaticFlowEntryPusher implements IOFSwitchListener, IFloodlightModule, IStaticFlowEntryPusherService, - IStorageSourceListener, IOFMessageListener, IHARoleListener { + IStorageSourceListener, IOFMessageListener, IHAListener { protected static Logger log = LoggerFactory.getLogger(StaticFlowEntryPusher.class); public static final String StaticFlowName = "staticflowentry"; @@ -606,7 +606,7 @@ public class StaticFlowEntryPusher } - // IHARoleListener + // IHAListener @Override public void roleChanged(Role oldRole, Role newRole) { @@ -627,4 +627,13 @@ public class StaticFlowEntryPusher break; } } + + @Override + public void controllerNodeIPsChanged( + Map<String, String> curControllerNodeIPs, + Map<String, String> addedControllerNodeIPs, + Map<String, String> removedControllerNodeIPs) { + // ignore + } + } diff --git a/src/main/java/net/floodlightcontroller/topology/TopologyManager.java b/src/main/java/net/floodlightcontroller/topology/TopologyManager.java index 762e19186f6c392c162ab70aaf6a97dca40ad898..eaef7d8f1eecb23ab4ab55b51c631b32e944a84b 100644 --- a/src/main/java/net/floodlightcontroller/topology/TopologyManager.java +++ b/src/main/java/net/floodlightcontroller/topology/TopologyManager.java @@ -13,7 +13,7 @@ import java.util.concurrent.TimeUnit; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IFloodlightProviderService.Role; -import net.floodlightcontroller.core.IHARoleListener; +import net.floodlightcontroller.core.IHAListener; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.module.FloodlightModuleException; import net.floodlightcontroller.core.module.IFloodlightModule; @@ -43,7 +43,7 @@ import org.slf4j.LoggerFactory; public class TopologyManager implements IFloodlightModule, ITopologyService, IRoutingService, - ILinkDiscoveryListener, IHARoleListener { + ILinkDiscoveryListener, IHAListener { protected static Logger log = LoggerFactory.getLogger(TopologyManager.class); @@ -484,6 +484,8 @@ public class TopologyManager tunnelLinks.clear(); createNewInstance(); } + + // IHAListener @Override public void roleChanged(Role oldRole, Role newRole) { @@ -502,6 +504,15 @@ public class TopologyManager break; } } + + @Override + public void controllerNodeIPsChanged( + Map<String, String> curControllerNodeIPs, + Map<String, String> addedControllerNodeIPs, + Map<String, String> removedControllerNodeIPs) { + // ignore + } + @Override public Set<NodePortTuple> getBroadcastDomainLinks() { diff --git a/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java b/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java index 58b92ea9bf97b6d58cd8283654383f1a10d34b59..ef93bdad8cd15290fb16fb7e019b6b615ab6f336 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java +++ b/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java @@ -24,6 +24,7 @@ import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.List; +import java.util.Map; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; @@ -31,10 +32,13 @@ import java.util.concurrent.TimeUnit; import net.floodlightcontroller.core.FloodlightProvider; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; +import net.floodlightcontroller.core.IHAListener; import net.floodlightcontroller.core.IOFMessageFilterManagerService; import net.floodlightcontroller.core.IOFMessageListener; +import net.floodlightcontroller.core.IFloodlightProviderService.Role; import net.floodlightcontroller.core.IOFMessageListener.Command; import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.IOFSwitchListener; import net.floodlightcontroller.core.OFMessageFilterManager; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.test.MockFloodlightProvider; @@ -76,6 +80,7 @@ import org.openflow.protocol.statistics.OFStatisticsType; * @author David Erickson (daviderickson@cs.stanford.edu) */ public class ControllerTest extends FloodlightTestCase { + private Controller controller; private MockThreadPoolService tp; @@ -133,6 +138,16 @@ public class ControllerTest extends FloodlightTestCase { sr.setFlags((short) 1); return sr; } + + /** + * Run the controller's main loop so that updates are processed + */ + protected class ControllerRunThread extends Thread { + public void run() { + controller.openFlowPort = 0; // Don't listen + controller.run(); + } + } /** * Verify that a listener that throws an exception halts further @@ -472,4 +487,178 @@ public class ControllerTest extends FloodlightTestCase { verify(newsw, channel, channel2); } + + @Test + public void testUpdateQueue() throws Exception { + class DummySwitchListener implements IOFSwitchListener { + public int nAdded; + public int nRemoved; + public DummySwitchListener() { + nAdded = 0; + nRemoved = 0; + } + public synchronized void addedSwitch(IOFSwitch sw) { + nAdded++; + notifyAll(); + } + public synchronized void removedSwitch(IOFSwitch sw) { + nRemoved++; + notifyAll(); + } + public String getName() { + return "dummy"; + } + } + DummySwitchListener switchListener = new DummySwitchListener(); + IOFSwitch sw = createMock(IOFSwitch.class); + ControllerRunThread t = new ControllerRunThread(); + t.start(); + + controller.addOFSwitchListener(switchListener); + synchronized(switchListener) { + controller.updates.put(controller.new SwitchUpdate(sw, true)); + switchListener.wait(500); + assertTrue("IOFSwitchListener.addedSwitch() was not called", + switchListener.nAdded == 1); + controller.updates.put(controller.new SwitchUpdate(sw, false)); + switchListener.wait(500); + assertTrue("IOFSwitchListener.removedSwitch() was not called", + switchListener.nRemoved == 1); + } + } + + + private Map<String,Object> getFakeControllerIPRow(String id, String controllerId, + String type, int number, String discoveredIP ) { + HashMap<String, Object> row = new HashMap<String,Object>(); + row.put(Controller.CONTROLLER_INTERFACE_ID, id); + row.put(Controller.CONTROLLER_INTERFACE_CONTROLLER_ID, controllerId); + row.put(Controller.CONTROLLER_INTERFACE_TYPE, type); + row.put(Controller.CONTROLLER_INTERFACE_NUMBER, number); + row.put(Controller.CONTROLLER_INTERFACE_DISCOVERED_IP, discoveredIP); + return row; + } + + /** + * Test notifications for controller node IP changes. This requires + * synchronization between the main test thread and another thread + * that runs Controller's main loop and takes / handles updates. We + * synchronize with wait(timeout) / notifyAll(). We check for the + * expected condition after the wait returns. However, if wait returns + * due to the timeout (or due to spurious awaking) and the check fails we + * might just not have waited long enough. Using a long enough timeout + * mitigates this but we cannot get rid of the fundamental "issue". + * + * @throws Exception + */ + @Test + public void testControllerNodeIPChanges() throws Exception { + class DummyHAListener implements IHAListener { + public Map<String, String> curControllerNodeIPs; + public Map<String, String> addedControllerNodeIPs; + public Map<String, String> removedControllerNodeIPs; + public int nCalled; + + public DummyHAListener() { + this.nCalled = 0; + } + + @Override + public void roleChanged(Role oldRole, Role newRole) { + // ignore + } + + @Override + public synchronized void controllerNodeIPsChanged( + Map<String, String> curControllerNodeIPs, + Map<String, String> addedControllerNodeIPs, + Map<String, String> removedControllerNodeIPs) { + this.curControllerNodeIPs = curControllerNodeIPs; + this.addedControllerNodeIPs = addedControllerNodeIPs; + this.removedControllerNodeIPs = removedControllerNodeIPs; + this.nCalled++; + notifyAll(); + } + + public void do_assert(int nCalled, + Map<String, String> curControllerNodeIPs, + Map<String, String> addedControllerNodeIPs, + Map<String, String> removedControllerNodeIPs) { + assertEquals("nCalled is not as expected", nCalled, this.nCalled); + assertEquals("curControllerNodeIPs is not as expected", + curControllerNodeIPs, this.curControllerNodeIPs); + assertEquals("addedControllerNodeIPs is not as expected", + addedControllerNodeIPs, this.addedControllerNodeIPs); + assertEquals("removedControllerNodeIPs is not as expected", + removedControllerNodeIPs, this.removedControllerNodeIPs); + + } + } + long waitTimeout = 250; // ms + DummyHAListener listener = new DummyHAListener(); + HashMap<String,String> expectedCurMap = new HashMap<String, String>(); + HashMap<String,String> expectedAddedMap = new HashMap<String, String>(); + HashMap<String,String> expectedRemovedMap = new HashMap<String, String>(); + + controller.addHAListener(listener); + ControllerRunThread t = new ControllerRunThread(); + t.start(); + + synchronized(listener) { + // Insert a first entry + controller.storageSource.insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, + getFakeControllerIPRow("row1", "c1", "Ethernet", 0, "1.1.1.1")); + expectedCurMap.clear(); + expectedAddedMap.clear(); + expectedRemovedMap.clear(); + expectedCurMap.put("c1", "1.1.1.1"); + expectedAddedMap.put("c1", "1.1.1.1"); + listener.wait(waitTimeout); + listener.do_assert(1, expectedCurMap, expectedAddedMap, expectedRemovedMap); + + // Add an interface that we want to ignore. + controller.storageSource.insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, + getFakeControllerIPRow("row2", "c1", "Ethernet", 1, "1.1.1.2")); + listener.wait(waitTimeout); // TODO: do a different check. This call will have to wait for the timeout + assertTrue("controllerNodeIPsChanged() should not have been called here", + listener.nCalled == 1); + + // Add another entry + controller.storageSource.insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, + getFakeControllerIPRow("row3", "c2", "Ethernet", 0, "2.2.2.2")); + expectedCurMap.clear(); + expectedAddedMap.clear(); + expectedRemovedMap.clear(); + expectedCurMap.put("c1", "1.1.1.1"); + expectedCurMap.put("c2", "2.2.2.2"); + expectedAddedMap.put("c2", "2.2.2.2"); + listener.wait(waitTimeout); + listener.do_assert(2, expectedCurMap, expectedAddedMap, expectedRemovedMap); + + + // Update an entry + controller.storageSource.updateRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, + "row3", getFakeControllerIPRow("row3", "c2", "Ethernet", 0, "2.2.2.3")); + expectedCurMap.clear(); + expectedAddedMap.clear(); + expectedRemovedMap.clear(); + expectedCurMap.put("c1", "1.1.1.1"); + expectedCurMap.put("c2", "2.2.2.3"); + expectedAddedMap.put("c2", "2.2.2.3"); + expectedRemovedMap.put("c2", "2.2.2.2"); + listener.wait(waitTimeout); + listener.do_assert(3, expectedCurMap, expectedAddedMap, expectedRemovedMap); + + // Delete an entry + controller.storageSource.deleteRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, + "row3"); + expectedCurMap.clear(); + expectedAddedMap.clear(); + expectedRemovedMap.clear(); + expectedCurMap.put("c1", "1.1.1.1"); + expectedRemovedMap.put("c2", "2.2.2.3"); + listener.wait(waitTimeout); + listener.do_assert(4, expectedCurMap, expectedAddedMap, expectedRemovedMap); + } + } } diff --git a/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java b/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java index 56dc20fcba11f7448c5e8705cbad83c50ec77931..84a2cabd35f75b5bba8224c005f9fcbbf06f5298 100644 --- a/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java +++ b/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java @@ -27,7 +27,7 @@ import java.util.concurrent.CopyOnWriteArrayList; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; -import net.floodlightcontroller.core.IHARoleListener; +import net.floodlightcontroller.core.IHAListener; import net.floodlightcontroller.core.IInfoProvider; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; @@ -52,7 +52,7 @@ import org.openflow.protocol.factory.BasicFactory; public class MockFloodlightProvider implements IFloodlightModule, IFloodlightProviderService { protected Map<OFType, List<IOFMessageListener>> listeners; protected List<IOFSwitchListener> switchListeners; - protected List<IHARoleListener> haListeners; + protected List<IHAListener> haListeners; protected Map<Long, IOFSwitch> switches; protected BasicFactory factory; @@ -63,7 +63,7 @@ public class MockFloodlightProvider implements IFloodlightModule, IFloodlightPro listeners = new ConcurrentHashMap<OFType, List<IOFMessageListener>>(); switches = new ConcurrentHashMap<Long, IOFSwitch>(); switchListeners = new CopyOnWriteArrayList<IOFSwitchListener>(); - haListeners = new CopyOnWriteArrayList<IHARoleListener>(); + haListeners = new CopyOnWriteArrayList<IHAListener>(); factory = new BasicFactory(); } @@ -250,12 +250,12 @@ public class MockFloodlightProvider implements IFloodlightModule, IFloodlightPro } @Override - public void addHAListener(IHARoleListener listener) { + public void addHAListener(IHAListener listener) { haListeners.add(listener); } @Override - public void removeHAListener(IHARoleListener listener) { + public void removeHAListener(IHAListener listener) { haListeners.remove(listener); } @@ -275,7 +275,7 @@ public class MockFloodlightProvider implements IFloodlightModule, IFloodlightPro * @param newRole */ public void dispatchRoleChanged(Role oldRole, Role newRole) { - for (IHARoleListener rl : haListeners) { + for (IHAListener rl : haListeners) { rl.roleChanged(oldRole, newRole); } }