diff --git a/src/main/java/net/floodlightcontroller/core/OFSwitchBase.java b/src/main/java/net/floodlightcontroller/core/OFSwitchBase.java index de29c2ab7dd5be473012e5813fc65328b9d337ab..1c4e03f5c56e200dc94b86bdd702eb89d4250dcb 100644 --- a/src/main/java/net/floodlightcontroller/core/OFSwitchBase.java +++ b/src/main/java/net/floodlightcontroller/core/OFSwitchBase.java @@ -155,7 +155,7 @@ public abstract class OFSwitchBase implements IOFSwitch { this.portLock = new Object(); this.portsByNumber = new ConcurrentHashMap<Short, OFPhysicalPort>(); this.portsByName = new ConcurrentHashMap<String, OFPhysicalPort>(); - this.connected = true; + this.connected = false; this.statsFutureMap = new ConcurrentHashMap<Integer,OFStatisticsFuture>(); this.featuresFutureMap = new ConcurrentHashMap<Integer,OFFeaturesReplyFuture>(); this.iofMsgListenersMap = new ConcurrentHashMap<Integer,IOFMessageListener>(); @@ -231,6 +231,8 @@ public abstract class OFSwitchBase implements IOFSwitch { }) public void writeThrottled(OFMessage m, FloodlightContext bc) throws IOException { + if (channel == null || !isConnected()) + return; /** * By default, channel uses an unbounded send queue. Enable throttling * prevents the queue from growing big. @@ -262,6 +264,8 @@ public abstract class OFSwitchBase implements IOFSwitch { @Override public void write(OFMessage m, FloodlightContext bc) { + if (channel == null || !isConnected()) + return; //throws IOException { Map<IOFSwitch,List<OFMessage>> msg_buffer_map = local_msg_buffer.get(); List<OFMessage> msg_buffer = msg_buffer_map.get(this); @@ -288,6 +292,8 @@ public abstract class OFSwitchBase implements IOFSwitch { recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) public void write(List<OFMessage> msglist, FloodlightContext bc) { + if (channel == null || !isConnected()) + return; for (OFMessage m : msglist) { if (role == Role.SLAVE) { switch (m.getType()) { @@ -313,11 +319,15 @@ public abstract class OFSwitchBase implements IOFSwitch { * @throws IOException */ protected void write(List<OFMessage> msglist) { + if (channel == null || !isConnected()) + return; this.channel.write(msglist); } @Override public void disconnectOutputStream() { + if (channel == null) + return; channel.close(); } @@ -561,7 +571,7 @@ public abstract class OFSwitchBase implements IOFSwitch { @Override public boolean isActive() { // no lock needed since we use volatile - return isConnected() && this.role != Role.SLAVE; + return isConnected() && this.role == Role.MASTER; } @Override @@ -584,6 +594,8 @@ public abstract class OFSwitchBase implements IOFSwitch { @Override public void clearAllFlowMods() { + if (channel == null || !isConnected()) + return; // Delete all pre-existing flows OFMatch match = new OFMatch().setWildcards(OFMatch.OFPFW_ALL); OFMessage fm = ((OFFlowMod) floodlightProvider.getOFMessageFactory() diff --git a/src/main/java/net/floodlightcontroller/core/SwitchSyncRepresentation.java b/src/main/java/net/floodlightcontroller/core/SwitchSyncRepresentation.java index 2a8cbed2f143a1d11ae7c90c3a6cec148c14442d..3bd7958a650e2eaa6c59681bba95ca8e32abf0d0 100644 --- a/src/main/java/net/floodlightcontroller/core/SwitchSyncRepresentation.java +++ b/src/main/java/net/floodlightcontroller/core/SwitchSyncRepresentation.java @@ -1,12 +1,20 @@ package net.floodlightcontroller.core; import java.util.ArrayList; +import java.util.Collection; +import java.util.List; + +import net.floodlightcontroller.util.MACAddress; import org.openflow.protocol.OFFeaturesReply; import org.openflow.protocol.OFPhysicalPort; import org.openflow.protocol.statistics.OFDescriptionStatistics; import org.openflow.util.HexString; +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; + /** * Represents a switch in the BigSync store. It works out nicely that we * just need to store the FeaturesReply and the DescriptionStatistics in the @@ -15,76 +23,238 @@ import org.openflow.util.HexString; * */ public class SwitchSyncRepresentation { - // Alaes, these can't be final since we need to de-serialize them from - // Jackson - private OFFeaturesReply featuresReply; - private OFDescriptionStatistics description; + private static class SyncedPort { + @JsonProperty + public short portNumber; + @JsonProperty + public long hardwareAddress; + @JsonProperty + public String name; + @JsonProperty + public int config; + @JsonProperty + public int state; + @JsonProperty + public int currentFeatures; + @JsonProperty + public int advertisedFeatures; + @JsonProperty + public int supportedFeatures; + @JsonProperty + public int peerFeatures; + + public static SyncedPort fromOFPhysicalPort(OFPhysicalPort p) { + SyncedPort rv = new SyncedPort(); + rv.portNumber = p.getPortNumber(); + if (p.getHardwareAddress() == null) { + rv.hardwareAddress = 0; + } else { + rv.hardwareAddress = + MACAddress.valueOf(p.getHardwareAddress()).toLong(); + } + rv.name = p.getName(); + rv.config = p.getConfig(); + rv.state = p.getState(); + rv.currentFeatures = p.getCurrentFeatures(); + rv.advertisedFeatures = p.getAdvertisedFeatures(); + rv.supportedFeatures = p.getSupportedFeatures(); + rv.peerFeatures = p.getPeerFeatures(); + return rv; + } + + public OFPhysicalPort toOFPhysicalPort() { + OFPhysicalPort p = new OFPhysicalPort(); + p.setPortNumber(portNumber); + p.setHardwareAddress(MACAddress.valueOf(hardwareAddress).toBytes()); + p.setName(name); + p.setConfig(config); + p.setState(state); + p.setCurrentFeatures(currentFeatures); + p.setAdvertisedFeatures(advertisedFeatures); + p.setSupportedFeatures(supportedFeatures); + p.setPeerFeatures(peerFeatures); + return p; + } + } - public SwitchSyncRepresentation() { - featuresReply = new OFFeaturesReply(); - description = new OFDescriptionStatistics(); + // From FeaturesReply + private final long dpid; + private final int buffers; + private final byte tables; + private final int capabilities; + private final int actions; + private final List<SyncedPort> ports; + + // From OFDescriptionStatistics + private final String manufacturerDescription; + private final String hardwareDescription; + private final String softwareDescription; + private final String serialNumber; + private final String datapathDescription; + + + + /** + * @param dpid + * @param buffers + * @param tables + * @param capabilities + * @param actions + * @param ports + * @param manufacturerDescription + * @param hardwareDescription + * @param softwareDescription + * @param serialNumber + * @param datapathDescription + */ + @JsonCreator + public SwitchSyncRepresentation( + @JsonProperty("dpid") long dpid, + @JsonProperty("buffers") int buffers, + @JsonProperty("tables") byte tables, + @JsonProperty("capabilities") int capabilities, + @JsonProperty("actions") int actions, + @JsonProperty("ports") List<SyncedPort> ports, + @JsonProperty("manufacturerDescription") String manufacturerDescription, + @JsonProperty("hardwareDescription") String hardwareDescription, + @JsonProperty("softwareDescription") String softwareDescription, + @JsonProperty("serialNumber") String serialNumber, + @JsonProperty("datapathDescription") String datapathDescription) { + this.dpid = dpid; + this.buffers = buffers; + this.tables = tables; + this.capabilities = capabilities; + this.actions = actions; + this.ports = ports; + this.manufacturerDescription = manufacturerDescription; + this.hardwareDescription = hardwareDescription; + this.softwareDescription = softwareDescription; + this.serialNumber = serialNumber; + this.datapathDescription = datapathDescription; } public SwitchSyncRepresentation(IOFSwitch sw) { - description = sw.getDescriptionStatistics(); - featuresReply = new OFFeaturesReply(); - featuresReply.setDatapathId(sw.getId()); - featuresReply.setBuffers(sw.getBuffers()); - featuresReply.setTables(sw.getTables()); - featuresReply.setCapabilities(sw.getCapabilities()); - featuresReply.setActions(sw.getActions()); - featuresReply.setPorts(new ArrayList<OFPhysicalPort>(sw.getPorts())); + this.dpid = sw.getId(); + this.buffers = sw.getBuffers(); + this.tables = sw.getTables(); + this.capabilities = sw.getCapabilities(); + this.actions = sw.getActions(); + this.ports = toSyncedPortList(sw.getPorts()); + + OFDescriptionStatistics d = sw.getDescriptionStatistics(); + this.manufacturerDescription = d.getManufacturerDescription(); + this.hardwareDescription = d.getHardwareDescription(); + this.softwareDescription = d.getSoftwareDescription(); + this.serialNumber = d.getSerialNumber(); + this.datapathDescription = d.getDatapathDescription(); } - public OFFeaturesReply getFeaturesReply() { - return featuresReply; + public SwitchSyncRepresentation(OFFeaturesReply fr, + OFDescriptionStatistics d) { + this.dpid = fr.getDatapathId(); + this.buffers = fr.getBuffers(); + this.tables = fr.getTables(); + this.capabilities = fr.getCapabilities(); + this.actions = fr.getActions(); + this.ports = toSyncedPortList(fr.getPorts()); + + this.manufacturerDescription = d.getManufacturerDescription(); + this.hardwareDescription = d.getHardwareDescription(); + this.softwareDescription = d.getSoftwareDescription(); + this.serialNumber = d.getSerialNumber(); + this.datapathDescription = d.getDatapathDescription(); } - public void setFeaturesReply(OFFeaturesReply featuresReply) { - this.featuresReply = featuresReply; + private static List<SyncedPort> toSyncedPortList(Collection<OFPhysicalPort> ports) { + List<SyncedPort> rv = new ArrayList<SyncedPort>(ports.size()); + for (OFPhysicalPort p: ports) { + rv.add(SyncedPort.fromOFPhysicalPort(p)); + } + return rv; + } + + private static List<OFPhysicalPort> toOFPhysicalPortList(Collection<SyncedPort> ports) { + List<OFPhysicalPort> rv = new ArrayList<OFPhysicalPort>(ports.size()); + for (SyncedPort p: ports) { + rv.add(p.toOFPhysicalPort()); + } + return rv; + } + @JsonIgnore + public OFFeaturesReply getFeaturesReply() { + OFFeaturesReply fr = new OFFeaturesReply(); + fr.setDatapathId(dpid); + fr.setBuffers(buffers); + fr.setTables(tables); + fr.setCapabilities(capabilities); + fr.setActions(actions); + fr.setPorts(toOFPhysicalPortList(ports)); + return fr; + } + + @JsonIgnore public OFDescriptionStatistics getDescription() { - return description; + OFDescriptionStatistics desc = new OFDescriptionStatistics(); + desc.setManufacturerDescription(manufacturerDescription); + desc.setHardwareDescription(hardwareDescription); + desc.setSoftwareDescription(softwareDescription); + desc.setSerialNumber(serialNumber); + desc.setDatapathDescription(datapathDescription); + return desc; + } + + + + public long getDpid() { + return dpid; } - public void setDescription(OFDescriptionStatistics description) { - this.description = description; + public int getBuffers() { + return buffers; } - @Override - public int hashCode() { - final int prime = 31; - int result = 1; - result = prime * result - + ((description == null) ? 0 : description.hashCode()); - result = prime * result - + ((featuresReply == null) ? 0 : featuresReply.hashCode()); - return result; + public byte getTables() { + return tables; } - @Override - public boolean equals(Object obj) { - if (this == obj) return true; - if (obj == null) return false; - if (getClass() != obj.getClass()) return false; - SwitchSyncRepresentation other = (SwitchSyncRepresentation) obj; - if (description == null) { - if (other.description != null) return false; - } else if (!description.equals(other.description)) return false; - if (featuresReply == null) { - if (other.featuresReply != null) return false; - } else if (!featuresReply.equals(other.featuresReply)) return false; - return true; + public int getCapabilities() { + return capabilities; + } + + public int getActions() { + return actions; + } + + public List<SyncedPort> getPorts() { + return ports; + } + + public String getManufacturerDescription() { + return manufacturerDescription; + } + + public String getHardwareDescription() { + return hardwareDescription; + } + + public String getSoftwareDescription() { + return softwareDescription; + } + + public String getSerialNumber() { + return serialNumber; + } + + public String getDatapathDescription() { + return datapathDescription; } @Override public String toString() { String dpidString; - if (featuresReply == null) - dpidString = "?"; - else - dpidString = HexString.toHexString(featuresReply.getDatapathId()); + dpidString = HexString.toHexString(dpid); return "SwitchSyncRepresentation [DPID=" + dpidString + "]"; } } diff --git a/src/main/java/net/floodlightcontroller/core/internal/Controller.java b/src/main/java/net/floodlightcontroller/core/internal/Controller.java index bb5b3de550c51bae2d7dcfb25b4e93f8100bb576..d2ab376df737324d94b8ab6016e90591a6d602df 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/Controller.java +++ b/src/main/java/net/floodlightcontroller/core/internal/Controller.java @@ -323,7 +323,7 @@ public class Controller implements IFloodlightProviderService, return; } if (this.role == Role.MASTER && role == Role.SLAVE) { - log.info("Received role request to transition from MASTER " + log.info("Received role request to transition from MASTER to " + " SLAVE (reason: {}). Terminating floodlight.", roleChangeDescription); System.exit(0); @@ -507,7 +507,7 @@ public class Controller implements IFloodlightProviderService, sw.clearAllFlowMods(); addUpdateToQueue(new SwitchUpdate(dpid, SwitchUpdateType.ACTIVATED)); - sendNotificationsIfSwitchDiffers(oldSw, sw); + sendNotificationsIfSwitchDiffers(storedSwitch, sw); } } @@ -590,7 +590,7 @@ public class Controller implements IFloodlightProviderService, Set<OFPhysicalPort> sw1Ports = new HashSet<OFPhysicalPort>(sw1.getPorts()); Set<OFPhysicalPort> sw2Ports = - new HashSet<OFPhysicalPort>(sw1.getPorts()); + new HashSet<OFPhysicalPort>(sw2.getPorts()); if (! sw1Ports.equals(sw2Ports)) { addUpdateToQueue( new SwitchUpdate(sw2.getId(), @@ -612,6 +612,7 @@ public class Controller implements IFloodlightProviderService, private synchronized void consolidateStore() { if (role == Role.SLAVE) return; + log.info("Consolidating synced switches after MASTER transition"); this.syncedSwitches.clear(); IClosableIterator<Map.Entry<Long,Versioned<SwitchSyncRepresentation>>> iter = null; @@ -625,9 +626,10 @@ public class Controller implements IFloodlightProviderService, while(iter.hasNext()) { Entry<Long, Versioned<SwitchSyncRepresentation>> entry = iter.next(); - if (!this.activeSwitches.contains(entry.getKey())) { + if (!this.activeSwitches.containsKey(entry.getKey())) { removeSwitchFromStore(entry.getKey()); - //addUpdateToQueue(new SwitchUpdate(sw, switchUpdateType)) + addUpdateToQueue(new SwitchUpdate(entry.getKey(), + SwitchUpdateType.REMOVED)); } } } finally { @@ -748,16 +750,8 @@ public class Controller implements IFloodlightProviderService, log.debug("Dispatching HA Role update newRole = {}", newRole); } - /* - if (newRole == Role.SLAVE) { - messageDispatchGuard.disableDispatch(); - Controller.this.notifiedRole = newRole; - } - */ - if (haListeners != null) { - for (IHAListener listener : haListeners.getOrderedListeners()) { - listener.transitionToMaster(); - } + for (IHAListener listener : haListeners.getOrderedListeners()) { + listener.transitionToMaster(); } if (newRole != Role.SLAVE) { Controller.this.notifiedRole = newRole; @@ -869,8 +863,8 @@ public class Controller implements IFloodlightProviderService, * switch update. * @param sw */ - protected void notifyPortChanged(IOFSwitch sw) { - SwitchUpdate update = new SwitchUpdate(sw.getId(), + void notifyPortChanged(long dpid) { + SwitchUpdate update = new SwitchUpdate(dpid, SwitchUpdateType.PORTCHANGED); addUpdateToQueue(update); } @@ -1128,7 +1122,7 @@ public class Controller implements IFloodlightProviderService, ListenerDispatcher<OFType, IOFMessageListener> ldd = entry.getValue(); - StringBuffer sb = new StringBuffer(); + StringBuilder sb = new StringBuilder(); sb.append("OFListeners for "); sb.append(type); sb.append(": "); @@ -1138,6 +1132,14 @@ public class Controller implements IFloodlightProviderService, } log.debug(sb.toString()); } + + StringBuilder sb = new StringBuilder(); + sb.append("HAListeners: "); + for (IHAListener l: haListeners.getOrderedListeners()) { + sb.append(l.getName()); + sb.append(", "); + } + log.debug(sb.toString()); } public void removeOFMessageListeners(OFType type) { @@ -1620,9 +1622,6 @@ public class Controller implements IFloodlightProviderService, this.alwaysClearFlowsOnSwAdd = value; } - public boolean getAlwaysClearFlowsOnSwAdd() { - return this.alwaysClearFlowsOnSwAdd; - } @Override public Map<String, Long> getMemory() { @@ -1657,6 +1656,20 @@ public class Controller implements IFloodlightProviderService, } + @LogMessageDoc(level="WARN", + message="Failure adding update {} to queue", + explanation="The controller tried to add an internal notification" + + " to its message queue but the add failed.", + recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) + private void addUpdateToQueue(IUpdate update) { + try { + this.updates.put(update); + } catch (InterruptedException e) { + // This should never happen + log.error("Failure adding update {} to queue.", update); + } + } + void flushAll() { // Flush all flow-mods/packet-out/stats generated from this "train" OFSwitchBase.flush_all(); @@ -1685,18 +1698,13 @@ public class Controller implements IFloodlightProviderService, this.consolidateStoreTimeDelayMs = consolidateStoreTaskDelayMs; } - @LogMessageDoc(level="WARN", - message="Failure adding update {} to queue", - explanation="The controller tried to add an internal notification" + - " to its message queue but the add failed.", - recommendation=LogMessageDoc.REPORT_CONTROLLER_BUG) - private void addUpdateToQueue(IUpdate update) { - try { - this.updates.put(update); - } catch (InterruptedException e) { - // This should never happen - log.error("Failure adding update {} to queue.", update); - } + /** + * FOR TESTING ONLY + * returns the store listener so we can send events to the listener + */ + IStoreListener<Long> getStoreListener() { + return this.switchManager; } + } diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java b/src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java index 7cc676f79f364fbeb924334322be9415052c9961..75c9651097dfa7c83cf10f6afc7d96945c2e182f 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java @@ -713,15 +713,20 @@ class OFChannelHandler @Override void processOFPortStatus(OFChannelHandler h, OFPortStatus m) throws IOException { - // TODO handlePortStatusMessage(h, m); - h.dispatchMessage(m); + // FIXME: ---> h.dispatchMessage(m); } @Override void processOFPacketIn(OFChannelHandler h, OFPacketIn m) throws IOException { h.dispatchMessage(m); } + + @Override + void processOFFlowRemoved(OFChannelHandler h, + OFFlowRemoved m) throws IOException { + h.dispatchMessage(m); + } }, /** @@ -743,9 +748,12 @@ class OFChannelHandler } } + + @Override void processOFStatisticsReply(OFChannelHandler h, OFStatisticsReply m) { + // FIXME. h.sw.deliverStatisticsReply(m); } @@ -764,15 +772,13 @@ class OFChannelHandler @Override void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m) throws IOException { - h.sw.setFeaturesReply(m); - h.sw.deliverOFFeaturesReply(m); + // do nothing } @Override void processOFPortStatus(OFChannelHandler h, OFPortStatus m) throws IOException { - // TODO - handlePortStatusMessage(h, m); + // do nothing } @Override @@ -790,7 +796,6 @@ class OFChannelHandler log.warn("Received PacketIn from switch {} while" + "being slave. Reasserting slave role.", h.sw); h.controller.reassertRole(h, Role.SLAVE); - } }; @@ -944,18 +949,23 @@ class OFChannelHandler OFPortStatus m) { short portNumber = m.getDesc().getPortNumber(); OFPhysicalPort port = m.getDesc(); - if (m.getReason() == (byte)OFPortReason.OFPPR_MODIFY.ordinal()) { - h.sw.setPort(port); - log.debug("Port #{} modified for {}", portNumber, h.sw); - } else if (m.getReason() == (byte)OFPortReason.OFPPR_ADD.ordinal()) { - h.sw.setPort(port); - log.debug("Port #{} added for {}", portNumber, h.sw); - } else if (m.getReason() == - (byte)OFPortReason.OFPPR_DELETE.ordinal()) { - h.sw.deletePort(portNumber); - log.debug("Port #{} deleted for {}", portNumber, h.sw); + OFPortReason reason = OFPortReason.fromReasonCode(m.getReason()); + + switch(reason) { + case OFPPR_MODIFY: + h.sw.setPort(port); + log.debug("Port #{} modified for {}", portNumber, h.sw); + break; + case OFPPR_ADD: + h.sw.setPort(port); + log.debug("Port #{} added for {}", portNumber, h.sw); + break; + case OFPPR_DELETE: + h.sw.deletePort(portNumber); + log.debug("Port #{} deleted for {}", portNumber, h.sw); + break; } - h.controller.notifyPortChanged(h.sw); + h.controller.notifyPortChanged(h.sw.getId()); } /** diff --git a/src/main/java/net/floodlightcontroller/core/util/ListenerDispatcher.java b/src/main/java/net/floodlightcontroller/core/util/ListenerDispatcher.java index 58b543c65d6cc79c154576ae3b521c3101c514b7..c5ecf33c717c59c985a6faefbbdd7af033ce0074 100644 --- a/src/main/java/net/floodlightcontroller/core/util/ListenerDispatcher.java +++ b/src/main/java/net/floodlightcontroller/core/util/ListenerDispatcher.java @@ -1,7 +1,7 @@ /** -* Copyright 2011, Big Switch Networks, Inc. +* Copyright 2011, Big Switch Networks, Inc. * Originally created by David Erickson, Stanford University -* +* * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at @@ -28,20 +28,20 @@ import net.floodlightcontroller.core.IListener; import net.floodlightcontroller.core.annotations.LogMessageDoc; /** - * Maintain lists of listeners ordered by dependency. - * + * Maintain lists of listeners ordered by dependency. + * * @author readams * */ public class ListenerDispatcher<U, T extends IListener<U>> { protected static Logger logger = LoggerFactory.getLogger(ListenerDispatcher.class); - List<T> listeners = null; - - private void visit(List<T> newlisteners, U type, HashSet<T> visited, + List<T> listeners = new ArrayList<T>(); + + private void visit(List<T> newlisteners, U type, HashSet<T> visited, List<T> ordering, T listener) { if (!visited.contains(listener)) { visited.add(listener); - + for (T i : newlisteners) { if (ispre(type, i, listener)) { visit(newlisteners, type, visited, ordering, i); @@ -50,12 +50,12 @@ public class ListenerDispatcher<U, T extends IListener<U>> { ordering.add(listener); } } - + private boolean ispre(U type, T l1, T l2) { return (l2.isCallbackOrderingPrereq(type, l1.getName()) || l1.isCallbackOrderingPostreq(type, l2.getName())); } - + /** * Add a listener to the list of listeners * @param listener @@ -75,7 +75,7 @@ public class ListenerDispatcher<U, T extends IListener<U>> { newlisteners.add(listener); // Find nodes without outgoing edges - List<T> terminals = new ArrayList<T>(); + List<T> terminals = new ArrayList<T>(); for (T i : newlisteners) { boolean isterm = true; for (T j : newlisteners) { @@ -88,18 +88,18 @@ public class ListenerDispatcher<U, T extends IListener<U>> { terminals.add(i); } } - + if (terminals.size() == 0) { logger.error("No listener dependency solution: " + "No listeners without incoming dependencies"); listeners = newlisteners; return; } - + // visit depth-first traversing in the opposite order from // the dependencies. Note we will not generally detect cycles HashSet<T> visited = new HashSet<T>(); - List<T> ordering = new ArrayList<T>(); + List<T> ordering = new ArrayList<T>(); for (T term : terminals) { visit(newlisteners, type, visited, ordering, term); } @@ -118,16 +118,16 @@ public class ListenerDispatcher<U, T extends IListener<U>> { listeners = newlisteners; } } - + /** * Clear all listeners */ public void clearListeners() { listeners = new ArrayList<T>(); } - - /** - * Get the ordered list of listeners ordered by dependencies + + /** + * Get the ordered list of listeners ordered by dependencies * @return */ public List<T> getOrderedListeners() { diff --git a/src/main/java/org/openflow/protocol/OFPortStatus.java b/src/main/java/org/openflow/protocol/OFPortStatus.java index 8bde6e7842c00224320f98c909360230e0fd85ec..b7a3158ceee2ef7d6cc99086089cbe8e4203531f 100644 --- a/src/main/java/org/openflow/protocol/OFPortStatus.java +++ b/src/main/java/org/openflow/protocol/OFPortStatus.java @@ -1,7 +1,7 @@ /** * Copyright (c) 2008 The Board of Trustees of The Leland Stanford Junior * University -* +* * Licensed under the Apache License, Version 2.0 (the "License"); you may * not use this file except in compliance with the License. You may obtain * a copy of the License at @@ -29,9 +29,27 @@ public class OFPortStatus extends OFMessage { public static int MINIMUM_LENGTH = 64; public enum OFPortReason { - OFPPR_ADD, - OFPPR_DELETE, - OFPPR_MODIFY + OFPPR_ADD((byte)0), + OFPPR_DELETE((byte)1), + OFPPR_MODIFY((byte)2); + + private byte reason; + + private OFPortReason(byte reason) { + this.reason = reason; + } + + public byte getReasonCode() { + return this.reason; + } + + public static OFPortReason fromReasonCode(byte reason) { + for (OFPortReason r: OFPortReason.values()) { + if (r.getReasonCode() == reason) + return r; + } + return null; + } } protected byte reason; diff --git a/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java b/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java index fc9859d2fc95fe305ed772f8f2debbcfb09ebb7f..2b09daf29b00cdee5a5fd0975b9ec76ca14ee939 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java +++ b/src/test/java/net/floodlightcontroller/core/internal/ControllerTest.java @@ -19,19 +19,30 @@ package net.floodlightcontroller.core.internal; import static org.easymock.EasyMock.*; +import static org.junit.Assert.*; + import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.HashSet; +import java.util.Iterator; import java.util.List; +import java.util.Map; +import java.util.Set; import java.util.concurrent.ConcurrentHashMap; import net.floodlightcontroller.core.FloodlightContext; +import net.floodlightcontroller.core.HAListenerTypeMarker; import net.floodlightcontroller.core.IFloodlightProviderService; +import net.floodlightcontroller.core.IFloodlightProviderService.Role; +import net.floodlightcontroller.core.IHAListener; +import net.floodlightcontroller.core.IListener; import net.floodlightcontroller.core.IListener.Command; import net.floodlightcontroller.core.IOFMessageFilterManagerService; import net.floodlightcontroller.core.IOFMessageListener; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.IOFSwitchListener; import net.floodlightcontroller.core.OFMessageFilterManager; +import net.floodlightcontroller.core.RoleInfo; import net.floodlightcontroller.core.SwitchSyncRepresentation; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.test.MockFloodlightProvider; @@ -51,6 +62,8 @@ import net.floodlightcontroller.storage.memory.MemoryStorageSource; import net.floodlightcontroller.test.FloodlightTestCase; import net.floodlightcontroller.threadpool.IThreadPoolService; +import org.junit.After; +import org.junit.Before; import org.junit.Test; import org.openflow.protocol.OFFeaturesReply; import org.openflow.protocol.OFPacketIn; @@ -66,26 +79,31 @@ import org.openflow.protocol.statistics.OFDescriptionStatistics; import org.openflow.util.HexString; import org.sdnplatform.sync.IStoreClient; import org.sdnplatform.sync.ISyncService; +import org.sdnplatform.sync.IStoreListener.UpdateType; import org.sdnplatform.sync.test.MockSyncService; -/** - * - * @author David Erickson (daviderickson@cs.stanford.edu) - */ public class ControllerTest extends FloodlightTestCase { - //implements IOFSwitchDriver { private Controller controller; private MockThreadPoolService tp; private MockSyncService syncService; private IStoreClient<Long, SwitchSyncRepresentation> storeClient; + private IPacket testPacket; + private OFPacketIn pi; @Override + @Before public void setUp() throws Exception { + doSetUp(Role.MASTER); + } + + + public void doSetUp(Role role) throws Exception { super.setUp(); FloodlightModuleContext fmc = new FloodlightModuleContext(); FloodlightProvider cm = new FloodlightProvider(); + fmc.addConfigParam(cm, "role", role.toString()); controller = (Controller)cm.getServiceImpls().get(IFloodlightProviderService.class); fmc.addService(IFloodlightProviderService.class, controller); @@ -128,6 +146,38 @@ public class ControllerTest extends FloodlightTestCase { Long.class, SwitchSyncRepresentation.class); + testPacket = new Ethernet() + .setSourceMACAddress("00:44:33:22:11:00") + .setDestinationMACAddress("00:11:22:33:44:55") + .setEtherType(Ethernet.TYPE_ARP) + .setPayload( + new ARP() + .setHardwareType(ARP.HW_TYPE_ETHERNET) + .setProtocolType(ARP.PROTO_TYPE_IP) + .setHardwareAddressLength((byte) 6) + .setProtocolAddressLength((byte) 4) + .setOpCode(ARP.OP_REPLY) + .setSenderHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:00")) + .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.1")) + .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55")) + .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2"))); + byte[] testPacketSerialized = testPacket.serialize(); + + pi = ((OFPacketIn) BasicFactory.getInstance().getMessage(OFType.PACKET_IN)) + .setBufferId(-1) + .setInPort((short) 1) + .setPacketData(testPacketSerialized) + .setReason(OFPacketInReason.NO_MATCH) + .setTotalLength((short) testPacketSerialized.length); + + } + + @Override + @After + public void tearDown() { + tp.getScheduledExecutor().shutdownNow(); + // Make sure thare are not left over updates in the queue + controller.processUpdateQueueForTesting(); } public Controller getController() { @@ -162,6 +212,7 @@ public class ControllerTest extends FloodlightTestCase { } if (featuresReply == null) { featuresReply = createOFFeaturesReply(); + featuresReply.setDatapathId(dpid); } expect(sw.getId()).andReturn(dpid).anyTimes(); @@ -179,15 +230,25 @@ public class ControllerTest extends FloodlightTestCase { .andReturn(featuresReply.getPorts()).atLeastOnce(); } - /** - * Run the controller's main loop so that updates are processed - */ - protected class ControllerRunThread extends Thread { - @Override - public void run() { - controller.openFlowPort = 0; // Don't listen - controller.run(); - } + @SuppressWarnings("unchecked") + private <T> void setupListenerOrdering(IListener<T> listener) { + listener.isCallbackOrderingPostreq((T)anyObject(), + anyObject(String.class)); + expectLastCall().andReturn(false).anyTimes(); + + listener.isCallbackOrderingPrereq((T)anyObject(), + anyObject(String.class)); + expectLastCall().andReturn(false).anyTimes(); + } + + @Test + public void testHandleMessagesNoListeners() throws Exception { + IOFSwitch sw = createMock(IOFSwitch.class); + expect(sw.getId()).andReturn(0L).anyTimes(); + expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes(); + replay(sw); + controller.handleMessage(sw, pi, null); + verify(sw); } /** @@ -197,59 +258,37 @@ public class ControllerTest extends FloodlightTestCase { */ @Test public void testHandleMessages() throws Exception { - Controller controller = getController(); controller.removeOFMessageListeners(OFType.PACKET_IN); IOFSwitch sw = createMock(IOFSwitch.class); expect(sw.getId()).andReturn(0L).anyTimes(); expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes(); - // Build our test packet - IPacket testPacket = new Ethernet() - .setSourceMACAddress("00:44:33:22:11:00") - .setDestinationMACAddress("00:11:22:33:44:55") - .setEtherType(Ethernet.TYPE_ARP) - .setPayload( - new ARP() - .setHardwareType(ARP.HW_TYPE_ETHERNET) - .setProtocolType(ARP.PROTO_TYPE_IP) - .setHardwareAddressLength((byte) 6) - .setProtocolAddressLength((byte) 4) - .setOpCode(ARP.OP_REPLY) - .setSenderHardwareAddress(Ethernet.toMACAddress("00:44:33:22:11:00")) - .setSenderProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.1")) - .setTargetHardwareAddress(Ethernet.toMACAddress("00:11:22:33:44:55")) - .setTargetProtocolAddress(IPv4.toIPv4AddressBytes("192.168.1.2"))); - byte[] testPacketSerialized = testPacket.serialize(); - - // Build the PacketIn - OFPacketIn pi = ((OFPacketIn) BasicFactory.getInstance().getMessage(OFType.PACKET_IN)) - .setBufferId(-1) - .setInPort((short) 1) - .setPacketData(testPacketSerialized) - .setReason(OFPacketInReason.NO_MATCH) - .setTotalLength((short) testPacketSerialized.length); - IOFMessageListener test1 = createMock(IOFMessageListener.class); expect(test1.getName()).andReturn("test1").anyTimes(); - expect(test1.isCallbackOrderingPrereq((OFType)anyObject(), (String)anyObject())).andReturn(false).anyTimes(); - expect(test1.isCallbackOrderingPostreq((OFType)anyObject(), (String)anyObject())).andReturn(false).anyTimes(); - expect(test1.receive(eq(sw), eq(pi), isA(FloodlightContext.class))).andThrow(new RuntimeException("This is NOT an error! We are testing exception catching.")); + setupListenerOrdering(test1); + expect(test1.receive(eq(sw), eq(pi), isA(FloodlightContext.class))).andReturn(Command.CONTINUE); IOFMessageListener test2 = createMock(IOFMessageListener.class); expect(test2.getName()).andReturn("test2").anyTimes(); + expect(test2.isCallbackOrderingPrereq((OFType)anyObject(), eq("test1"))).andReturn(false).anyTimes(); expect(test2.isCallbackOrderingPrereq((OFType)anyObject(), (String)anyObject())).andReturn(false).anyTimes(); expect(test2.isCallbackOrderingPostreq((OFType)anyObject(), (String)anyObject())).andReturn(false).anyTimes(); + expect(test2.receive(eq(sw), eq(pi), isA(FloodlightContext.class))).andThrow(new RuntimeException("This is NOT an error! We are testing exception catching.")); // expect no calls to test2.receive() since test1.receive() threw an exception replay(test1, test2, sw); controller.addOFMessageListener(OFType.PACKET_IN, test1); controller.addOFMessageListener(OFType.PACKET_IN, test2); + boolean exceptionThrown = false; try { controller.handleMessage(sw, pi, null); } catch (RuntimeException e) { assertEquals(e.getMessage().startsWith("This is NOT an error!"), true); + exceptionThrown = true; } verify(test1, test2, sw); + assertTrue("Expected exception was not thrown by test2", + exceptionThrown); // verify STOP works reset(test1, test2, sw); @@ -261,6 +300,92 @@ public class ControllerTest extends FloodlightTestCase { verify(test1, test2, sw); } + @Test + public void testHandleMessagesSlave() throws Exception { + doSetUp(Role.SLAVE); + IOFSwitch sw = createMock(IOFSwitch.class); + expect(sw.getId()).andReturn(0L).anyTimes(); + expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes(); + + IOFMessageListener test1 = createMock(IOFMessageListener.class); + expect(test1.getName()).andReturn("test1").atLeastOnce(); + expect(test1.isCallbackOrderingPrereq((OFType)anyObject(), + (String)anyObject())) + .andReturn(false).atLeastOnce(); + expect(test1.isCallbackOrderingPostreq((OFType)anyObject(), + (String)anyObject())) + .andReturn(false).atLeastOnce(); + + replay(test1, sw); + controller.addOFMessageListener(OFType.PACKET_IN, test1); + // message should not be dispatched + controller.handleMessage(sw, pi, null); + verify(test1); + + //--------------------------------- + // transition to Master + //-------------------------------- + controller.setRole(Role.MASTER, "FooBar"); + + // transitioned but HA listeneres not yet notified. + // message should not be dispatched + reset(test1); + replay(test1); + controller.handleMessage(sw, pi, null); + verify(test1); + + // notify HA listeners + controller.processUpdateQueueForTesting(); + // no message should be dispatched + reset(test1); + expect(test1.receive(eq(sw), eq(pi), isA(FloodlightContext.class))).andReturn(Command.STOP); + replay(test1); + controller.handleMessage(sw, pi, null); + verify(test1); + + verify(sw); + } + + + @Test + public void testHandleMessageWithContext() throws Exception { + IOFSwitch sw = createMock(IOFSwitch.class); + expect(sw.getId()).andReturn(0L).anyTimes(); + expect(sw.getStringId()).andReturn("00:00:00:00:00:00:00").anyTimes(); + + IOFMessageListener test1 = createMock(IOFMessageListener.class); + expect(test1.getName()).andReturn("test1").anyTimes(); + expect(test1.isCallbackOrderingPrereq((OFType)anyObject(), + (String)anyObject())) + .andReturn(false).anyTimes(); + expect(test1.isCallbackOrderingPostreq((OFType)anyObject(), + (String)anyObject())) + .andReturn(false).anyTimes(); + FloodlightContext cntx = new FloodlightContext(); + expect(test1.receive(same(sw), same(pi) , same(cntx))) + .andReturn(Command.CONTINUE); + + IOFMessageListener test2 = createMock(IOFMessageListener.class); + expect(test2.getName()).andReturn("test2").anyTimes(); + expect(test2.isCallbackOrderingPrereq((OFType)anyObject(), + (String)anyObject())) + .andReturn(false).anyTimes(); + expect(test2.isCallbackOrderingPostreq((OFType)anyObject(), + (String)anyObject())) + .andReturn(false).anyTimes(); + // test2 will not receive any message! + + replay(test1, test2, sw); + controller.addOFMessageListener(OFType.PACKET_IN, test1); + controller.addOFMessageListener(OFType.ERROR, test2); + controller.handleMessage(sw, pi, cntx); + verify(test1, test2, sw); + + Ethernet eth = IFloodlightProviderService.bcStore.get(cntx, + IFloodlightProviderService.CONTEXT_PI_PAYLOAD); + assertArrayEquals(testPacket.serialize(), eth.serialize()); + } + @Test public void testMessageFilterManager() throws Exception { @@ -382,27 +507,176 @@ public class ControllerTest extends FloodlightTestCase { } + @Test + public void testGetRoleInfoDefault() { + RoleInfo info = controller.getRoleInfo(); + assertEquals(Role.MASTER.toString(), info.getRole()); + assertNotNull(info.getRoleChangeDescription()); + assertEquals(Role.MASTER, controller.getRole()); + // FIXME: RoleInfo's date. but the format is kinda broken + } -/* + /* + * Test interaction with OFChannelHandler when the current role is + * master. + */ @Test - public void testSwitchActivatedNoClearFM() throws Exception { - IOFSwitch sw = createMock(IOFSwitch.class); - setupSwitchForAddSwitch(sw, 0L, null, null); + public void testChannelHandlerMaster() { + OFChannelHandler h = createMock(OFChannelHandler.class); - IOFSwitchListener listener = createMock(IOFSwitchListener.class); - listener.addedSwitch(sw); + // Add the handler. The controller should call sendRoleRequest + h.sendRoleRequest(Role.MASTER); + expectLastCall().once(); + replay(h); + controller.addSwitchChannelAndSendInitialRole(h); + verify(h); + + // Reassert the role. + reset(h); + h.sendRoleRequestIfNotPending(Role.MASTER); + replay(h); + controller.reassertRole(h, Role.MASTER); + verify(h); + + // reassert a different role: no-op + reset(h); + replay(h); + controller.reassertRole(h, Role.SLAVE); + verify(h); + } + + /* + * Start as SLAVE then set role to MASTER + * Tests normal role change transition. Check that connected channels + * receive a setRole request + */ + @Test + public void testSetRole() throws Exception { + doSetUp(Role.SLAVE); + RoleInfo info = controller.getRoleInfo(); + assertEquals(Role.SLAVE.toString(), info.getRole()); + assertEquals(Role.SLAVE, controller.getRole()); + + + OFChannelHandler h = createMock(OFChannelHandler.class); + + // Add the channel handler. The controller should call sendRoleRequest + h.sendRoleRequest(Role.SLAVE); + expectLastCall().once(); + replay(h); + controller.addSwitchChannelAndSendInitialRole(h); + verify(h); + + // Reassert the role. + reset(h); + h.sendRoleRequestIfNotPending(Role.SLAVE); + replay(h); + controller.reassertRole(h, Role.SLAVE); + verify(h); + + // reassert a different role: no-op + reset(h); + replay(h); + controller.reassertRole(h, Role.MASTER); + verify(h); + + // Change role to MASTER + reset(h); + h.sendRoleRequest(Role.MASTER); + expectLastCall().once(); + IHAListener listener = createMock(IHAListener.class); + expect(listener.getName()).andReturn("foo").anyTimes(); + setupListenerOrdering(listener); + listener.transitionToMaster(); expectLastCall().once(); replay(listener); - controller.addOFSwitchListener(listener); + replay(h); + controller.addHAListener(listener); + controller.setRole(Role.MASTER, "FooBar"); + controller.processUpdateQueueForTesting(); + verify(h); + verify(listener); + info = controller.getRoleInfo(); + assertEquals(Role.MASTER.toString(), info.getRole()); + assertEquals("FooBar", info.getRoleChangeDescription()); + assertEquals(Role.MASTER, controller.getRole()); - replay(sw); - controller.switchActivated(sw); - verify(sw); - assertEquals(sw, controller.getSwitch(0L)); + + } + + /* Test other setRole cases: re-setting role to the current role, + * setting role to equal, etc. + */ + @Test + public void testSetRoleOthercases() throws Exception { + doSetUp(Role.SLAVE); + + OFChannelHandler h = createMock(OFChannelHandler.class); + + // Add the channel handler. The controller should call sendRoleRequest + h.sendRoleRequest(Role.SLAVE); + expectLastCall().once(); + replay(h); + controller.addSwitchChannelAndSendInitialRole(h); + verify(h); + + // remove the channel. Nothing should + reset(h); + replay(h); + controller.removeSwitchChannel(h); + + // Create and add the HA listener + IHAListener listener = createMock(IHAListener.class); + expect(listener.getName()).andReturn("foo").anyTimes(); + setupListenerOrdering(listener); + replay(listener); + controller.addHAListener(listener); + + // Set role to slave again. Nothing should happen + controller.setRole(Role.SLAVE, "FooBar"); + controller.processUpdateQueueForTesting(); + verify(listener); + + reset(listener); + listener.transitionToMaster(); + expectLastCall().once(); + replay(listener); + + // set role to equal. Should set to master internally + controller.setRole(Role.EQUAL, "ToEqual"); controller.processUpdateQueueForTesting(); verify(listener); + RoleInfo info = controller.getRoleInfo(); + assertEquals(Role.MASTER.toString(), info.getRole()); + assertEquals("ToEqual", info.getRoleChangeDescription()); + assertEquals(Role.MASTER, controller.getRole()); + + + verify(h); // no calls should have happened on h } -*/ + + + + @Test + public void testSetRoleNPE() { + try { + controller.setRole(null, ""); + fail("Should have thrown an Exception"); + } + catch (NullPointerException e) { + //exptected + } + try { + controller.setRole(Role.MASTER, null); + fail("Should have thrown an Exception"); + } + catch (NullPointerException e) { + //exptected + } + } + + + @Test @@ -442,6 +716,452 @@ public class ControllerTest extends FloodlightTestCase { } + /* + * Create and activate a new switch with the given dpid, features reply + * and description. If description and/or features reply are null we'll + * allocate the default one + * The mocked switch instance will be returned. It wil be reset. + */ + public IOFSwitch doActivateNewSwitch(long dpid, + OFDescriptionStatistics desc, + OFFeaturesReply featuresReply) + throws Exception { + controller.setAlwaysClearFlowsOnSwAdd(true); + + IOFSwitch sw = createMock(IOFSwitch.class); + if (featuresReply == null) { + featuresReply = createOFFeaturesReply(); + featuresReply.setDatapathId(dpid); + } + if (desc == null) { + desc = createOFDescriptionStatistics(); + } + setupSwitchForAddSwitch(sw, dpid, desc, featuresReply); + sw.clearAllFlowMods(); + expectLastCall().once(); + + replay(sw); + controller.switchActivated(sw); + verify(sw); + assertEquals(sw, controller.getSwitch(dpid)); + // drain updates and ignore + controller.processUpdateQueueForTesting(); + + SwitchSyncRepresentation storedSwitch = storeClient.getValue(dpid); + assertEquals(featuresReply, storedSwitch.getFeaturesReply()); + assertEquals(desc, storedSwitch.getDescription()); + reset(sw); + return sw; + } + + + /* + * Create a switch sync representation and add it to the store and + * notify the store listener. + * If the description and/or features reply are null, we'll allocate + * the default one + */ + public void doAddSwitchToStore(long dpid, + OFDescriptionStatistics desc, + OFFeaturesReply featuresReply) + throws Exception { + if (featuresReply == null) { + featuresReply = createOFFeaturesReply(); + featuresReply.setDatapathId(dpid); + } + if (desc == null) { + desc = createOFDescriptionStatistics(); + } + + SwitchSyncRepresentation ssr = + new SwitchSyncRepresentation(featuresReply, desc); + storeClient.put(dpid, ssr); + + Iterator<Long> keysToNotify = Collections.singletonList(dpid).iterator(); + controller.getStoreListener().keysModified(keysToNotify, + UpdateType.REMOTE); + } + + + /* add switch to store while master. no-op */ + @Test + public void testAddSwitchToStoreMaster() throws Exception { + IOFSwitchListener listener = createMock(IOFSwitchListener.class); + controller.addOFSwitchListener(listener); + replay(listener); + + doAddSwitchToStore(1L, null, null); + controller.processUpdateQueueForTesting(); + IOFSwitch sw = controller.getSwitch(1L); + verify(listener); + assertNull("There shouldn't be a switch", sw); + } + + + /* + * add switch to store while slave. should get notification and switch + * should be added + */ + @Test + public void testAddSwitchToStoreSlave() throws Exception { + doSetUp(Role.SLAVE); + + IOFSwitchListener listener = createMock(IOFSwitchListener.class); + listener.switchAdded(1L); + expectLastCall().once(); + controller.addOFSwitchListener(listener); + replay(listener); + + OFDescriptionStatistics desc = createOFDescriptionStatistics(); + desc.setDatapathDescription("The Switch"); + doAddSwitchToStore(1L, desc, null); + controller.processUpdateQueueForTesting(); + verify(listener); + + IOFSwitch sw = controller.getSwitch(1L); + assertNotNull("Switch should be present", sw); + assertEquals(1L, sw.getId()); + assertFalse("Switch should be inactive", sw.isActive()); + assertEquals("The Switch", + sw.getDescriptionStatistics().getDatapathDescription()); + } + + private static OFPhysicalPort createOFPhysicalPort(String name, int number) { + OFPhysicalPort p = new OFPhysicalPort(); + p.setHardwareAddress(new byte [] { 0, 0, 0, 0, 0, 0 }); + p.setPortNumber((short)number); + p.setName(name); + return p; + } + + /* + * This test goes through the SLAVE->MASTER program flow. We'll start as + * SLAVE. Add switches to the store while slave, update these switches + * then transition to master, make most (but not all switches) "connect" + * We also check correct behavior of getAllSwitchDpids() and + * getAllSwitchMap() + */ + @Test + public void testSwitchAddWithRoleChange() throws Exception { + int consolidateStoreDelayMs = 50; + doSetUp(Role.SLAVE); + + // Add HA Listener + IHAListener haListener = createMock(IHAListener.class); + expect(haListener.getName()).andReturn("foo").anyTimes(); + setupListenerOrdering(haListener); + replay(haListener); + controller.addHAListener(haListener); + verify(haListener); + reset(haListener); + + // Add switch listener + IOFSwitchListener switchListener = createMock(IOFSwitchListener.class); + controller.addOFSwitchListener(switchListener); + + //--------------------------------------- + // Initialization + //--------------------------------------- + + // Switch 1 + OFFeaturesReply fr1a = createOFFeaturesReply(); + fr1a.setDatapathId(1L); + OFPhysicalPort p = createOFPhysicalPort("P1", 1); + List<OFPhysicalPort> ports1a = Collections.singletonList(p); + fr1a.setPorts(ports1a); + // an alternative featuers reply + OFFeaturesReply fr1b = createOFFeaturesReply(); + fr1b.setDatapathId(1L); + p = new OFPhysicalPort(); + p = createOFPhysicalPort("P1", 1); // same port as above + List<OFPhysicalPort> ports1b = new ArrayList<OFPhysicalPort>(); + ports1b.add(p); + p = createOFPhysicalPort("P2", 42000); + ports1b.add(p); + fr1b.setPorts(ports1b); + + // Switch 2 + OFFeaturesReply fr2a = createOFFeaturesReply(); + fr2a.setDatapathId(2L); + List<OFPhysicalPort> ports2a = new ArrayList<OFPhysicalPort>(ports1a); + fr2a.setPorts(ports2a); + // an alternative features reply + OFFeaturesReply fr2b = createOFFeaturesReply(); + fr2b.setDatapathId(2L); + p = new OFPhysicalPort(); + p = createOFPhysicalPort("P1", 2); // port number changed + List<OFPhysicalPort> ports2b = Collections.singletonList(p); + fr2b.setPorts(ports2b); + + //--------------------------------------- + // Adding switches to store + //--------------------------------------- + + replay(haListener); // nothing should happen to haListener + + // add switch1 with fr1a to store + reset(switchListener); + switchListener.switchAdded(1L); + expectLastCall().once(); + replay(switchListener); + doAddSwitchToStore(1L, null, fr1a); + controller.processUpdateQueueForTesting(); + verify(switchListener); + reset(switchListener); + + IOFSwitch sw = controller.getSwitch(1L); + assertNotNull("Switch should be present", sw); + assertEquals(1L, sw.getId()); + assertFalse("Switch should be inactive", sw.isActive()); + assertEquals(new HashSet<OFPhysicalPort>(ports1a), + new HashSet<OFPhysicalPort>(sw.getPorts())); + + // add switch 2 with fr2a to store + reset(switchListener); + switchListener.switchAdded(2L); + expectLastCall().once(); + replay(switchListener); + doAddSwitchToStore(2L, null, fr2a); + controller.processUpdateQueueForTesting(); + verify(switchListener); + reset(switchListener); + + sw = controller.getSwitch(2L); + assertNotNull("Switch should be present", sw); + assertEquals(2L, sw.getId()); + assertFalse("Switch should be inactive", sw.isActive()); + assertEquals(new HashSet<OFPhysicalPort>(ports2a), + new HashSet<OFPhysicalPort>(sw.getPorts())); + + // add switch 3 to store + reset(switchListener); + switchListener.switchAdded(3L); + expectLastCall().once(); + replay(switchListener); + doAddSwitchToStore(3L, null, null); + controller.processUpdateQueueForTesting(); + verify(switchListener); + reset(switchListener); + + sw = controller.getSwitch(3L); + assertNotNull("Switch should be present", sw); + assertEquals(3L, sw.getId()); + assertFalse("Switch should be inactive", sw.isActive()); + + // update switch 1 with fr1b + reset(switchListener); + switchListener.switchPortChanged(1L); + expectLastCall().once(); + replay(switchListener); + doAddSwitchToStore(1L, null, fr1b); + controller.processUpdateQueueForTesting(); + verify(switchListener); + reset(switchListener); + + sw = controller.getSwitch(1L); + assertNotNull("Switch should be present", sw); + assertEquals(1L, sw.getId()); + assertFalse("Switch should be inactive", sw.isActive()); + assertEquals(new HashSet<OFPhysicalPort>(ports1b), + new HashSet<OFPhysicalPort>(sw.getPorts())); + + // Check getAllSwitchDpids() and getAllSwitchMap() + Set<Long> expectedDpids = new HashSet<Long>(); + expectedDpids.add(1L); + expectedDpids.add(2L); + expectedDpids.add(3L); + assertEquals(expectedDpids, controller.getAllSwitchDpids()); + Map<Long, IOFSwitch> expectedSwitchMap = new HashMap<Long, IOFSwitch>(); + expectedSwitchMap.put(1L, controller.getSwitch(1L)); + expectedSwitchMap.put(2L, controller.getSwitch(2L)); + expectedSwitchMap.put(3L, controller.getSwitch(3L)); + assertEquals(expectedSwitchMap, controller.getAllSwitchMap()); + + verify(haListener); + //-------------------------------------- + // Transition to master + //-------------------------------------- + reset(haListener); + haListener.transitionToMaster(); + expectLastCall().once(); + replay(haListener); + controller.setConsolidateStoreTaskDelay(consolidateStoreDelayMs); + controller.setRole(Role.MASTER, "FooBar"); + controller.processUpdateQueueForTesting(); + verify(haListener); + reset(haListener); + replay(haListener); + + //-------------------------------------- + // Activate switches + //-------------------------------------- + + // Activate switch 1 + IOFSwitch sw1 = createMock(IOFSwitch.class); + setupSwitchForAddSwitch(sw1, 1L, null, fr1b); + reset(switchListener); + switchListener.switchActivated(1L); + expectLastCall().once(); + replay(sw1); + replay(switchListener); + controller.switchActivated(sw1); + controller.processUpdateQueueForTesting(); + verify(switchListener); + verify(sw1); + + sw = controller.getSwitch(1L); + assertNotNull("Switch should be present", sw); + assertSame(sw1, sw); // the mock switch should be returned + + // Activate switch 2 with different features reply + // should get portChanged + IOFSwitch sw2 = createMock(IOFSwitch.class); + setupSwitchForAddSwitch(sw2, 2L, null, fr2b); + reset(switchListener); + switchListener.switchActivated(2L); + expectLastCall().once(); + switchListener.switchPortChanged(2L); + expectLastCall().once(); + replay(sw2); + replay(switchListener); + controller.switchActivated(sw2); + controller.processUpdateQueueForTesting(); + verify(switchListener); + verify(sw2); + + sw = controller.getSwitch(2L); + assertNotNull("Switch should be present", sw); + assertSame(sw2, sw); // the mock switch should be returned + + + // Do not activate switch 3, but it should still be present + sw = controller.getSwitch(3L); + IOFSwitch sw3 = sw; + assertNotNull("Switch should be present", sw); + assertEquals(3L, sw.getId()); + assertFalse("Switch should be inactive", sw.isActive()); + + // Check getAllSwitchDpids() and getAllSwitchMap() + expectedDpids = new HashSet<Long>(); + expectedDpids.add(1L); + expectedDpids.add(2L); + expectedDpids.add(3L); + assertEquals(expectedDpids, controller.getAllSwitchDpids()); + expectedSwitchMap = new HashMap<Long, IOFSwitch>(); + expectedSwitchMap.put(1L, sw1); + expectedSwitchMap.put(2L, sw2); + expectedSwitchMap.put(3L, sw3); + assertEquals(expectedSwitchMap, controller.getAllSwitchMap()); + + //-------------------------------- + // Wait for consolidateStore + //-------------------------------- + reset(switchListener); + switchListener.switchRemoved(3L); + replay(switchListener); + Thread.sleep(consolidateStoreDelayMs + 5); + controller.processUpdateQueueForTesting(); + verify(switchListener); + + // Verify the expected switches are all there. no more no less + sw = controller.getSwitch(1L); + assertNotNull("Switch should be present", sw); + assertSame(sw1, sw); // the mock switch should be returned + + sw = controller.getSwitch(2L); + assertNotNull("Switch should be present", sw); + assertSame(sw2, sw); // the mock switch should be returned + + // Do not activate switch 3, but it should still be present + sw = controller.getSwitch(3L); + assertNull("Switch should NOT be present", sw); + + // Check getAllSwitchDpids() and getAllSwitchMap() + expectedDpids = new HashSet<Long>(); + expectedDpids.add(1L); + expectedDpids.add(2L); + assertEquals(expectedDpids, controller.getAllSwitchDpids()); + expectedSwitchMap = new HashMap<Long, IOFSwitch>(); + expectedSwitchMap.put(1L, sw1); + expectedSwitchMap.put(2L, sw2); + assertEquals(expectedSwitchMap, controller.getAllSwitchMap()); + + verify(haListener); + } + + + + /* + * Disconnect a switch. normal program flow + */ + @Test + public void testSwitchDisconnected() throws Exception { + IOFSwitch sw = doActivateNewSwitch(1L, null, null); + expect(sw.getId()).andReturn(1L).anyTimes(); + expect(sw.getStringId()).andReturn(HexString.toHexString(1L)).anyTimes(); + sw.cancelAllStatisticsReplies(); + expectLastCall().once(); + IOFSwitchListener listener = createMock(IOFSwitchListener.class); + listener.switchRemoved(1L); + expectLastCall().once(); + controller.addOFSwitchListener(listener); + replay(sw, listener); + controller.switchDisconnected(sw); + controller.processUpdateQueueForTesting(); + verify(sw, listener); + + assertNull(controller.getSwitch(1L)); + assertNull(storeClient.getValue(1L)); + } + + /* + * Remove a nonexisting switch. should be ignored + */ + @Test + public void testNonexistingSwitchDisconnected() throws Exception { + IOFSwitch sw = createMock(IOFSwitch.class); + expect(sw.getId()).andReturn(1L).anyTimes(); + expect(sw.getStringId()).andReturn(HexString.toHexString(1L)).anyTimes(); + IOFSwitchListener listener = createMock(IOFSwitchListener.class); + controller.addOFSwitchListener(listener); + replay(sw, listener); + controller.switchDisconnected(sw); + controller.processUpdateQueueForTesting(); + verify(sw, listener); + + assertNull(controller.getSwitch(1L)); + assertNull(storeClient.getValue(1L)); + } + + /* + * Try to remove a switch that's different from what's in the active + * switch map. Should be ignored + */ + @Test + public void testSwitchDisconnectedOther() throws Exception { + IOFSwitch origSw = doActivateNewSwitch(1L, null, null); + // create a new mock switch + IOFSwitch sw = createMock(IOFSwitch.class); + expect(sw.getId()).andReturn(1L).anyTimes(); + expect(sw.getStringId()).andReturn(HexString.toHexString(1L)).anyTimes(); + IOFSwitchListener listener = createMock(IOFSwitchListener.class); + controller.addOFSwitchListener(listener); + replay(sw, listener); + controller.switchDisconnected(sw); + controller.processUpdateQueueForTesting(); + verify(sw, listener); + + assertSame(origSw, controller.getSwitch(1L)); + assertNotNull(storeClient.getValue(1L)); + } + + + + /* + * Try to activate a switch that's already active (which can happen if + * two different switches have the same DPIP or if a switch reconnects + * while the old TCP connection is still alive + */ @Test public void testSwitchActivatedWithAlreadyActiveSwitch() throws Exception { OFDescriptionStatistics oldDesc = createOFDescriptionStatistics(); @@ -497,16 +1217,7 @@ public class ControllerTest extends FloodlightTestCase { verify(listener); } - @Test - public void testSetRoleNull() { - try { - controller.setRole(null, null); - fail("Should have thrown an Exception"); - } - catch (NullPointerException e) { - //exptected - } - } + /** * Tests that you can't remove a switch from the map returned by @@ -526,270 +1237,182 @@ public class ControllerTest extends FloodlightTestCase { } -// -// 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); -// } -// } -// -// @Test -// public void testGetControllerNodeIPs() { -// HashMap<String,String> expectedCurMap = new HashMap<String, String>(); -// -// controller.storageSource.insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, -// getFakeControllerIPRow("row1", "c1", "Ethernet", 0, "1.1.1.1")); -// controller.storageSource.insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, -// getFakeControllerIPRow("row2", "c1", "Ethernet", 1, "1.1.1.2")); -// controller.storageSource.insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, -// getFakeControllerIPRow("row3", "c2", "Ethernet", 0, "2.2.2.2")); -// expectedCurMap.put("c1", "1.1.1.1"); -// expectedCurMap.put("c2", "2.2.2.2"); -// assertEquals("expectedControllerNodeIPs is not as expected", -// expectedCurMap, controller.getControllerNodeIPs()); -// } -// -// -// @Test -// public void testSetRole() { -// controller.connectedSwitches.add(new OFSwitchImpl()); -// RoleChanger roleChanger = createMock(RoleChanger.class); -// roleChanger.submitRequest(controller.connectedSwitches, Role.SLAVE); -// controller.roleChanger = roleChanger; -// -// assertEquals("Check that update queue is empty", 0, -// controller.updates.size()); -// -// replay(roleChanger); -// controller.setRole(Role.SLAVE, "Testing"); -// controller.doSetRole(); // avoid wait -// verify(roleChanger); -// -// Controller.IUpdate upd = controller.updates.poll(); -// assertNotNull("Check that update queue has an update", upd); -// assertTrue("Check that update is HARoleUpdate", -// upd instanceof Controller.HARoleUpdate); -// Controller.HARoleUpdate roleUpd = (Controller.HARoleUpdate)upd; -// assertSame(Role.MASTER, roleUpd.oldRole); -// assertSame(Role.SLAVE, roleUpd.newRole); -// } -// -// @SuppressWarnings("unchecked") -// @Test -// public void testCheckSwitchReady() { -// OFChannelState state = new OFChannelState(); -// OFChannelHandler chdlr = new OFChannelHandler(controller, state); -// Channel channel = createMock(Channel.class); -// chdlr.channel = channel; -// OFDescriptionStatistics desc = new OFDescriptionStatistics(); -// OFFeaturesReply featuresReply = new OFFeaturesReply(); -// featuresReply.setPorts(new ArrayList<OFPhysicalPort>()); -// -// // Wrong current state -// // Should not go to READY -// state.hsState = OFChannelState.HandshakeState.HELLO; -// state.hasDescription = false; -// state.hasGetConfigReply = false; -// state.switchBindingDone = false; -// expect(channel.getRemoteAddress()).andReturn(null).anyTimes(); -// expect(channel.write(EasyMock.anyObject())).andReturn(null).anyTimes(); -// replay(channel); -// chdlr.checkSwitchReady(); -// assertSame(OFChannelState.HandshakeState.HELLO, state.hsState); -// -// // Have only config reply -// state.hsState = OFChannelState.HandshakeState.FEATURES_REPLY; -// state.hasDescription = false; -// state.hasGetConfigReply = true; -// state.featuresReply = featuresReply; -// state.switchBindingDone = false; -// chdlr.checkSwitchReady(); -// assertSame(OFChannelState.HandshakeState.FEATURES_REPLY, state.hsState); -// assertTrue(controller.connectedSwitches.isEmpty()); -// assertTrue(controller.activeSwitches.isEmpty()); -// -// // Have only desc reply -// state.hsState = OFChannelState.HandshakeState.FEATURES_REPLY; -// state.hasDescription = true; -// state.description = desc; -// state.hasGetConfigReply = false; -// state.switchBindingDone = false; -// chdlr.checkSwitchReady(); -// assertSame(OFChannelState.HandshakeState.FEATURES_REPLY, state.hsState); -// assertTrue(controller.connectedSwitches.isEmpty()); -// assertTrue(controller.activeSwitches.isEmpty()); -// -// ////////////////////////////////////////// -// // Finally, everything is right. Should advance to READY -// ////////////////////////////////////////// -// controller.roleChanger = createMock(RoleChanger.class); -// state.hsState = OFChannelState.HandshakeState.FEATURES_REPLY; -// state.hasDescription = true; -// state.description = desc; -// state.hasGetConfigReply = true; -// state.featuresReply = featuresReply; -// state.switchBindingDone = false; -// // Role support disabled. Switch should be promoted to active switch -// // list. -// // setupSwitchForAddSwitch(chdlr.sw, 0L); -// // chdlr.sw.clearAllFlowMods(); -// desc.setManufacturerDescription("test vendor"); -// controller.roleChanger.submitRequest( -// (List<IOFSwitch>)EasyMock.anyObject(), -// (Role)EasyMock.anyObject()); -// replay(controller.roleChanger); -// chdlr.checkSwitchReady(); -// verify(controller.roleChanger); -// assertSame(OFChannelState.HandshakeState.READY, state.hsState); -// reset(controller.roleChanger); -// controller.connectedSwitches.clear(); -// controller.activeSwitches.clear(); -// -// -// // Role support enabled. -// state.hsState = OFChannelState.HandshakeState.FEATURES_REPLY; -// controller.role = Role.MASTER; -// Capture<Collection<IOFSwitch>> swListCapture = -// new Capture<Collection<IOFSwitch>>(); -// controller.roleChanger.submitRequest(capture(swListCapture), -// same(Role.MASTER)); -// replay(controller.roleChanger); -// chdlr.checkSwitchReady(); -// verify(controller.roleChanger); -// assertSame(OFChannelState.HandshakeState.READY, state.hsState); -// assertTrue(controller.activeSwitches.isEmpty()); -// assertFalse(controller.connectedSwitches.isEmpty()); -// Collection<IOFSwitch> swList = swListCapture.getValue(); -// assertEquals(1, swList.size()); -// } + + 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 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); + + } + + @Override + public String getName() { + return null; + } + + @Override + public boolean + isCallbackOrderingPrereq(HAListenerTypeMarker type, + String name) { + return false; + } + + @Override + public boolean + isCallbackOrderingPostreq(HAListenerTypeMarker type, + String name) { + return false; + } + + @Override + public void transitionToMaster() { + } + } + 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); + + synchronized(listener) { + // Insert a first entry + controller.getStorageSourceService() + .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"); + controller.processUpdateQueueForTesting(); + listener.do_assert(1, expectedCurMap, expectedAddedMap, expectedRemovedMap); + + // Add an interface that we want to ignore. + controller.getStorageSourceService() + .insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, + getFakeControllerIPRow("row2", "c1", "Ethernet", 1, "1.1.1.2")); + // TODO: do a different check. This call will have to wait for the timeout + controller.processUpdateQueueForTesting(); + assertTrue("controllerNodeIPsChanged() should not have been called here", + listener.nCalled == 1); + + // Add another entry + controller.getStorageSourceService() + .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"); + controller.processUpdateQueueForTesting(); + listener.do_assert(2, expectedCurMap, expectedAddedMap, expectedRemovedMap); + + + // Update an entry + controller.getStorageSourceService() + .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"); + controller.processUpdateQueueForTesting(); + listener.do_assert(3, expectedCurMap, expectedAddedMap, expectedRemovedMap); + + // Delete an entry + controller.getStorageSourceService() + .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"); + controller.processUpdateQueueForTesting(); + listener.do_assert(4, expectedCurMap, expectedAddedMap, expectedRemovedMap); + } + } + + @Test + public void testGetControllerNodeIPs() { + HashMap<String,String> expectedCurMap = new HashMap<String, String>(); + + controller.getStorageSourceService() + .insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, + getFakeControllerIPRow("row1", "c1", "Ethernet", 0, "1.1.1.1")); + controller.getStorageSourceService() + .insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, + getFakeControllerIPRow("row2", "c1", "Ethernet", 1, "1.1.1.2")); + controller.getStorageSourceService() + .insertRow(Controller.CONTROLLER_INTERFACE_TABLE_NAME, + getFakeControllerIPRow("row3", "c2", "Ethernet", 0, "2.2.2.2")); + expectedCurMap.put("c1", "1.1.1.1"); + expectedCurMap.put("c2", "2.2.2.2"); + assertEquals("expectedControllerNodeIPs is not as expected", + expectedCurMap, controller.getControllerNodeIPs()); + } + + + // // public class TestSwitchClass extends OFSwitchImpl { // } diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerTest.java b/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerTest.java index 520e45c965943d060dcbaa14cb637f760dad12b4..881f71fc153a351e35a97e922a0f1d8eb86f4c1a 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerTest.java +++ b/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerTest.java @@ -34,8 +34,11 @@ import org.openflow.protocol.OFFlowMod; import org.openflow.protocol.OFGetConfigReply; import org.openflow.protocol.OFMatch; import org.openflow.protocol.OFMessage; +import org.openflow.protocol.OFPacketIn; import org.openflow.protocol.OFPhysicalPort; import org.openflow.protocol.OFPort; +import org.openflow.protocol.OFPortStatus; +import org.openflow.protocol.OFPortStatus.OFPortReason; import org.openflow.protocol.OFSetConfig; import org.openflow.protocol.OFStatisticsReply; import org.openflow.protocol.OFStatisticsRequest; @@ -158,7 +161,7 @@ public class OFChannelHandlerTest { verify(sw); } - /* Reset the channel mock and set basic method call expectations */ + /** Reset the channel mock and set basic method call expectations */ void resetChannel() { reset(channel); expect(channel.getPipeline()).andReturn(pipeline).anyTimes(); @@ -166,7 +169,7 @@ public class OFChannelHandlerTest { } - /* reset, setup, and replay the messageEvent mock for the given + /** reset, setup, and replay the messageEvent mock for the given * messages */ void setupMessageEvent(List<OFMessage> messages) { @@ -176,7 +179,7 @@ public class OFChannelHandlerTest { } - /* reset, setup, and replay the messageEvent mock for the given + /** reset, setup, and replay the messageEvent mock for the given * messages, mock controller send message to channel handler * * This method will reset, start replay on controller, and then verify @@ -196,7 +199,7 @@ public class OFChannelHandlerTest { verify(controller); } - /* reset, setup, and replay the messageEvent mock for the given + /** reset, setup, and replay the messageEvent mock for the given * messages, mock controller send message to channel handler * * This method will start replay on controller, and then verify @@ -213,7 +216,7 @@ public class OFChannelHandlerTest { verify(controller); } - /* + /** * Extract the list of OFMessages that was captured by the Channel.write() * capture. Will check that something was actually captured first. We'll * collapse the messages from multiple writes into a single list of @@ -234,7 +237,7 @@ public class OFChannelHandlerTest { } - /* + /** * Verify that the given exception event capture (as returned by * getAndInitExceptionCapture) has thrown an exception of the given * expectedExceptionClass. @@ -249,7 +252,7 @@ public class OFChannelHandlerTest { exceptionEventCapture.reset(); } - /* make sure that the transaction ids in the given messages are + /** make sure that the transaction ids in the given messages are * not 0 and differ between each other. * While it's not a defect per se if the xids are we want to ensure * we use different ones for each message we send. @@ -321,7 +324,7 @@ public class OFChannelHandlerTest { verifyUniqueXids(msgs); } - /* FIXME: + /** FIXME: * Verify that the given FlowMod indeed removes all flows from the * switch */ @@ -333,7 +336,7 @@ public class OFChannelHandlerTest { assertEquals(OFPort.OFPP_NONE.getValue(), fm.getOutPort()); } - /* Move the channel from scratch to WAIT_FEATURES_REPLY state + /** Move the channel from scratch to WAIT_FEATURES_REPLY state * Builds on moveToWaitHello() * adds testing for WAIT_HELLO state */ @@ -358,7 +361,7 @@ public class OFChannelHandlerTest { } - /* Move the channel from scratch to WAIT_CONFIG_REPLY state + /** Move the channel from scratch to WAIT_CONFIG_REPLY state * Builds on moveToWaitFeaturesReply * adds testing for WAIT_FEATURES_REPLY state */ @@ -384,7 +387,7 @@ public class OFChannelHandlerTest { handler.getStateForTesting()); } - /* Move the channel from scratch to WAIT_DESCRIPTION_STAT_REPLY state + /** Move the channel from scratch to WAIT_DESCRIPTION_STAT_REPLY state * Builds on moveToWaitConfigReply() * adds testing for WAIT_CONFIG_REPLY state */ @@ -416,7 +419,7 @@ public class OFChannelHandlerTest { } - /* A helper bean that represents the config for a particular switch in + /** A helper bean that represents the config for a particular switch in * the storage source. */ private class MockStorageSourceConfig { @@ -428,7 +431,7 @@ public class OFChannelHandlerTest { // the value of isCoreSwitch public boolean isCoreSwitch; } - /* setup and replay a mock storage source and result set that + /** setup and replay a mock storage source and result set that * contains the IsCoreSwitch setting */ private void setupMockStorageSource(MockStorageSourceConfig cfg) { @@ -459,7 +462,7 @@ public class OFChannelHandlerTest { verify(storageResultSet); } - /* Move the channel from scratch to WAIT_INITIAL_ROLE state + /** Move the channel from scratch to WAIT_INITIAL_ROLE state * Builds on moveToWaitDescriptionStatReply() * adds testing for WAIT_DESCRIPTION_STAT_REPLY state * @param storageSourceConfig paramterizes the contents of the storage @@ -533,7 +536,7 @@ public class OFChannelHandlerTest { } @Test - /* Test WaitDescriptionReplyState. No config for switch in storage */ + /** Test WaitDescriptionReplyState. No config for switch in storage */ public void testWaitDescriptionReplyState1() throws Exception { MockStorageSourceConfig cfg = new MockStorageSourceConfig(); cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); @@ -542,7 +545,7 @@ public class OFChannelHandlerTest { } @Test - /* Test WaitDescriptionReplyState. switch is core switch */ + /** Test WaitDescriptionReplyState. switch is core switch */ public void testWaitDescriptionReplyState2() throws Exception { MockStorageSourceConfig cfg = new MockStorageSourceConfig(); cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); @@ -552,7 +555,7 @@ public class OFChannelHandlerTest { } @Test - /* Test WaitDescriptionReplyState. switch is NOT core switch */ + /** Test WaitDescriptionReplyState. switch is NOT core switch */ public void testWaitDescriptionReplyState3() throws Exception { MockStorageSourceConfig cfg = new MockStorageSourceConfig(); cfg.dpid = HexString.toHexString(featuresReply.getDatapathId()); @@ -561,7 +564,7 @@ public class OFChannelHandlerTest { doMoveToWaitInitialRole(cfg); } - /* + /** * Helper * Verify that the given OFMessage is a correct Nicira RoleRequest message * for the given role using the given xid. @@ -581,7 +584,7 @@ public class OFChannelHandlerTest { assertEquals(expectedRole.toNxRole(), requestData.getRole()); } - /* + /** * Setup the mock switch and write capture for a role request, set the * role and verify mocks. * @param supportsNxRole whether the switch supports role request messages @@ -613,7 +616,7 @@ public class OFChannelHandlerTest { verify(sw); } - /* + /** * Setup the mock switch for a role change request where the switch * does not support roles. * @@ -648,7 +651,7 @@ public class OFChannelHandlerTest { verify(sw); } - /* Return a Nicira RoleReply message for the given role */ + /** Return a Nicira RoleReply message for the given role */ private OFMessage getRoleReply(int xid, Role role) { OFVendor vm = (OFVendor)BasicFactory.getInstance() .getMessage(OFType.VENDOR); @@ -660,7 +663,7 @@ public class OFChannelHandlerTest { return vm; } - /* Return an OFError of the given type with the given xid */ + /** Return an OFError of the given type with the given xid */ private OFMessage getErrorMessage(OFErrorType type, int i, int xid) { @@ -673,7 +676,7 @@ public class OFChannelHandlerTest { } - /* Move the channel from scratch to MASTER state + /** Move the channel from scratch to MASTER state * Builds on doMoveToWaitInitialRole() * adds testing for WAIT_INITAL_ROLE state * @@ -718,7 +721,7 @@ public class OFChannelHandlerTest { handler.getStateForTesting()); } - /* Move the channel from scratch to SLAVE state + /** Move the channel from scratch to SLAVE state * Builds on doMoveToWaitInitialRole() * adds testing for WAIT_INITAL_ROLE state * @@ -763,7 +766,7 @@ public class OFChannelHandlerTest { handler.getStateForTesting()); } - /* Move the channel from scratch to MASTER state + /** Move the channel from scratch to MASTER state * Builds on doMoveToWaitInitialRole() * adds testing for WAIT_INITAL_ROLE state * @@ -824,7 +827,7 @@ public class OFChannelHandlerTest { } - /* Move the channel from scratch to MASTER state + /** Move the channel from scratch to MASTER state * Builds on doMoveToWaitInitialRole() * adds testing for WAIT_INITAL_ROLE state * @@ -876,7 +879,7 @@ public class OFChannelHandlerTest { } - /* Move the channel from scratch to SLAVE state + /** Move the channel from scratch to SLAVE state * Builds on doMoveToWaitInitialRole() * adds testing for WAIT_INITAL_ROLE state * @@ -934,7 +937,7 @@ public class OFChannelHandlerTest { } - /* Move the channel from scratch to SLAVE state + /** Move the channel from scratch to SLAVE state * Builds on doMoveToWaitInitialRole() * adds testing for WAIT_INITAL_ROLE state * @@ -980,7 +983,7 @@ public class OFChannelHandlerTest { } - /* Move channel from scratch to WAIT_INITIAL_STATE, then MASTER, + /** Move channel from scratch to WAIT_INITIAL_STATE, then MASTER, * then SLAVE for cases where the switch does not support roles. * I.e., the final SLAVE transition should disconnect the switch. */ @@ -1004,7 +1007,7 @@ public class OFChannelHandlerTest { } - /* Move the channel to MASTER state + /** Move the channel to MASTER state * Expects that the channel is in MASTER or SLAVE state. * */ @@ -1039,7 +1042,7 @@ public class OFChannelHandlerTest { handler.getStateForTesting()); } - /* Move the channel to SLAVE state + /** Move the channel to SLAVE state * Expects that the channel is in MASTER or SLAVE state. * */ @@ -1094,7 +1097,7 @@ public class OFChannelHandlerTest { changeRoleToSlaveWithRequest(); } - /* Start from scratch and reply with an unexpected error to the role + /** Start from scratch and reply with an unexpected error to the role * change request * Builds on doMoveToWaitInitialRole() * adds testing for WAIT_INITAL_ROLE state @@ -1131,5 +1134,97 @@ public class OFChannelHandlerTest { verifyExceptionCaptured(SwitchStateException.class); } + /** + * Test dispatch of messages while in MASTER role + */ + @Test + public void testMessageDispatchMaster() throws Exception { + testInitialMoveToMasterWithRole(); + + // Send packet in. expect dispatch + OFPacketIn pi = (OFPacketIn) + BasicFactory.getInstance().getMessage(OFType.PACKET_IN); + reset(controller); + controller.handleMessage(sw, pi, null); + expectLastCall().once(); + sendMessageToHandlerNoControllerReset( + Collections.<OFMessage>singletonList(pi)); + verify(controller); + // TODO: many more to go + } + + /** + * Test port status message handling while MASTER + * + */ + @Test + public void testPortStatusMessageMaster() throws Exception { + long dpid = featuresReply.getDatapathId(); + testInitialMoveToMasterWithRole(); + + OFPhysicalPort p = new OFPhysicalPort(); + p.setName("Port1"); + p.setPortNumber((short)1); + OFPortStatus ps = (OFPortStatus) + BasicFactory.getInstance().getMessage(OFType.PORT_STATUS); + ps.setDesc(p); + + + // Add port + ps.setReason(OFPortReason.OFPPR_ADD.getReasonCode()); + reset(sw); + expect(sw.inputThrottled(anyObject(OFMessage.class))) + .andReturn(false).anyTimes(); + expect(sw.getId()).andReturn(dpid).anyTimes(); + sw.setPort(p); + expectLastCall().once(); + replay(sw); + + reset(controller); + controller.notifyPortChanged(dpid); + expectLastCall().once(); + sendMessageToHandlerNoControllerReset( + Collections.<OFMessage>singletonList(ps)); + verify(sw); + verify(controller); + + // modify port + ps.setReason(OFPortReason.OFPPR_MODIFY.getReasonCode()); + reset(sw); + expect(sw.inputThrottled(anyObject(OFMessage.class))) + .andReturn(false).anyTimes(); + expect(sw.getId()).andReturn(dpid).anyTimes(); + sw.setPort(p); + expectLastCall().once(); + replay(sw); + + reset(controller); + controller.notifyPortChanged(dpid); + expectLastCall().once(); + sendMessageToHandlerNoControllerReset( + Collections.<OFMessage>singletonList(ps)); + verify(sw); + verify(controller); + + // delete port + ps.setReason(OFPortReason.OFPPR_DELETE.getReasonCode()); + reset(sw); + expect(sw.inputThrottled(anyObject(OFMessage.class))) + .andReturn(false).anyTimes(); + expect(sw.getId()).andReturn(dpid).anyTimes(); + sw.deletePort(p.getPortNumber()); + expectLastCall().once(); + replay(sw); + + reset(controller); + controller.notifyPortChanged(dpid); + expectLastCall().once(); + sendMessageToHandlerNoControllerReset( + Collections.<OFMessage>singletonList(ps)); + verify(sw); + verify(controller); + + + } }