diff --git a/build.xml b/build.xml index a430867363dfacede2ee55be60f7af9389a72113..3ef76bce1ac4da9f4a0ae443a906b44c5ad7e191 100644 --- a/build.xml +++ b/build.xml @@ -69,7 +69,7 @@ <include name="org.restlet.ext.jsslutils.jar"/> <include name="org.simpleframework.jar"/> <include name="org.jsslutils.jar"/> - <include name="netty-3.10.0.Final.jar"/> + <include name="netty-all-4.0.31.Final.jar"/> <include name="args4j-2.0.16.jar"/> <include name="concurrentlinkedhashmap-lru-1.2.jar"/> <include name="jython-2.5.2.jar"/> @@ -78,8 +78,8 @@ <include name="findbugs-annotations-2.0.1.jar" /> <include name="findbugs-jsr305-2.0.1.jar" /> <include name="derby-10.9.1.0.jar"/> - <include name="openflowj-0.9.0-SNAPSHOT.jar"/> - <include name="openflowj-0.9.0-SNAPSHOT-javadoc.jar"/> + <include name="openflowj-2.0.0-SNAPSHOT.jar"/> + <include name="openflowj-2.0.0-SNAPSHOT-javadoc.jar"/> <include name="hamcrest-core-1.3.jar"/> <include name="hamcrest-integration-1.3.jar"/> <include name="hamcrest-library-1.3.jar"/> diff --git a/lib/netty-3.10.0.Final.jar b/lib/netty-3.10.0.Final.jar deleted file mode 100644 index 5f8aa2e2eb08d43a1a8c90c13bafcec5fdd11d63..0000000000000000000000000000000000000000 Binary files a/lib/netty-3.10.0.Final.jar and /dev/null differ diff --git a/lib/netty-all-4.0.31.Final.jar b/lib/netty-all-4.0.31.Final.jar new file mode 100644 index 0000000000000000000000000000000000000000..ef9477a323af6de76bd1a0b4cda9c4da0e72860d Binary files /dev/null and b/lib/netty-all-4.0.31.Final.jar differ diff --git a/lib/openflowj-0.9.0-SNAPSHOT-sources.jar b/lib/openflowj-0.9.0-SNAPSHOT-sources.jar deleted file mode 100644 index abdf436f91750e82a1e50f3376105bf590526477..0000000000000000000000000000000000000000 Binary files a/lib/openflowj-0.9.0-SNAPSHOT-sources.jar and /dev/null differ diff --git a/lib/openflowj-0.9.0-SNAPSHOT-javadoc.jar b/lib/openflowj-2.0.0-SNAPSHOT-javadoc.jar similarity index 53% rename from lib/openflowj-0.9.0-SNAPSHOT-javadoc.jar rename to lib/openflowj-2.0.0-SNAPSHOT-javadoc.jar index 3b2cd87c8da1a2ba622894c2241b4321b59e6ce2..25f13543563de36b77d132b3c5434728fc7395bd 100644 Binary files a/lib/openflowj-0.9.0-SNAPSHOT-javadoc.jar and b/lib/openflowj-2.0.0-SNAPSHOT-javadoc.jar differ diff --git a/lib/openflowj-2.0.0-SNAPSHOT-sources.jar b/lib/openflowj-2.0.0-SNAPSHOT-sources.jar new file mode 100644 index 0000000000000000000000000000000000000000..f2403b8ef0d5ab3f73ba02ffa09690909da8d786 Binary files /dev/null and b/lib/openflowj-2.0.0-SNAPSHOT-sources.jar differ diff --git a/lib/openflowj-0.9.0-SNAPSHOT.jar b/lib/openflowj-2.0.0-SNAPSHOT.jar similarity index 63% rename from lib/openflowj-0.9.0-SNAPSHOT.jar rename to lib/openflowj-2.0.0-SNAPSHOT.jar index 2a2140a0038065ee1bc875723a42ae9b9cf8ece0..7f25d80d801bfca393c2ea807f9df8349c418fac 100644 Binary files a/lib/openflowj-0.9.0-SNAPSHOT.jar and b/lib/openflowj-2.0.0-SNAPSHOT.jar differ diff --git a/logback.xml b/logback.xml index f5163007d4c2af4ea21c0c62b023614e2689ee79..0485717cddefe39bd795685712cce3e4c0af18ad 100644 --- a/logback.xml +++ b/logback.xml @@ -7,9 +7,8 @@ <root level="DEBUG"> <appender-ref ref="STDOUT" /> </root> - <logger name="org" level="DEBUG"/> + <logger name="io" level="INFO"></logger> <!-- Netty logging --> <logger name="LogService" level="DEBUG"/> <!-- Restlet access logging --> - <logger name="net.floodlightcontroller" level="DEBUG"/> - <logger name="net.floodlightcontroller.logging" level="DEBUG"/> + <logger name="net.floodlightcontroller" level="INFO"/> <logger name="org.sdnplatform" level="INFO"/> </configuration> diff --git a/pom.xml b/pom.xml index 05c0b1784d45c95844c78a6f0f48256f030475db..d10617a645d8a137a9204b081e002a3b41660298 100644 --- a/pom.xml +++ b/pom.xml @@ -212,12 +212,12 @@ <dependency> <groupId>org.projectfloodlight</groupId> <artifactId>openflowj</artifactId> - <version>0.9.0-SNAPSHOT</version> + <version>2.0.0-SNAPSHOT</version> </dependency> <dependency> <groupId>io.netty</groupId> <artifactId>netty</artifactId> - <version>3.9.0.Final</version> + <version>4.0.31.Final</version> </dependency> <dependency> <groupId>com.fasterxml.jackson.dataformat</groupId> diff --git a/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java b/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java index d676bfe207b4cf179675be641d4add2cc7761c9b..74043b84a824eff5683a6fb924a6da2010631dc5 100644 --- a/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java +++ b/src/main/java/net/floodlightcontroller/core/IFloodlightProviderService.java @@ -23,9 +23,7 @@ import java.util.Map; import net.floodlightcontroller.core.module.IFloodlightService; import net.floodlightcontroller.packet.Ethernet; - -import org.jboss.netty.util.Timer; - +import io.netty.util.Timer; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.HARole; import net.floodlightcontroller.core.IHAListener; @@ -36,10 +34,12 @@ import net.floodlightcontroller.core.RoleInfo; import net.floodlightcontroller.core.internal.RoleManager; import net.floodlightcontroller.core.internal.Controller.IUpdate; import net.floodlightcontroller.core.internal.Controller.ModuleLoaderState; - import net.floodlightcontroller.core.FloodlightContextStore; + import org.projectfloodlight.openflow.protocol.OFMessage; import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.TransportPort; /** * The interface exposed by the core bundle that allows you to interact * with connected switches. @@ -115,16 +115,16 @@ public interface IFloodlightProviderService extends public String getControllerId(); /** - * Gets the controller hostname - * @return the controller hostname + * Gets the controller addresses + * @return the controller addresses */ - public String getOFHostname(); + public Set<IPv4Address> getOFAddresses(); /** * Gets the controller's openflow port * @return the controller's openflow port */ - public int getOFPort(); + public TransportPort getOFPort(); /** * Set the role of the controller diff --git a/src/main/java/net/floodlightcontroller/core/IOFConnection.java b/src/main/java/net/floodlightcontroller/core/IOFConnection.java index 21855fd33aeb96714451ae38d5dee1b88dc16383..2537d40a7984c6843ed8b4b206e4cdce53034711 100644 --- a/src/main/java/net/floodlightcontroller/core/IOFConnection.java +++ b/src/main/java/net/floodlightcontroller/core/IOFConnection.java @@ -22,12 +22,6 @@ public interface IOFConnection extends IOFMessageWriter { */ Date getConnectedSince(); - /** - * Flush all flows queued for this switch in the current thread. - * NOTE: The contract is limited to the current thread - */ - void flush(); - /** @return the DatapathId of the switch associated with the connection */ DatapathId getDatapathId(); diff --git a/src/main/java/net/floodlightcontroller/core/IOFMessageWriter.java b/src/main/java/net/floodlightcontroller/core/IOFMessageWriter.java index bd27181f19e9170fac71db1557942dcc8cef0510..a8a7e3191dda047e57a143f959be27cb5ba5dbae 100644 --- a/src/main/java/net/floodlightcontroller/core/IOFMessageWriter.java +++ b/src/main/java/net/floodlightcontroller/core/IOFMessageWriter.java @@ -17,6 +17,7 @@ package net.floodlightcontroller.core; +import java.util.Collection; import java.util.List; import org.projectfloodlight.openflow.protocol.OFMessage; @@ -33,25 +34,24 @@ import com.google.common.util.concurrent.ListenableFuture; public interface IOFMessageWriter{ - /** - * Writes to the OFMessage to the output stream. - * - * <p><b>Note:</b> this method has fire-and-forget semantics. When the connection is - * not currently connected, it will silently discard the messages. + /** + * Writes the OFMessage to the output stream. * * @param m + * @return true upon success; false if message could not be written */ - void write(OFMessage m); + boolean write(OFMessage m); /** * Writes the list of messages to the output stream. * - * <p><b>Note:</b> this method has fire-and-forget semantics. When the connection is - * not currently connected, it will silently discard the messages. + * Any messages that could not be written due to channel disconnect + * will be returned. * * @param msglist + * @return list of messages that could not be written */ - void write(Iterable<OFMessage> msglist); + Collection<OFMessage> write(Iterable<OFMessage> msgList); /** write an OpenFlow Request message, register for a single corresponding reply message * or error message. diff --git a/src/main/java/net/floodlightcontroller/core/IOFSwitch.java b/src/main/java/net/floodlightcontroller/core/IOFSwitch.java index 4cc47498d8534a5cce3f24e78243f4cac7313394..b6a96f31ffc161d5443ee066acaefd9ffc136bdb 100644 --- a/src/main/java/net/floodlightcontroller/core/IOFSwitch.java +++ b/src/main/java/net/floodlightcontroller/core/IOFSwitch.java @@ -40,6 +40,7 @@ import org.projectfloodlight.openflow.types.U64; import com.google.common.collect.ImmutableList; import com.google.common.util.concurrent.ListenableFuture; +import net.floodlightcontroller.core.internal.OFConnection; import net.floodlightcontroller.core.internal.TableFeatures; import net.floodlightcontroller.core.web.serializers.IOFSwitchSerializer; @@ -289,13 +290,6 @@ public interface IOFSwitch extends IOFMessageWriter { */ OFFactory getOFFactory(); - /** - * Flush all flows queued for this switch on all connections that were written by the current thread. - * - * - */ - void flush(); - /** * Gets the OF connections for this switch instance * @return Collection of IOFConnection @@ -306,15 +300,17 @@ public interface IOFSwitch extends IOFMessageWriter { * Writes a message to the connection specified by the logical OFMessage category * @param m an OF Message * @param category the category of the OF Message to be sent + * @return true upon success; false upon failure */ - void write(OFMessage m, LogicalOFMessageCategory category); + boolean write(OFMessage m, LogicalOFMessageCategory category); /** * Writes a message list to the connection specified by the logical OFMessage category * @param msglist an OF Message list * @param category the category of the OF Message list to be sent + * @return list of failed messages, if any; success denoted by empty list */ - void write(Iterable<OFMessage> msglist, LogicalOFMessageCategory category); + Iterable<OFMessage> write(Iterable<OFMessage> msglist, LogicalOFMessageCategory category); /** * Get a connection specified by the logical OFMessage category diff --git a/src/main/java/net/floodlightcontroller/core/OFConnection.java b/src/main/java/net/floodlightcontroller/core/OFConnection.java deleted file mode 100644 index c72c618c6d2073cd1738baa0d35c7a3c95a3b5e6..0000000000000000000000000000000000000000 --- a/src/main/java/net/floodlightcontroller/core/OFConnection.java +++ /dev/null @@ -1,430 +0,0 @@ -/** - * Copyright 2012, 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 - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT - * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the - * License for the specific language governing permissions and limitations - * under the License. - **/ - -package net.floodlightcontroller.core; - -import java.net.SocketAddress; -import java.util.ArrayList; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.TimeoutException; - -import javax.annotation.Nonnull; - -import org.jboss.netty.channel.Channel; -import org.jboss.netty.util.Timeout; -import org.jboss.netty.util.Timer; -import org.jboss.netty.util.TimerTask; - -import java.util.Date; - -import net.floodlightcontroller.core.internal.Controller; -import net.floodlightcontroller.core.internal.IOFConnectionListener; -import net.floodlightcontroller.debugcounter.IDebugCounterService; - -import org.projectfloodlight.openflow.protocol.OFErrorMsg; -import org.projectfloodlight.openflow.protocol.OFFactory; -import org.projectfloodlight.openflow.protocol.OFMessage; -import org.projectfloodlight.openflow.protocol.OFRequest; -import org.projectfloodlight.openflow.protocol.OFStatsReply; -import org.projectfloodlight.openflow.protocol.OFStatsReplyFlags; -import org.projectfloodlight.openflow.protocol.OFStatsRequest; -import org.projectfloodlight.openflow.protocol.OFType; -import org.projectfloodlight.openflow.types.DatapathId; -import org.projectfloodlight.openflow.types.OFAuxId; -import org.projectfloodlight.openflow.types.U64; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import com.google.common.base.Preconditions; -import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Iterables; -import com.google.common.util.concurrent.Futures; -import com.google.common.util.concurrent.ListenableFuture; - -/** - * Implementation of an openflow connection to switch. Encapsulates a - * {@link Channel}, and provides message write and request/response handling - * capabilities. - * - * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> - */ -public class OFConnection implements IOFConnection, IOFConnectionBackend{ - private static final Logger logger = LoggerFactory.getLogger(OFConnection.class); - private final DatapathId dpid; - private final OFFactory factory; - private final Channel channel; - private final OFAuxId auxId; - private final Timer timer; - - private final Date connectedSince; - - private final Map<Long, Deliverable<?>> xidDeliverableMap; - - protected final static ThreadLocal<List<OFMessage>> localMsgBuffer = - new ThreadLocal<List<OFMessage>>(); - - private static final long DELIVERABLE_TIME_OUT = 60; - private static final TimeUnit DELIVERABLE_TIME_OUT_UNIT = TimeUnit.SECONDS; - - private final OFConnectionCounters counters; - private IOFConnectionListener listener; - - private volatile U64 latency; - - public OFConnection(@Nonnull DatapathId dpid, - @Nonnull OFFactory factory, - @Nonnull Channel channel, - @Nonnull OFAuxId auxId, - @Nonnull IDebugCounterService debugCounters, - @Nonnull Timer timer) { - Preconditions.checkNotNull(dpid, "dpid"); - Preconditions.checkNotNull(factory, "factory"); - Preconditions.checkNotNull(channel, "channel"); - Preconditions.checkNotNull(timer, "timer"); - Preconditions.checkNotNull(debugCounters); - - this.listener = NullConnectionListener.INSTANCE; - this.dpid = dpid; - this.factory = factory; - this.channel = channel; - this.auxId = auxId; - this.connectedSince = new Date(); - this.xidDeliverableMap = new ConcurrentHashMap<>(); - this.counters = new OFConnectionCounters(debugCounters, dpid, this.auxId); - this.timer = timer; - this.latency = U64.ZERO; - } - - @Override - public void write(OFMessage m) { - if (!isConnected()) { - if (logger.isDebugEnabled()) - logger.debug("{}: not connected - dropping message {}", this, m); - return; - } - if (logger.isDebugEnabled()) - logger.debug("{}: send {}", this, m); - List<OFMessage> msgBuffer = localMsgBuffer.get(); - if (msgBuffer == null) { - msgBuffer = new ArrayList<OFMessage>(); - localMsgBuffer.set(msgBuffer); - } - - counters.updateWriteStats(m); - msgBuffer.add(m); - - if ((msgBuffer.size() >= Controller.BATCH_MAX_SIZE) || ((m.getType() != OFType.PACKET_OUT) && (m.getType() != OFType.FLOW_MOD))) { - this.write(msgBuffer); - localMsgBuffer.set(null); - } - } - - @Override - public <R extends OFMessage> ListenableFuture<R> writeRequest(OFRequest<R> request) { - if (!isConnected()) - return Futures.immediateFailedFuture(new SwitchDisconnectedException(getDatapathId())); - - DeliverableListenableFuture<R> future = new DeliverableListenableFuture<R>(); - xidDeliverableMap.put(request.getXid(), future); - listener.messageWritten(this, request); - write(request); - return future; - } - - @Override - public void write(Iterable<OFMessage> msglist) { - if (!isConnected()) { - if (logger.isDebugEnabled()) - logger.debug(this.toString() + " : not connected - dropping {} element msglist {} ", - Iterables.size(msglist), - String.valueOf(msglist).substring(0, 80)); - return; - } - for (OFMessage m : msglist) { - if (logger.isTraceEnabled()) - logger.trace("{}: send {}", this, m); - counters.updateWriteStats(m); - } - this.channel.write(msglist); - } - - // Notifies the connection object that the channel has been disconnected - public void disconnected() { - SwitchDisconnectedException exception = new SwitchDisconnectedException(getDatapathId()); - for (Long xid : xidDeliverableMap.keySet()) { - // protect against other mechanisms running at the same time - // (timeout) - Deliverable<?> removed = xidDeliverableMap.remove(xid); - if (removed != null) { - removed.deliverError(exception); - } - } - } - - @Override - public void disconnect() { - this.channel.disconnect(); - this.counters.uninstallCounters(); - } - - @Override - public String toString() { - String channelString = (channel != null) ? String.valueOf(channel.getRemoteAddress()): "?"; - return "OFConnection [" + getDatapathId() + "(" + getAuxId() + ")" + "@" + channelString + "]"; - } - - @Override - public Date getConnectedSince() { - return connectedSince; - } - - @Override - public <REPLY extends OFStatsReply> ListenableFuture<List<REPLY>> writeStatsRequest( - OFStatsRequest<REPLY> request) { - if (!isConnected()) - return Futures.immediateFailedFuture(new SwitchDisconnectedException(getDatapathId())); - - final DeliverableListenableFuture<List<REPLY>> future = - new DeliverableListenableFuture<List<REPLY>>(); - - Deliverable<REPLY> deliverable = new Deliverable<REPLY>() { - private final List<REPLY> results = Collections - .synchronizedList(new ArrayList<REPLY>()); - - @Override - public void deliver(REPLY reply) { - results.add(reply); - if (!reply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) { - // done - future.deliver(results); - } - } - - @Override - public void deliverError(Throwable cause) { - future.deliverError(cause); - } - - @Override - public boolean isDone() { - return future.isDone(); - } - - @Override - public boolean cancel(boolean mayInterruptIfRunning) { - return future.cancel(mayInterruptIfRunning); - } - }; - - registerDeliverable(request.getXid(), deliverable); - this.write(request); - return future; - } - - private void registerDeliverable(long xid, Deliverable<?> deliverable) { - this.xidDeliverableMap.put(xid, deliverable); - timer.newTimeout(new TimeOutDeliverable(xid), DELIVERABLE_TIME_OUT, DELIVERABLE_TIME_OUT_UNIT); - } - - public boolean handleGenericDeliverable(OFMessage reply) { - counters.updateReadStats(reply); - @SuppressWarnings("unchecked") - Deliverable<OFMessage> deliverable = - (Deliverable<OFMessage>) this.xidDeliverableMap.get(reply.getXid()); - if (deliverable != null) { - if(reply instanceof OFErrorMsg) { - deliverable.deliverError(new OFErrorMsgException((OFErrorMsg) reply)); - } else { - deliverable.deliver(reply); - } - if (deliverable.isDone()) - this.xidDeliverableMap.remove(reply.getXid()); - return true; - } else { - return false; - } - } - - @Override - public void cancelAllPendingRequests() { - /* - * we don't need to be synchronized here. Even if another thread - * modifies the map while we're cleaning up the future will eventually - * timeout - */ - for (Deliverable<?> d : xidDeliverableMap.values()) { - d.cancel(true); - } - xidDeliverableMap.clear(); - } - - @Override - public boolean isConnected() { - return channel.isConnected(); - } - - @Override - public void flush() { - List<OFMessage> msglist = localMsgBuffer.get(); - if ((msglist != null) && (msglist.size() > 0)) { - this.write(msglist); - localMsgBuffer.set(null); - } - } - - @Override - public SocketAddress getRemoteInetAddress() { - return channel.getRemoteAddress(); - } - - @Override - public SocketAddress getLocalInetAddress() { - return channel.getLocalAddress(); - } - - public boolean deliverResponse(OFMessage m) { - if (handleGenericDeliverable(m)) - return true; - else - return false; - } - - @Override - public boolean isWritable() { - return channel.isWritable(); - } - - @Override - public DatapathId getDatapathId() { - return dpid; - } - - @Override - public OFAuxId getAuxId() { - return auxId; - } - - Set<Long> getPendingRequestIds() { - return ImmutableSet.copyOf(xidDeliverableMap.keySet()); - } - - @Override - public OFFactory getOFFactory() { - return this.factory; - } - - /** - * Timeout class instantiated for deliverables. Will throw a timeout exception - * if proper responses are not received in time. - * - */ - private class TimeOutDeliverable implements TimerTask { - private final long xid; - - public TimeOutDeliverable(long xid) { - this.xid = xid; - } - - @Override - public void run(Timeout timeout) throws Exception { - Deliverable<?> removed = xidDeliverableMap.remove(xid); - if (removed != null && !removed.isDone()) { - removed.deliverError(new TimeoutException( - "timeout - did not receive answer for xid " + xid)); - } - - } - } - - public IOFConnectionListener getListener() { - return listener; - } - - /** set the connection listener - * <p> - * Note: this is assumed to be called from the Connection's IO Thread. - * - * @param listener - */ - @Override - public void setListener(IOFConnectionListener listener) { - this.listener = listener; - } - - public void messageReceived(OFMessage m) { - // Check if message was a response for a xid waiting at the switch - if(!deliverResponse(m)){ - listener.messageReceived(this, m); - } - } - - @Override - public U64 getLatency() { - return this.latency; - } - - @Override - public void updateLatency(U64 latency) { - if (latency == null) { - logger.error("Latency must be non-null. Ignoring null latency value."); - return; - } else if (this.latency.equals(U64.ZERO)) { - logger.debug("Recording previously 0ms switch {} latency as {}ms", this.getDatapathId(), latency.getValue()); - this.latency = latency; - return; - } else { - double oldWeight = 0.30; - this.latency = U64.of((long) (this.latency.getValue() * oldWeight + latency.getValue() * (1 - oldWeight))); - logger.debug("Switch {} latency updated to {}ms", this.getDatapathId(), this.latency.getValue()); - } - } - - /** A dummy connection listener that just logs warn messages. Saves us a few null checks - * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> - */ - private static class NullConnectionListener implements IOFConnectionListener { - public final static NullConnectionListener INSTANCE = new NullConnectionListener(); - - private NullConnectionListener() { } - - @Override - public void connectionClosed(IOFConnectionBackend connection) { - logger.warn("NullConnectionListener for {} - received connectionClosed", connection); - } - - @Override - public void messageReceived(IOFConnectionBackend connection, OFMessage m) { - logger.warn("NullConnectionListener for {} - received messageReceived: {}", connection, m); - } - - @Override - public boolean isSwitchHandshakeComplete(IOFConnectionBackend connection) { - return false; - } - - @Override - public void messageWritten(IOFConnectionBackend connection, OFMessage m) { - // TODO Auto-generated method stub - - } - } -} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/internal/Controller.java b/src/main/java/net/floodlightcontroller/core/internal/Controller.java index f1a3743c81b1371c953a5aee733f38f8e75721ca..02ab55c44359f36a2faf23d13ad6a3eafb940593 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/Controller.java +++ b/src/main/java/net/floodlightcontroller/core/internal/Controller.java @@ -22,6 +22,7 @@ import java.lang.management.RuntimeMXBean; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; +import java.util.HashSet; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; @@ -35,9 +36,8 @@ import java.util.concurrent.ConcurrentLinkedQueue; import java.util.concurrent.ConcurrentMap; import java.util.concurrent.LinkedBlockingQueue; -import org.jboss.netty.util.HashedWheelTimer; -import org.jboss.netty.util.Timer; - +import io.netty.util.HashedWheelTimer; +import io.netty.util.Timer; import net.floodlightcontroller.core.ControllerId; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.HAListenerTypeMarker; @@ -68,7 +68,8 @@ import org.projectfloodlight.openflow.protocol.OFPacketIn; import org.projectfloodlight.openflow.protocol.OFPortDesc; import org.projectfloodlight.openflow.protocol.OFType; import org.projectfloodlight.openflow.types.DatapathId; -import org.python.modules.synchronize; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.TransportPort; import net.floodlightcontroller.packet.Ethernet; import net.floodlightcontroller.perfmon.IPktInProcessingTimeService; @@ -135,9 +136,10 @@ public class Controller implements IFloodlightProviderService, IStorageSourceLis private IShutdownService shutdownService; // Configuration options - protected int openFlowPort = 6653; // new registered OF port number - private String openFlowHostname = null; - protected int workerThreads = 0; + private static TransportPort openFlowPort = TransportPort.of(6653); // new registered OF port number + private static Set<IPv4Address> openFlowAddresses = new HashSet<IPv4Address>(); + public static final int SEND_BUFFER_SIZE = 4 * 1024 * 1024; + protected int workerThreads = 16; // The id for this controller node. Should be unique for each controller // node in a controller cluster. @@ -147,8 +149,7 @@ public class Controller implements IFloodlightProviderService, IStorageSourceLis // if they should operate in ACTIVE / STANDBY protected volatile HARole notifiedRole; - private static final String - INITIAL_ROLE_CHANGE_DESCRIPTION = "Controller startup."; + private static final String INITIAL_ROLE_CHANGE_DESCRIPTION = "Controller startup."; /** * NOTE: roleManager is not 'final' because it's initialized at run time * based on parameters that are only available in init() @@ -180,10 +181,6 @@ public class Controller implements IFloodlightProviderService, IStorageSourceLis FLOW_COLUMN_ACCESS_PRIORITY, FLOW_COLUMN_CORE_PRIORITY }; - - // Perf. related configuration - protected static final int SEND_BUFFER_SIZE = 128 * 1024; - public static final int BATCH_MAX_SIZE = 1; //TODO @Ryan this was 100. Causes packet_out messages to stall until 100 accumulated... protected static final boolean ALWAYS_DECODE_ETH = true; // Set of port name prefixes that will be classified as uplink ports, @@ -524,11 +521,12 @@ public class Controller implements IFloodlightProviderService, IStorageSourceLis } @Override - public String getOFHostname() { - return openFlowHostname; + public Set<IPv4Address> getOFAddresses() { + return Collections.unmodifiableSet(openFlowAddresses); } + @Override - public int getOFPort() { + public TransportPort getOFPort() { return openFlowPort; } @@ -616,7 +614,6 @@ public class Controller implements IFloodlightProviderService, IStorageSourceLis if (m == null) throw new NullPointerException("OFMessage must not be null"); - // FIXME floodlight context not supported any more FloodlightContext bc = new FloodlightContext(); List<IOFMessageListener> listeners = null; @@ -706,22 +703,35 @@ public class Controller implements IFloodlightProviderService, IStorageSourceLis } private void setConfigParams(Map<String, String> configParams) throws FloodlightModuleException { - String ofPort = configParams.get("openflowPort"); + String ofPort = configParams.get("openFlowPort"); if (!Strings.isNullOrEmpty(ofPort)) { try { - this.openFlowPort = Integer.parseInt(ofPort); - } catch (NumberFormatException e) { - log.error("invalid openflow port specifier", e); - throw new FloodlightModuleException("invalid port specifier in cofig"); + openFlowPort = TransportPort.of(Integer.parseInt(ofPort)); + } catch (Exception e) { + log.error("Invalid OpenFlow port {}, {}", ofPort, e); + throw new FloodlightModuleException("Invalid OpenFlow port of " + ofPort + " in config"); } - log.debug("OpenFlow port set to {}", this.openFlowPort); } + log.info("OpenFlow port set to {}", openFlowPort); - String threads = configParams.get("workerthreads"); + String threads = configParams.get("workerThreads"); if (!Strings.isNullOrEmpty(threads)) { this.workerThreads = Integer.parseInt(threads); } - log.debug("Number of worker threads set to {}", this.workerThreads); + log.info("Number of worker threads set to {}", this.workerThreads); + + String addresses = configParams.get("openFlowAddresses"); + if (!Strings.isNullOrEmpty(addresses)) { + try { + openFlowAddresses = Collections.singleton(IPv4Address.of(addresses)); //TODO support list of addresses for multi-honed controllers + } catch (Exception e) { + log.error("Invalid OpenFlow address {}, {}", addresses, e); + throw new FloodlightModuleException("Invalid OpenFlow address of " + addresses + " in config"); + } + log.info("OpenFlow addresses set to {}", openFlowAddresses); + } else { + openFlowAddresses.add(IPv4Address.NONE); + } } /** diff --git a/src/main/java/net/floodlightcontroller/core/internal/ControllerCounters.java b/src/main/java/net/floodlightcontroller/core/internal/ControllerCounters.java index de5b99255c8e2968a7d914b50dc7c0115f330d4c..d14e88d78e91a28fbe37e773cf5c684d4bda51f3 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/ControllerCounters.java +++ b/src/main/java/net/floodlightcontroller/core/internal/ControllerCounters.java @@ -1,11 +1,9 @@ package net.floodlightcontroller.core.internal; import net.floodlightcontroller.core.IOFSwitchBackend; -import net.floodlightcontroller.core.OFConnectionCounters; import net.floodlightcontroller.debugcounter.IDebugCounter; import net.floodlightcontroller.debugcounter.IDebugCounterService; import net.floodlightcontroller.debugcounter.IDebugCounterService.MetaData; - import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; @SuppressFBWarnings(value = "URF_UNREAD_PUBLIC_OR_PROTECTED_FIELD", diff --git a/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutHandler.java b/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutHandler.java index 89ef68109bdc715eb2c16ea3f59f6c2c7b33fc67..2bf6a7c919072e5f6da9fa42cc85b00856b422bb 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutHandler.java +++ b/src/main/java/net/floodlightcontroller/core/internal/HandshakeTimeoutHandler.java @@ -19,22 +19,18 @@ package net.floodlightcontroller.core.internal; import java.util.concurrent.TimeUnit; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelStateEvent; -import org.jboss.netty.channel.Channels; -import org.jboss.netty.channel.SimpleChannelUpstreamHandler; -import org.jboss.netty.util.Timeout; -import org.jboss.netty.util.Timer; -import org.jboss.netty.util.TimerTask; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; /** * Trigger a timeout if a switch fails to complete handshake soon enough */ -public class HandshakeTimeoutHandler - extends SimpleChannelUpstreamHandler { +public class HandshakeTimeoutHandler extends ChannelInboundHandlerAdapter { - static final HandshakeTimeoutException EXCEPTION = - new HandshakeTimeoutException(); + static final HandshakeTimeoutException EXCEPTION = new HandshakeTimeoutException(); final OFChannelHandler handshakeHandler; final Timer timer; @@ -52,22 +48,20 @@ public class HandshakeTimeoutHandler } @Override - public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) - throws Exception { + public void channelActive(ChannelHandlerContext ctx) throws Exception { if (timeoutNanos > 0) { - timeout = timer.newTimeout(new HandshakeTimeoutTask(ctx), - timeoutNanos, TimeUnit.NANOSECONDS); + timeout = timer.newTimeout(new HandshakeTimeoutTask(ctx), timeoutNanos, TimeUnit.NANOSECONDS); } - ctx.sendUpstream(e); + super.channelActive(ctx); } @Override - public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) - throws Exception { + public void channelInactive(ChannelHandlerContext ctx) throws Exception { if (timeout != null) { timeout.cancel(); timeout = null; } + super.channelInactive(ctx); } private final class HandshakeTimeoutTask implements TimerTask { @@ -84,11 +78,11 @@ public class HandshakeTimeoutHandler return; } - if (!ctx.getChannel().isOpen()) { + if (!ctx.channel().isOpen()) { return; } if (!handshakeHandler.isSwitchHandshakeComplete()) - Channels.fireExceptionCaught(ctx, EXCEPTION); + ctx.fireExceptionCaught(EXCEPTION); } } -} +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/internal/NaiveSwitchDriverRegistry.java b/src/main/java/net/floodlightcontroller/core/internal/NaiveSwitchDriverRegistry.java index b6bb90358f88b8c2fa38c6c478c2e8d9cbec64ac..04c68118d54f7e844e8e7988489e3277ee66c51f 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/NaiveSwitchDriverRegistry.java +++ b/src/main/java/net/floodlightcontroller/core/internal/NaiveSwitchDriverRegistry.java @@ -11,7 +11,6 @@ import javax.annotation.Nonnull; import net.floodlightcontroller.core.IOFConnectionBackend; import net.floodlightcontroller.core.IOFSwitchBackend; import net.floodlightcontroller.core.IOFSwitchDriver; -import net.floodlightcontroller.core.OFSwitch; import net.floodlightcontroller.core.SwitchDescription; import org.projectfloodlight.openflow.protocol.OFFactory; diff --git a/src/main/java/net/floodlightcontroller/core/NullConnection.java b/src/main/java/net/floodlightcontroller/core/internal/NullConnection.java similarity index 84% rename from src/main/java/net/floodlightcontroller/core/NullConnection.java rename to src/main/java/net/floodlightcontroller/core/internal/NullConnection.java index 34f225e1343a1f32953f786a61555cf053438f8d..4854a1341a868771d42914197944a0465288430a 100644 --- a/src/main/java/net/floodlightcontroller/core/NullConnection.java +++ b/src/main/java/net/floodlightcontroller/core/internal/NullConnection.java @@ -1,10 +1,14 @@ -package net.floodlightcontroller.core; +package net.floodlightcontroller.core.internal; import java.net.SocketAddress; +import java.util.Collection; import java.util.List; import java.util.Date; -import net.floodlightcontroller.core.internal.IOFConnectionListener; +import net.floodlightcontroller.core.IOFConnectionBackend; +import net.floodlightcontroller.core.IOFMessageWriter; +import net.floodlightcontroller.core.SwitchDisconnectedException; +import net.floodlightcontroller.util.IterableUtils; import org.projectfloodlight.openflow.protocol.OFFactories; import org.projectfloodlight.openflow.protocol.OFFactory; @@ -40,13 +44,15 @@ public class NullConnection implements IOFConnectionBackend, IOFMessageWriter { } @Override - public void write(OFMessage m) { + public boolean write(OFMessage m) { warn(); + return false; } @Override - public void write(Iterable<OFMessage> msglist) { + public Collection<OFMessage> write(Iterable<OFMessage> msgList) { warn(); + return IterableUtils.toCollection(msgList); } @Override @@ -75,11 +81,6 @@ public class NullConnection implements IOFConnectionBackend, IOFMessageWriter { // noop } - @Override - public void flush() { - // noop - } - @Override public <R extends OFMessage> ListenableFuture<R> writeRequest(OFRequest<R> request) { return Futures.immediateFailedFuture(new SwitchDisconnectedException(getDatapathId())); diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java b/src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java index 7d45c1ee862f08dea0111bed7a63c37d94caf15e..0611648a5252cd623f1ade599777b3a9151c0067 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFChannelHandler.java @@ -10,25 +10,19 @@ import java.util.concurrent.RejectedExecutionException; import javax.annotation.Nonnull; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.ChannelStateEvent; -import org.jboss.netty.channel.Channels; -import org.jboss.netty.channel.ExceptionEvent; -import org.jboss.netty.channel.MessageEvent; -import org.jboss.netty.handler.timeout.IdleStateAwareChannelHandler; -import org.jboss.netty.handler.timeout.IdleStateEvent; -import org.jboss.netty.handler.timeout.IdleStateHandler; -import org.jboss.netty.handler.timeout.ReadTimeoutException; -import org.jboss.netty.util.Timer; - +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.SimpleChannelInboundHandler; +import io.netty.handler.timeout.IdleStateHandler; +import io.netty.handler.timeout.ReadTimeoutException; +import io.netty.util.AttributeKey; +import io.netty.util.Timer; import net.floodlightcontroller.core.IOFConnectionBackend; -import net.floodlightcontroller.core.OFConnection; -import net.floodlightcontroller.core.internal.OpenflowPipelineFactory.PipelineHandler; -import net.floodlightcontroller.core.internal.OpenflowPipelineFactory.PipelineHandshakeTimeout; -import net.floodlightcontroller.core.internal.OpenflowPipelineFactory.PipelineIdleReadTimeout; -import net.floodlightcontroller.core.internal.OpenflowPipelineFactory.PipelineIdleWriteTimeout; +import net.floodlightcontroller.core.internal.OFChannelInitializer.PipelineHandler; +import net.floodlightcontroller.core.internal.OFChannelInitializer.PipelineHandshakeTimeout; +import net.floodlightcontroller.core.internal.OFChannelInitializer.PipelineIdleReadTimeout; +import net.floodlightcontroller.core.internal.OFChannelInitializer.PipelineIdleWriteTimeout; import net.floodlightcontroller.debugcounter.IDebugCounterService; import org.projectfloodlight.openflow.exceptions.OFParseError; @@ -62,10 +56,12 @@ import com.google.common.base.Preconditions; * messages to the higher orders of control. * @author Jason Parraga <Jason.Parraga@Bigswitch.com> */ -class OFChannelHandler extends IdleStateAwareChannelHandler { +class OFChannelHandler extends SimpleChannelInboundHandler<Iterable<OFMessage>> { private static final Logger log = LoggerFactory.getLogger(OFChannelHandler.class); + public static final AttributeKey<OFChannelInfo> ATTR_CHANNEL_INFO = AttributeKey.valueOf("channelInfo"); + private final ChannelPipeline pipeline; private final INewOFConnectionListener newConnectionListener; private final SwitchManagerCounters counters; @@ -83,10 +79,10 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { * We will count down */ private long handshakeTransactionIds = 0x00FFFFFFFFL; - - private volatile long echoSendTime; - private volatile long featuresLatency; - + + private volatile long echoSendTime; + private volatile long featuresLatency; + /** * Default implementation for message handlers in any OFChannelState. * @@ -336,11 +332,11 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { /* Lookup highest, common supported OpenFlow version */ commonVersion = computeOFVersionFromBitmap(bitmaps); if (commonVersion == null) { - log.error("Could not negotiate common OpenFlow version for {} with greatest version bitmap algorithm.", channel.getRemoteAddress()); + log.error("Could not negotiate common OpenFlow version for {} with greatest version bitmap algorithm.", channel.remoteAddress()); channel.disconnect(); return; } else { - log.info("Negotiated OpenFlow version of {} for {} with greatest version bitmap algorithm.", commonVersion.toString(), channel.getRemoteAddress()); + log.info("Negotiated OpenFlow version of {} for {} with greatest version bitmap algorithm.", commonVersion.toString(), channel.remoteAddress()); factory = OFFactories.getFactory(commonVersion); OFMessageDecoder decoder = pipeline.get(OFMessageDecoder.class); decoder.setVersion(commonVersion); @@ -348,16 +344,16 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { } /* If there's not a bitmap present, choose the lower of the two supported versions. */ else if (theirVersion.compareTo(factory.getVersion()) < 0) { - log.info("Negotiated down to switch OpenFlow version of {} for {} using lesser hello header algorithm.", theirVersion.toString(), channel.getRemoteAddress()); + log.info("Negotiated down to switch OpenFlow version of {} for {} using lesser hello header algorithm.", theirVersion.toString(), channel.remoteAddress()); factory = OFFactories.getFactory(theirVersion); OFMessageDecoder decoder = pipeline.get(OFMessageDecoder.class); decoder.setVersion(theirVersion); } /* else The controller's version is < or = the switch's, so keep original controller factory. */ else if (theirVersion.equals(factory.getVersion())) { - log.info("Negotiated equal OpenFlow version of {} for {} using lesser hello header algorithm.", factory.getVersion().toString(), channel.getRemoteAddress()); + log.info("Negotiated equal OpenFlow version of {} for {} using lesser hello header algorithm.", factory.getVersion().toString(), channel.remoteAddress()); } else { - log.info("Negotiated down to controller OpenFlow version of {} for {} using lesser hello header algorithm.", factory.getVersion().toString(), channel.getRemoteAddress()); + log.info("Negotiated down to controller OpenFlow version of {} for {} using lesser hello header algorithm.", factory.getVersion().toString(), channel.remoteAddress()); } setState(new WaitFeaturesReplyState()); @@ -382,7 +378,7 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { void processOFFeaturesReply(OFFeaturesReply m) throws IOException { featuresReply = m; - + featuresLatency = (System.currentTimeMillis() - featuresLatency) / 2; // Mark handshake as completed @@ -409,7 +405,7 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { * we are. */ if (m.getVersion().equals(factory.getVersion())) { - log.warn("Ignoring second hello from {} in state {}. Might be a Brocade.", channel.getRemoteAddress(), state.toString()); + log.warn("Ignoring second hello from {} in state {}. Might be a Brocade.", channel.remoteAddress(), state.toString()); } else { super.processOFHello(m); /* Versions don't match as they should; abort */ } @@ -417,7 +413,7 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { @Override void processOFPortStatus(OFPortStatus m) { - log.warn("Ignoring PORT_STATUS message from {} during OpenFlow channel establishment. Ports will be explicitly queried in a later state.", channel.getRemoteAddress()); + log.warn("Ignoring PORT_STATUS message from {} during OpenFlow channel establishment. Ports will be explicitly queried in a later state.", channel.remoteAddress()); } @Override @@ -429,7 +425,7 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { @Override void processOFMessage(OFMessage m) throws IOException { if (m.getType().equals(OFType.PACKET_IN)) { - log.warn("Ignoring PACKET_IN message from {} during OpenFlow channel establishment.", channel.getRemoteAddress()); + log.warn("Ignoring PACKET_IN message from {} during OpenFlow channel establishment.", channel.remoteAddress()); } else { super.processOFMessage(m); } @@ -453,11 +449,11 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { setSwitchHandshakeTimeout(); // Handle non 1.3 connections - if(featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0){ + if (featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0){ connection = new OFConnection(featuresReply.getDatapathId(), factory, channel, OFAuxId.MAIN, debugCounters, timer); } // Handle 1.3 connections - else{ + else { connection = new OFConnection(featuresReply.getDatapathId(), factory, channel, featuresReply.getAuxiliaryId(), debugCounters, timer); // If this is an aux connection, we set a longer echo idle time @@ -465,10 +461,10 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { setAuxChannelIdle(); } } - + connection.updateLatency(U64.of(featuresLatency)); echoSendTime = 0; - + // Notify the connection broker notifyConnectionOpened(connection); } @@ -588,21 +584,18 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { } @Override - public void channelConnected(ChannelHandlerContext ctx, - ChannelStateEvent e) throws Exception { + public void channelActive(ChannelHandlerContext ctx) throws Exception { log.debug("channelConnected on OFChannelHandler {}", String.format("%08x", System.identityHashCode(this))); counters.switchConnected.increment(); - channel = e.getChannel(); - log.info("New switch connection from {}", - channel.getRemoteAddress()); + channel = ctx.channel(); + log.info("New switch connection from {}", channel.remoteAddress()); setState(new WaitHelloState()); } @Override - public void channelDisconnected(ChannelHandlerContext ctx, - ChannelStateEvent e) throws Exception { + public void channelInactive(ChannelHandlerContext ctx) { // Only handle cleanup connection is even known - if(this.connection != null){ + if (this.connection != null) { // Alert the connection object that the channel has been disconnected this.connection.disconnected(); // Punt the cleanup to the Switch Manager @@ -612,19 +605,19 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { } @Override - public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e) + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { - if (e.getCause() instanceof ReadTimeoutException) { + if (cause instanceof ReadTimeoutException) { if (featuresReply.getVersion().compareTo(OFVersion.OF_13) < 0) { log.error("Disconnecting switch {} due to read timeout on main cxn.", getConnectionInfoString()); - ctx.getChannel().close(); + ctx.channel().close(); } else { if (featuresReply.getAuxiliaryId().equals(OFAuxId.MAIN)) { log.error("Disconnecting switch {} due to read timeout on main cxn.", getConnectionInfoString()); - ctx.getChannel().close(); + ctx.channel().close(); } else { // We only don't disconnect on aux connections log.warn("Switch {} encountered read timeout on aux cxn.", @@ -634,95 +627,84 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { // Increment counters counters.switchDisconnectReadTimeout.increment(); - } else if (e.getCause() instanceof HandshakeTimeoutException) { + } else if (cause instanceof HandshakeTimeoutException) { log.error("Disconnecting switch {}: failed to complete handshake. Channel handshake complete : {}", getConnectionInfoString(), this.state.channelHandshakeComplete); counters.switchDisconnectHandshakeTimeout.increment(); - ctx.getChannel().close(); - } else if (e.getCause() instanceof ClosedChannelException) { + ctx.channel().close(); + } else if (cause instanceof ClosedChannelException) { log.debug("Channel for sw {} already closed", getConnectionInfoString()); - } else if (e.getCause() instanceof IOException) { + } else if (cause instanceof IOException) { log.error("Disconnecting switch {} due to IO Error: {}", - getConnectionInfoString(), e.getCause().getMessage()); + getConnectionInfoString(), cause.getMessage()); if (log.isDebugEnabled()) { // still print stack trace if debug is enabled - log.debug("StackTrace for previous Exception: ", e.getCause()); + log.debug("StackTrace for previous Exception: ", cause); } counters.switchDisconnectIOError.increment(); - ctx.getChannel().close(); - } else if (e.getCause() instanceof SwitchStateException) { + ctx.channel().close(); + } else if (cause instanceof SwitchStateException) { log.error("Disconnecting switch {} due to switch state error: {}", - getConnectionInfoString(), e.getCause().getMessage()); + getConnectionInfoString(), cause.getMessage()); if (log.isDebugEnabled()) { // still print stack trace if debug is enabled - log.debug("StackTrace for previous Exception: ", e.getCause()); + log.debug("StackTrace for previous Exception: ", cause); } counters.switchDisconnectSwitchStateException.increment(); - ctx.getChannel().close(); - } else if (e.getCause() instanceof OFAuxException) { + ctx.channel().close(); + } else if (cause instanceof OFAuxException) { log.error("Disconnecting switch {} due to OF Aux error: {}", - getConnectionInfoString(), e.getCause().getMessage()); + getConnectionInfoString(), cause.getMessage()); if (log.isDebugEnabled()) { // still print stack trace if debug is enabled - log.debug("StackTrace for previous Exception: ", e.getCause()); + log.debug("StackTrace for previous Exception: ", cause); } counters.switchDisconnectSwitchStateException.increment(); - ctx.getChannel().close(); - } else if (e.getCause() instanceof OFParseError) { + ctx.channel().close(); + } else if (cause instanceof OFParseError) { log.error("Disconnecting switch " + getConnectionInfoString() + " due to message parse failure", - e.getCause()); + cause); counters.switchDisconnectParseError.increment(); - ctx.getChannel().close(); - } else if (e.getCause() instanceof RejectedExecutionException) { + ctx.channel().close(); + } else if (cause instanceof RejectedExecutionException) { log.warn("Could not process message: queue full"); counters.rejectedExecutionException.increment(); - } else if (e.getCause() instanceof IllegalArgumentException) { - log.error("Illegal argument exception with switch {}. {}", getConnectionInfoString(), e.getCause()); + } else if (cause instanceof IllegalArgumentException) { + log.error("Illegal argument exception with switch {}. {}", getConnectionInfoString(), cause); counters.switchSslConfigurationError.increment(); - ctx.getChannel().close(); + ctx.channel().close(); } else { log.error("Error while processing message from switch " + getConnectionInfoString() - + "state " + this.state, e.getCause()); + + "state " + this.state, cause); counters.switchDisconnectOtherException.increment(); - ctx.getChannel().close(); + ctx.channel().close(); } } @Override - public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e) - throws Exception { - + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { log.debug("channelIdle on OFChannelHandler {}", String.format("%08x", System.identityHashCode(this))); - OFChannelHandler handler = ctx.getPipeline().get(OFChannelHandler.class); + OFChannelHandler handler = ctx.pipeline().get(OFChannelHandler.class); handler.sendEchoRequest(); } @Override - public void messageReceived(ChannelHandlerContext ctx, MessageEvent e) - throws Exception { - if (e.getMessage() instanceof List) { - @SuppressWarnings("unchecked") - List<OFMessage> msglist = (List<OFMessage>)e.getMessage(); - for (OFMessage ofm : msglist) { - try { - // Do the actual packet processing - state.processOFMessage(ofm); - } - catch (Exception ex) { - // We are the last handler in the stream, so run the - // exception through the channel again by passing in - // ctx.getChannel(). - Channels.fireExceptionCaught(ctx.getChannel(), ex); - } + public void channelRead0(ChannelHandlerContext ctx, Iterable<OFMessage> msgList) throws Exception { + for (OFMessage ofm : msgList) { + try { + // Do the actual packet processing + state.processOFMessage(ofm); + } + catch (Exception ex) { + // We are the last handler in the stream, so run the + // exception through the channel again by passing in + // ctx.getChannel(). + ctx.fireExceptionCaught(ex); } - } - else { - Channels.fireExceptionCaught(ctx.getChannel(), - new AssertionError("Message received from channel is not a list")); } } @@ -731,9 +713,7 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { * This is specifically for aux channels. */ private void setAuxChannelIdle() { - IdleStateHandler idleHandler = new IdleStateHandler( - this.timer, PipelineIdleReadTimeout.AUX, PipelineIdleWriteTimeout.AUX, 0); @@ -765,10 +745,10 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { private String getConnectionInfoString() { String channelString; - if (channel == null || channel.getRemoteAddress() == null) { + if (channel == null || channel.remoteAddress() == null) { channelString = "?"; } else { - channelString = channel.getRemoteAddress().toString(); + channelString = channel.remoteAddress().toString(); if(channelString.startsWith("/")) channelString = channelString.substring(1); } @@ -807,7 +787,7 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { OFFeaturesRequest m = factory.buildFeaturesRequest() .setXid(handshakeTransactionIds--) .build(); - channel.write(Collections.singletonList(m)); + write(m); } /** @@ -830,8 +810,8 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { OFHello m = builder.setXid(handshakeTransactionIds--) .build(); - - channel.write(Collections.singletonList(m)); + + write(m); log.debug("Send hello: {}", m); } @@ -841,7 +821,7 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { .build(); /* Record for latency calculation */ echoSendTime = System.currentTimeMillis(); - channel.write(Collections.singletonList(request)); + write(request); } private void sendEchoReply(OFEchoRequest request) { @@ -849,7 +829,11 @@ class OFChannelHandler extends IdleStateAwareChannelHandler { .setXid(request.getXid()) .setData(request.getData()) .build(); - channel.write(Collections.singletonList(reply)); + write(reply); + } + + private void write(OFMessage m) { + channel.writeAndFlush(Collections.singletonList(m)); } OFChannelState getStateForTesting() { diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFChannelInfo.java b/src/main/java/net/floodlightcontroller/core/internal/OFChannelInfo.java new file mode 100644 index 0000000000000000000000000000000000000000..e4a347e18dafccc9a90f90d48c6e287f1908da02 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/OFChannelInfo.java @@ -0,0 +1,59 @@ +package net.floodlightcontroller.core.internal; + +import java.net.InetSocketAddress; +import java.net.SocketAddress; + +import javax.annotation.Nonnull; + +import io.netty.channel.Channel; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.IPAddress; +import org.projectfloodlight.openflow.types.OFAuxId; + +import com.google.common.base.Preconditions; + +/** Basic information that {@link OFChannelHandler} attaches to the + * netty channel via {@link Channel#setAttachment(Object)}, mainly + * for the purpose of being able to log the connection information. + * + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + */ +public class OFChannelInfo { + private final DatapathId id; + private final OFAuxId auxId; + private final IPAddress<?> address; + private final int port; + + public OFChannelInfo(@Nonnull DatapathId id, @Nonnull OFAuxId auxId, @Nonnull SocketAddress address) { + Preconditions.checkNotNull(id, "id should not be null"); + Preconditions.checkNotNull(auxId, "auxId should not be null"); + Preconditions.checkNotNull(address, "address should not be null"); + + this.id = id; + this.auxId = auxId; + InetSocketAddress socketAddress = (InetSocketAddress) address; + this.address = IPAddress.of(socketAddress.getHostString()); + this.port = socketAddress.getPort(); + } + + public DatapathId getId() { + return id; + } + + public OFAuxId getAuxId() { + return auxId; + } + + public IPAddress<?> getAddress() { + return address; + } + + public int getPort() { + return port; + } + + @Override + public String toString() { + return id + "/" + auxId + "@" + address + ":" + port; + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/internal/OpenflowPipelineFactory.java b/src/main/java/net/floodlightcontroller/core/internal/OFChannelInitializer.java similarity index 54% rename from src/main/java/net/floodlightcontroller/core/internal/OpenflowPipelineFactory.java rename to src/main/java/net/floodlightcontroller/core/internal/OFChannelInitializer.java index 11827d02fb50a490c7661179bab38e9b2b46292a..3b3709c2e288c5e3fbd31911c7f6248cded12185 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OpenflowPipelineFactory.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFChannelInitializer.java @@ -21,7 +21,6 @@ import java.io.FileInputStream; import java.security.KeyStore; import java.util.List; -import javax.annotation.Nonnull; import javax.net.ssl.KeyManager; import javax.net.ssl.KeyManagerFactory; import javax.net.ssl.SSLContext; @@ -29,85 +28,64 @@ import javax.net.ssl.SSLEngine; import javax.net.ssl.TrustManager; import javax.net.ssl.TrustManagerFactory; -import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.ChannelPipelineFactory; -import org.jboss.netty.channel.Channels; -import org.jboss.netty.handler.ssl.SslHandler; -import org.jboss.netty.handler.timeout.IdleStateHandler; -import org.jboss.netty.handler.timeout.ReadTimeoutHandler; -import org.jboss.netty.util.ExternalResourceReleasable; -import org.jboss.netty.util.Timer; import org.projectfloodlight.openflow.protocol.OFFactory; import org.projectfloodlight.openflow.types.U32; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import net.floodlightcontroller.core.internal.IOFSwitchManager; +import net.floodlightcontroller.core.internal.HandshakeTimeoutHandler; +import net.floodlightcontroller.core.internal.INewOFConnectionListener; +import net.floodlightcontroller.core.internal.OFChannelHandler; import net.floodlightcontroller.debugcounter.IDebugCounterService; +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.handler.ssl.SslHandler; +import io.netty.handler.timeout.IdleStateHandler; +import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.util.Timer; /** * Creates a ChannelPipeline for a server-side openflow channel - * @author readams, sovietaced + * @author readams, sovietaced, rizard, andi-bigswitch */ -public class OpenflowPipelineFactory -implements ChannelPipelineFactory, ExternalResourceReleasable { - private static final Logger log = LoggerFactory.getLogger(OpenflowPipelineFactory.class); - protected IOFSwitchManager switchManager; - protected INewOFConnectionListener connectionListener; - protected Timer timer; - protected IdleStateHandler idleHandler; - protected ReadTimeoutHandler readTimeoutHandler; - protected IDebugCounterService debugCounters; +public class OFChannelInitializer extends ChannelInitializer<Channel> { + private static final Logger log = LoggerFactory.getLogger(OFChannelInitializer.class); + + private IOFSwitchManager switchManager; + private INewOFConnectionListener connectionListener; + private Timer timer; + private IDebugCounterService debugCounters; private String keyStore; private String keyStorePassword; private OFFactory defaultFactory; private List<U32> ofBitmaps; - private void init(IOFSwitchManager switchManager, Timer timer, + public OFChannelInitializer(IOFSwitchManager switchManager, INewOFConnectionListener connectionListener, IDebugCounterService debugCounters, - @Nonnull List<U32> ofBitmaps, - @Nonnull OFFactory defaultFactory) { + Timer timer, + List<U32> ofBitmaps, + OFFactory defaultFactory, + String keyStore, + String keyStorePassword) { + super(); this.switchManager = switchManager; this.connectionListener = connectionListener; this.timer = timer; this.debugCounters = debugCounters; this.defaultFactory = defaultFactory; this.ofBitmaps = ofBitmaps; - this.idleHandler = new IdleStateHandler( - timer, - PipelineIdleReadTimeout.MAIN, - PipelineIdleWriteTimeout.MAIN, - 0); - this.readTimeoutHandler = new ReadTimeoutHandler(timer, 30); - } - - public OpenflowPipelineFactory(IOFSwitchManager switchManager, Timer timer, - INewOFConnectionListener connectionListener, - IDebugCounterService debugCounters, - @Nonnull List<U32> ofBitmaps, - @Nonnull OFFactory defaultFactory) { - super(); - init(switchManager,timer, connectionListener, debugCounters, ofBitmaps, defaultFactory); - this.keyStore = null; - this.keyStorePassword = null; - } - - public OpenflowPipelineFactory(IOFSwitchManager switchManager, Timer timer, - INewOFConnectionListener connectionListener, - IDebugCounterService debugCounters, - @Nonnull List<U32> ofBitmaps, - @Nonnull OFFactory defaultFactory, - @Nonnull String keyStore, @Nonnull String keyStorePassword) { - super(); - init(switchManager,timer, connectionListener, debugCounters, ofBitmaps, defaultFactory); this.keyStore = keyStore; this.keyStorePassword = keyStorePassword; } @Override - public ChannelPipeline getPipeline() throws Exception { - ChannelPipeline pipeline = Channels.pipeline(); - OFChannelHandler handler = new OFChannelHandler(switchManager, + protected void initChannel(Channel ch) throws Exception { + ChannelPipeline pipeline = ch.pipeline(); + OFChannelHandler handler = new OFChannelHandler( + switchManager, connectionListener, pipeline, debugCounters, @@ -153,61 +131,57 @@ implements ChannelPipelineFactory, ExternalResourceReleasable { throw e; /* If we wanted secure but didn't get it, we should bail. */ } } - - /* SSL handler will have been added first if we're using it. */ + pipeline.addLast(PipelineHandler.OF_MESSAGE_DECODER, new OFMessageDecoder()); pipeline.addLast(PipelineHandler.OF_MESSAGE_ENCODER, new OFMessageEncoder()); - pipeline.addLast(PipelineHandler.MAIN_IDLE, idleHandler); - pipeline.addLast(PipelineHandler.READ_TIMEOUT, readTimeoutHandler); + pipeline.addLast(PipelineHandler.MAIN_IDLE, + new IdleStateHandler(PipelineIdleReadTimeout.MAIN, + PipelineIdleWriteTimeout.MAIN, + 0)); + pipeline.addLast(PipelineHandler.READ_TIMEOUT, new ReadTimeoutHandler(30)); pipeline.addLast(PipelineHandler.CHANNEL_HANDSHAKE_TIMEOUT, new HandshakeTimeoutHandler( handler, timer, PipelineHandshakeTimeout.CHANNEL)); - pipeline.addLast(PipelineHandler.CHANNEL_HANDLER, handler); - return pipeline; - } - @Override - public void releaseExternalResources() { - timer.stop(); + pipeline.addLast(PipelineHandler.CHANNEL_HANDLER, handler); } public static class PipelineHandler { - final static String CHANNEL_HANDSHAKE_TIMEOUT = "channelhandshaketimeout"; - final static String SWITCH_HANDSHAKE_TIMEOUT = "switchhandshaketimeout"; - final static String CHANNEL_HANDLER = "channelhandler"; - final static String MAIN_IDLE = "mainidle"; - final static String AUX_IDLE = "auxidle"; - final static String OF_MESSAGE_DECODER = "ofmessagedecoder"; - final static String OF_MESSAGE_ENCODER = "ofmessageencoder"; - final static String READ_TIMEOUT = "readtimeout"; - final static String SSL_TLS_ENCODER_DECODER = "ofsecurechannelencoderdecoder"; - } + public final static String CHANNEL_HANDSHAKE_TIMEOUT = "channelhandshaketimeout"; + public final static String SWITCH_HANDSHAKE_TIMEOUT = "switchhandshaketimeout"; + public final static String CHANNEL_HANDLER = "channelhandler"; + public final static String MAIN_IDLE = "mainidle"; + public final static String AUX_IDLE = "auxidle"; + public final static String OF_MESSAGE_DECODER = "ofmessagedecoder"; + public final static String OF_MESSAGE_ENCODER = "ofmessageencoder"; + public final static String READ_TIMEOUT = "readtimeout"; + public final static String SSL_TLS_ENCODER_DECODER = "ofsecurechannelencoderdecoder"; } /** * Timeouts for parts of the handshake, in seconds */ - public static class PipelineHandshakeTimeout { - final static int CHANNEL = 10; - final static int SWITCH = 30; - } - - /** - * Timeouts for writes on connections, in seconds - */ - public static class PipelineIdleWriteTimeout { - final static int MAIN = 2; - final static int AUX = 15; - } - - /** - * Timeouts for reads on connections, in seconds - */ - public static class PipelineIdleReadTimeout { - final static int MAIN = 3 * PipelineIdleWriteTimeout.MAIN; - final static int AUX = 3 * PipelineIdleWriteTimeout.AUX; - } -} + public static class PipelineHandshakeTimeout { + final static int CHANNEL = 10; + public final static int SWITCH = 30; + } + + /** + * Timeouts for writes on connections, in seconds + */ + public static class PipelineIdleWriteTimeout { + final static int MAIN = 2; + public final static int AUX = 15; + } + + /** + * Timeouts for reads on connections, in seconds + */ + public static class PipelineIdleReadTimeout { + final static int MAIN = 3 * PipelineIdleWriteTimeout.MAIN; + public final static int AUX = 3 * PipelineIdleWriteTimeout.AUX; + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFConnection.java b/src/main/java/net/floodlightcontroller/core/internal/OFConnection.java new file mode 100644 index 0000000000000000000000000000000000000000..0bf1afadc329daeed35cb92494fdc63f4b9edb56 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/internal/OFConnection.java @@ -0,0 +1,452 @@ +/** + * Copyright 2012, 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + **/ + +package net.floodlightcontroller.core.internal; + +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import javax.annotation.Nonnull; + +import io.netty.channel.Channel; +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; + +import java.util.Date; + +import net.floodlightcontroller.core.Deliverable; +import net.floodlightcontroller.core.DeliverableListenableFuture; +import net.floodlightcontroller.core.IOFConnection; +import net.floodlightcontroller.core.IOFConnectionBackend; +import net.floodlightcontroller.core.SwitchDisconnectedException; +import net.floodlightcontroller.debugcounter.IDebugCounterService; +import net.floodlightcontroller.util.IterableUtils; + +import org.projectfloodlight.openflow.protocol.OFErrorMsg; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFRequest; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsReplyFlags; +import org.projectfloodlight.openflow.protocol.OFStatsRequest; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFAuxId; +import org.projectfloodlight.openflow.types.U64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Iterables; +import com.google.common.util.concurrent.Futures; +import com.google.common.util.concurrent.ListenableFuture; + +/** + * Implementation of an openflow connection to switch. Encapsulates a + * {@link Channel}, and provides message write and request/response handling + * capabilities. + * + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + */ +public class OFConnection implements IOFConnection, IOFConnectionBackend{ + private static final Logger logger = LoggerFactory.getLogger(OFConnection.class); + private final DatapathId dpid; + private final OFFactory factory; + + /** CAREFUL CAREFUL CAREFUL: + * + * Netty4 does not guarantee order of messages that are written into the channel any more. + * To ensure messages do not get reordered, never directly call {@link Channel#write(Object)}. + * + * Instead, use {@link #write(Iterable)}, which queue up write request on the EventLoop, + * to make sure they are handled in order. + */ + private final Channel channel; + + private final OFAuxId auxId; + private final Timer timer; + + private final Date connectedSince; + + private final Map<Long, Deliverable<?>> xidDeliverableMap; + + private static final long DELIVERABLE_TIME_OUT = 60; + private static final TimeUnit DELIVERABLE_TIME_OUT_UNIT = TimeUnit.SECONDS; + + private final OFConnectionCounters counters; + private IOFConnectionListener listener; + + private volatile U64 latency; + + /** + * Used to write messages to ensure order w/Netty4. + * It also ensures we do not reuse the array, since + * Netty4 will write the object, not the items. + */ + private class WriteMessageTask implements Runnable { + private final Iterable<OFMessage> msglist; + + public WriteMessageTask(Iterable<OFMessage> msglist) { + this.msglist = msglist; + } + + @Override + public void run() { + for (OFMessage m : msglist) { + if (logger.isTraceEnabled()) + logger.trace("{}: send {}", this, m); + counters.updateWriteStats(m); + } + channel.writeAndFlush(msglist); + } + } + + public OFConnection(@Nonnull DatapathId dpid, + @Nonnull OFFactory factory, + @Nonnull Channel channel, + @Nonnull OFAuxId auxId, + @Nonnull IDebugCounterService debugCounters, + @Nonnull Timer timer) { + Preconditions.checkNotNull(dpid, "dpid"); + Preconditions.checkNotNull(factory, "factory"); + Preconditions.checkNotNull(channel, "channel"); + Preconditions.checkNotNull(timer, "timer"); + Preconditions.checkNotNull(debugCounters); + + this.listener = NullConnectionListener.INSTANCE; + this.dpid = dpid; + this.factory = factory; + this.channel = channel; + this.auxId = auxId; + this.connectedSince = new Date(); + this.xidDeliverableMap = new ConcurrentHashMap<>(); + this.counters = new OFConnectionCounters(debugCounters, dpid, this.auxId); + this.timer = timer; + this.latency = U64.ZERO; + } + + /** + * All write methods chain into this write() to use WriteMessageTask. + * + * Write the list of messages to the switch + * + * @param msgList list of messages to write + * @return list of failed messages; can only fail if channel disconnected + */ + @Override + public Collection<OFMessage> write(final Iterable<OFMessage> msgList) { + if (!isConnected()) { + if (logger.isDebugEnabled()) + logger.debug(this.toString() + " : not connected - dropping {} element msglist {} ", + Iterables.size(msgList), + String.valueOf(msgList).substring(0, 80)); + return IterableUtils.toCollection(msgList); + } + for (OFMessage m : msgList) { + if (logger.isTraceEnabled()) { + logger.trace("{}: send {}", this, m); + counters.updateWriteStats(m); + } + } + this.channel.eventLoop().execute(new WriteMessageTask(msgList)); + return Collections.emptyList(); + } + + /** + * Write the single message to the channel + * @param m + * @return true upon success; false upon failure; can only fail if channel disconnected + */ + @Override + public boolean write(OFMessage m) { + return this.write(Collections.singletonList(m)).isEmpty(); + } + + @Override + public <R extends OFMessage> ListenableFuture<R> writeRequest(OFRequest<R> request) { + if (!isConnected()) { + return Futures.immediateFailedFuture(new SwitchDisconnectedException(getDatapathId())); + } + + DeliverableListenableFuture<R> future = new DeliverableListenableFuture<R>(); + xidDeliverableMap.put(request.getXid(), future); + listener.messageWritten(this, request); + this.write(request); + return future; + } + + @Override + public <REPLY extends OFStatsReply> ListenableFuture<List<REPLY>> writeStatsRequest( + OFStatsRequest<REPLY> request) { + if (!isConnected()) { + return Futures.immediateFailedFuture(new SwitchDisconnectedException(getDatapathId())); + } + + final DeliverableListenableFuture<List<REPLY>> future = + new DeliverableListenableFuture<List<REPLY>>(); + + Deliverable<REPLY> deliverable = new Deliverable<REPLY>() { + private final List<REPLY> results = Collections + .synchronizedList(new ArrayList<REPLY>()); + + @Override + public void deliver(REPLY reply) { + results.add(reply); + if (!reply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) { + // done + future.deliver(results); + } + } + + @Override + public void deliverError(Throwable cause) { + future.deliverError(cause); + } + + @Override + public boolean isDone() { + return future.isDone(); + } + + @Override + public boolean cancel(boolean mayInterruptIfRunning) { + return future.cancel(mayInterruptIfRunning); + } + }; + + registerDeliverable(request.getXid(), deliverable); + this.write(request); + return future; + } + + public void disconnected() { + SwitchDisconnectedException exception = new SwitchDisconnectedException(getDatapathId()); + for (Long xid : xidDeliverableMap.keySet()) { + // protect against other mechanisms running at the same time + // (timeout) + Deliverable<?> removed = xidDeliverableMap.remove(xid); + if (removed != null) { + removed.deliverError(exception); + } + } + } + + @Override + public void disconnect() { + this.channel.disconnect(); + this.counters.uninstallCounters(); + } + + @Override + public String toString() { + String channelString = (channel != null) ? String.valueOf(channel.remoteAddress()): "?"; + return "OFConnection [" + getDatapathId() + "(" + getAuxId() + ")" + "@" + channelString + "]"; + } + + @Override + public Date getConnectedSince() { + return connectedSince; + } + + private void registerDeliverable(long xid, Deliverable<?> deliverable) { + this.xidDeliverableMap.put(xid, deliverable); + timer.newTimeout(new TimeOutDeliverable(xid), DELIVERABLE_TIME_OUT, DELIVERABLE_TIME_OUT_UNIT); + } + + public boolean handleGenericDeliverable(OFMessage reply) { + counters.updateReadStats(reply); + @SuppressWarnings("unchecked") + Deliverable<OFMessage> deliverable = + (Deliverable<OFMessage>) this.xidDeliverableMap.get(reply.getXid()); + if (deliverable != null) { + if(reply instanceof OFErrorMsg) { + deliverable.deliverError(new OFErrorMsgException((OFErrorMsg) reply)); + } else { + deliverable.deliver(reply); + } + if (deliverable.isDone()) + this.xidDeliverableMap.remove(reply.getXid()); + return true; + } else { + return false; + } + } + + @Override + public void cancelAllPendingRequests() { + /* + * we don't need to be synchronized here. Even if another thread + * modifies the map while we're cleaning up the future will eventually + * timeout + */ + for (Deliverable<?> d : xidDeliverableMap.values()) { + d.cancel(true); + } + xidDeliverableMap.clear(); + } + + @Override + public boolean isConnected() { + return channel.isActive(); + } + + @Override + public SocketAddress getRemoteInetAddress() { + return channel.remoteAddress(); + } + + @Override + public SocketAddress getLocalInetAddress() { + return channel.localAddress(); + } + + public boolean deliverResponse(OFMessage m) { + if (handleGenericDeliverable(m)) + return true; + else + return false; + } + + @Override + public boolean isWritable() { + return channel.isWritable(); + } + + @Override + public DatapathId getDatapathId() { + return dpid; + } + + @Override + public OFAuxId getAuxId() { + return auxId; + } + + Set<Long> getPendingRequestIds() { + return ImmutableSet.copyOf(xidDeliverableMap.keySet()); + } + + @Override + public OFFactory getOFFactory() { + return this.factory; + } + + /** + * Timeout class instantiated for deliverables. Will throw a timeout exception + * if proper responses are not received in time. + * + */ + private class TimeOutDeliverable implements TimerTask { + private final long xid; + + public TimeOutDeliverable(long xid) { + this.xid = xid; + } + + @Override + public void run(Timeout timeout) throws Exception { + Deliverable<?> removed = xidDeliverableMap.remove(xid); + if (removed != null && !removed.isDone()) { + removed.deliverError(new TimeoutException( + "timeout - did not receive answer for xid " + xid)); + } + + } + } + + public IOFConnectionListener getListener() { + return listener; + } + + /** set the connection listener + * <p> + * Note: this is assumed to be called from the Connection's IO Thread. + * + * @param listener + */ + @Override + public void setListener(IOFConnectionListener listener) { + this.listener = listener; + } + + public void messageReceived(OFMessage m) { + // Check if message was a response for a xid waiting at the switch + if(!deliverResponse(m)){ + listener.messageReceived(this, m); + } + } + + @Override + public U64 getLatency() { + return this.latency; + } + + @Override + public void updateLatency(U64 latency) { + if (latency == null) { + logger.error("Latency must be non-null. Ignoring null latency value."); + return; + } else if (this.latency.equals(U64.ZERO)) { + logger.debug("Recording previously 0ms switch {} latency as {}ms", this.getDatapathId(), latency.getValue()); + this.latency = latency; + return; + } else { + double oldWeight = 0.30; + this.latency = U64.of((long) (this.latency.getValue() * oldWeight + latency.getValue() * (1 - oldWeight))); + logger.debug("Switch {} latency updated to {}ms", this.getDatapathId(), this.latency.getValue()); + } + } + + /** A dummy connection listener that just logs warn messages. Saves us a few null checks + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + */ + private static class NullConnectionListener implements IOFConnectionListener { + public final static NullConnectionListener INSTANCE = new NullConnectionListener(); + + private NullConnectionListener() { } + + @Override + public void connectionClosed(IOFConnectionBackend connection) { + logger.warn("NullConnectionListener for {} - received connectionClosed", connection); + } + + @Override + public void messageReceived(IOFConnectionBackend connection, OFMessage m) { + logger.warn("NullConnectionListener for {} - received messageReceived: {}", connection, m); + } + + @Override + public boolean isSwitchHandshakeComplete(IOFConnectionBackend connection) { + return false; + } + + @Override + public void messageWritten(IOFConnectionBackend connection, OFMessage m) { + // TODO Auto-generated method stub + + } + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/OFConnectionCounters.java b/src/main/java/net/floodlightcontroller/core/internal/OFConnectionCounters.java similarity index 99% rename from src/main/java/net/floodlightcontroller/core/OFConnectionCounters.java rename to src/main/java/net/floodlightcontroller/core/internal/OFConnectionCounters.java index aef32b24d9743be5bf8528d3c02714cfdf425eb4..b92ab9ed71a031376f0805b0723c55ea6dcd7570 100644 --- a/src/main/java/net/floodlightcontroller/core/OFConnectionCounters.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFConnectionCounters.java @@ -1,6 +1,5 @@ -package net.floodlightcontroller.core; +package net.floodlightcontroller.core.internal; -import net.floodlightcontroller.core.internal.OFSwitchManager; import net.floodlightcontroller.debugcounter.IDebugCounter; import net.floodlightcontroller.debugcounter.IDebugCounterService; diff --git a/src/main/java/net/floodlightcontroller/core/OFErrorMsgException.java b/src/main/java/net/floodlightcontroller/core/internal/OFErrorMsgException.java similarity index 95% rename from src/main/java/net/floodlightcontroller/core/OFErrorMsgException.java rename to src/main/java/net/floodlightcontroller/core/internal/OFErrorMsgException.java index f990f0a451052dfac060673e5bca7b20d6e802ad..bdb526153ac5cc2ce6b9ebf7d0aac2d6d4bfc276 100644 --- a/src/main/java/net/floodlightcontroller/core/OFErrorMsgException.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFErrorMsgException.java @@ -1,4 +1,4 @@ -package net.floodlightcontroller.core; +package net.floodlightcontroller.core.internal; import org.projectfloodlight.openflow.protocol.OFErrorMsg; import org.projectfloodlight.openflow.protocol.OFRequest; diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFMessageDecoder.java b/src/main/java/net/floodlightcontroller/core/internal/OFMessageDecoder.java index 12faa3be6b49a43d70a8d8fbffec769184524b77..40e6434baeef2c7538437f02ca674b70894f1487 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFMessageDecoder.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFMessageDecoder.java @@ -1,29 +1,30 @@ /** -* 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 -* -* http://www.apache.org/licenses/LICENSE-2.0 -* -* Unless required by applicable law or agreed to in writing, software -* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT -* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the -* License for the specific language governing permissions and limitations -* under the License. -**/ + * 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 + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations + * under the License. + **/ package net.floodlightcontroller.core.internal; import java.util.ArrayList; +import java.util.Collections; import java.util.List; -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.handler.codec.frame.FrameDecoder; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.ByteToMessageDecoder; + import org.projectfloodlight.openflow.protocol.OFFactories; import org.projectfloodlight.openflow.protocol.OFFactory; import org.projectfloodlight.openflow.protocol.OFMessage; @@ -33,49 +34,71 @@ import org.projectfloodlight.openflow.protocol.OFVersion; /** * Decode an openflow message from a channel, for use in a netty pipeline. * - * @author readams + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> */ -public class OFMessageDecoder extends FrameDecoder { +public class OFMessageDecoder extends ByteToMessageDecoder { + + private OFMessageReader<OFMessage> reader; - private OFMessageReader<OFMessage> reader; + public OFMessageDecoder() { + setReader(); + } - public OFMessageDecoder() { - reader = OFFactories.getGenericReader(); - } + public OFMessageDecoder(OFVersion version) { + setVersion(version); + setReader(); + } - public OFMessageDecoder(OFVersion version) { - setVersion(version); - } + private void setReader() { + reader = OFFactories.getGenericReader(); + } - public void setVersion(OFVersion version) { - OFFactory factory = OFFactories.getFactory(version); - this.reader = factory.getReader(); - } + public void setVersion(OFVersion version) { + OFFactory factory = OFFactories.getFactory(version); + this.reader = factory.getReader(); + } - @Override - protected Object decode(ChannelHandlerContext ctx, Channel channel, - ChannelBuffer buffer) throws Exception { - if (!channel.isConnected()) { - // In testing, I see decode being called AFTER decode last. - // This check avoids that from reading corrupted frames - return null; - } + @Override + protected void decode(ChannelHandlerContext ctx, ByteBuf in, List<Object> out) throws Exception { + if (!ctx.channel().isActive()) { + // In testing, I see decode being called AFTER decode last. + // This check avoids that from reading corrupted frames + return; + } - List<OFMessage> messageList = new ArrayList<OFMessage>(); - for (;;) { - OFMessage message = reader.readFrom(buffer); - if (message == null) - break; - messageList.add(message); - } - return messageList.isEmpty() ? null : messageList; - } + // Note(andiw): netty4 adds support for more efficient handling of lists messages in the + // pipeline itself. + // Instead of constructing a list of messages here, we could also just add the individual + // messages to the "out" list provided by netty. This would require changing all the handlers + // in the pipeline to accept "OFMessage" instead of "Iterable<OFMessage>". Probably + // a good idea, but left for a future cleanup. - @Override - protected Object decodeLast(ChannelHandlerContext ctx, Channel channel, - ChannelBuffer buffer) throws Exception { - // This is not strictly needed at this time. It is used to detect - // connection reset detection from netty (for debug) - return null; - } -} + OFMessage singleMessage = null; + List<OFMessage> list = null; + boolean first = true; + for (;;) { + OFMessage message = reader.readFrom(in); + if (message == null) { + break; + } + if (first) { + // first message read + singleMessage = message; + first = false; + } else { + // more messages read, use the list + if (list == null) { + list = new ArrayList<>(); + list.add(singleMessage); + singleMessage = null; + } + list.add(message); + } + } + if (list != null) { + out.add(list); + } else if (singleMessage != null) { + out.add(Collections.singletonList(singleMessage)); + } + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFMessageEncoder.java b/src/main/java/net/floodlightcontroller/core/internal/OFMessageEncoder.java index 4d6912ad69a9246da975063127fca3b8d97fe0b0..09ca7bd4441b89150d1417c508080243cd98765f 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFMessageEncoder.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFMessageEncoder.java @@ -17,33 +17,24 @@ package net.floodlightcontroller.core.internal; -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBuffers; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.handler.codec.oneone.OneToOneEncoder; import org.projectfloodlight.openflow.protocol.OFMessage; +import io.netty.buffer.ByteBuf; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + + /** - * Encode an openflow message for output into a ChannelBuffer, for use in a + * Encode an iterable of openflow messages for output into a ByteBuf, for use in a * netty pipeline - * @author readams + * + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> */ -public class OFMessageEncoder extends OneToOneEncoder { - +public class OFMessageEncoder extends MessageToByteEncoder<Iterable<OFMessage>> { @Override - protected Object encode(ChannelHandlerContext ctx, Channel channel, - Object msg) throws Exception { - if (!(msg instanceof Iterable)) - return msg; - - @SuppressWarnings("unchecked") - Iterable<OFMessage> msgList = (Iterable<OFMessage>)msg; - - ChannelBuffer buf = ChannelBuffers.dynamicBuffer(); + protected void encode(ChannelHandlerContext ctx, Iterable<OFMessage> msgList, ByteBuf out) throws Exception { for (OFMessage ofm : msgList) { - ofm.writeTo(buf); + ofm.writeTo(out); } - return buf; } -} +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/OFSwitch.java b/src/main/java/net/floodlightcontroller/core/internal/OFSwitch.java similarity index 86% rename from src/main/java/net/floodlightcontroller/core/OFSwitch.java rename to src/main/java/net/floodlightcontroller/core/internal/OFSwitch.java index 1b72fef170fd0a8b3b5827dd1ce39bc747dad533..b31c379666c451b181fa630f2634cd1b711be4d4 100644 --- a/src/main/java/net/floodlightcontroller/core/OFSwitch.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFSwitch.java @@ -15,7 +15,7 @@ * under the License. **/ -package net.floodlightcontroller.core; +package net.floodlightcontroller.core.internal; import java.net.InetSocketAddress; import java.net.SocketAddress; @@ -27,6 +27,8 @@ import java.util.Comparator; import java.util.Date; import java.util.EnumSet; import java.util.HashMap; +import java.util.HashSet; +import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; @@ -37,8 +39,16 @@ import java.util.concurrent.locks.ReentrantReadWriteLock; import javax.annotation.Nonnull; -import net.floodlightcontroller.core.internal.IOFSwitchManager; -import net.floodlightcontroller.core.internal.TableFeatures; +import net.floodlightcontroller.core.IOFConnection; +import net.floodlightcontroller.core.IOFConnectionBackend; +import net.floodlightcontroller.core.IOFSwitchBackend; +import net.floodlightcontroller.core.LogicalOFMessageCategory; +import net.floodlightcontroller.core.PortChangeEvent; +import net.floodlightcontroller.core.PortChangeType; +import net.floodlightcontroller.core.SwitchDescription; +import net.floodlightcontroller.core.SwitchDriverSubHandshakeAlreadyStarted; +import net.floodlightcontroller.core.SwitchDriverSubHandshakeCompleted; +import net.floodlightcontroller.core.SwitchDriverSubHandshakeNotStarted; import net.floodlightcontroller.core.util.AppCookie; import net.floodlightcontroller.core.util.URIUtil; @@ -63,6 +73,7 @@ import org.projectfloodlight.openflow.protocol.OFStatsReply; import org.projectfloodlight.openflow.protocol.OFStatsRequest; import org.projectfloodlight.openflow.protocol.OFTableFeatures; import org.projectfloodlight.openflow.protocol.OFTableFeaturesStatsReply; +import org.projectfloodlight.openflow.protocol.OFType; import org.projectfloodlight.openflow.protocol.OFVersion; import org.projectfloodlight.openflow.types.DatapathId; import org.projectfloodlight.openflow.types.OFAuxId; @@ -70,6 +81,7 @@ import org.projectfloodlight.openflow.types.OFPort; import org.projectfloodlight.openflow.types.TableId; import org.projectfloodlight.openflow.types.U64; +import net.floodlightcontroller.util.IterableUtils; import net.floodlightcontroller.util.LinkedHashSetWrapper; import net.floodlightcontroller.util.OrderedCollection; @@ -669,6 +681,76 @@ public class OFSwitch implements IOFSwitchBackend { } } + protected static class SwitchRoleMessageValidator { + private static final Map<OFVersion, Set<OFType>> invalidSlaveMsgsByOFVersion; + static { + Map<OFVersion, Set<OFType>> m = new HashMap<OFVersion, Set<OFType>>(); + Set<OFType> s = new HashSet<OFType>(); + s.add(OFType.PACKET_OUT); + s.add(OFType.FLOW_MOD); + s.add(OFType.PORT_MOD); + s.add(OFType.TABLE_MOD); + s.add(OFType.BARRIER_REQUEST); + m.put(OFVersion.OF_10, Collections.unmodifiableSet(s)); + + s = new HashSet<OFType>(); + s.addAll(m.get(OFVersion.OF_10)); + s.add(OFType.GROUP_MOD); + s.add(OFType.TABLE_MOD); + m.put(OFVersion.OF_11, Collections.unmodifiableSet(s)); + + s = new HashSet<OFType>(); + s.addAll(m.get(OFVersion.OF_11)); + m.put(OFVersion.OF_12, Collections.unmodifiableSet(s)); + + s = new HashSet<OFType>(); + s.addAll(m.get(OFVersion.OF_12)); + s.add(OFType.METER_MOD); + m.put(OFVersion.OF_13, Collections.unmodifiableSet(s)); + + s = new HashSet<OFType>(); + s.addAll(m.get(OFVersion.OF_13)); + s.add(OFType.BUNDLE_ADD_MESSAGE); + s.add(OFType.BUNDLE_CONTROL); + m.put(OFVersion.OF_14, Collections.unmodifiableSet(s)); + + invalidSlaveMsgsByOFVersion = Collections.unmodifiableMap(m); + } + + /** + * Sorts any invalid messages by moving them from the msgList. The net result + * is a new list returned containing the invalid messages and a pruned msgList + * containing only those messages that are valid for the given role of the controller + * and OpenFlow version of the switch. + * + * @param msgList the list of messages to sort + * @param valid the list of valid messages (caller must allocate) + * @param swVersion the OFVersion of the switch + * @param isSlave true if controller is slave; false otherwise + * @return list of messages that are not valid, removed from input parameter msgList + */ + protected static Collection<OFMessage> pruneInvalidMessages(Iterable<OFMessage> msgList, Collection<OFMessage> valid, OFVersion swVersion, boolean isActive) { + if (isActive) { /* master or equal/other support all */ + valid.addAll(IterableUtils.toCollection(msgList)); + return Collections.emptyList(); + } else { /* slave */ + Set<OFType> invalidSlaveMsgs = invalidSlaveMsgsByOFVersion.get(swVersion); + List<OFMessage> invalid = new ArrayList<OFMessage>(); + Iterator<OFMessage> itr = msgList.iterator(); + while (itr.hasNext()) { + OFMessage m = itr.next(); + if (invalidSlaveMsgs.contains(m.getType())) { + invalid.add(m); + } else { + valid.add(m); + } + } + + return invalid; + } + } + } + @Override public boolean attributeEquals(String name, Object other) { Object attr = this.attributes.get(name); @@ -720,17 +802,6 @@ public class OFSwitch implements IOFSwitchBackend { this.connections.remove(connection.getAuxId()); } - @Override - public void write(OFMessage m) { - log.trace("Channel: {}, Connected: {}", connections.get(OFAuxId.MAIN).getRemoteInetAddress(), connections.get(OFAuxId.MAIN).isConnected()); - if (isActive()) { - connections.get(OFAuxId.MAIN).write(m); - switchManager.handleOutgoingMessage(this, m); - } else { - log.warn("Attempted to write to switch {} that is SLAVE.", this.getId().toString()); - } - } - /** * Gets a connection specified by aux Id. * @param auxId the specified aux id for the connection desired. @@ -738,14 +809,14 @@ public class OFSwitch implements IOFSwitchBackend { */ public IOFConnection getConnection(OFAuxId auxId) { IOFConnection connection = this.connections.get(auxId); - if(connection == null){ + if (connection == null) { throw new IllegalArgumentException("OF Connection for " + this + " with " + auxId + " does not exist."); } return connection; } public IOFConnection getConnection(LogicalOFMessageCategory category) { - if(switchManager.isCategoryRegistered(category)){ + if (switchManager.isCategoryRegistered(category)) { return getConnection(category.getAuxId()); } else{ @@ -753,26 +824,72 @@ public class OFSwitch implements IOFSwitchBackend { } } + /** + * Write a single message to the switch + * + * @param m the message to write + * @return true upon success; false upon failure; + * failure can occur either from sending a message not supported in the current role, or + * from the channel being disconnected + */ @Override - public void write(OFMessage m, LogicalOFMessageCategory category) { - if (isActive()) { - this.getConnection(category).write(m); - switchManager.handleOutgoingMessage(this, m); - } else { - log.warn("Attempted to write to switch {} that is SLAVE.", this.getId().toString()); - } + public boolean write(OFMessage m) { + return this.write(Collections.singletonList(m)).isEmpty(); } + + /** + * Write a list of messages to the switch + * + * @param msglist list of messages to write + * @return list of failed messages; messages can fail if sending the messages is not supported + * in the current role, or from the channel becoming disconnected + */ @Override - public void write(Iterable<OFMessage> msglist, LogicalOFMessageCategory category) { - if (isActive()) { - this.getConnection(category).write(msglist); + public Collection<OFMessage> write(Iterable<OFMessage> msglist) { + return this.write(msglist, LogicalOFMessageCategory.MAIN); + } - for(OFMessage m : msglist) { - switchManager.handleOutgoingMessage(this, m); + @Override + public boolean write(OFMessage m, LogicalOFMessageCategory category) { + return this.write(Collections.singletonList(m), category).isEmpty(); + } + + @Override + public Collection<OFMessage> write(Iterable<OFMessage> msgList, LogicalOFMessageCategory category) { + IOFConnection conn = this.getConnection(category); /* do first to check for supported category */ + Collection<OFMessage> validMsgs = new ArrayList<OFMessage>(); + Collection<OFMessage> invalidMsgs = SwitchRoleMessageValidator.pruneInvalidMessages( + msgList, validMsgs, this.getOFFactory().getVersion(), this.isActive()); + if (log.isDebugEnabled()) { + log.debug("MESSAGES: {}, VALID: {}, INVALID: {}", new Object[] { msgList, validMsgs, invalidMsgs}); + } + /* Try to write all valid messages */ + Collection<OFMessage> unsent = conn.write(validMsgs); + for (OFMessage m : validMsgs) { + if (!unsent.contains(m)) { + switchManager.handleOutgoingMessage(this, m); } + } + + /* Collect invalid and unsent messages */ + Collection<OFMessage> ret = null; + if (!unsent.isEmpty()) { + log.warn("Could not send messages {} due to channel disconnection on switch {}", unsent, this.getId()); + ret = IterableUtils.toCollection(unsent); + } + if (!invalidMsgs.isEmpty()) { + log.warn("Could not send messages {} while in SLAVE role on switch {}", invalidMsgs, this.getId()); + if (ret == null) { + ret = IterableUtils.toCollection(invalidMsgs); + } else { + ret.addAll(IterableUtils.toCollection(invalidMsgs)); + } + } + if (ret == null) { + return Collections.emptyList(); } else { - log.warn("Attempted to write to switch {} that is SLAVE.", this.getId().toString()); + return ret; } } @@ -788,27 +905,13 @@ public class OFSwitch implements IOFSwitchBackend { @Override public <R extends OFMessage> ListenableFuture<R> writeRequest(OFRequest<R> request) { - return connections.get(OFAuxId.MAIN).writeRequest(request); - } - - @Override - public void write(Iterable<OFMessage> msglist) { - if (isActive()) { - connections.get(OFAuxId.MAIN).write(msglist); - - for(OFMessage m : msglist) { - switchManager.handleOutgoingMessage(this, m); - } - } else { - log.warn("Attempted to write to switch {} that is SLAVE.", this.getId().toString()); - } + return writeRequest(request, LogicalOFMessageCategory.MAIN); } @Override public void disconnect() { - // Iterate through connections and perform cleanup - for(Entry<OFAuxId, IOFConnectionBackend> entry : this.connections.entrySet()){ + for (Entry<OFAuxId, IOFConnectionBackend> entry : this.connections.entrySet()) { entry.getValue().disconnect(); this.connections.remove(entry.getKey()); } @@ -943,12 +1046,9 @@ public class OFSwitch implements IOFSwitchBackend { return datapathId; } - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ @Override public String toString() { - return "OFSwitchBase DPID[" + ((datapathId != null) ? datapathId.toString() : "?") + "]"; + return "OFSwitch DPID[" + ((datapathId != null) ? datapathId.toString() : "?") + "]"; } @Override @@ -1057,13 +1157,6 @@ public class OFSwitch implements IOFSwitchBackend { this.role = role; } - @Override - public void flush() { - for(Entry<OFAuxId, IOFConnectionBackend> entry : this.connections.entrySet()){ - entry.getValue().flush(); - } - } - /** * Get the IP Address for the switch * @return the inet address diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchAppHandshakePlugin.java b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchAppHandshakePlugin.java index ddfd008720a6853262905afeb7072cf91c99c3f5..9652f4ac64d60b1e5f31bb2e31b1023fd12120c7 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchAppHandshakePlugin.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchAppHandshakePlugin.java @@ -2,9 +2,9 @@ package net.floodlightcontroller.core.internal; import java.util.concurrent.TimeUnit; -import org.jboss.netty.util.Timeout; -import org.jboss.netty.util.Timer; -import org.jboss.netty.util.TimerTask; +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.internal.OFSwitchHandshakeHandler.WaitAppHandshakeState; import org.projectfloodlight.openflow.protocol.OFMessage; diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java index 9621b995b44892ab6d1ca1ba4307b8f15ab71650..47671f5cedd4b502bfc8aa69a7354943568d62c2 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandler.java @@ -12,8 +12,7 @@ import java.util.concurrent.TimeUnit; import javax.annotation.Nonnull; -import org.jboss.netty.util.Timer; - +import io.netty.util.Timer; import net.floodlightcontroller.core.HARole; import net.floodlightcontroller.core.IOFConnection; import net.floodlightcontroller.core.IOFConnectionBackend; @@ -23,13 +22,11 @@ import net.floodlightcontroller.core.IOFSwitchBackend; import net.floodlightcontroller.core.PortChangeEvent; import net.floodlightcontroller.core.SwitchDescription; import net.floodlightcontroller.core.internal.OFSwitchAppHandshakePlugin.PluginResultType; -import net.floodlightcontroller.util.OFDPAUtils; import org.projectfloodlight.openflow.protocol.OFActionType; import org.projectfloodlight.openflow.protocol.OFBadRequestCode; import org.projectfloodlight.openflow.protocol.OFBarrierReply; import org.projectfloodlight.openflow.protocol.OFBarrierRequest; -import org.projectfloodlight.openflow.protocol.OFBucket; import org.projectfloodlight.openflow.protocol.OFControllerRole; import org.projectfloodlight.openflow.protocol.OFDescStatsReply; import org.projectfloodlight.openflow.protocol.OFDescStatsRequest; @@ -46,7 +43,6 @@ import org.projectfloodlight.openflow.protocol.OFFlowModFailedCode; import org.projectfloodlight.openflow.protocol.OFFlowRemoved; import org.projectfloodlight.openflow.protocol.OFGetConfigReply; import org.projectfloodlight.openflow.protocol.OFGetConfigRequest; -import org.projectfloodlight.openflow.protocol.OFGroupAdd; import org.projectfloodlight.openflow.protocol.OFGroupDelete; import org.projectfloodlight.openflow.protocol.OFGroupType; import org.projectfloodlight.openflow.protocol.OFMessage; @@ -54,7 +50,6 @@ import org.projectfloodlight.openflow.protocol.OFNiciraControllerRole; import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleReply; import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleRequest; import org.projectfloodlight.openflow.protocol.OFPacketIn; -import org.projectfloodlight.openflow.protocol.OFPortDesc; import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply; import org.projectfloodlight.openflow.protocol.OFPortStatus; import org.projectfloodlight.openflow.protocol.OFQueueGetConfigReply; @@ -74,17 +69,12 @@ import org.projectfloodlight.openflow.protocol.actionid.OFActionId; import org.projectfloodlight.openflow.protocol.errormsg.OFBadRequestErrorMsg; import org.projectfloodlight.openflow.protocol.errormsg.OFFlowModFailedErrorMsg; import org.projectfloodlight.openflow.protocol.instruction.OFInstruction; -import org.projectfloodlight.openflow.protocol.match.MatchField; import org.projectfloodlight.openflow.types.DatapathId; import org.projectfloodlight.openflow.types.OFAuxId; -import org.projectfloodlight.openflow.types.OFBufferId; import org.projectfloodlight.openflow.types.OFGroup; import org.projectfloodlight.openflow.types.OFPort; -import org.projectfloodlight.openflow.types.OFVlanVidMatch; import org.projectfloodlight.openflow.types.TableId; -import org.projectfloodlight.openflow.types.U16; import org.projectfloodlight.openflow.types.U64; -import org.projectfloodlight.openflow.types.VlanVid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -529,7 +519,7 @@ public class OFSwitchHandshakeHandler implements IOFConnectionListener { OFFlowAdd defaultFlow = this.factory.buildFlowAdd() .setTableId(tid) .setPriority(0) - .setActions(actions) + .setInstructions(Collections.singletonList((OFInstruction) this.factory.instructions().buildApplyActions().setActions(actions).build())) .build(); flows.add(defaultFlow); break; /* Stop searching for actions and go to the next table in the list */ @@ -555,142 +545,6 @@ public class OFSwitchHandshakeHandler implements IOFConnectionListener { } } - private void addBroadcomOFDPAFlows() { - /* - * By default, we'll assume everyone's on the same VLAN, - * and all switch ports are configured as access ports. - * As such, all packets on the wire will be untagged - * and will only be tagged internally in the switch for - * pipeline processing. - * - * If you would like to configure trunks on switches, then - * each switch will need to be configured specifically, as - * we won't be able to automatically handle such a topology. - * - * Ingress port table (0) = empty --> default to VLAN table - * VLAN table (10) = match untagged, apply internal tag, goto termination MAC - * Termination MAC table (20) = match vlan tag and dst MAC, goto bridging table (default miss-->bridging) - * match only vlan tag, goto controller (DLF or Dest Lookup Failure) - * Bridging table (50) = default send to policy ACL table - * Policy ACL table (60) = priority=0 go to controller - * write action group of forwarding decision - * Group tables - * One per interface per VLAN - * One per VLAN (for flooding) - * - * TABLE_INGRESS = 0 - * TABLE_VLAN = 10 - * TABLE_MAC = 20 - * TABLE_UNICAST = 30 - * TABLE_MULTICAST = 40 - * TABLE_BRIDGING = 50 - * TABLE_ACL = 60 - */ - - /* - * Add flow to match all untagged packets from all ports in VLAN table - */ - List<OFAction> al = new ArrayList<OFAction>(1); - /* al.add(factory.actions().pushVlan(EthType.IPv4)); might not need this */ - al.add(factory.actions().setVlanVid(VlanVid.ofVlan(1))); /* we'll use 1 internally, just because */ - - List<OFInstruction> il = new ArrayList<OFInstruction>(2); - il.add(factory.instructions().gotoTable(TableId.of(20))); /* 20 is the termination MAC table */ - il.add(factory.instructions().applyActions(al)); - OFFlowAdd fa = factory.buildFlowAdd() - .setTableId(TableId.of(10)) - .setOutPort(OFPort.ANY) - .setBufferId(OFBufferId.NO_BUFFER) - .setCookie(U64.ZERO) - .setMatch(factory.buildMatch() - .setExact(MatchField.VLAN_VID, OFVlanVidMatch.UNTAGGED) /* this flow handles untagged */ - /* do we have to match on the in port here? */ - .build() - ) - .setInstructions(il) - .setPriority(1000) - .build(); - sw.write(fa); - - /* - * The termination MAC flow table must proactively forward to controller specific dst MACs, - * so we need to wait to do wildcarded dst MACs in bridging table upon a miss. Send to bridging - * table by default here. - */ - - /* - * Add flow to match all vlan=1 packets to forward to controller in bridging table (DLF). - * Default is to send to policy ACL if this does not match. - */ - al = new ArrayList<OFAction>(1); - al.add(factory.actions().output(OFPort.CONTROLLER, 0xffFFffFF)); - - il = new ArrayList<OFInstruction>(1); - il.add(factory.instructions().applyActions(al)); - fa = factory.buildFlowAdd() - .setTableId(TableId.of(50)) - .setOutPort(OFPort.ANY) - .setBufferId(OFBufferId.NO_BUFFER) - .setCookie(U64.ZERO) - .setMatch(factory.buildMatch() - .setExact(MatchField.VLAN_VID, OFVlanVidMatch.ofVlan(1)) /* this flow handles recently-tagged VLAN=1 */ - .build() - ) - .setInstructions(il) - .setPriority(1) - .build(); - sw.write(fa); - - /* - * Lastly, add a group for flooding and for each port. - * This group is only for VLAN=1. - * - * The flood group has buckets with goto group actions - * for each port's individual L2 group for VLAN=1. - * - * This means we must first add the individual groups. - */ - ArrayList<OFBucket> buckets = new ArrayList<OFBucket>(); - for (OFPortDesc pd : this.sw.getPorts()) { - OFPort p = pd.getPortNo(); - if ((p.getShortPortNumber() & 0xFF00) == 0) { /* TODO Is this correct for special ports? */ - OFGroupAdd ga = factory.buildGroupAdd() - .setGroupType(OFGroupType.INDIRECT) - .setBuckets(Collections.singletonList(factory.buildBucket() - .setActions( - Collections.singletonList((OFAction) factory.actions().buildOutput() - .setMaxLen(0xffFFffFF) - .setPort(p) - .build())) - .build())) - .setGroup(OFDPAUtils.GroupIds.createL2Interface(p, VlanVid.ofVlan(100))) - .build(); - sw.write(ga); - - /* - * Add the port+bucket for creating the FLOOD group below. - * All L2_INTERFACE groups in a VLAN should be within a - * corresponding L2_FLOOD group of type ALL. - */ - buckets.add(factory.buildBucket().setActions( - Collections.singletonList( - (OFAction) factory.actions().buildOutput() - .setMaxLen(0xffFFffFF) - .setPort(p) - .build() - ) - ).build()); - } - } - - OFGroupAdd ga = factory.buildGroupAdd() - .setGroupType(OFGroupType.ALL) - .setBuckets(buckets) - .setGroup(OFDPAUtils.GroupIds.createL2Flood(U16.ZERO, VlanVid.ofVlan(100))) - .build(); - sw.write(ga); - } - /** * Default implementation for message handlers in any state. * diff --git a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchManager.java b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchManager.java index dbf9cc2449c9cfe77cf562296071662f5f478c61..9b22851539d7f2e344514bcc9583a7ea23544145 100644 --- a/src/main/java/net/floodlightcontroller/core/internal/OFSwitchManager.java +++ b/src/main/java/net/floodlightcontroller/core/internal/OFSwitchManager.java @@ -1,6 +1,7 @@ package net.floodlightcontroller.core.internal; import java.io.IOException; +import java.net.InetAddress; import java.net.InetSocketAddress; import java.util.ArrayList; import java.util.Arrays; @@ -16,13 +17,6 @@ import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.CopyOnWriteArraySet; -import java.util.concurrent.Executors; - -import org.jboss.netty.bootstrap.ServerBootstrap; -import org.jboss.netty.channel.ChannelPipelineFactory; -import org.jboss.netty.channel.group.ChannelGroup; -import org.jboss.netty.channel.group.DefaultChannelGroup; -import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.HAListenerTypeMarker; @@ -60,6 +54,7 @@ import org.projectfloodlight.openflow.protocol.OFMessage; import org.projectfloodlight.openflow.protocol.OFPortDesc; import org.projectfloodlight.openflow.protocol.OFVersion; import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.IPv4Address; import org.projectfloodlight.openflow.types.OFAuxId; import org.projectfloodlight.openflow.types.TableId; import org.projectfloodlight.openflow.types.U32; @@ -80,6 +75,13 @@ import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.ChannelOption; +import io.netty.channel.group.DefaultChannelGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.util.concurrent.GlobalEventExecutor; + /** * The Switch Manager class contains most of the code involved with dealing * with switches. The Switch manager keeps track of the switches known to the controller, @@ -102,7 +104,6 @@ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListen private static String keyStorePassword; private static String keyStore; - private static boolean useSsl = false; protected static boolean clearTablesOnInitialConnectAsMaster = false; protected static boolean clearTablesOnEachTransitionToMaster = false; @@ -112,7 +113,7 @@ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListen protected static List<U32> ofBitmaps; protected static OFFactory defaultFactory; - + private ConcurrentHashMap<DatapathId, OFSwitchHandshakeHandler> switchHandlers; private ConcurrentHashMap<DatapathId, IOFSwitchBackend> switches; private ConcurrentHashMap<DatapathId, IOFSwitch> syncedSwitches; @@ -129,9 +130,13 @@ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListen protected Set<IOFSwitchListener> switchListeners; // Module Dependencies - IFloodlightProviderService floodlightProvider; - IDebugEventService debugEventService; - IDebugCounterService debugCounterService; + private IFloodlightProviderService floodlightProvider; + private IDebugEventService debugEventService; + private IDebugCounterService debugCounterService; + + private NioEventLoopGroup bossGroup; + private NioEventLoopGroup workerGroup; + private DefaultChannelGroup cg; /** IHAListener Implementation **/ @Override @@ -181,7 +186,7 @@ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListen addUpdateToQueue(new SwitchUpdate(dpid, SwitchUpdateType.REMOVED)); oldSw.disconnect(); } - + /* * Set some other config options for this switch. */ @@ -480,7 +485,7 @@ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListen public IOFSwitchBackend getOFSwitchInstance(IOFConnectionBackend connection, SwitchDescription description, OFFactory factory, DatapathId datapathId) { - + return this.driverRegistry.getOFSwitchInstance(connection, description, factory, datapathId); } @@ -488,7 +493,7 @@ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListen public void handleMessage(IOFSwitchBackend sw, OFMessage m, FloodlightContext bContext) { floodlightProvider.handleMessage(sw, m, bContext); } - + @Override public void handleOutgoingMessage(IOFSwitch sw, OFMessage m) { floodlightProvider.handleOutgoingMessage(sw, m); @@ -653,7 +658,7 @@ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListen driverRegistry = new NaiveSwitchDriverRegistry(this); this.switchListeners = new CopyOnWriteArraySet<IOFSwitchListener>(); - + /* TODO @Ryan try { this.storeClient = this.syncService.getStoreClient( @@ -685,13 +690,11 @@ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListen ) ) { log.warn("SSL disabled. Using unsecure connections between Floodlight and switches."); - OFSwitchManager.useSsl = false; OFSwitchManager.keyStore = null; OFSwitchManager.keyStorePassword = null; } else { log.info("SSL enabled. Using secure connections between Floodlight and switches."); log.info("SSL keystore path: {}, password: {}", path, (pass == null ? "" : pass)); - OFSwitchManager.useSsl = true; OFSwitchManager.keyStore = path; OFSwitchManager.keyStorePassword = (pass == null ? "" : pass); } @@ -762,7 +765,7 @@ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListen maxPerDpid = configParams.get("maxTableToReceiveTableMissFlowPerDpid"); } forwardToControllerFlowsUpToTableByDpid = jsonToSwitchTableIdMap(maxPerDpid); - + /* * Get config to determine what versions of OpenFlow we will * support. The versions will determine the hello's header @@ -809,7 +812,7 @@ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListen defaultFactory = computeInitialFactory(ofVersions); ofBitmaps = computeOurVersionBitmaps(ofVersions); } - + /** * Find the max version supplied in the supported * versions list and use it as the default, which @@ -845,7 +848,7 @@ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListen */ return OFFactories.getFactory(highest); } - + /** * Based on the list of OFVersions provided as input (or from Loxi), * create a list of bitmaps for use in version negotiation during a @@ -862,7 +865,7 @@ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListen if (ofVersions == null || ofVersions.isEmpty()) { throw new IllegalStateException("OpenFlow version list should never be null or empty at this point. Make sure it's set in the OFSwitchManager."); } - + int pos = 1; /* initial bitmap in list */ int size = 32; /* size of a U32 */ int tempBitmap = 0; /* maintain the current bitmap we're working on */ @@ -978,45 +981,51 @@ public class OFSwitchManager implements IOFSwitchManager, INewOFConnectionListen */ public void bootstrapNetty() { try { - final ServerBootstrap bootstrap = createServerBootStrap(); - - bootstrap.setOption("reuseAddr", true); - bootstrap.setOption("child.keepAlive", true); - bootstrap.setOption("child.tcpNoDelay", true); - bootstrap.setOption("child.sendBufferSize", Controller.SEND_BUFFER_SIZE); - - ChannelPipelineFactory pfact = useSsl ? new OpenflowPipelineFactory(this, floodlightProvider.getTimer(), this, debugCounterService, ofBitmaps, defaultFactory, keyStore, keyStorePassword) : - new OpenflowPipelineFactory(this, floodlightProvider.getTimer(), this, debugCounterService, ofBitmaps, defaultFactory); + bossGroup = new NioEventLoopGroup(); + workerGroup = new NioEventLoopGroup(); + + ServerBootstrap bootstrap = new ServerBootstrap() + .group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .option(ChannelOption.SO_REUSEADDR, true) + .option(ChannelOption.SO_KEEPALIVE, true) + .option(ChannelOption.TCP_NODELAY, true) + .option(ChannelOption.SO_SNDBUF, Controller.SEND_BUFFER_SIZE); + + + OFChannelInitializer initializer = new OFChannelInitializer( + this, + this, + debugCounterService, + floodlightProvider.getTimer(), + ofBitmaps, + defaultFactory, + keyStore, + keyStorePassword); + + bootstrap.childHandler(initializer); + + cg = new DefaultChannelGroup(GlobalEventExecutor.INSTANCE); + + Set<InetSocketAddress> addrs = new HashSet<InetSocketAddress>(); + if (floodlightProvider.getOFAddresses().isEmpty()) { + cg.add(bootstrap.bind(new InetSocketAddress(InetAddress.getByAddress(IPv4Address.NONE.getBytes()), floodlightProvider.getOFPort().getPort())).channel()); + } else { + for (IPv4Address ip : floodlightProvider.getOFAddresses()) { + addrs.add(new InetSocketAddress(InetAddress.getByAddress(ip.getBytes()), floodlightProvider.getOFPort().getPort())); + } + } - bootstrap.setPipelineFactory(pfact); - InetSocketAddress sa = new InetSocketAddress(floodlightProvider.getOFPort()); - final ChannelGroup cg = new DefaultChannelGroup(); - cg.add(bootstrap.bind(sa)); + for (InetSocketAddress sa : addrs) { + cg.add(bootstrap.bind(sa).channel()); + log.info("Listening for switch connections on {}", sa); + } - log.info("Listening for switch connections on {}", sa); } catch (Exception e) { throw new RuntimeException(e); } } - /** - * Helper that bootstrapNetty. - * @return - */ - private ServerBootstrap createServerBootStrap() { - if (floodlightProvider.getWorkerThreads() == 0) { - return new ServerBootstrap( - new NioServerSocketChannelFactory( - Executors.newCachedThreadPool(), - Executors.newCachedThreadPool())); - } else { - return new ServerBootstrap( - new NioServerSocketChannelFactory( - Executors.newCachedThreadPool(), - Executors.newCachedThreadPool(), floodlightProvider.getWorkerThreads())); - } - } - /** * Performs startup related actions for logical OF message categories. * Setting the categories list to immutable ensures that unsupported operation diff --git a/src/main/java/net/floodlightcontroller/core/util/ThriftFrameDecoder.java b/src/main/java/net/floodlightcontroller/core/util/ThriftFrameDecoder.java new file mode 100644 index 0000000000000000000000000000000000000000..1dd2d9297c3243fc3d91d09702a99fe6c6796172 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/util/ThriftFrameDecoder.java @@ -0,0 +1,64 @@ +package net.floodlightcontroller.core.util; + +import java.util.ArrayList; +import java.util.List; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufInputStream; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.LengthFieldBasedFrameDecoder; + +import org.apache.thrift.TBase; +import org.apache.thrift.protocol.TCompactProtocol; +import org.apache.thrift.transport.TIOStreamTransport; + +/** + * Slice and decode Thrift Serialized Messages from the channel, push a {@link List} of Messages upstream. + * + * Each message is preceeded by a four-byte length field. + * + * Subclasses should implement {@link #allocateMessage()} to allocate an instance of the + * desired message type. + * + * Implementation note: this decoder class was initially built for netty3, and is unnecessarily + * complex and inelegant. In particular, netty has efficient support for handling lists of messages + * now, so this could be replaced by a plain {@link LengthFieldBasedFrameDecoder} to do the slicing + * and then a simple ThriftDecoder that would just decode one message at a time. + * + * @author readams + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + */ +public abstract class ThriftFrameDecoder<T extends TBase<?,?>> extends LengthFieldBasedFrameDecoder { + public ThriftFrameDecoder(int maxSize) { + super(maxSize, 0, 4, 0, 4); + } + + protected abstract T allocateMessage(); + + @Override + protected final Object decode(ChannelHandlerContext ctx, + ByteBuf buffer) throws Exception { + /* This is initialized to null because the decode function must return + * null if the buffer does not contain a complete frame and cannot be + * decoded. + */ + List<T> ms = null; + ByteBuf frame = null; + while (null != (frame = (ByteBuf) super.decode(ctx, buffer))) { + if (ms == null) ms = new ArrayList<T>(); + ByteBufInputStream is = new ByteBufInputStream(frame); + TCompactProtocol thriftProtocol = + new TCompactProtocol(new TIOStreamTransport(is)); + T message = allocateMessage(); + message.read(thriftProtocol); + ms.add(message); + } + return ms; + } + + @Override + protected final ByteBuf extractFrame(ChannelHandlerContext ctx, ByteBuf buffer, int index, + int length) { + return buffer.slice(index, length); + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/util/ThriftFrameEncoder.java b/src/main/java/net/floodlightcontroller/core/util/ThriftFrameEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..3abd51318477abf6bf132b1d9a7a979d91180493 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/core/util/ThriftFrameEncoder.java @@ -0,0 +1,45 @@ +package net.floodlightcontroller.core.util; + +import org.apache.thrift.TBase; +import org.apache.thrift.protocol.TCompactProtocol; +import org.apache.thrift.transport.TIOStreamTransport; + +import io.netty.buffer.ByteBuf; +import io.netty.buffer.ByteBufOutputStream; +import io.netty.channel.ChannelHandlerContext; +import io.netty.handler.codec.MessageToByteEncoder; + + +/** + * Serialize a single Thrift message into the channel. + * + * It uses thrift's {@link TCompactProtocol} and frames the message by preprending a four + * byte length field (big endian). + * + * Note: needs to be subclasses with a concrete type implementing T. + * + * @author readams + * @author Andreas Wundsam <andreas.wundsam@bigswitch.com> + */ +public abstract class ThriftFrameEncoder<T extends TBase<?,?>> extends MessageToByteEncoder<T> { + @Override + protected void encode(ChannelHandlerContext ctx, T msg, ByteBuf out) + throws Exception { + + int lengthIndex = out.writerIndex(); + // length field, will be filled in later. + out.writeInt(0); + + int startIndex = out.writerIndex(); + ByteBufOutputStream os = new ByteBufOutputStream(out); + TCompactProtocol thriftProtocol = + new TCompactProtocol(new TIOStreamTransport(os)); + msg.write(thriftProtocol); + os.close(); + int endIndex = out.writerIndex(); + + // update the length field + int length = endIndex - startIndex; + out.setInt(lengthIndex, length); + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/core/web/serializers/StatsReplySerializer.java b/src/main/java/net/floodlightcontroller/core/web/serializers/StatsReplySerializer.java index 3481e974505c4bcdfe50801a60a40848796c6ecd..e3981da5fcd74ae4b0abc54b52348c52d3830bdd 100644 --- a/src/main/java/net/floodlightcontroller/core/web/serializers/StatsReplySerializer.java +++ b/src/main/java/net/floodlightcontroller/core/web/serializers/StatsReplySerializer.java @@ -42,6 +42,7 @@ import org.projectfloodlight.openflow.protocol.OFGroupFeaturesStatsReply; import org.projectfloodlight.openflow.protocol.OFGroupStatsEntry; import org.projectfloodlight.openflow.protocol.OFGroupStatsReply; import org.projectfloodlight.openflow.protocol.OFMeterBandStats; +import org.projectfloodlight.openflow.protocol.OFMeterConfig; import org.projectfloodlight.openflow.protocol.OFMeterConfigStatsReply; import org.projectfloodlight.openflow.protocol.OFMeterFeatures; import org.projectfloodlight.openflow.protocol.OFMeterFeaturesStatsReply; @@ -409,37 +410,46 @@ public class StatsReplySerializer extends JsonSerializer<StatsReply> { jGen.writeStringField("version", meterConfigReply.getVersion().toString()); //return the enum name jGen.writeFieldName("meterConfig"); jGen.writeStartArray(); - for(OFMeterBand band : meterConfigReply.getEntries()) { + for (OFMeterConfig config : meterConfigReply.getEntries()) { jGen.writeStartObject(); - short type = (short)band.getType(); - jGen.writeNumberField("bandType",type); - - switch (type) { - case OFMeterBandTypeSerializerVer13.DROP_VAL: - OFMeterBandDrop bandDrop = (OFMeterBandDrop) band; - jGen.writeNumberField("rate", bandDrop.getRate()); - jGen.writeNumberField("burstSize", bandDrop.getBurstSize()); - break; + jGen.writeNumberField("meterId", config.getMeterId()); + jGen.writeNumberField("flags", config.getFlags()); + jGen.writeFieldName("meterBands"); + jGen.writeStartArray(); + for (OFMeterBand band : config.getEntries()) { + jGen.writeStartObject(); + short type = (short)band.getType(); + jGen.writeNumberField("bandType",type); + + switch (type) { + case OFMeterBandTypeSerializerVer13.DROP_VAL: + OFMeterBandDrop bandDrop = (OFMeterBandDrop) band; + jGen.writeNumberField("rate", bandDrop.getRate()); + jGen.writeNumberField("burstSize", bandDrop.getBurstSize()); + break; - case OFMeterBandTypeSerializerVer13.DSCP_REMARK_VAL: - OFMeterBandDscpRemark bandDscp = (OFMeterBandDscpRemark) band; - jGen.writeNumberField("rate", bandDscp.getRate()); - jGen.writeNumberField("burstSize", bandDscp.getBurstSize()); - jGen.writeNumberField("precLevel", bandDscp.getPrecLevel()); - break; + case OFMeterBandTypeSerializerVer13.DSCP_REMARK_VAL: + OFMeterBandDscpRemark bandDscp = (OFMeterBandDscpRemark) band; + jGen.writeNumberField("rate", bandDscp.getRate()); + jGen.writeNumberField("burstSize", bandDscp.getBurstSize()); + jGen.writeNumberField("precLevel", bandDscp.getPrecLevel()); + break; - case OFMeterBandTypeSerializerVer13.EXPERIMENTER_VAL: - OFMeterBandExperimenter bandExp = (OFMeterBandExperimenter) band; - jGen.writeNumberField("rate", bandExp.getRate()); - jGen.writeNumberField("burstSize", bandExp.getBurstSize()); - jGen.writeNumberField("experimenter", bandExp.getExperimenter()); - break; + case OFMeterBandTypeSerializerVer13.EXPERIMENTER_VAL: + OFMeterBandExperimenter bandExp = (OFMeterBandExperimenter) band; + jGen.writeNumberField("rate", bandExp.getRate()); + jGen.writeNumberField("burstSize", bandExp.getBurstSize()); + jGen.writeNumberField("experimenter", bandExp.getExperimenter()); + break; - default: - // shouldn't ever get here - break; - }//end of Switch Case + default: + // shouldn't ever get here + break; + }//end of Switch Case + jGen.writeEndObject(); + }//end of for loop + jGen.writeEndArray(); jGen.writeEndObject(); }//end of for loop jGen.writeEndArray(); diff --git a/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java b/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java index 1db7e728d08fa06904b74fb185ec1640b58e159a..19a51d1eb77eb45d8f729447e153a0ddeedb7012 100644 --- a/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java +++ b/src/main/java/net/floodlightcontroller/forwarding/Forwarding.java @@ -28,6 +28,8 @@ import java.util.Set; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.IOFSwitchListener; +import net.floodlightcontroller.core.PortChangeType; import net.floodlightcontroller.core.internal.IOFSwitchService; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.module.FloodlightModuleException; @@ -49,11 +51,17 @@ import net.floodlightcontroller.routing.IRoutingService; import net.floodlightcontroller.routing.Route; import net.floodlightcontroller.topology.ITopologyService; import net.floodlightcontroller.topology.NodePortTuple; +import net.floodlightcontroller.util.FlowModUtils; +import net.floodlightcontroller.util.OFDPAUtils; +import net.floodlightcontroller.util.OFPortMode; +import net.floodlightcontroller.util.OFPortModeTuple; import org.projectfloodlight.openflow.protocol.OFFlowMod; import org.projectfloodlight.openflow.protocol.OFFlowModCommand; +import org.projectfloodlight.openflow.protocol.OFGroupType; import org.projectfloodlight.openflow.protocol.OFPacketIn; import org.projectfloodlight.openflow.protocol.OFPacketOut; +import org.projectfloodlight.openflow.protocol.OFPortDesc; import org.projectfloodlight.openflow.protocol.OFVersion; import org.projectfloodlight.openflow.protocol.action.OFAction; import org.projectfloodlight.openflow.protocol.match.Match; @@ -65,14 +73,16 @@ import org.projectfloodlight.openflow.types.IPv6Address; import org.projectfloodlight.openflow.types.IpProtocol; import org.projectfloodlight.openflow.types.MacAddress; import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.OFGroup; import org.projectfloodlight.openflow.types.OFPort; import org.projectfloodlight.openflow.types.OFVlanVidMatch; +import org.projectfloodlight.openflow.types.TableId; import org.projectfloodlight.openflow.types.U64; import org.projectfloodlight.openflow.types.VlanVid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public class Forwarding extends ForwardingBase implements IFloodlightModule { +public class Forwarding extends ForwardingBase implements IFloodlightModule, IOFSwitchListener { protected static Logger log = LoggerFactory.getLogger(Forwarding.class); @Override @@ -130,8 +140,9 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule { .setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT) .setBufferId(OFBufferId.NO_BUFFER) .setMatch(m) - .setActions(actions) // empty list .setPriority(FLOWMOD_DEFAULT_PRIORITY); + + FlowModUtils.setActions(fmb, actions, sw); try { if (log.isDebugEnabled()) { @@ -509,5 +520,57 @@ public class Forwarding extends ForwardingBase implements IFloodlightModule { @Override public void startUp(FloodlightModuleContext context) { super.startUp(); + switchService.addOFSwitchListener(this); + } + + @Override + public void switchAdded(DatapathId switchId) { + } + + @Override + public void switchRemoved(DatapathId switchId) { + } + + @Override + public void switchActivated(DatapathId switchId) { + IOFSwitch sw = switchService.getSwitch(switchId); + if (sw == null) { + log.warn("Switch {} was activated but had no switch object in the switch service. Perhaps it quickly disconnected", switchId); + return; + } + if (OFDPAUtils.isOFDPASwitch(sw)) { + sw.write(sw.getOFFactory().buildFlowDelete() + .setTableId(TableId.ALL) + .build() + ); + sw.write(sw.getOFFactory().buildGroupDelete() + .setGroup(OFGroup.ANY) + .setGroupType(OFGroupType.ALL) + .build() + ); + sw.write(sw.getOFFactory().buildGroupDelete() + .setGroup(OFGroup.ANY) + .setGroupType(OFGroupType.INDIRECT) + .build() + ); + sw.write(sw.getOFFactory().buildBarrierRequest().build()); + + List<OFPortModeTuple> portModes = new ArrayList<OFPortModeTuple>(); + for (OFPortDesc p : sw.getPorts()) { + portModes.add(OFPortModeTuple.of(p.getPortNo(), OFPortMode.ACCESS)); + } + if (log.isWarnEnabled()) { + log.warn("For OF-DPA switch {}, initializing VLAN {} on ports {}", new Object[] { switchId, VlanVid.ZERO, portModes}); + } + OFDPAUtils.addLearningSwitchPrereqs(sw, VlanVid.ZERO, portModes); + } + } + + @Override + public void switchPortChanged(DatapathId switchId, OFPortDesc port, PortChangeType type) { + } + + @Override + public void switchChanged(DatapathId switchId) { } } diff --git a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java index a26d8d8fd4a6c39539b2c92c347d0848a3d6ecdd..c50930ed6580cb5cf7a499d83b912522fd2c2821 100644 --- a/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java +++ b/src/main/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManager.java @@ -1219,7 +1219,6 @@ IFloodlightModule, IInfoProvider { // send // no more try-catch. switch will silently fail iofSwitch.write(pob.build()); - iofSwitch.flush(); } /** diff --git a/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java b/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java index 4e110e0c8a0453abb608c5c315e16be7846fbd2a..2cf719931d20e71f150e59f254fdedd978aab14e 100644 --- a/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java +++ b/src/main/java/net/floodlightcontroller/routing/ForwardingBase.java @@ -41,7 +41,9 @@ import net.floodlightcontroller.routing.IRoutingDecision; import net.floodlightcontroller.routing.Route; import net.floodlightcontroller.topology.ITopologyService; import net.floodlightcontroller.topology.NodePortTuple; +import net.floodlightcontroller.util.FlowModUtils; import net.floodlightcontroller.util.MatchUtils; +import net.floodlightcontroller.util.OFDPAUtils; import net.floodlightcontroller.util.OFMessageDamper; import net.floodlightcontroller.util.TimedCache; @@ -246,14 +248,15 @@ public abstract class ForwardingBase implements IOFMessageListener { } fmb.setMatch(mb.build()) - .setActions(actions) .setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT) .setHardTimeout(FLOWMOD_DEFAULT_HARD_TIMEOUT) .setBufferId(OFBufferId.NO_BUFFER) .setCookie(cookie) .setOutPort(outPort) .setPriority(FLOWMOD_DEFAULT_PRIORITY); - + + FlowModUtils.setActions(fmb, actions, sw); + try { if (log.isTraceEnabled()) { log.trace("Pushing Route flowmod routeIndx={} " + @@ -263,7 +266,18 @@ public abstract class ForwardingBase implements IOFMessageListener { fmb.getMatch().get(MatchField.IN_PORT), outPort }); } - messageDamper.write(sw, fmb.build()); + + if (OFDPAUtils.isOFDPASwitch(sw)) { + OFDPAUtils.addLearningSwitchFlow(sw, cookie, + FLOWMOD_DEFAULT_PRIORITY, + FLOWMOD_DEFAULT_HARD_TIMEOUT, + FLOWMOD_DEFAULT_IDLE_TIMEOUT, + fmb.getMatch(), + null, // TODO how to determine output VLAN for lookup of L2 interface group + outPort); + } else { + messageDamper.write(sw, fmb.build()); + } /* Push the packet out the first hop switch */ if (sw.getId().equals(pinSwitch) && @@ -433,8 +447,9 @@ public abstract class ForwardingBase implements IOFMessageListener { .setIdleTimeout(FLOWMOD_DEFAULT_IDLE_TIMEOUT) .setPriority(FLOWMOD_DEFAULT_PRIORITY) .setBufferId(OFBufferId.NO_BUFFER) - .setMatch(mb.build()) - .setActions(actions); + .setMatch(mb.build()); + + FlowModUtils.setActions(fmb, actions, sw); log.debug("write drop flow-mod sw={} match={} flow-mod={}", new Object[] { sw, mb.build(), fmb.build() }); diff --git a/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java b/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java index d4e95df918bb5792906e633c6bf5cab14c9e3780..7875e70267b68508c23d65ad099c50bb66282829 100644 --- a/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java +++ b/src/main/java/net/floodlightcontroller/staticflowentry/StaticFlowEntryPusher.java @@ -610,7 +610,6 @@ implements IOFSwitchListener, IFloodlightModule, IStaticFlowEntryPusherService, log.debug("Sending {} new entries to {}", messages.size(), dpid); } ofswitch.write(messages); - ofswitch.flush(); } } @@ -626,7 +625,6 @@ implements IOFSwitchListener, IFloodlightModule, IStaticFlowEntryPusherService, log.debug("Sending 1 new entries to {}", dpid.toString()); } ofswitch.write(message); - ofswitch.flush(); } } @@ -654,7 +652,6 @@ implements IOFSwitchListener, IFloodlightModule, IStaticFlowEntryPusherService, */ private void writeFlowModToSwitch(IOFSwitch sw, OFFlowMod flowMod) { sw.write(flowMod); - sw.flush(); } @Override public String getName() { diff --git a/src/main/java/net/floodlightcontroller/statistics/IStatisticsService.java b/src/main/java/net/floodlightcontroller/statistics/IStatisticsService.java new file mode 100644 index 0000000000000000000000000000000000000000..d9a3f462fcc24906bb8fc52f3962b78cc1a161be --- /dev/null +++ b/src/main/java/net/floodlightcontroller/statistics/IStatisticsService.java @@ -0,0 +1,18 @@ +package net.floodlightcontroller.statistics; + +import java.util.Map; + +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; + +import net.floodlightcontroller.core.module.IFloodlightService; +import net.floodlightcontroller.topology.NodePortTuple; + +public interface IStatisticsService extends IFloodlightService { + + public SwitchPortBandwidth getBandwidthConsumption(DatapathId dpid, OFPort p); + + public Map<NodePortTuple, SwitchPortBandwidth> getBandwidthConsumption(); + + public void collectStatistics(boolean collect); +} diff --git a/src/main/java/net/floodlightcontroller/statistics/StatisticsCollector.java b/src/main/java/net/floodlightcontroller/statistics/StatisticsCollector.java new file mode 100644 index 0000000000000000000000000000000000000000..11f33ded0770de03ed3f3d32a1da73ad79e1ac49 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/statistics/StatisticsCollector.java @@ -0,0 +1,467 @@ +package net.floodlightcontroller.statistics; + +import java.lang.Thread.State; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.concurrent.ScheduledFuture; +import java.util.concurrent.TimeUnit; + +import org.projectfloodlight.openflow.protocol.OFPortStatsEntry; +import org.projectfloodlight.openflow.protocol.OFPortStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsReply; +import org.projectfloodlight.openflow.protocol.OFStatsRequest; +import org.projectfloodlight.openflow.protocol.OFStatsType; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.protocol.match.Match; +import org.projectfloodlight.openflow.protocol.ver13.OFMeterSerializerVer13; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.TableId; +import org.projectfloodlight.openflow.types.U64; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.primitives.UnsignedLong; +import com.google.common.util.concurrent.ListenableFuture; + +import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.internal.IOFSwitchService; +import net.floodlightcontroller.core.module.FloodlightModuleContext; +import net.floodlightcontroller.core.module.FloodlightModuleException; +import net.floodlightcontroller.core.module.IFloodlightModule; +import net.floodlightcontroller.core.module.IFloodlightService; +import net.floodlightcontroller.restserver.IRestApiService; +import net.floodlightcontroller.statistics.web.SwitchStatisticsWebRoutable; +import net.floodlightcontroller.threadpool.IThreadPoolService; +import net.floodlightcontroller.topology.NodePortTuple; + +public class StatisticsCollector implements IFloodlightModule, IStatisticsService { + private static final Logger log = LoggerFactory.getLogger(StatisticsCollector.class); + + private static IOFSwitchService switchService; + private static IThreadPoolService threadPoolService; + private static IRestApiService restApiService; + + private static boolean isEnabled = false; + + private static int portStatsInterval = 10; /* could be set by REST API, so not final */ + private static ScheduledFuture<?> portStatsCollector; + + private static final long BITS_PER_BYTE = 8; + private static final long MILLIS_PER_SEC = 1000; + + private static final String INTERVAL_PORT_STATS_STR = "collectionIntervalPortStatsSeconds"; + private static final String ENABLED_STR = "enable"; + + private static final HashMap<NodePortTuple, SwitchPortBandwidth> portStats = new HashMap<NodePortTuple, SwitchPortBandwidth>(); + private static final HashMap<NodePortTuple, SwitchPortBandwidth> tentativePortStats = new HashMap<NodePortTuple, SwitchPortBandwidth>(); + + /** + * Run periodically to collect all port statistics. This only collects + * bandwidth stats right now, but it could be expanded to record other + * information as well. The difference between the most recent and the + * current RX/TX bytes is used to determine the "elapsed" bytes. A + * timestamp is saved each time stats results are saved to compute the + * bits per second over the elapsed time. There isn't a better way to + * compute the precise bandwidth unless the switch were to include a + * timestamp in the stats reply message, which would be nice but isn't + * likely to happen. It would be even better if the switch recorded + * bandwidth and reported bandwidth directly. + * + * Stats are not reported unless at least two iterations have occurred + * for a single switch's reply. This must happen to compare the byte + * counts and to get an elapsed time. + * + * @author Ryan Izard, ryan.izard@bigswitch.com, rizard@g.clemson.edu + * + */ + private class PortStatsCollector implements Runnable { + + @Override + public void run() { + Map<DatapathId, List<OFStatsReply>> replies = getSwitchStatistics(switchService.getAllSwitchDpids(), OFStatsType.PORT); + for (Entry<DatapathId, List<OFStatsReply>> e : replies.entrySet()) { + for (OFStatsReply r : e.getValue()) { + OFPortStatsReply psr = (OFPortStatsReply) r; + for (OFPortStatsEntry pse : psr.getEntries()) { + NodePortTuple npt = new NodePortTuple(e.getKey(), pse.getPortNo()); + SwitchPortBandwidth spb; + if (portStats.containsKey(npt) || tentativePortStats.containsKey(npt)) { + if (portStats.containsKey(npt)) { /* update */ + spb = portStats.get(npt); + } else if (tentativePortStats.containsKey(npt)) { /* finish */ + spb = tentativePortStats.get(npt); + tentativePortStats.remove(npt); + } else { + log.error("Inconsistent state between tentative and official port stats lists."); + return; + } + + /* Get counted bytes over the elapsed period. Check for counter overflow. */ + U64 rxBytesCounted; + U64 txBytesCounted; + if (spb.getPriorByteValueRx().compareTo(pse.getRxBytes()) > 0) { /* overflow */ + U64 upper = U64.NO_MASK.subtract(spb.getPriorByteValueRx()); + U64 lower = pse.getRxBytes(); + rxBytesCounted = upper.add(lower); + } else { + rxBytesCounted = pse.getRxBytes().subtract(spb.getPriorByteValueRx()); + } + if (spb.getPriorByteValueTx().compareTo(pse.getTxBytes()) > 0) { /* overflow */ + U64 upper = U64.NO_MASK.subtract(spb.getPriorByteValueTx()); + U64 lower = pse.getTxBytes(); + txBytesCounted = upper.add(lower); + } else { + txBytesCounted = pse.getTxBytes().subtract(spb.getPriorByteValueTx()); + } + long timeDifSec = (System.currentTimeMillis() - spb.getUpdateTime()) / MILLIS_PER_SEC; + portStats.put(npt, SwitchPortBandwidth.of(npt.getNodeId(), npt.getPortId(), + U64.ofRaw((rxBytesCounted.getValue() * BITS_PER_BYTE) / timeDifSec), + U64.ofRaw((txBytesCounted.getValue() * BITS_PER_BYTE) / timeDifSec), + pse.getRxBytes(), pse.getTxBytes()) + ); + + } else { /* initialize */ + tentativePortStats.put(npt, SwitchPortBandwidth.of(npt.getNodeId(), npt.getPortId(), U64.ZERO, U64.ZERO, pse.getRxBytes(), pse.getTxBytes())); + } + } + } + } + } + } + + /** + * Single thread for collecting switch statistics and + * containing the reply. + * + * @author Ryan Izard, ryan.izard@bigswitch.com, rizard@g.clemson.edu + * + */ + private class GetStatisticsThread extends Thread { + private List<OFStatsReply> statsReply; + private DatapathId switchId; + private OFStatsType statType; + + public GetStatisticsThread(DatapathId switchId, OFStatsType statType) { + this.switchId = switchId; + this.statType = statType; + this.statsReply = null; + } + + public List<OFStatsReply> getStatisticsReply() { + return statsReply; + } + + public DatapathId getSwitchId() { + return switchId; + } + + @Override + public void run() { + statsReply = getSwitchStatistics(switchId, statType); + } + } + + /* + * IFloodlightModule implementation + */ + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleServices() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<Class<? extends IFloodlightService>>(); + l.add(IStatisticsService.class); + return l; + } + + @Override + public Map<Class<? extends IFloodlightService>, IFloodlightService> getServiceImpls() { + Map<Class<? extends IFloodlightService>, IFloodlightService> m = + new HashMap<Class<? extends IFloodlightService>, IFloodlightService>(); + m.put(IStatisticsService.class, this); + return m; + } + + @Override + public Collection<Class<? extends IFloodlightService>> getModuleDependencies() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<Class<? extends IFloodlightService>>(); + l.add(IOFSwitchService.class); + l.add(IThreadPoolService.class); + l.add(IRestApiService.class); + return l; + } + + @Override + public void init(FloodlightModuleContext context) + throws FloodlightModuleException { + switchService = context.getServiceImpl(IOFSwitchService.class); + threadPoolService = context.getServiceImpl(IThreadPoolService.class); + restApiService = context.getServiceImpl(IRestApiService.class); + + Map<String, String> config = context.getConfigParams(this); + if (config.containsKey(ENABLED_STR)) { + try { + isEnabled = Boolean.parseBoolean(config.get(ENABLED_STR).trim()); + } catch (Exception e) { + log.error("Could not parse '{}'. Using default of {}", ENABLED_STR, isEnabled); + } + } + log.info("Statistics collection {}", isEnabled ? "enabled" : "disabled"); + + if (config.containsKey(INTERVAL_PORT_STATS_STR)) { + try { + portStatsInterval = Integer.parseInt(config.get(INTERVAL_PORT_STATS_STR).trim()); + } catch (Exception e) { + log.error("Could not parse '{}'. Using default of {}", INTERVAL_PORT_STATS_STR, portStatsInterval); + } + } + log.info("Port statistics collection interval set to {}s", portStatsInterval); + } + + @Override + public void startUp(FloodlightModuleContext context) + throws FloodlightModuleException { + restApiService.addRestletRoutable(new SwitchStatisticsWebRoutable()); + if (isEnabled) { + startStatisticsCollection(); + } + } + + /* + * IStatisticsService implementation + */ + + @Override + public SwitchPortBandwidth getBandwidthConsumption(DatapathId dpid, OFPort p) { + return portStats.get(new NodePortTuple(dpid, p)); + } + + + @Override + public Map<NodePortTuple, SwitchPortBandwidth> getBandwidthConsumption() { + return Collections.unmodifiableMap(portStats); + } + + @Override + public synchronized void collectStatistics(boolean collect) { + if (collect && !isEnabled) { + startStatisticsCollection(); + isEnabled = true; + } else if (!collect && isEnabled) { + stopStatisticsCollection(); + isEnabled = false; + } + /* otherwise, state is not changing; no-op */ + } + + /* + * Helper functions + */ + + /** + * Start all stats threads. + */ + private void startStatisticsCollection() { + portStatsCollector = threadPoolService.getScheduledExecutor().scheduleAtFixedRate(new PortStatsCollector(), portStatsInterval, portStatsInterval, TimeUnit.SECONDS); + tentativePortStats.clear(); /* must clear out, otherwise might have huge BW result if present and wait a long time before re-enabling stats */ + log.warn("Statistics collection thread(s) started"); + } + + /** + * Stop all stats threads. + */ + private void stopStatisticsCollection() { + if (!portStatsCollector.cancel(false)) { + log.error("Could not cancel port stats thread"); + } else { + log.warn("Statistics collection thread(s) stopped"); + } + } + + /** + * Retrieve the statistics from all switches in parallel. + * @param dpids + * @param statsType + * @return + */ + private Map<DatapathId, List<OFStatsReply>> getSwitchStatistics(Set<DatapathId> dpids, OFStatsType statsType) { + HashMap<DatapathId, List<OFStatsReply>> model = new HashMap<DatapathId, List<OFStatsReply>>(); + + List<GetStatisticsThread> activeThreads = new ArrayList<GetStatisticsThread>(dpids.size()); + List<GetStatisticsThread> pendingRemovalThreads = new ArrayList<GetStatisticsThread>(); + GetStatisticsThread t; + for (DatapathId d : dpids) { + t = new GetStatisticsThread(d, statsType); + activeThreads.add(t); + t.start(); + } + + /* Join all the threads after the timeout. Set a hard timeout + * of 12 seconds for the threads to finish. If the thread has not + * finished the switch has not replied yet and therefore we won't + * add the switch's stats to the reply. + */ + for (int iSleepCycles = 0; iSleepCycles < portStatsInterval; iSleepCycles++) { + for (GetStatisticsThread curThread : activeThreads) { + if (curThread.getState() == State.TERMINATED) { + model.put(curThread.getSwitchId(), curThread.getStatisticsReply()); + pendingRemovalThreads.add(curThread); + } + } + + /* remove the threads that have completed the queries to the switches */ + for (GetStatisticsThread curThread : pendingRemovalThreads) { + activeThreads.remove(curThread); + } + + /* clear the list so we don't try to double remove them */ + pendingRemovalThreads.clear(); + + /* if we are done finish early */ + if (activeThreads.isEmpty()) { + break; + } + + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + log.error("Interrupted while waiting for statistics", e); + } + } + + return model; + } + + /** + * Get statistics from a switch. + * @param switchId + * @param statsType + * @return + */ + @SuppressWarnings("unchecked") + protected List<OFStatsReply> getSwitchStatistics(DatapathId switchId, OFStatsType statsType) { + IOFSwitch sw = switchService.getSwitch(switchId); + ListenableFuture<?> future; + List<OFStatsReply> values = null; + Match match; + if (sw != null) { + OFStatsRequest<?> req = null; + switch (statsType) { + case FLOW: + match = sw.getOFFactory().buildMatch().build(); + req = sw.getOFFactory().buildFlowStatsRequest() + .setMatch(match) + .setOutPort(OFPort.ANY) + .setTableId(TableId.ALL) + .build(); + break; + case AGGREGATE: + match = sw.getOFFactory().buildMatch().build(); + req = sw.getOFFactory().buildAggregateStatsRequest() + .setMatch(match) + .setOutPort(OFPort.ANY) + .setTableId(TableId.ALL) + .build(); + break; + case PORT: + req = sw.getOFFactory().buildPortStatsRequest() + .setPortNo(OFPort.ANY) + .build(); + break; + case QUEUE: + req = sw.getOFFactory().buildQueueStatsRequest() + .setPortNo(OFPort.ANY) + .setQueueId(UnsignedLong.MAX_VALUE.longValue()) + .build(); + break; + case DESC: + req = sw.getOFFactory().buildDescStatsRequest() + .build(); + break; + case GROUP: + if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_10) > 0) { + req = sw.getOFFactory().buildGroupStatsRequest() + .build(); + } + break; + + case METER: + if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_13) >= 0) { + req = sw.getOFFactory().buildMeterStatsRequest() + .setMeterId(OFMeterSerializerVer13.ALL_VAL) + .build(); + } + break; + + case GROUP_DESC: + if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_10) > 0) { + req = sw.getOFFactory().buildGroupDescStatsRequest() + .build(); + } + break; + + case GROUP_FEATURES: + if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_10) > 0) { + req = sw.getOFFactory().buildGroupFeaturesStatsRequest() + .build(); + } + break; + + case METER_CONFIG: + if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_13) >= 0) { + req = sw.getOFFactory().buildMeterConfigStatsRequest() + .build(); + } + break; + + case METER_FEATURES: + if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_13) >= 0) { + req = sw.getOFFactory().buildMeterFeaturesStatsRequest() + .build(); + } + break; + + case TABLE: + if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_10) > 0) { + req = sw.getOFFactory().buildTableStatsRequest() + .build(); + } + break; + + case TABLE_FEATURES: + if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_10) > 0) { + req = sw.getOFFactory().buildTableFeaturesStatsRequest() + .build(); + } + break; + case PORT_DESC: + if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_13) >= 0) { + req = sw.getOFFactory().buildPortDescStatsRequest() + .build(); + } + break; + case EXPERIMENTER: + default: + log.error("Stats Request Type {} not implemented yet", statsType.name()); + break; + } + + try { + if (req != null) { + future = sw.writeStatsRequest(req); + values = (List<OFStatsReply>) future.get(portStatsInterval / 2, TimeUnit.SECONDS); + } + } catch (Exception e) { + log.error("Failure retrieving statistics from switch {}. {}", sw, e); + } + } + return values; + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/statistics/SwitchPortBandwidth.java b/src/main/java/net/floodlightcontroller/statistics/SwitchPortBandwidth.java new file mode 100644 index 0000000000000000000000000000000000000000..56648ca0307ccaa5611e660c294684860da049d2 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/statistics/SwitchPortBandwidth.java @@ -0,0 +1,113 @@ +package net.floodlightcontroller.statistics; + +import java.util.Date; + +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.U64; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import net.floodlightcontroller.statistics.web.SwitchPortBandwidthSerializer; + +@JsonSerialize(using=SwitchPortBandwidthSerializer.class) +public class SwitchPortBandwidth { + private DatapathId id; + private OFPort pt; + private U64 rx; + private U64 tx; + private Date time; + private U64 rxValue; + private U64 txValue; + + private SwitchPortBandwidth() {} + private SwitchPortBandwidth(DatapathId d, OFPort p, U64 rx, U64 tx, U64 rxValue, U64 txValue) { + id = d; + pt = p; + this.rx = rx; + this.tx = tx; + time = new Date(); + this.rxValue = rxValue; + this.txValue = txValue; + } + + public static SwitchPortBandwidth of(DatapathId d, OFPort p, U64 rx, U64 tx, U64 rxValue, U64 txValue) { + if (d == null) { + throw new IllegalArgumentException("Datapath ID cannot be null"); + } + if (p == null) { + throw new IllegalArgumentException("Port cannot be null"); + } + if (rx == null) { + throw new IllegalArgumentException("RX bandwidth cannot be null"); + } + if (tx == null) { + throw new IllegalArgumentException("TX bandwidth cannot be null"); + } + if (rxValue == null) { + throw new IllegalArgumentException("RX value cannot be null"); + } + if (txValue == null) { + throw new IllegalArgumentException("TX value cannot be null"); + } + return new SwitchPortBandwidth(d, p, rx, tx, rxValue, txValue); + } + + public DatapathId getSwitchId() { + return id; + } + + public OFPort getSwitchPort() { + return pt; + } + + public U64 getBitsPerSecondRx() { + return rx; + } + + public U64 getBitsPerSecondTx() { + return tx; + } + + protected U64 getPriorByteValueRx() { + return rxValue; + } + + protected U64 getPriorByteValueTx() { + return txValue; + } + + public long getUpdateTime() { + return time.getTime(); + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + result = prime * result + ((id == null) ? 0 : id.hashCode()); + result = prime * result + ((pt == null) ? 0 : pt.hashCode()); + return result; + } + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (getClass() != obj.getClass()) + return false; + SwitchPortBandwidth other = (SwitchPortBandwidth) obj; + if (id == null) { + if (other.id != null) + return false; + } else if (!id.equals(other.id)) + return false; + if (pt == null) { + if (other.pt != null) + return false; + } else if (!pt.equals(other.pt)) + return false; + return true; + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/statistics/web/BandwidthResource.java b/src/main/java/net/floodlightcontroller/statistics/web/BandwidthResource.java new file mode 100644 index 0000000000000000000000000000000000000000..a09e5f9e0d4d49fce8467f7825f0310eaa981b52 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/statistics/web/BandwidthResource.java @@ -0,0 +1,75 @@ +package net.floodlightcontroller.statistics.web; + +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; + +import net.floodlightcontroller.core.internal.IOFSwitchService; +import net.floodlightcontroller.statistics.IStatisticsService; +import net.floodlightcontroller.statistics.SwitchPortBandwidth; + +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.types.DatapathId; +import org.projectfloodlight.openflow.types.OFPort; +import org.restlet.resource.Get; +import org.restlet.resource.ServerResource; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BandwidthResource extends ServerResource { + private static final Logger log = LoggerFactory.getLogger(BandwidthResource.class); + + @Get("json") + public Object retrieve() { + IStatisticsService statisticsService = (IStatisticsService) getContext().getAttributes().get(IStatisticsService.class.getCanonicalName()); + IOFSwitchService switchService = (IOFSwitchService) getContext().getAttributes().get(IOFSwitchService.class.getCanonicalName()); + + String d = (String) getRequestAttributes().get(SwitchStatisticsWebRoutable.DPID_STR); + String p = (String) getRequestAttributes().get(SwitchStatisticsWebRoutable.PORT_STR); + + DatapathId dpid = DatapathId.NONE; + + if (!d.trim().equalsIgnoreCase("all")) { + try { + dpid = DatapathId.of(d); + } catch (Exception e) { + log.error("Could not parse DPID {}", d); + return Collections.singletonMap("ERROR", "Could not parse DPID" + d); + } + } /* else assume it's all */ + + OFPort port = OFPort.ALL; + if (!p.trim().equalsIgnoreCase("all")) { + try { + port = OFPort.of(Integer.parseInt(p)); + } catch (Exception e) { + log.error("Could not parse port {}", p); + return Collections.singletonMap("ERROR", "Could not parse port" + p); + } + } + + Set<SwitchPortBandwidth> spbs; + if (dpid.equals(DatapathId.NONE)) { /* do all DPIDs */ + if (port.equals(OFPort.ALL)) { /* do all ports */ + spbs = new HashSet<SwitchPortBandwidth>(statisticsService.getBandwidthConsumption().values()); + } else { + spbs = new HashSet<SwitchPortBandwidth>(); + for (DatapathId id : switchService.getAllSwitchDpids()) { + SwitchPortBandwidth spb = statisticsService.getBandwidthConsumption(id, port); + if (spb != null) { + spbs.add(spb); + } + } + } + } else { + spbs = new HashSet<SwitchPortBandwidth>(); + for (OFPortDesc pd : switchService.getSwitch(dpid).getPorts()) { + SwitchPortBandwidth spb = statisticsService.getBandwidthConsumption(dpid, pd.getPortNo()); + if (spb != null) { + spbs.add(spb); + } + } + } + return spbs; + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/statistics/web/ConfigResource.java b/src/main/java/net/floodlightcontroller/statistics/web/ConfigResource.java new file mode 100644 index 0000000000000000000000000000000000000000..881f256c85c042d5e5776dc991f89b9d880cbf2e --- /dev/null +++ b/src/main/java/net/floodlightcontroller/statistics/web/ConfigResource.java @@ -0,0 +1,30 @@ +package net.floodlightcontroller.statistics.web; + +import java.util.Collections; + +import net.floodlightcontroller.statistics.IStatisticsService; + +import org.restlet.resource.Post; +import org.restlet.resource.Put; +import org.restlet.resource.ServerResource; + +public class ConfigResource extends ServerResource { + + @Post + @Put + public Object config() { + IStatisticsService statisticsService = (IStatisticsService) getContext().getAttributes().get(IStatisticsService.class.getCanonicalName()); + + if (getReference().getPath().contains(SwitchStatisticsWebRoutable.ENABLE_STR)) { + statisticsService.collectStatistics(true); + return Collections.singletonMap("statistics-collection", "enabled"); + } + + if (getReference().getPath().contains(SwitchStatisticsWebRoutable.DISABLE_STR)) { + statisticsService.collectStatistics(false); + return Collections.singletonMap("statistics-collection", "disabled"); + } + + return Collections.singletonMap("ERROR", "Unimplemented configuration option"); + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/statistics/web/SwitchPortBandwidthSerializer.java b/src/main/java/net/floodlightcontroller/statistics/web/SwitchPortBandwidthSerializer.java new file mode 100644 index 0000000000000000000000000000000000000000..253bb8c8798c0048a75393ccefb7b983097972f4 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/statistics/web/SwitchPortBandwidthSerializer.java @@ -0,0 +1,29 @@ +package net.floodlightcontroller.statistics.web; + +import java.io.IOException; +import java.util.Date; + +import net.floodlightcontroller.statistics.SwitchPortBandwidth; + +import com.fasterxml.jackson.core.JsonGenerator; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.JsonGenerator.Feature; +import com.fasterxml.jackson.databind.JsonSerializer; +import com.fasterxml.jackson.databind.SerializerProvider; + +public class SwitchPortBandwidthSerializer extends JsonSerializer<SwitchPortBandwidth> { + + @Override + public void serialize(SwitchPortBandwidth spb, JsonGenerator jGen, SerializerProvider serializer) throws IOException, JsonProcessingException { + jGen.configure(Feature.WRITE_NUMBERS_AS_STRINGS, true); + + jGen.writeStartObject(); + jGen.writeStringField("dpid", spb.getSwitchId().toString()); + jGen.writeStringField("port", spb.getSwitchPort().toString()); + jGen.writeStringField("updated", new Date(spb.getUpdateTime()).toString()); + jGen.writeStringField("bits-per-second-rx", spb.getBitsPerSecondRx().getBigInteger().toString()); + jGen.writeStringField("bits-per-second-tx", spb.getBitsPerSecondTx().getBigInteger().toString()); + jGen.writeEndObject(); + } + +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/statistics/web/SwitchStatisticsWebRoutable.java b/src/main/java/net/floodlightcontroller/statistics/web/SwitchStatisticsWebRoutable.java new file mode 100644 index 0000000000000000000000000000000000000000..378d696f5e85504cd9c0a732268f5eda45bf9b79 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/statistics/web/SwitchStatisticsWebRoutable.java @@ -0,0 +1,31 @@ +package net.floodlightcontroller.statistics.web; + +import net.floodlightcontroller.restserver.RestletRoutable; + + +import org.restlet.Context; +import org.restlet.routing.Router; + +public class SwitchStatisticsWebRoutable implements RestletRoutable { + protected static final String DPID_STR = "dpid"; + protected static final String PORT_STR = "port"; + protected static final String ENABLE_STR = "enable"; + protected static final String DISABLE_STR = "disable"; + + @Override + public Router getRestlet(Context context) { + Router router = new Router(context); + router.attach("/bandwidth/{" + DPID_STR + "}/{" + PORT_STR + "}/json", BandwidthResource.class); + router.attach("/config/enable/json", ConfigResource.class); + router.attach("/config/disable/json", ConfigResource.class); + return router; + } + + /** + * Set the base path for the Topology + */ + @Override + public String basePath() { + return "/wm/statistics"; + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/util/ActionUtils.java b/src/main/java/net/floodlightcontroller/util/ActionUtils.java index 1f8c8269f19ac092adf2d8dfb526eafff7564031..0620c718f68559c064ebad11d3f635ae5babac19 100644 --- a/src/main/java/net/floodlightcontroller/util/ActionUtils.java +++ b/src/main/java/net/floodlightcontroller/util/ActionUtils.java @@ -1179,7 +1179,7 @@ public class ActionUtils { if (n.matches()) { if (n.group(1) != null) { try { - TransportPort portnum = TransportPort.of(get_short(n.group(1))); + TransportPort portnum = TransportPort.of(get_int(n.group(1))); OFActionSetTpSrc.Builder ab = OFFactories.getFactory(version).actions().buildSetTpSrc(); ab.setTpPort(portnum); log.debug("action {}", ab.build()); @@ -1213,7 +1213,7 @@ public class ActionUtils { if (n.matches()) { if (n.group(1) != null) { try { - TransportPort portnum = TransportPort.of(get_short(n.group(1))); + TransportPort portnum = TransportPort.of(get_int(n.group(1))); OFActionSetTpDst.Builder ab = OFFactories.getFactory(version).actions().buildSetTpDst(); ab.setTpPort(portnum); log.debug("action {}", ab.build()); diff --git a/src/main/java/net/floodlightcontroller/util/FlowModUtils.java b/src/main/java/net/floodlightcontroller/util/FlowModUtils.java index 9a8d6cdbdeaada086daa8f8627da1c426d3f5f1c..8f7645b725bdcc467b6c5e98fce6b3247d7cffe3 100644 --- a/src/main/java/net/floodlightcontroller/util/FlowModUtils.java +++ b/src/main/java/net/floodlightcontroller/util/FlowModUtils.java @@ -1,5 +1,10 @@ package net.floodlightcontroller.util; +import java.util.Collections; +import java.util.List; + +import net.floodlightcontroller.core.IOFSwitch; + import org.projectfloodlight.openflow.protocol.OFFactories; import org.projectfloodlight.openflow.protocol.OFFlowAdd; import org.projectfloodlight.openflow.protocol.OFFlowDelete; @@ -8,6 +13,8 @@ import org.projectfloodlight.openflow.protocol.OFFlowMod; import org.projectfloodlight.openflow.protocol.OFFlowModify; import org.projectfloodlight.openflow.protocol.OFFlowModifyStrict; import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.instruction.OFInstruction; /** * Convert an OFFlowMod to a specific OFFlowMod-OFFlowModCommand. @@ -221,4 +228,23 @@ public class FlowModUtils { .build(); } } + + /** + * Sets the actions in fmb according to the sw version. + * + * @param fmb the FlowMod Builder that is being built + * @param actions the actions to set + * @param sw the switch that will receive the FlowMod + */ + public static void setActions(OFFlowMod.Builder fmb, + List<OFAction> actions, IOFSwitch sw) { + if (sw.getOFFactory().getVersion().compareTo(OFVersion.OF_11) >= 0) { + // Instructions are used starting in OF 1.1 + fmb.setInstructions(Collections.singletonList((OFInstruction) sw + .getOFFactory().instructions().applyActions(actions))); + } else { + // OF 1.0 only supports actions + fmb.setActions(actions); + } + } } diff --git a/src/main/java/net/floodlightcontroller/util/IterableUtils.java b/src/main/java/net/floodlightcontroller/util/IterableUtils.java new file mode 100644 index 0000000000000000000000000000000000000000..d94d81160641de3c046741c9073245ce35552ff1 --- /dev/null +++ b/src/main/java/net/floodlightcontroller/util/IterableUtils.java @@ -0,0 +1,28 @@ +package net.floodlightcontroller.util; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Because it's handy. + * @author Ryan Izard, ryan.izard@bigswitch.com, rizard@g.clemson.edu + */ +public class IterableUtils { + + /** + * Convert an Iterable to a Collection (ArrayList under the hood). All items in the + * Iterable will be retained. + * @param i + * @return + */ + public static <T> Collection<T> toCollection(Iterable<T> i) { + if (i == null) { + throw new IllegalArgumentException("Iterable 'i' cannot be null"); + } + Collection<T> c = new ArrayList<T>(); + for (T t : i) { + c.add(t); + } + return c; + } +} \ No newline at end of file diff --git a/src/main/java/net/floodlightcontroller/util/OFDPAUtils.java b/src/main/java/net/floodlightcontroller/util/OFDPAUtils.java index f2744985b46813c045828174c8142dab3f418e54..b3a7904125066496f6b282bf24fe66d9d0a76801 100644 --- a/src/main/java/net/floodlightcontroller/util/OFDPAUtils.java +++ b/src/main/java/net/floodlightcontroller/util/OFDPAUtils.java @@ -71,42 +71,41 @@ public class OFDPAUtils { public static final int DLF_PRIORITY = 0; public static final int HARD_TIMEOUT = 0; public static final int IDLE_TIMEOUT = 0; - private static final long APP_COOKIE = 1000; - public static final U64 COOKIE = U64.of(APP_COOKIE); + public static final U64 APP_COOKIE = U64.of(Long.parseLong("00FFDDBBAA", 16)); /* OF-DPA sub B for P :-) */ private static class OFDPAGroupType { - static final int L2_INTERFACE = 0; /* 0 */ - static final int L2_REWRITE = 1; /* 1 */ - static final int L3_UNICAST = 2; /* 2 */ - static final int L2_MULTICAST = 3; /* 3 */ - static final int L2_FLOOD = 4; /* 4 */ - static final int L3_INTERFACE = 5; /* 5 */ - static final int L3_MULTICAST = 6; /* 6 */ - static final int L3_ECMP = 7; /* 7 */ - static final int L2_DATA_CENTER_OVERLAY = 8; /* 8 */ - static final int MPLS_LABEL = 9; /* 9 */ - static final int MPLS_FORWARDING = 10; /* 10 */ - static final int L2_UNFILTERED_INTERFACE = 11; /* 11 */ - static final int L2_LOOPBACK = 12; /* 12 */ + private static final int L2_INTERFACE = 0; /* 0 */ + private static final int L2_REWRITE = 1; /* 1 */ + private static final int L3_UNICAST = 2; /* 2 */ + private static final int L2_MULTICAST = 3; /* 3 */ + private static final int L2_FLOOD = 4; /* 4 */ + private static final int L3_INTERFACE = 5; /* 5 */ + private static final int L3_MULTICAST = 6; /* 6 */ + private static final int L3_ECMP = 7; /* 7 */ + private static final int L2_DATA_CENTER_OVERLAY = 8; /* 8 */ + private static final int MPLS_LABEL = 9; /* 9 */ + private static final int MPLS_FORWARDING = 10; /* 10 */ + private static final int L2_UNFILTERED_INTERFACE = 11; /* 11 */ + private static final int L2_LOOPBACK = 12; /* 12 */ } private static class L2OverlaySubType { - static final int L2_OVERLAY_FLOOD_OVER_UNICAST_TUNNELS = 0; - static final int L2_OVERLAY_FLOOD_OVER_MULTICAST_TUNNELS = 1; - static final int L2_OVERLAY_MULTICAST_OVER_UNICAST_TUNNELS = 2; - static final int L2_OVERLAY_MULTICAST_OVER_MULTICAST_TUNNELS = 3; + private static final int L2_OVERLAY_FLOOD_OVER_UNICAST_TUNNELS = 0; + private static final int L2_OVERLAY_FLOOD_OVER_MULTICAST_TUNNELS = 1; + private static final int L2_OVERLAY_MULTICAST_OVER_UNICAST_TUNNELS = 2; + private static final int L2_OVERLAY_MULTICAST_OVER_MULTICAST_TUNNELS = 3; } private static class MPLSSubType { - static final int MPLS_INTERFACE = 0; - static final int MPLS_L2_VPN_LABEL = 1; - static final int MPLS_L3_VPN_LABEL = 2; - static final int MPLS_TUNNEL_LABEL_1 = 3; - static final int MPLS_TUNNEL_LABEL_2 = 4; - static final int MPLS_SWAP_LABEL = 5; - static final int MPLS_FAST_FAILOVER = 6; - static final int MPLS_ECMP = 8; - static final int MPLS_L2_TAG = 10; + private static final int MPLS_INTERFACE = 0; + private static final int MPLS_L2_VPN_LABEL = 1; + private static final int MPLS_L3_VPN_LABEL = 2; + private static final int MPLS_TUNNEL_LABEL_1 = 3; + private static final int MPLS_TUNNEL_LABEL_2 = 4; + private static final int MPLS_SWAP_LABEL = 5; + private static final int MPLS_FAST_FAILOVER = 6; + private static final int MPLS_ECMP = 8; + private static final int MPLS_L2_TAG = 10; } public static class Tables { @@ -209,17 +208,19 @@ public class OFDPAUtils { } /** - * Add the OFDPA groups and flows necessary to facilitate future forwarding/learning + * Add the OF-DPA groups and flows necessary to facilitate future forwarding/learning * switch flows. The switch provided must be an OFDPA 2.0 compliant switch. * Use VLAN tag of null or VlanVid.ZERO for untagged. VLAN tag 1 may not be used; it is - * reserved as an internal VLAN. + * reserved as an internal VLAN for untagged ports on no VLAN (like a home switch). * * This function will add the flows that permit all packets in the VLAN specified and - * on the ports specified to reach the policy ACL table. The policy ACL table (60) of - * the OF-DPA switch will contain a DLF, zero-priority flow to forward all packets to - * the controller for processing. A packet forwarded to the controller from the policy - * ACL table can be either handled manually or have a flow inserted for it via - * {@link OFDPAUtils#addBridgingFlow(IOFSwitch, U64, int, int, int, Match, VlanVid, OFPort) this function}. + * on the ports specified to reach the bridging table, where a DLF flow will forward + * them to the controller if another higher priority L2 flow does not match in the + * bridging table. All ethertypes will match this DLF flow. + * + * Use {@link OFDPAUtils#addLearningSwitchFlow(IOFSwitch, U64, int, int, int, Match, VlanVid, OFPort) this function } + * to insert learning switch flows at a later point based on packets forwarded to the + * controller from the DLF flow. * * @param sw * @param vlan @@ -228,11 +229,9 @@ public class OFDPAUtils { */ public static boolean addLearningSwitchPrereqs(@Nonnull IOFSwitch sw, VlanVid vlan, @Nonnull List<OFPortModeTuple> ports) { /* - * Both of these must complete. If the first fails, the second will not be executed. (AND short-circuit) - * - * Groups must be added last, since they require the VLANs to be added first. + * Both of these must complete. If the first fails, the second will not be executed. */ - return addLearningSwitchPrereqFlows(sw, vlan, ports) && addLearningSwitchPrereqGroups(sw, vlan, ports) ; + return addLearningSwitchPrereqGroups(sw, vlan, ports) && addLearningSwitchPrereqFlows(sw, vlan, ports); } /** @@ -251,7 +250,7 @@ public class OFDPAUtils { * @param ports * @return */ - public static boolean addLearningSwitchPrereqGroups(@Nonnull IOFSwitch sw, VlanVid vlan, @Nonnull List<OFPortModeTuple> ports) { + private static boolean addLearningSwitchPrereqGroups(@Nonnull IOFSwitch sw, VlanVid vlan, @Nonnull List<OFPortModeTuple> ports) { if (sw == null) { throw new NullPointerException("Switch cannot be null."); } @@ -269,7 +268,7 @@ public class OFDPAUtils { for (OFPortModeTuple p : ports) { if (sw.getOFFactory().getVersion().equals(OFVersion.OF_10) && (sw.getPort(p.getPort()) == null || p.getPort().getShortPortNumber() > 0xFF00)) { throw new IllegalArgumentException("Port " + p.getPort().getPortNumber() + " is not a valid port on switch " + sw.getId().toString()); - } else if (!sw.getOFFactory().getVersion().equals(OFVersion.OF_10) && (sw.getPort(p.getPort()) == null || p.getPort().getPortNumber() > 0xffFFff00)) { + } else if (!sw.getOFFactory().getVersion().equals(OFVersion.OF_10) && (sw.getPort(p.getPort()) == null || U32.of(p.getPort().getPortNumber()).compareTo(U32.of(0xffFFff00)) != -1)) { throw new IllegalArgumentException("Port " + p.getPort().getPortNumber() + " is not a valid port on switch " + sw.getId().toString()); } } @@ -286,7 +285,7 @@ public class OFDPAUtils { actions.add(sw.getOFFactory().actions().output(p.getPort(), 0xffFFffFF)); OFGroupAdd ga = sw.getOFFactory().buildGroupAdd() - .setGroup(GroupIds.createL2Interface(p.getPort(), (vlan.equals(VlanVid.ZERO) ? VlanVid.ofVlan(1) : vlan))) + .setGroup(GroupIds.createL2Interface(p.getPort(), vlan.equals(VlanVid.ZERO) ? VlanVid.ofVlan(1) : vlan)) .setGroupType(OFGroupType.INDIRECT) .setBuckets(Collections.singletonList( sw.getOFFactory().buildBucket() @@ -303,15 +302,12 @@ public class OFDPAUtils { List<OFBucket> bucketList = new ArrayList<OFBucket>(ports.size()); for (OFPortModeTuple p : ports) { List<OFAction> actions = new ArrayList<OFAction>(); - if (vlan.equals(VlanVid.ZERO) || p.getMode() == OFPortMode.ACCESS) { /* ditto */ - actions.add(sw.getOFFactory().actions().popVlan()); - } - actions.add(sw.getOFFactory().actions().output(p.getPort(), 0xffFFffFF)); + actions.add(sw.getOFFactory().actions().group(GroupIds.createL2Interface(p.getPort(), vlan.equals(VlanVid.ZERO) ? VlanVid.ofVlan(1) : vlan))); bucketList.add(sw.getOFFactory().buildBucket().setActions(actions).build()); } OFGroupAdd ga = sw.getOFFactory().buildGroupAdd() /* use the VLAN ID as the group ID */ - .setGroup(GroupIds.createL2Flood(U16.of(vlan.getVlan()), vlan.equals(VlanVid.ZERO) ? VlanVid.ofVlan(1) : vlan)) - .setGroupType(OFGroupType.INDIRECT) + .setGroup(GroupIds.createL2Flood(U16.of((vlan.equals(VlanVid.ZERO) ? VlanVid.ofVlan(1) : vlan).getVlan()), vlan.equals(VlanVid.ZERO) ? VlanVid.ofVlan(1) : vlan)) + .setGroupType(OFGroupType.ALL) .setBuckets(bucketList) .build(); sw.write(ga); @@ -335,7 +331,7 @@ public class OFDPAUtils { * @param ports * @return */ - public static boolean addLearningSwitchPrereqFlows(@Nonnull IOFSwitch sw, VlanVid vlan, @Nonnull List<OFPortModeTuple> ports) { + private static boolean addLearningSwitchPrereqFlows(@Nonnull IOFSwitch sw, VlanVid vlan, @Nonnull List<OFPortModeTuple> ports) { if (sw == null) { throw new NullPointerException("Switch cannot be null."); } @@ -353,7 +349,7 @@ public class OFDPAUtils { for (OFPortModeTuple p : ports) { if (sw.getOFFactory().getVersion().equals(OFVersion.OF_10) && (sw.getPort(p.getPort()) == null || p.getPort().getShortPortNumber() > 0xFF00)) { throw new IllegalArgumentException("Port " + p.getPort().getPortNumber() + " is not a valid port on switch " + sw.getId().toString()); - } else if (!sw.getOFFactory().getVersion().equals(OFVersion.OF_10) && (sw.getPort(p.getPort()) == null || p.getPort().getPortNumber() > 0xffFFff00)) { + } else if (!sw.getOFFactory().getVersion().equals(OFVersion.OF_10) && (sw.getPort(p.getPort()) == null || U32.of(p.getPort().getPortNumber()).compareTo(U32.of(0xffFFff00)) != -1)) { throw new IllegalArgumentException("Port " + p.getPort().getPortNumber() + " is not a valid port on switch " + sw.getId().toString()); } } @@ -368,72 +364,103 @@ public class OFDPAUtils { * default tag of 1. Only VLANs are handled in this table. */ ArrayList<OFInstruction> instructions = new ArrayList<OFInstruction>(); - ArrayList<OFAction> actions = new ArrayList<OFAction>(); + ArrayList<OFAction> applyActions = new ArrayList<OFAction>(); + ArrayList<OFAction> writeActions = new ArrayList<OFAction>(); Match.Builder mb = sw.getOFFactory().buildMatch(); OFFlowAdd.Builder fab = sw.getOFFactory().buildFlowAdd(); /* These are common to all flows for VLAN flow table. */ fab.setBufferId(OFBufferId.NO_BUFFER) - .setCookie(COOKIE) + .setCookie(APP_COOKIE) .setHardTimeout(HARD_TIMEOUT) .setIdleTimeout(IDLE_TIMEOUT) .setPriority(PRIORITY) .setTableId(Tables.VLAN); + /* + * For each port, if it's an access port, must first push flow for tagged, + * THEN push flow for untagged. If it's trunk and access, do the same. + * If it's just trunk, then only push the tagged flow. + */ for (OFPortModeTuple p : ports) { - /* If VLAN tag not present add a tag (internal=1 or defined) */ - if (vlan.equals(VlanVid.ZERO) || p.getMode() == OFPortMode.ACCESS) { /* push tag if access or untagged entirely */ - mb.setExact(MatchField.VLAN_VID, OFVlanVidMatch.UNTAGGED); - // happens automatically actions.add(sw.getOFFactory().actions().buildPushVlan().setEthertype(EthType.VLAN_FRAME).build()); - actions.add(sw.getOFFactory().actions().setField(sw.getOFFactory().oxms().vlanVid(OFVlanVidMatch.ofVlanVid((vlan.equals(VlanVid.ZERO) ? VlanVid.ofVlan(1) : vlan))))); - instructions.add(sw.getOFFactory().instructions().applyActions(actions)); - } else { - mb.setExact(MatchField.VLAN_VID, OFVlanVidMatch.ofVlanVid((vlan.equals(VlanVid.ZERO) ? VlanVid.ofVlan(1) : vlan))); - } + /* OF-DPA requires match on VLAN tag for both trunk and access ports */ + mb.setExact(MatchField.VLAN_VID, OFVlanVidMatch.ofVlanVid((vlan.equals(VlanVid.ZERO) ? VlanVid.ofVlan(1) : vlan))); /* No matter what, we need to match on the ingress port */ mb.setExact(MatchField.IN_PORT, p.getPort()); /* We have to do this for OF-DPA. It's like adding the VLAN to the switch on that port. */ - instructions.add(sw.getOFFactory().instructions().applyActions(Collections.singletonList((OFAction) - sw.getOFFactory().actions().setField(sw.getOFFactory().oxms().vlanVid(OFVlanVidMatch.ofVlanVid((vlan.equals(VlanVid.ZERO) ? VlanVid.ofVlan(1) : vlan)))) - ))); + /* Happens automatically: actions.add(sw.getOFFactory().actions().buildPushVlan().setEthertype(EthType.VLAN_FRAME).build()); */ + applyActions.add(sw.getOFFactory().actions().setField(sw.getOFFactory().oxms().vlanVid(OFVlanVidMatch.ofVlanVid((vlan.equals(VlanVid.ZERO) ? VlanVid.ofVlan(1) : vlan))))); + instructions.add(sw.getOFFactory().instructions().applyActions(applyActions)); /* No matter what, output to the next table */ instructions.add(sw.getOFFactory().instructions().gotoTable(Tables.TERMINATION_MAC)); - /* Set the new stuff. */ fab.setInstructions(instructions) .setMatch(mb.build()) .build(); sw.write(fab.build()); - log.debug("Writing prereq flow to VLAN flow table {}", fab.build().toString()); + if (log.isDebugEnabled()) { + log.debug("Writing tagged prereq flow to VLAN flow table {}", fab.build().toString()); + } /* Don't forget to empty out our containers for the next iteration (or below). */ instructions.clear(); - actions.clear(); + applyActions.clear(); mb = sw.getOFFactory().buildMatch(); + + /* Here, if the port is access, add another untagged flow */ + if (vlan.equals(VlanVid.ZERO) || p.getMode() == OFPortMode.ACCESS) { + mb.setExact(MatchField.VLAN_VID, OFVlanVidMatch.UNTAGGED); //TODO verify this + mb.setExact(MatchField.IN_PORT, p.getPort()); + + applyActions.add(sw.getOFFactory().actions().setField(sw.getOFFactory().oxms().vlanVid(OFVlanVidMatch.ofVlanVid((vlan.equals(VlanVid.ZERO) ? VlanVid.ofVlan(1) : vlan))))); + instructions.add(sw.getOFFactory().instructions().applyActions(applyActions)); + instructions.add(sw.getOFFactory().instructions().gotoTable(Tables.TERMINATION_MAC)); + + fab.setInstructions(instructions) + .setMatch(mb.build()) + .build(); + sw.write(fab.build()); + if (log.isDebugEnabled()) { + log.debug("Writing untagged prereq flow to VLAN flow table {}", fab.build().toString()); + } + + /* Don't forget to empty out our containers for the next iteration (or below). */ + instructions.clear(); + applyActions.clear(); + mb = sw.getOFFactory().buildMatch(); + } } + /* Termination MAC table auto-forwards to bridging table (50) */ + /* - * We will insert a DLF flow to send to controller in the Policy ACL table (60). - * TODO Maybe this isn't the best choice, since we assume bypass/auto-forwarding of bridging and unicast/mulicast routing tables. + * We will insert a DLF flow to send to controller in the bridging table (50). */ - actions.add(sw.getOFFactory().actions().output(OFPort.CONTROLLER, 0xffFFffFF)); - instructions.add(sw.getOFFactory().instructions().applyActions(actions)); - fab = fab.setMatch(sw.getOFFactory().buildMatch().build()) /* clear match */ + writeActions.add(sw.getOFFactory().actions().group(OFDPAUtils.GroupIds.createL2Flood( + U16.of((vlan.equals(VlanVid.ZERO) ? VlanVid.ofVlan(1) : vlan).getVlan()) /* ID */, + vlan.equals(VlanVid.ZERO) ? VlanVid.ofVlan(1) : vlan))); /* bogus action */ + applyActions.add(sw.getOFFactory().actions().output(OFPort.CONTROLLER, 0xffFFffFF)); /* real, intended action */ + instructions.add(sw.getOFFactory().instructions().writeActions(writeActions)); + instructions.add(sw.getOFFactory().instructions().applyActions(applyActions)); + instructions.add(sw.getOFFactory().instructions().gotoTable(Tables.POLICY_ACL)); /* must go to policy ACL otherwise dropped; bogus though */ + fab = fab.setMatch(sw.getOFFactory().buildMatch() + .setExact(MatchField.VLAN_VID, OFVlanVidMatch.ofVlanVid(vlan.equals(VlanVid.ZERO) ? VlanVid.ofVlan(1) : vlan)) /* must match on just VLAN; dst MAC wildcarded */ + .build()) .setInstructions(instructions) - .setPriority(DLF_PRIORITY) /* different zero priority and table ID here */ - .setTableId(Tables.POLICY_ACL); + .setPriority(DLF_PRIORITY) /* lower priority */ + .setTableId(Tables.BRIDGING); sw.write(fab.build()); - log.debug("Writing DLF flow to policy ACL table {}", fab.build().toString()); + if (log.isDebugEnabled()) { + log.debug("Writing DLF flow to bridging table {}", fab.build().toString()); + } return true; } /** - * Note: Must have individually added {@link OFDPAUtils#addLearningSwitchPrereqGroups(IOFSwitch, VlanVid, List) groups} - * and then {@link OFDPAUtils#addLearningSwitchPrereqFlows(IOFSwitch, VlanVid, List) flows}, - * or must have done {@link OFDPAUtils#addLearningSwitchPrereqs(IOFSwitch, VlanVid, List) both} prior to calling + * Note: Must have called {@link OFDPAUtils#addLearningSwitchPrereqs(IOFSwitch, VlanVid, List) this function } prior to calling * this function. It is assumed you have done the aforementioned with the same VLAN and ports, otherwise you will likely * get a very grumpy OF-DPA switch. * @@ -463,7 +490,7 @@ public class OFDPAUtils { * @param outPort, either a valid physical port number or ZERO (for drop), ALL, FLOOD, or CONTROLLER * @return true upon success; false if switch is not an OF-DPA switch */ - public static boolean addBridgingFlow(IOFSwitch sw, U64 cookie, int priority, int hardTimeout, int idleTimeout, Match match, VlanVid outVlan, OFPort outPort) { + public static boolean addLearningSwitchFlow(IOFSwitch sw, U64 cookie, int priority, int hardTimeout, int idleTimeout, Match match, VlanVid outVlan, OFPort outPort) { if (!isOFDPASwitch(sw)) { log.error("Switch {} is not an OF-DPA switch. Not inserting flows.", sw.getId().toString()); return false; @@ -477,7 +504,10 @@ public class OFDPAUtils { hardTimeout = (hardTimeout < 0 ? 0 : hardTimeout); idleTimeout = (idleTimeout < 0 ? 0 : idleTimeout); if (match == null || !match.isExact(MatchField.ETH_DST)) { - log.error("OF-DPA 2.0 requires at least the destination MAC be matched in order to forward through its pipeline."); + log.error("OF-DPA 2.0 requires the destination MAC be matched in order to forward through its pipeline."); + return false; + } else if (match == null || !match.isExact(MatchField.VLAN_VID)) { + log.error("OF-DPA 2.0 requires the VLAN be matched in order to forward through its pipeline."); return false; } else { List<MatchFields> mfs = checkMatchFields(match); @@ -490,85 +520,49 @@ public class OFDPAUtils { outPort = (outPort == null ? OFPort.ZERO : outPort); /* - * Ingress flow table (0) will automatically send to the - * VLAN flow table (10), so insert nothing here. - */ - - /* - * VLAN flow table (10) is handled by prereq flows. - */ - - /* - * Termination MAC table (20) will automatically send to the - * bridging flow table (50), so also insert nothing here. - * - * Can send to controller. + * Add flow to bridging table that matches on dst MAC and outputs + * to the known port where the next hop or destination resides. */ + ArrayList<OFInstruction> instructions = new ArrayList<OFInstruction>(); + ArrayList<OFAction> actions = new ArrayList<OFAction>(); + + actions.add(sw.getOFFactory().actions().group(GroupIds.createL2Interface(outPort, (outVlan.equals(VlanVid.ZERO) ? VlanVid.ofVlan(1) : outVlan)))); + instructions.add(sw.getOFFactory().instructions().writeActions(actions)); + instructions.add(sw.getOFFactory().instructions().gotoTable(Tables.POLICY_ACL)); /* must go here or dropped */ - /* - * Unicast routing (30) and multicast routing (40) flow tables - * are special use-case tables the application should program - * directly. As such, we won't consider them here. - * - * Can send to controller. - */ + OFFlowAdd fa = sw.getOFFactory().buildFlowAdd() + .setMatch(sw.getOFFactory().buildMatch() + .setExact(MatchField.VLAN_VID, match.get(MatchField.VLAN_VID)) + .setExact(MatchField.ETH_DST, match.get(MatchField.ETH_DST)) + .build()) + .setPriority(priority) + .setIdleTimeout(idleTimeout) + .setHardTimeout(hardTimeout) + .setBufferId(OFBufferId.NO_BUFFER) + .setCookie(OFDPAUtils.APP_COOKIE) + .setTableId(OFDPAUtils.Tables.BRIDGING) + .setInstructions(instructions) + .build(); + log.debug("Writing learning switch flow to bridging table: {}", fa); + sw.write(fa); /* - * Bridging table (50) should assign a write-action goto-group - * depending on the desired output action (single-port or - * flood). But, the default on miss is to go to the policy ACL - * table (60), which we will do. Policy ACL can also assign the group. - * which we will do. Bridging must match on the VLAN VID and the - * dest MAC address of the packet. It must have a priority greater - * than all less-specific flows in the table (i.e. wildcarded - * flows). We will reserve priority 0 for a DLF (destination - * lookup failure) flow, which would have all fields wildcarded. - * - * Can send to controller. + * Policy ACL table (60) allows for more detailed matches. The + * write-actions goto group inserted by the bridging table (50) + * will be the output action taken upon a match. The policy ACL + * table allows for (optional) additional matches to take place. + * If we want to drop a packet instead of forwarding it, then + * the output group must be cleared in a flow that matches on + * the destination MAC and VLAN (same as bridging). */ - /* - * Policy ACL table (60) allows for more detailed matches. This - * is where we will implement all the matches specified in the Match - * object. The write-actions goto group inserted by the bridging - * table (50), or here (60), will be the output action taken upon - * a match, since this is the end of the pipeline. If we want to - * drop a packet for not matching, then no output group will be - * assigned to the packet, thus dropping it. - * - * A DLF (destination lookup failure) flow can also be inserted - * here to forward packets to the controller. + /* + * For now, let's assume we only match on VLAN and destination MAC. + * If that's the case, we should be able to do L2 forwarding b/t + * ports residing on the same VLAN. * - * Can send to controller. - */ - - ArrayList<OFInstruction> instructions = new ArrayList<OFInstruction>(); - ArrayList<OFAction> actions = new ArrayList<OFAction>(); - - /* Set the group to which we want to output. This might be a L2 flood or interface. */ - if (outPort.equals(OFPort.ZERO)) { - /* Don't add a group at all --> DROP */ - } else if (outPort.equals(OFPort.FLOOD) || outPort.equals(OFPort.ALL)) { // TODO how to distinguish in OF-DPA? - actions.add( - sw.getOFFactory().actions().group( // TODO Assume there is only one flood group per VLAN - GroupIds.createL2Flood(U16.ZERO, (outVlan.equals(VlanVid.ZERO) ? VlanVid.ofVlan(1) : outVlan)) - ) - ); - instructions.add(sw.getOFFactory().instructions().writeActions(actions)); - } else if (outPort.equals(OFPort.CONTROLLER)) { - actions.add(sw.getOFFactory().actions().output(OFPort.CONTROLLER, 0xFFffFFff)); - instructions.add(sw.getOFFactory().instructions().applyActions(actions)); - } else { /* assume port is a number valid on the switch */ - actions.add( - sw.getOFFactory().actions().group( - GroupIds.createL2Interface(outPort, (outVlan.equals(VlanVid.ZERO) ? VlanVid.ofVlan(1) : outVlan)) - ) - ); - instructions.add(sw.getOFFactory().instructions().writeActions(actions)); - } - - /* We're allowed to match on anything in the Match (supplied as an argument to this function) at this point. */ - OFFlowAdd fa = sw.getOFFactory().buildFlowAdd() + * We're allowed to match on anything in the Match (supplied as an argument to this function) at this point. + fa = sw.getOFFactory().buildFlowAdd() .setBufferId(OFBufferId.NO_BUFFER) .setCookie(cookie) .setHardTimeout(hardTimeout) @@ -578,7 +572,8 @@ public class OFDPAUtils { .setMatch(match) .setInstructions(instructions) .build(); - sw.write(fa); + log.debug("Writing learning switch flow to policy ACL table: {}", fa); + sw.write(fa); */ return true; } diff --git a/src/main/java/net/floodlightcontroller/util/OFMessageDamper.java b/src/main/java/net/floodlightcontroller/util/OFMessageDamper.java index 296adc157236de7dac02f5f15d544abc66228450..3e1b86f59362d077e513705b76da1c235f956b7b 100644 --- a/src/main/java/net/floodlightcontroller/util/OFMessageDamper.java +++ b/src/main/java/net/floodlightcontroller/util/OFMessageDamper.java @@ -114,24 +114,8 @@ public class OFMessageDamper { * @throws IOException */ public boolean write(IOFSwitch sw, OFMessage msg) throws IOException { - return write(sw, msg, false); - } - - /** - * write the message to the switch according to our dampening settings - * @param sw - * @param msg - * @param flush true to flush the packet immediately - * @return true if the message was written to the switch, false if - * the message was dampened. - * @throws IOException - */ - public boolean write(IOFSwitch sw, OFMessage msg, boolean flush) throws IOException { if (!msgTypesToCache.contains(msg.getType())) { sw.write(msg); - if (flush) { - sw.flush(); - } return true; } @@ -141,9 +125,6 @@ public class OFMessageDamper { return false; } else { sw.write(msg); - if (flush) { - sw.flush(); - } return true; } } diff --git a/src/main/java/net/floodlightcontroller/util/OFPortMode.java b/src/main/java/net/floodlightcontroller/util/OFPortMode.java index 9a02a47fb93a3f54c5960ae5af93490d2a85d2bc..e036cffa61049c2e925812952c6dd573c007a404 100644 --- a/src/main/java/net/floodlightcontroller/util/OFPortMode.java +++ b/src/main/java/net/floodlightcontroller/util/OFPortMode.java @@ -2,13 +2,12 @@ package net.floodlightcontroller.util; /** * Define the different operating modes of a switch - * port as trunk, access, and both (trunk and access). + * port as trunk or access. * * @author Ryan Izard, ryan.izard@bigswitch.com, rizard@g.clemson.edu */ public enum OFPortMode { TRUNK, - ACCESS, - TRUNK_AND_ACCESS + ACCESS } diff --git a/src/main/java/org/sdnplatform/sync/internal/SyncManager.java b/src/main/java/org/sdnplatform/sync/internal/SyncManager.java index 9db240c9c665b3da816a1b3f8803d1fd49914dc2..540fa42b1c1155ee49869caf8403c937e4d83052 100644 --- a/src/main/java/org/sdnplatform/sync/internal/SyncManager.java +++ b/src/main/java/org/sdnplatform/sync/internal/SyncManager.java @@ -1,5 +1,8 @@ package org.sdnplatform.sync.internal; +import io.netty.util.HashedWheelTimer; +import io.netty.util.Timer; + import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; @@ -68,737 +71,745 @@ import net.floodlightcontroller.threadpool.IThreadPoolService; * @see ISyncService */ public class SyncManager extends AbstractSyncManager { - protected static final Logger logger = - LoggerFactory.getLogger(SyncManager.class.getName()); - - protected IThreadPoolService threadPool; - protected IDebugCounterService debugCounter; - - /** - * The store registry holds the storage engines that provide - * access to the data - */ - private StoreRegistry storeRegistry = null; - - private IClusterConfigProvider clusterConfigProvider; - private ClusterConfig clusterConfig = new ClusterConfig(); - - protected RPCService rpcService = null; - - /** - * Interval between cleanup tasks in seconds - */ - private static final int CLEANUP_INTERVAL = 60 * 60; - - /** - * Interval between antientropy tasks in seconds - */ - private static final int ANTIENTROPY_INTERVAL = 5 * 60; - - /** - * Interval between configuration rescans - */ - private static final int CONFIG_RESCAN_INTERVAL = 10; - - /** - * Task for performing periodic maintenance/cleanup on local stores - */ - private SingletonTask cleanupTask; - - /** - * Task for periodic antientropy between nodes - */ - private SingletonTask antientropyTask; - - /** - * Task to periodically rescan configuration - */ - private SingletonTask updateConfigTask; - - /** - * Number of {@link HintWorker} workers used to drain the queue of writes - * that need to be sent to the connected nodes - */ - private static final int SYNC_WORKER_POOL = 2; - - /** - * A thread pool for the {@link HintWorker} threads. - */ - private ExecutorService hintThreadPool; - - /** - * Random number generator - */ - private final Random random = new Random(); - - /** - * A map of the currently-allocated cursors - */ - private final Map<Integer, Cursor> cursorMap = - new ConcurrentHashMap<Integer, Cursor>(); - - /** - * Whether to allow persistent stores or to use in-memory even - * when persistence is requested - */ - private boolean persistenceEnabled = true; - - private static final String PACKAGE = - ISyncService.class.getPackage().getName(); - - /** - * Debug Counters - */ - public static IDebugCounter counterHints; - public static IDebugCounter counterSentValues; - public static IDebugCounter counterReceivedValues; - public static IDebugCounter counterPuts; - public static IDebugCounter counterGets; - public static IDebugCounter counterIterators; - public static IDebugCounter counterErrorRemote; - public static IDebugCounter counterErrorProcessing; - - // ************ - // ISyncService - // ************ - - @Override - public void registerStore(String storeName, Scope scope) { - try { - storeRegistry.register(storeName, scope, false); - } catch (PersistException e) { - // not possible - throw new SyncRuntimeException(e); - } - } - - @Override - public void registerPersistentStore(String storeName, Scope scope) - throws PersistException { - storeRegistry.register(storeName, scope, persistenceEnabled); - } - - // ************************** - // SyncManager public methods - // ************************** - - /** - * Get the cluster configuration object - * @return the {@link ClusterConfig} object - * @see ClusterConfig - */ - public ClusterConfig getClusterConfig() { - return clusterConfig; - } - - /** - * Perform periodic scheduled cleanup. Note that this will be called - * automatically and you shouldn't generally call it directly except for - * testing - * @throws SyncException - */ - public void cleanup() throws SyncException { - for (SynchronizingStorageEngine store : storeRegistry.values()) { - store.cleanupTask(); - } - } - - /** - * Perform a synchronization with the node specified - */ - public void antientropy(Node node) { - if (!rpcService.isConnected(node.getNodeId())) return; - - logger.info("[{}->{}] Synchronizing local state to remote node", - getLocalNodeId(), node.getNodeId()); - - for (SynchronizingStorageEngine store : storeRegistry.values()) { - if (Scope.LOCAL.equals(store.getScope())) { - if (node.getDomainId() != - getClusterConfig().getNode().getDomainId()) - continue; - } else if (Scope.UNSYNCHRONIZED.equals(store.getScope())) { - continue; - } - - IClosableIterator<Entry<ByteArray, - List<Versioned<byte[]>>>> entries = - store.entries(); - try { - SyncMessage bsm = - TProtocolUtil.getTSyncOfferMessage(store.getName(), - store.getScope(), - store.isPersistent()); - int count = 0; - while (entries.hasNext()) { - if (!rpcService.isConnected(node.getNodeId())) return; - - Entry<ByteArray, List<Versioned<byte[]>>> pair = - entries.next(); - KeyedVersions kv = - TProtocolUtil.getTKeyedVersions(pair.getKey(), - pair.getValue()); - bsm.getSyncOffer().addToVersions(kv); - count += 1; - if (count >= 50) { - sendSyncOffer(node.getNodeId(), bsm); - bsm.getSyncOffer().unsetVersions(); - count = 0; - } - } - sendSyncOffer(node.getNodeId(), bsm); - } catch (InterruptedException e) { - // This can't really happen - throw new RuntimeException(e); - } finally { - entries.close(); - } - } - } - - /** - * Communicate with a random node and do a full synchronization of the - * all the stores on each node that have the appropriate scope. - */ - public void antientropy() { - ArrayList<Node> candidates = new ArrayList<Node>(); - for (Node n : clusterConfig.getNodes()) - if (rpcService.isConnected(n.getNodeId())) - candidates.add(n); - - int numNodes = candidates.size(); - if (numNodes == 0) return; - Node[] nodes = candidates.toArray(new Node[numNodes]); - int rn = random.nextInt(numNodes); - antientropy(nodes[rn]); - } - - /** - * Write a value synchronized from another node, bypassing some of the - * usual logic when a client writes data. If the store is not known, - * this will automatically register it - * @param storeName the store name - * @param scope the scope for the store - * @param persist TODO - * @param key the key to write - * @param values a list of versions for the key to write - * @throws PersistException - */ - public void writeSyncValue(String storeName, Scope scope, - boolean persist, - byte[] key, Iterable<Versioned<byte[]>> values) - throws PersistException { - SynchronizingStorageEngine store = storeRegistry.get(storeName); - if (store == null) { - store = storeRegistry.register(storeName, scope, persist); - } - store.writeSyncValue(new ByteArray(key), values); - } - - /** - * Check whether any of the specified versions for the key are not older - * than the versions we already have - * @param storeName the store to check - * @param key the key to check - * @param versions an iterable over the versions - * @return true if we'd like a copy of the data indicated - * @throws SyncException - */ - public boolean handleSyncOffer(String storeName, - byte[] key, - Iterable<VectorClock> versions) - throws SyncException { - SynchronizingStorageEngine store = storeRegistry.get(storeName); - if (store == null) return true; - - List<Versioned<byte[]>> values = store.get(new ByteArray(key)); - if (values == null || values.size() == 0) return true; - - // check whether any of the versions are not older than what we have - for (VectorClock vc : versions) { - for (Versioned<byte[]> value : values) { - VectorClock existingVc = (VectorClock)value.getVersion(); - if (!vc.compare(existingVc).equals(Occurred.BEFORE)) - return true; - } - } - - return false; - } - - /** - * Get access to the raw storage engine. This is useful for some - * on-the-wire communication - * @param storeName the store name to get - * @return the {@link IStorageEngine} - * @throws UnknownStoreException - */ - public IStorageEngine<ByteArray, byte[]> getRawStore(String storeName) - throws UnknownStoreException { - return getStoreInternal(storeName); - } - - /** - * Return the threadpool - * @return the {@link IThreadPoolService} - */ - public IThreadPoolService getThreadPool() { - return threadPool; - } - - /** - * Queue a synchronization of the specified {@link KeyedValues} to all nodes - * assocatiated with the storage engine specified - * @param e the storage engine for the values - * @param kv the values to synchronize - */ - public void queueSyncTask(SynchronizingStorageEngine e, - ByteArray key, Versioned<byte[]> value) { - storeRegistry.queueHint(e.getName(), key, value); - } - - @Override - public void addListener(String storeName, MappingStoreListener listener) - throws UnknownStoreException { - SynchronizingStorageEngine store = getStoreInternal(storeName); - store.addListener(listener); - } - - /** - * Update the node configuration to add or remove nodes - * @throws FloodlightModuleException - */ - public void updateConfiguration() { - if (updateConfigTask != null) - updateConfigTask.reschedule(500, TimeUnit.MILLISECONDS); - } - - /** - * Retrieve the cursor, if any, for the given cursor ID - * @param cursorId the cursor ID - * @return the {@link Cursor} - */ - public Cursor getCursor(int cursorId) { - return cursorMap.get(Integer.valueOf(cursorId)); - } - - /** - * Allocate a new cursor for the given store name - * @param storeName the store name - * @return the {@link Cursor} - * @throws SyncException - */ - public Cursor newCursor(String storeName) throws UnknownStoreException { - IStore<ByteArray, byte[]> store = getStore(storeName); - int cursorId = rpcService.getTransactionId(); - Cursor cursor = new Cursor(cursorId, store.entries()); - cursorMap.put(Integer.valueOf(cursorId), cursor); - return cursor; - } - - /** - * Close the given cursor and remove it from the map - * @param cursor the cursor to close - */ - public void closeCursor(Cursor cursor) { - cursor.close(); - cursorMap.remove(Integer.valueOf(cursor.getCursorId())); - } - - // ******************* - // AbstractSyncManager - // ******************* - - @Override - public IStore<ByteArray,byte[]> getStore(String storeName) - throws UnknownStoreException { - return getRawStore(storeName); - } - - @Override - public short getLocalNodeId() { - Node l = clusterConfig.getNode(); - if (l == null) return Short.MAX_VALUE; - return l.getNodeId(); - } - - @Override - public void shutdown() { - logger.debug("Shutting down Sync Manager: {} {}", - clusterConfig.getNode().getHostname(), - clusterConfig.getNode().getPort()); - - if (rpcService != null) { - rpcService.shutdown(); - } - if (hintThreadPool != null) { - hintThreadPool.shutdown(); - } - if (storeRegistry != null) { - storeRegistry.shutdown(); - } - hintThreadPool = null; - rpcService = null; - } - - // ***************** - // IFloodlightModule - // ***************** - - @Override - public void init(FloodlightModuleContext context) - throws FloodlightModuleException { - threadPool = context.getServiceImpl(IThreadPoolService.class); - debugCounter = context.getServiceImpl(IDebugCounterService.class); - Map<String, String> config = context.getConfigParams(this); - storeRegistry = new StoreRegistry(this, config.get("dbPath")); - - String[] configProviders = - {PropertyCCProvider.class.getName(), - SyncStoreCCProvider.class.getName(), - StorageCCProvider.class.getName(), - FallbackCCProvider.class.getName()}; - try { - if (config.containsKey("persistenceEnabled")) { - persistenceEnabled = - Boolean.parseBoolean(config.get("persistenceEnabled")); - } - if (config.containsKey("configProviders")) { - configProviders = config.get("configProviders").split(","); - } - DelegatingCCProvider dprovider = new DelegatingCCProvider(); - for (String configProvider : configProviders) { - Class<?> cClass = Class.forName(configProvider); - IClusterConfigProvider provider = - (IClusterConfigProvider) cClass.newInstance(); - dprovider.addProvider(provider); - } - dprovider.init(this, context); - clusterConfigProvider = dprovider; - } catch (Exception e) { - throw new FloodlightModuleException("Could not instantiate config" + - "providers " + Arrays.toString(configProviders), e); - } - - String manualStoreString = config.get("manualStores"); - if (manualStoreString != null) { - List<String> manualStores = null; - try { - manualStores = - (new ObjectMapper()).readValue(manualStoreString, - new TypeReference<List<String>>() {}); - } catch (Exception e) { - throw new FloodlightModuleException("Failed to parse sync " + - "manager manual stores: " + manualStoreString, e); - } - for (String s : manualStores) { - registerStore(s, Scope.GLOBAL); - } - } - registerDebugCounters(context); - } - - private void registerDebugCounters(FloodlightModuleContext context) - throws FloodlightModuleException { - if (context != null) { - debugCounter.registerModule(PACKAGE); - counterHints = debugCounter.registerCounter(PACKAGE, "hints", - "Queued sync events processed"); - counterSentValues = debugCounter.registerCounter(PACKAGE, "sent-values", - "Values synced to remote node"); - counterReceivedValues = debugCounter.registerCounter(PACKAGE, "received-values", - "Values received from remote node"); - counterPuts = debugCounter.registerCounter(PACKAGE, "puts", - "Local puts to store"); - counterGets = debugCounter.registerCounter(PACKAGE, "gets", - "Local gets from store"); - counterIterators = debugCounter.registerCounter(PACKAGE, "iterators", - "Local iterators created over store"); - counterErrorRemote = debugCounter.registerCounter(PACKAGE, "error-remote", - "Number of errors sent from remote clients", - IDebugCounterService.MetaData.ERROR); - counterErrorProcessing = debugCounter.registerCounter(PACKAGE, - "error-processing", - "Number of errors processing messages from remote clients", - IDebugCounterService.MetaData.ERROR); - } - - } - - @Override - public void startUp(FloodlightModuleContext context) - throws FloodlightModuleException { - - rpcService = new RPCService(this, debugCounter); - - cleanupTask = new SingletonTask(threadPool.getScheduledExecutor(), - new CleanupTask()); - cleanupTask.reschedule(CLEANUP_INTERVAL + - random.nextInt(30), TimeUnit.SECONDS); - - antientropyTask = new SingletonTask(threadPool.getScheduledExecutor(), - new AntientropyTask()); - antientropyTask.reschedule(ANTIENTROPY_INTERVAL + - random.nextInt(30), TimeUnit.SECONDS); - - final ThreadGroup tg = new ThreadGroup("Hint Workers"); - tg.setMaxPriority(Thread.NORM_PRIORITY - 2); - ThreadFactory f = new ThreadFactory() { - AtomicInteger id = new AtomicInteger(); - - @Override - public Thread newThread(Runnable runnable) { - return new Thread(tg, runnable, - "HintWorker-" + id.getAndIncrement()); - } - }; - hintThreadPool = Executors.newCachedThreadPool(f); - for (int i = 0; i < SYNC_WORKER_POOL; i++) { - hintThreadPool.execute(new HintWorker()); - } - - doUpdateConfiguration(); - rpcService.run(); - - updateConfigTask = - new SingletonTask(threadPool.getScheduledExecutor(), - new UpdateConfigTask()); - updateConfigTask.reschedule(CONFIG_RESCAN_INTERVAL, TimeUnit.SECONDS); - } - - @Override - public Collection<Class<? extends IFloodlightService>> - getModuleDependencies() { - Collection<Class<? extends IFloodlightService>> l = - new ArrayList<Class<? extends IFloodlightService>>(); - l.add(IThreadPoolService.class); - l.add(IStorageSourceService.class); - l.add(IDebugCounterService.class); - return l; - } - - // *************** - // Local methods - // *************** - - protected void doUpdateConfiguration() - throws FloodlightModuleException { - - try { - ClusterConfig oldConfig = clusterConfig; - clusterConfig = clusterConfigProvider.getConfig(); - if (clusterConfig.equals(oldConfig)) return; - - logger.info("[{}] Updating sync configuration {}", - clusterConfig.getNode().getNodeId(), - clusterConfig); - if (oldConfig.getNode() != null && - !clusterConfig.getNode().equals(oldConfig.getNode())) { - logger.info("[{}] Local node configuration changed; restarting sync" + - "service", oldConfig.getNode().getNodeId()); - shutdown(); - startUp(null); - } - - for (Node n : clusterConfig.getNodes()) { - Node existing = oldConfig.getNode(n.getNodeId()); - if (existing != null && !n.equals(existing)) { - // we already had this node's configuration, but it's - // changed. Disconnect from the node and let it - // reinitialize - logger.debug("[{}->{}] Configuration for node has changed", - getLocalNodeId(), n.getNodeId()); - rpcService.disconnectNode(n.getNodeId()); - } - } - for (Node n : oldConfig.getNodes()) { - Node nn = clusterConfig.getNode(n.getNodeId()); - if (nn == null) { - // n is a node that doesn't appear in the new config - logger.debug("[{}->{}] Disconnecting deconfigured node", - getLocalNodeId(), n.getNodeId()); - rpcService.disconnectNode(n.getNodeId()); - } - } - } catch (Exception e) { - throw new FloodlightModuleException("Could not update " + - "configuration", e); - } - } - - protected SynchronizingStorageEngine getStoreInternal(String storeName) - throws UnknownStoreException { - SynchronizingStorageEngine store = storeRegistry.get(storeName); - if (store == null) { - throw new UnknownStoreException("Store " + storeName + - " has not been registered"); - } - return store; - } - - private void sendSyncOffer(short nodeId, SyncMessage bsm) - throws InterruptedException { - SyncOfferMessage som = bsm.getSyncOffer(); - if (!som.isSetVersions()) return; - if (logger.isTraceEnabled()) { - logger.trace("[{}->{}] Sending SyncOffer with {} elements", - new Object[]{getLocalNodeId(), nodeId, - som.getVersionsSize()}); - } - - som.getHeader().setTransactionId(rpcService.getTransactionId()); - rpcService.writeToNode(nodeId, bsm); - } - - /** - * Periodically perform cleanup - * @author readams - */ - protected class CleanupTask implements Runnable { - @Override - public void run() { - try { - if (rpcService != null) - cleanup(); - } catch (Exception e) { - logger.error("Cleanup task failed", e); - } - - if (rpcService != null) { - cleanupTask.reschedule(CLEANUP_INTERVAL + - random.nextInt(30), TimeUnit.SECONDS); - } - } - } - - /** - * Periodically perform antientropy - * @author readams - */ - protected class AntientropyTask implements Runnable { - @Override - public void run() { - try { - if (rpcService != null) - antientropy(); - } catch (Exception e) { - logger.error("Antientropy task failed", e); - } - - if (rpcService != null) { - antientropyTask.reschedule(ANTIENTROPY_INTERVAL + - random.nextInt(30), - TimeUnit.SECONDS); - } - } - } - - /** - * Worker task to periodically rescan the configuration - * @author readams - */ - protected class UpdateConfigTask implements Runnable { - @Override - public void run() { - try { - if (rpcService != null) - doUpdateConfiguration(); - } catch (Exception e) { - logger.error("Failed to update configuration", e); - } - if (rpcService != null) { - updateConfigTask.reschedule(CONFIG_RESCAN_INTERVAL, - TimeUnit.SECONDS); - } - } - } - - /** - * Worker thread that will drain the sync item queue and write the - * appropriate messages to the node I/O channels - * @author readams - */ - protected class HintWorker implements Runnable { - ArrayList<Hint> tasks = new ArrayList<Hint>(50); - protected Map<String, SyncMessage> messages = - new LinkedHashMap<String, SyncMessage>(); - - @Override - public void run() { - while (rpcService != null) { - try { - // Batch up sync tasks so we use fewer, larger messages - // XXX - todo - handle hints targeted to specific nodes - storeRegistry.takeHints(tasks, 50); - for (Hint task : tasks) { - counterHints.increment(); - SynchronizingStorageEngine store = - storeRegistry.get(task.getHintKey(). - getStoreName()); - SyncMessage bsm = getMessage(store); - KeyedValues kv = - TProtocolUtil. - getTKeyedValues(task.getHintKey().getKey(), - task.getValues()); - bsm.getSyncValue().addToValues(kv); - } - - Iterable<Node> nodes = getClusterConfig().getNodes(); - short localDomainId = - getClusterConfig().getNode().getDomainId(); - short localNodeId = - getClusterConfig().getNode().getNodeId(); - for (Node n : nodes) { - if (localNodeId == n.getNodeId()) - continue; - for (SyncMessage bsm : messages.values()) { - SyncValueMessage svm = bsm.getSyncValue(); - if (svm.getStore().getScope(). - equals(org.sdnplatform.sync.thrift. - Scope.LOCAL) && - n.getDomainId() != localDomainId) { - // This message is only for local domain - continue; - } - - svm.getHeader(). - setTransactionId(rpcService.getTransactionId()); - counterSentValues.add(bsm.getSyncValue().getValuesSize()); - rpcService.writeToNode(n.getNodeId(), bsm); - } - } - tasks.clear(); - clearMessages(); - - } catch (Exception e) { - logger.error("Error occured in synchronization worker", e); - } - } - } - - /** - * Clear the current list of pending messages - */ - private void clearMessages() { - messages.clear(); - } - - /** - * Allocate a partially-initialized {@link SyncMessage} object for - * the given store - * @param store the store - * @return the {@link SyncMessage} object - */ - private SyncMessage getMessage(SynchronizingStorageEngine store) { - String storeName = store.getName(); - SyncMessage bsm = messages.get(storeName); - if (bsm == null) { - bsm = TProtocolUtil.getTSyncValueMessage(storeName, - store.getScope(), - store.isPersistent()); - messages.put(storeName, bsm); - } - return bsm; - } - } + protected static final Logger logger = + LoggerFactory.getLogger(SyncManager.class.getName()); + + protected IThreadPoolService threadPool; + protected IDebugCounterService debugCounter; + + /** + * The store registry holds the storage engines that provide + * access to the data + */ + private StoreRegistry storeRegistry = null; + + private Timer timer; + + private IClusterConfigProvider clusterConfigProvider; + private ClusterConfig clusterConfig = new ClusterConfig(); + + protected RPCService rpcService = null; + + /** + * Interval between cleanup tasks in seconds + */ + private static final int CLEANUP_INTERVAL = 60 * 60; + + /** + * Interval between antientropy tasks in seconds + */ + private static final int ANTIENTROPY_INTERVAL = 5 * 60; + + /** + * Interval between configuration rescans + */ + private static final int CONFIG_RESCAN_INTERVAL = 10; + + /** + * Task for performing periodic maintenance/cleanup on local stores + */ + private SingletonTask cleanupTask; + + /** + * Task for periodic antientropy between nodes + */ + private SingletonTask antientropyTask; + + /** + * Task to periodically rescan configuration + */ + private SingletonTask updateConfigTask; + + /** + * Number of {@link HintWorker} workers used to drain the queue of writes + * that need to be sent to the connected nodes + */ + private static final int SYNC_WORKER_POOL = 2; + + /** + * A thread pool for the {@link HintWorker} threads. + */ + private ExecutorService hintThreadPool; + + /** + * Random number generator + */ + private final Random random = new Random(); + + /** + * A map of the currently-allocated cursors + */ + private final Map<Integer, Cursor> cursorMap = + new ConcurrentHashMap<Integer, Cursor>(); + + /** + * Whether to allow persistent stores or to use in-memory even + * when persistence is requested + */ + private boolean persistenceEnabled = true; + + private static final String PACKAGE = + ISyncService.class.getPackage().getName(); + + /** + * Debug Counters + */ + public static IDebugCounter counterHints; + public static IDebugCounter counterSentValues; + public static IDebugCounter counterReceivedValues; + public static IDebugCounter counterPuts; + public static IDebugCounter counterGets; + public static IDebugCounter counterIterators; + public static IDebugCounter counterErrorRemote; + public static IDebugCounter counterErrorProcessing; + + // ************ + // ISyncService + // ************ + + @Override + public void registerStore(String storeName, Scope scope) { + try { + storeRegistry.register(storeName, scope, false); + } catch (PersistException e) { + // not possible + throw new SyncRuntimeException(e); + } + } + + @Override + public void registerPersistentStore(String storeName, Scope scope) + throws PersistException { + storeRegistry.register(storeName, scope, persistenceEnabled); + } + + // ************************** + // SyncManager public methods + // ************************** + + /** + * Get the cluster configuration object + * @return the {@link ClusterConfig} object + * @see ClusterConfig + */ + public ClusterConfig getClusterConfig() { + return clusterConfig; + } + + /** + * Perform periodic scheduled cleanup. Note that this will be called + * automatically and you shouldn't generally call it directly except for + * testing + * @throws SyncException + */ + public void cleanup() throws SyncException { + for (SynchronizingStorageEngine store : storeRegistry.values()) { + store.cleanupTask(); + } + } + + /** + * Perform a synchronization with the node specified + */ + public void antientropy(Node node) { + if (!rpcService.isConnected(node.getNodeId())) return; + + logger.info("[{}->{}] Synchronizing local state to remote node", + getLocalNodeId(), node.getNodeId()); + + for (SynchronizingStorageEngine store : storeRegistry.values()) { + if (Scope.LOCAL.equals(store.getScope())) { + if (node.getDomainId() != + getClusterConfig().getNode().getDomainId()) + continue; + } else if (Scope.UNSYNCHRONIZED.equals(store.getScope())) { + continue; + } + + IClosableIterator<Entry<ByteArray, + List<Versioned<byte[]>>>> entries = + store.entries(); + try { + SyncMessage bsm = + TProtocolUtil.getTSyncOfferMessage(store.getName(), + store.getScope(), + store.isPersistent()); + int count = 0; + while (entries.hasNext()) { + if (!rpcService.isConnected(node.getNodeId())) return; + + Entry<ByteArray, List<Versioned<byte[]>>> pair = + entries.next(); + KeyedVersions kv = + TProtocolUtil.getTKeyedVersions(pair.getKey(), + pair.getValue()); + bsm.getSyncOffer().addToVersions(kv); + count += 1; + if (count >= 50) { + sendSyncOffer(node.getNodeId(), bsm); + // realloc sync message - it is still queued up by netty! + bsm = TProtocolUtil.getTSyncOfferMessage(store.getName(), + store.getScope(), + store.isPersistent()); + count = 0; + } + } + sendSyncOffer(node.getNodeId(), bsm); + } catch (InterruptedException e) { + // This can't really happen + throw new RuntimeException(e); + } finally { + entries.close(); + } + } + } + + /** + * Communicate with a random node and do a full synchronization of the + * all the stores on each node that have the appropriate scope. + */ + public void antientropy() { + ArrayList<Node> candidates = new ArrayList<Node>(); + for (Node n : clusterConfig.getNodes()) + if (rpcService.isConnected(n.getNodeId())) + candidates.add(n); + + int numNodes = candidates.size(); + if (numNodes == 0) return; + Node[] nodes = candidates.toArray(new Node[numNodes]); + int rn = random.nextInt(numNodes); + antientropy(nodes[rn]); + } + + /** + * Write a value synchronized from another node, bypassing some of the + * usual logic when a client writes data. If the store is not known, + * this will automatically register it + * @param storeName the store name + * @param scope the scope for the store + * @param persist TODO + * @param key the key to write + * @param values a list of versions for the key to write + * @throws PersistException + */ + public void writeSyncValue(String storeName, Scope scope, + boolean persist, + byte[] key, Iterable<Versioned<byte[]>> values) + throws PersistException { + SynchronizingStorageEngine store = storeRegistry.get(storeName); + if (store == null) { + store = storeRegistry.register(storeName, scope, persist); + } + store.writeSyncValue(new ByteArray(key), values); + } + + /** + * Check whether any of the specified versions for the key are not older + * than the versions we already have + * @param storeName the store to check + * @param key the key to check + * @param versions an iterable over the versions + * @return true if we'd like a copy of the data indicated + * @throws SyncException + */ + public boolean handleSyncOffer(String storeName, + byte[] key, + Iterable<VectorClock> versions) + throws SyncException { + SynchronizingStorageEngine store = storeRegistry.get(storeName); + if (store == null) return true; + + List<Versioned<byte[]>> values = store.get(new ByteArray(key)); + if (values == null || values.size() == 0) return true; + + // check whether any of the versions are not older than what we have + for (VectorClock vc : versions) { + for (Versioned<byte[]> value : values) { + VectorClock existingVc = (VectorClock)value.getVersion(); + if (!vc.compare(existingVc).equals(Occurred.BEFORE)) + return true; + } + } + + return false; + } + + /** + * Get access to the raw storage engine. This is useful for some + * on-the-wire communication + * @param storeName the store name to get + * @return the {@link IStorageEngine} + * @throws UnknownStoreException + */ + public IStorageEngine<ByteArray, byte[]> getRawStore(String storeName) + throws UnknownStoreException { + return getStoreInternal(storeName); + } + + /** + * Return the threadpool + * @return the {@link IThreadPoolService} + */ + public IThreadPoolService getThreadPool() { + return threadPool; + } + + /** + * Queue a synchronization of the specified {@link KeyedValues} to all nodes + * assocatiated with the storage engine specified + * @param e the storage engine for the values + * @param kv the values to synchronize + */ + public void queueSyncTask(SynchronizingStorageEngine e, + ByteArray key, Versioned<byte[]> value) { + storeRegistry.queueHint(e.getName(), key, value); + } + + @Override + public void addListener(String storeName, MappingStoreListener listener) + throws UnknownStoreException { + SynchronizingStorageEngine store = getStoreInternal(storeName); + store.addListener(listener); + } + + /** + * Update the node configuration to add or remove nodes + * @throws FloodlightModuleException + */ + public void updateConfiguration() { + if (updateConfigTask != null) + updateConfigTask.reschedule(500, TimeUnit.MILLISECONDS); + } + + /** + * Retrieve the cursor, if any, for the given cursor ID + * @param cursorId the cursor ID + * @return the {@link Cursor} + */ + public Cursor getCursor(int cursorId) { + return cursorMap.get(Integer.valueOf(cursorId)); + } + + /** + * Allocate a new cursor for the given store name + * @param storeName the store name + * @return the {@link Cursor} + * @throws SyncException + */ + public Cursor newCursor(String storeName) throws UnknownStoreException { + IStore<ByteArray, byte[]> store = getStore(storeName); + int cursorId = rpcService.getTransactionId(); + Cursor cursor = new Cursor(cursorId, store.entries()); + cursorMap.put(Integer.valueOf(cursorId), cursor); + return cursor; + } + + /** + * Close the given cursor and remove it from the map + * @param cursor the cursor to close + */ + public void closeCursor(Cursor cursor) { + cursor.close(); + cursorMap.remove(Integer.valueOf(cursor.getCursorId())); + } + + // ******************* + // AbstractSyncManager + // ******************* + + @Override + public IStore<ByteArray,byte[]> getStore(String storeName) + throws UnknownStoreException { + return getRawStore(storeName); + } + + @Override + public short getLocalNodeId() { + Node l = clusterConfig.getNode(); + if (l == null) return Short.MAX_VALUE; + return l.getNodeId(); + } + + @Override + public void shutdown() { + logger.info("Shutting down Sync Manager: {} {}", + clusterConfig.getNode().getHostname(), + clusterConfig.getNode().getPort()); + + if (rpcService != null) { + rpcService.shutdown(); + } + if (hintThreadPool != null) { + hintThreadPool.shutdown(); + } + if (storeRegistry != null) { + storeRegistry.shutdown(); + } + if (timer != null) + timer.stop(); + timer = null; + hintThreadPool = null; + rpcService = null; + } + + // ***************** + // IFloodlightModule + // ***************** + + @Override + public void init(FloodlightModuleContext context) + throws FloodlightModuleException { + threadPool = context.getServiceImpl(IThreadPoolService.class); + debugCounter = context.getServiceImpl(IDebugCounterService.class); + Map<String, String> config = context.getConfigParams(this); + storeRegistry = new StoreRegistry(this, config.get("dbPath")); + + String[] configProviders = + {PropertyCCProvider.class.getName(), + SyncStoreCCProvider.class.getName(), + StorageCCProvider.class.getName(), + FallbackCCProvider.class.getName()}; + try { + if (config.containsKey("persistenceEnabled")) { + persistenceEnabled = + Boolean.parseBoolean(config.get("persistenceEnabled")); + } + if (config.containsKey("configProviders")) { + configProviders = config.get("configProviders").split(","); + } + DelegatingCCProvider dprovider = new DelegatingCCProvider(); + for (String configProvider : configProviders) { + Class<?> cClass = Class.forName(configProvider); + IClusterConfigProvider provider = + (IClusterConfigProvider) cClass.newInstance(); + dprovider.addProvider(provider); + } + dprovider.init(this, context); + clusterConfigProvider = dprovider; + } catch (Exception e) { + throw new FloodlightModuleException("Could not instantiate config" + + "providers " + Arrays.toString(configProviders), e); + } + + String manualStoreString = config.get("manualStores"); + if (manualStoreString != null) { + List<String> manualStores = null; + try { + manualStores = + (new ObjectMapper()).readValue(manualStoreString, + new TypeReference<List<String>>() {}); + } catch (Exception e) { + throw new FloodlightModuleException("Failed to parse sync " + + "manager manual stores: " + manualStoreString, e); + } + for (String s : manualStores) { + registerStore(s, Scope.GLOBAL); + } + } + registerDebugCounters(context); + } + + private void registerDebugCounters(FloodlightModuleContext context) + throws FloodlightModuleException { + if (context != null) { + debugCounter.registerModule(PACKAGE); + counterHints = debugCounter.registerCounter(PACKAGE, "hints", + "Queued sync events processed"); + counterSentValues = debugCounter.registerCounter(PACKAGE, "sent-values", + "Values synced to remote node"); + counterReceivedValues = debugCounter.registerCounter(PACKAGE, "received-values", + "Values received from remote node"); + counterPuts = debugCounter.registerCounter(PACKAGE, "puts", + "Local puts to store"); + counterGets = debugCounter.registerCounter(PACKAGE, "gets", + "Local gets from store"); + counterIterators = debugCounter.registerCounter(PACKAGE, "iterators", + "Local iterators created over store"); + counterErrorRemote = debugCounter.registerCounter(PACKAGE, "error-remote", + "Number of errors sent from remote clients", + IDebugCounterService.MetaData.ERROR); + counterErrorProcessing = debugCounter.registerCounter(PACKAGE, + "error-processing", + "Number of errors processing messages from remote clients", + IDebugCounterService.MetaData.ERROR); + } + + } + + @Override + public void startUp(FloodlightModuleContext context) + throws FloodlightModuleException { + + timer = new HashedWheelTimer(); + rpcService = new RPCService(this, debugCounter, timer); + + cleanupTask = new SingletonTask(threadPool.getScheduledExecutor(), + new CleanupTask()); + cleanupTask.reschedule(CLEANUP_INTERVAL + + random.nextInt(30), TimeUnit.SECONDS); + + antientropyTask = new SingletonTask(threadPool.getScheduledExecutor(), + new AntientropyTask()); + antientropyTask.reschedule(ANTIENTROPY_INTERVAL + + random.nextInt(30), TimeUnit.SECONDS); + + final ThreadGroup tg = new ThreadGroup("Hint Workers"); + tg.setMaxPriority(Thread.NORM_PRIORITY - 2); + ThreadFactory f = new ThreadFactory() { + AtomicInteger id = new AtomicInteger(); + + @Override + public Thread newThread(Runnable runnable) { + return new Thread(tg, runnable, + "HintWorker-" + id.getAndIncrement()); + } + }; + hintThreadPool = Executors.newCachedThreadPool(f); + for (int i = 0; i < SYNC_WORKER_POOL; i++) { + hintThreadPool.execute(new HintWorker()); + } + + doUpdateConfiguration(); + rpcService.run(); + + updateConfigTask = + new SingletonTask(threadPool.getScheduledExecutor(), + new UpdateConfigTask()); + updateConfigTask.reschedule(CONFIG_RESCAN_INTERVAL, TimeUnit.SECONDS); + } + + @Override + public Collection<Class<? extends IFloodlightService>> + getModuleDependencies() { + Collection<Class<? extends IFloodlightService>> l = + new ArrayList<Class<? extends IFloodlightService>>(); + l.add(IThreadPoolService.class); + l.add(IStorageSourceService.class); + l.add(IDebugCounterService.class); + return l; + } + + // *************** + // Local methods + // *************** + + protected void doUpdateConfiguration() + throws FloodlightModuleException { + + try { + ClusterConfig oldConfig = clusterConfig; + clusterConfig = clusterConfigProvider.getConfig(); + if (clusterConfig.equals(oldConfig)) return; + + logger.info("[{}] Updating sync configuration {}", + clusterConfig.getNode().getNodeId(), + clusterConfig); + if (oldConfig.getNode() != null && + !clusterConfig.getNode().equals(oldConfig.getNode())) { + logger.info("[{}] Local node configuration changed; restarting sync" + + "service", oldConfig.getNode().getNodeId()); + shutdown(); + startUp(null); + } + + for (Node n : clusterConfig.getNodes()) { + Node existing = oldConfig.getNode(n.getNodeId()); + if (existing != null && !n.equals(existing)) { + // we already had this node's configuration, but it's + // changed. Disconnect from the node and let it + // reinitialize + logger.info("[{}->{}] Configuration for node has changed", + getLocalNodeId(), n.getNodeId()); + rpcService.disconnectNode(n.getNodeId()); + } + } + for (Node n : oldConfig.getNodes()) { + Node nn = clusterConfig.getNode(n.getNodeId()); + if (nn == null) { + // n is a node that doesn't appear in the new config + logger.info("[{}->{}] Disconnecting deconfigured node", + getLocalNodeId(), n.getNodeId()); + rpcService.disconnectNode(n.getNodeId()); + } + } + } catch (Exception e) { + throw new FloodlightModuleException("Could not update " + + "configuration", e); + } + } + + protected SynchronizingStorageEngine getStoreInternal(String storeName) + throws UnknownStoreException { + SynchronizingStorageEngine store = storeRegistry.get(storeName); + if (store == null) { + throw new UnknownStoreException("Store " + storeName + + " has not been registered"); + } + return store; + } + + private void sendSyncOffer(short nodeId, SyncMessage bsm) + throws InterruptedException { + SyncOfferMessage som = bsm.getSyncOffer(); + if (!som.isSetVersions()) return; + if (logger.isTraceEnabled()) { + logger.trace("[{}->{}] Sending SyncOffer with {} elements", + new Object[]{getLocalNodeId(), nodeId, + som.getVersionsSize()}); + } + + som.getHeader().setTransactionId(rpcService.getTransactionId()); + rpcService.writeToNode(nodeId, bsm); + } + + /** + * Periodically perform cleanup + * @author readams + */ + protected class CleanupTask implements Runnable { + @Override + public void run() { + try { + if (rpcService != null) + cleanup(); + } catch (Exception e) { + logger.error("Cleanup task failed", e); + } + + if (rpcService != null) { + cleanupTask.reschedule(CLEANUP_INTERVAL + + random.nextInt(30), TimeUnit.SECONDS); + } + } + } + + /** + * Periodically perform antientropy + * @author readams + */ + protected class AntientropyTask implements Runnable { + @Override + public void run() { + try { + if (rpcService != null) + antientropy(); + } catch (Exception e) { + logger.error("Antientropy task failed", e); + } + + if (rpcService != null) { + antientropyTask.reschedule(ANTIENTROPY_INTERVAL + + random.nextInt(30), + TimeUnit.SECONDS); + } + } + } + + /** + * Worker task to periodically rescan the configuration + * @author readams + */ + protected class UpdateConfigTask implements Runnable { + @Override + public void run() { + try { + if (rpcService != null) + doUpdateConfiguration(); + } catch (Exception e) { + logger.error("Failed to update configuration", e); + } + if (rpcService != null) { + updateConfigTask.reschedule(CONFIG_RESCAN_INTERVAL, + TimeUnit.SECONDS); + } + } + } + + /** + * Worker thread that will drain the sync item queue and write the + * appropriate messages to the node I/O channels + * @author readams + */ + protected class HintWorker implements Runnable { + ArrayList<Hint> tasks = new ArrayList<Hint>(50); + protected Map<String, SyncMessage> messages = + new LinkedHashMap<String, SyncMessage>(); + + @Override + public void run() { + while (rpcService != null) { + try { + // Batch up sync tasks so we use fewer, larger messages + // XXX - todo - handle hints targeted to specific nodes + storeRegistry.takeHints(tasks, 50); + for (Hint task : tasks) { + counterHints.increment(); + SynchronizingStorageEngine store = + storeRegistry.get(task.getHintKey(). + getStoreName()); + SyncMessage bsm = getMessage(store); + KeyedValues kv = + TProtocolUtil. + getTKeyedValues(task.getHintKey().getKey(), + task.getValues()); + bsm.getSyncValue().addToValues(kv); + } + + Iterable<Node> nodes = getClusterConfig().getNodes(); + short localDomainId = + getClusterConfig().getNode().getDomainId(); + short localNodeId = + getClusterConfig().getNode().getNodeId(); + for (Node n : nodes) { + if (localNodeId == n.getNodeId()) + continue; + for (SyncMessage bsm : messages.values()) { + SyncValueMessage svm = bsm.getSyncValue(); + if (svm.getStore().getScope(). + equals(org.sdnplatform.sync.thrift. + Scope.LOCAL) && + n.getDomainId() != localDomainId) { + // This message is only for local domain + continue; + } + + svm.getHeader().setTransactionId(rpcService.getTransactionId()); + counterSentValues.add(bsm.getSyncValue().getValuesSize()); + rpcService.writeToNode(n.getNodeId(), bsm); + } + } + tasks.clear(); + clearMessages(); + + } catch (Exception e) { + logger.error("Error occured in synchronization worker", e); + } + } + } + + /** + * Clear the current list of pending messages + */ + private void clearMessages() { + messages.clear(); + } + + /** + * Allocate a partially-initialized {@link SyncMessage} object for + * the given store + * @param store the store + * @return the {@link SyncMessage} object + */ + private SyncMessage getMessage(SynchronizingStorageEngine store) { + String storeName = store.getName(); + SyncMessage bsm = messages.get(storeName); + if (bsm == null) { + bsm = TProtocolUtil.getTSyncValueMessage(storeName, + store.getScope(), + store.isPersistent()); + messages.put(storeName, bsm); + } + return bsm; + } + } } diff --git a/src/main/java/org/sdnplatform/sync/internal/config/SyncStoreCCProvider.java b/src/main/java/org/sdnplatform/sync/internal/config/SyncStoreCCProvider.java index f1efbe49f5ae79ce1bfb529eeb720cb681085e54..4cd7442dfd745431dea125eb2ec544ec97326382 100644 --- a/src/main/java/org/sdnplatform/sync/internal/config/SyncStoreCCProvider.java +++ b/src/main/java/org/sdnplatform/sync/internal/config/SyncStoreCCProvider.java @@ -26,7 +26,7 @@ import org.sdnplatform.sync.Versioned; import org.sdnplatform.sync.error.ObsoleteVersionException; import org.sdnplatform.sync.error.SyncException; import org.sdnplatform.sync.internal.SyncManager; -import org.sdnplatform.sync.internal.config.bootstrap.Bootstrap; +import org.sdnplatform.sync.internal.config.bootstrap.BootstrapClient; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -345,7 +345,7 @@ public class SyncStoreCCProvider hosts.add(HostAndPort.fromString(s). withDefaultPort(6642)); } - Bootstrap bs = new Bootstrap(syncManager, + BootstrapClient bs = new BootstrapClient(syncManager, authScheme, keyStorePath, keyStorePassword); diff --git a/src/main/java/org/sdnplatform/sync/internal/config/bootstrap/BootstrapChannelHandler.java b/src/main/java/org/sdnplatform/sync/internal/config/bootstrap/BootstrapChannelHandler.java index 4e7802b7889db6c85b077fe9bc60a52ab1249973..1272dd849d0578d7a28a564bc6a05bd49206db13 100644 --- a/src/main/java/org/sdnplatform/sync/internal/config/bootstrap/BootstrapChannelHandler.java +++ b/src/main/java/org/sdnplatform/sync/internal/config/bootstrap/BootstrapChannelHandler.java @@ -1,8 +1,7 @@ package org.sdnplatform.sync.internal.config.bootstrap; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelStateEvent; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; import org.sdnplatform.sync.IStoreClient; import org.sdnplatform.sync.Versioned; import org.sdnplatform.sync.error.AuthException; @@ -30,10 +29,10 @@ public class BootstrapChannelHandler extends AbstractRPCChannelHandler { protected static final Logger logger = LoggerFactory.getLogger(BootstrapChannelHandler.class); - private Bootstrap bootstrap; + private BootstrapClient bootstrap; private Short remoteNodeId; - public BootstrapChannelHandler(Bootstrap bootstrap) { + public BootstrapChannelHandler(BootstrapClient bootstrap) { super(); this.bootstrap = bootstrap; } @@ -43,9 +42,9 @@ public class BootstrapChannelHandler extends AbstractRPCChannelHandler { // **************************** @Override - public void channelOpen(ChannelHandlerContext ctx, - ChannelStateEvent e) throws Exception { - bootstrap.cg.add(ctx.getChannel()); + public void channelActive(ChannelHandlerContext ctx) throws Exception { + bootstrap.getChannelGroup().add(ctx.channel()); + super.channelActive(ctx); } // ****************************************** @@ -73,7 +72,7 @@ public class BootstrapChannelHandler extends AbstractRPCChannelHandler { SyncMessage bsm = new SyncMessage(MessageType.CLUSTER_JOIN_REQUEST); bsm.setClusterJoinRequest(cjrm); - channel.write(bsm); + channel.writeAndFlush(bsm); } @Override @@ -107,7 +106,7 @@ public class BootstrapChannelHandler extends AbstractRPCChannelHandler { bootstrap.succeeded = true; } catch (Exception e) { logger.error("Error processing cluster join response", e); - channel.write(getError(response.getHeader().getTransactionId(), e, + channel.writeAndFlush(getError(response.getHeader().getTransactionId(), e, MessageType.CLUSTER_JOIN_RESPONSE)); } channel.disconnect(); diff --git a/src/main/java/org/sdnplatform/sync/internal/config/bootstrap/BootstrapChannelInitializer.java b/src/main/java/org/sdnplatform/sync/internal/config/bootstrap/BootstrapChannelInitializer.java new file mode 100644 index 0000000000000000000000000000000000000000..845836ae1db61d1a46a78bee1720474fa98c71a2 --- /dev/null +++ b/src/main/java/org/sdnplatform/sync/internal/config/bootstrap/BootstrapChannelInitializer.java @@ -0,0 +1,39 @@ +package org.sdnplatform.sync.internal.config.bootstrap; + +import org.sdnplatform.sync.internal.rpc.SyncMessageDecoder; +import org.sdnplatform.sync.internal.rpc.SyncMessageEncoder; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.util.Timer; + + + +public class BootstrapChannelInitializer extends ChannelInitializer<Channel> { + private final BootstrapClient bootstrap; + private static final int maxFrameSize = 1024 * 1024 * 10; + protected Timer timer; + + public BootstrapChannelInitializer(Timer timer, BootstrapClient bootstrap) { + super(); + this.timer = timer; + this.bootstrap = bootstrap; + } + + @Override + protected void initChannel(Channel ch) throws Exception { + BootstrapChannelHandler handler = + new BootstrapChannelHandler(bootstrap); + + ChannelPipeline pipeline = ch.pipeline(); + + pipeline.addLast("syncMessageDecoder", new SyncMessageDecoder(maxFrameSize)); + + pipeline.addLast("syncMessageEncoder", new SyncMessageEncoder()); + + pipeline.addLast("timeout", new BootstrapTimeoutHandler(timer, 10)); + + pipeline.addLast("handler", handler); + } +} \ No newline at end of file diff --git a/src/main/java/org/sdnplatform/sync/internal/config/bootstrap/Bootstrap.java b/src/main/java/org/sdnplatform/sync/internal/config/bootstrap/BootstrapClient.java similarity index 54% rename from src/main/java/org/sdnplatform/sync/internal/config/bootstrap/Bootstrap.java rename to src/main/java/org/sdnplatform/sync/internal/config/bootstrap/BootstrapClient.java index 6b994e3c8dc3960eb716d1f822bf8d9a45a9c578..8133635241d5ce9e954c345d565d7a5e300f9f14 100644 --- a/src/main/java/org/sdnplatform/sync/internal/config/bootstrap/Bootstrap.java +++ b/src/main/java/org/sdnplatform/sync/internal/config/bootstrap/BootstrapClient.java @@ -2,16 +2,22 @@ package org.sdnplatform.sync.internal.config.bootstrap; import java.net.InetSocketAddress; import java.net.SocketAddress; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.atomic.AtomicInteger; -import org.jboss.netty.bootstrap.ClientBootstrap; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelFuture; -import org.jboss.netty.channel.group.ChannelGroup; -import org.jboss.netty.channel.group.DefaultChannelGroup; -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.group.ChannelGroup; +import io.netty.channel.group.DefaultChannelGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.handler.timeout.TimeoutException; +import io.netty.util.HashedWheelTimer; +import io.netty.util.Timer; +import io.netty.util.concurrent.GlobalEventExecutor; + import org.sdnplatform.sync.error.SyncException; import org.sdnplatform.sync.internal.SyncManager; import org.sdnplatform.sync.internal.config.AuthScheme; @@ -27,14 +33,14 @@ import com.google.common.net.HostAndPort; * local system store * @author readams */ -public class Bootstrap { +public class BootstrapClient { protected static final Logger logger = - LoggerFactory.getLogger(Bootstrap.class); + LoggerFactory.getLogger(BootstrapClient.class); /** * Channel group that will hold all our channels */ - protected ChannelGroup cg; + private ChannelGroup cg; /** * Transaction ID used in message headers in the RPC protocol @@ -49,15 +55,16 @@ public class Bootstrap { protected final String keyStorePath; protected final String keyStorePassword; - ExecutorService bossExecutor = null; - ExecutorService workerExecutor = null; - ClientBootstrap bootstrap = null; - BootstrapPipelineFactory pipelineFactory; + EventLoopGroup workerExecutor = null; + Bootstrap bootstrap = null; + BootstrapChannelInitializer pipelineFactory; protected Node localNode; protected volatile boolean succeeded = false; + + private Timer timer; - public Bootstrap(SyncManager syncManager, AuthScheme authScheme, + public BootstrapClient(SyncManager syncManager, AuthScheme authScheme, String keyStorePath, String keyStorePassword) { super(); this.syncManager = syncManager; @@ -67,25 +74,23 @@ public class Bootstrap { } public void init() throws SyncException { - cg = new DefaultChannelGroup("Cluster Bootstrap"); - - bossExecutor = Executors.newCachedThreadPool(); - workerExecutor = Executors.newCachedThreadPool(); + cg = new DefaultChannelGroup("Cluster Bootstrap", GlobalEventExecutor.INSTANCE); - bootstrap = - new ClientBootstrap(new NioClientSocketChannelFactory(bossExecutor, - workerExecutor)); - bootstrap.setOption("child.reuseAddr", true); - bootstrap.setOption("child.keepAlive", true); - bootstrap.setOption("child.tcpNoDelay", true); - bootstrap.setOption("child.sendBufferSize", - RPCService.SEND_BUFFER_SIZE); - bootstrap.setOption("child.receiveBufferSize", - RPCService.SEND_BUFFER_SIZE); - bootstrap.setOption("child.connectTimeoutMillis", - RPCService.CONNECT_TIMEOUT); - pipelineFactory = new BootstrapPipelineFactory(this); - bootstrap.setPipelineFactory(pipelineFactory); + workerExecutor = new NioEventLoopGroup(); + timer = new HashedWheelTimer(); + + bootstrap = new Bootstrap() + .group(workerExecutor) + .channel(NioSocketChannel.class) + .option(ChannelOption.SO_REUSEADDR, true) + .option(ChannelOption.SO_KEEPALIVE, true) + .option(ChannelOption.TCP_NODELAY, true) + .option(ChannelOption.SO_SNDBUF, RPCService.SEND_BUFFER_SIZE) + .option(ChannelOption.SO_RCVBUF, RPCService.SEND_BUFFER_SIZE) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, RPCService.CONNECT_TIMEOUT); + + pipelineFactory = new BootstrapChannelInitializer(timer, this); + bootstrap.handler(pipelineFactory); } public void shutdown() { @@ -93,18 +98,20 @@ public class Bootstrap { cg.close().awaitUninterruptibly(); cg = null; } - if (bootstrap != null) - bootstrap.releaseExternalResources(); bootstrap = null; - if (pipelineFactory != null) - pipelineFactory.releaseExternalResources(); pipelineFactory = null; - if (workerExecutor != null) - workerExecutor.shutdown(); - workerExecutor = null; - if (bossExecutor != null) - bossExecutor.shutdown(); - bossExecutor = null; + if (workerExecutor != null) { + try { + workerExecutor.shutdownGracefully(); + } catch (TimeoutException e) { + logger.warn("Error waiting for gracefull shutdown of BootstrapClient {}", e); + } + workerExecutor = null; + } + if (timer != null) { + timer.stop(); + timer = null; + } } public boolean bootstrap(HostAndPort seed, @@ -116,20 +123,24 @@ public class Bootstrap { ChannelFuture future = bootstrap.connect(sa); future.awaitUninterruptibly(); if (!future.isSuccess()) { - logger.debug("Could not connect to " + seed, future.getCause()); + logger.debug("Could not connect to " + seed, future.cause()); return false; } - Channel channel = future.getChannel(); + Channel channel = future.channel(); logger.debug("[{}] Connected to {}", localNode != null ? localNode.getNodeId() : null, seed); try { - channel.getCloseFuture().await(); + channel.closeFuture().await(); } catch (InterruptedException e) { logger.debug("Interrupted while waiting for bootstrap"); return succeeded; } return succeeded; } + + public ChannelGroup getChannelGroup() { + return cg; + } } diff --git a/src/main/java/org/sdnplatform/sync/internal/config/bootstrap/BootstrapPipelineFactory.java b/src/main/java/org/sdnplatform/sync/internal/config/bootstrap/BootstrapPipelineFactory.java deleted file mode 100644 index f8850150fed79064b32ae678e1144bac79c2cbf0..0000000000000000000000000000000000000000 --- a/src/main/java/org/sdnplatform/sync/internal/config/bootstrap/BootstrapPipelineFactory.java +++ /dev/null @@ -1,46 +0,0 @@ -package org.sdnplatform.sync.internal.config.bootstrap; - -import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.ChannelPipelineFactory; -import org.jboss.netty.channel.Channels; -import org.jboss.netty.util.ExternalResourceReleasable; -import org.jboss.netty.util.HashedWheelTimer; -import org.jboss.netty.util.Timer; -import org.sdnplatform.sync.internal.rpc.ThriftFrameDecoder; -import org.sdnplatform.sync.internal.rpc.ThriftFrameEncoder; - -public class BootstrapPipelineFactory - implements ChannelPipelineFactory, ExternalResourceReleasable { - private Bootstrap bootstrap; - private static final int maxFrameSize = 1024 * 1024 * 10; - protected Timer timer; - - public BootstrapPipelineFactory(Bootstrap bootstrap) { - super(); - this.bootstrap = bootstrap; - this.timer = new HashedWheelTimer(); - } - - @Override - public ChannelPipeline getPipeline() throws Exception { - BootstrapChannelHandler handler = - new BootstrapChannelHandler(bootstrap); - ChannelPipeline pipeline = Channels.pipeline(); - - pipeline.addLast("frameDecoder", - new ThriftFrameDecoder(maxFrameSize)); - pipeline.addLast("frameEncoder", - new ThriftFrameEncoder()); - pipeline.addLast("timeout", - new BootstrapTimeoutHandler(timer, 10)); - - pipeline.addLast("handler", handler); - - return pipeline; - } - - @Override - public void releaseExternalResources() { - timer.stop(); - } -} diff --git a/src/main/java/org/sdnplatform/sync/internal/config/bootstrap/BootstrapTimeoutHandler.java b/src/main/java/org/sdnplatform/sync/internal/config/bootstrap/BootstrapTimeoutHandler.java index fa2de52701d9f7f75ac5389d87bc1d7fb55c9414..9ad9cb7d30a449e460646a030f7e8b8556fa24d4 100644 --- a/src/main/java/org/sdnplatform/sync/internal/config/bootstrap/BootstrapTimeoutHandler.java +++ b/src/main/java/org/sdnplatform/sync/internal/config/bootstrap/BootstrapTimeoutHandler.java @@ -18,18 +18,16 @@ package org.sdnplatform.sync.internal.config.bootstrap; import java.util.concurrent.TimeUnit; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelStateEvent; -import org.jboss.netty.channel.SimpleChannelUpstreamHandler; -import org.jboss.netty.util.Timeout; -import org.jboss.netty.util.Timer; -import org.jboss.netty.util.TimerTask; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; /** * Trigger a timeout if the bootstrap process stalls */ -public class BootstrapTimeoutHandler - extends SimpleChannelUpstreamHandler { +public class BootstrapTimeoutHandler extends ChannelInboundHandlerAdapter { final Timer timer; final long timeoutNanos; @@ -44,22 +42,23 @@ public class BootstrapTimeoutHandler } @Override - public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) + public void channelActive(ChannelHandlerContext ctx) throws Exception { if (timeoutNanos > 0) { timeout = timer.newTimeout(new HandshakeTimeoutTask(ctx), timeoutNanos, TimeUnit.NANOSECONDS); } - ctx.sendUpstream(e); + super.channelActive(ctx); } @Override - public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) + public void channelInactive(ChannelHandlerContext ctx) throws Exception { if (timeout != null) { timeout.cancel(); timeout = null; } + super.channelInactive(ctx); } private final class HandshakeTimeoutTask implements TimerTask { @@ -76,10 +75,10 @@ public class BootstrapTimeoutHandler return; } - if (!ctx.getChannel().isOpen()) { + if (!ctx.channel().isOpen()) { return; } - ctx.getChannel().disconnect(); + ctx.channel().disconnect(); } } } diff --git a/src/main/java/org/sdnplatform/sync/internal/remote/RSHandshakeTimeoutHandler.java b/src/main/java/org/sdnplatform/sync/internal/remote/RSHandshakeTimeoutHandler.java index b8cf1b7e9aa33791d34e2f5ea55d8aacb85886ea..4c6ad767c2ba91ff7f622e98db20311fb4d320e8 100644 --- a/src/main/java/org/sdnplatform/sync/internal/remote/RSHandshakeTimeoutHandler.java +++ b/src/main/java/org/sdnplatform/sync/internal/remote/RSHandshakeTimeoutHandler.java @@ -18,18 +18,16 @@ package org.sdnplatform.sync.internal.remote; import java.util.concurrent.TimeUnit; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelStateEvent; -import org.jboss.netty.channel.SimpleChannelUpstreamHandler; -import org.jboss.netty.util.Timeout; -import org.jboss.netty.util.Timer; -import org.jboss.netty.util.TimerTask; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; /** * Trigger a timeout if the bootstrap process stalls */ -public class RSHandshakeTimeoutHandler - extends SimpleChannelUpstreamHandler { +public class RSHandshakeTimeoutHandler extends ChannelInboundHandlerAdapter { final Timer timer; final long timeoutNanos; @@ -46,22 +44,23 @@ public class RSHandshakeTimeoutHandler } @Override - public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) + public void channelActive(ChannelHandlerContext ctx) throws Exception { if (timeoutNanos > 0) { timeout = timer.newTimeout(new HandshakeTimeoutTask(ctx), timeoutNanos, TimeUnit.NANOSECONDS); } - ctx.sendUpstream(e); + super.channelActive(ctx); } @Override - public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) + public void channelInactive(ChannelHandlerContext ctx) throws Exception { if (timeout != null) { timeout.cancel(); timeout = null; } + super.channelInactive(ctx); } private final class HandshakeTimeoutTask implements TimerTask { @@ -78,11 +77,11 @@ public class RSHandshakeTimeoutHandler return; } - if (!ctx.getChannel().isOpen()) { + if (!ctx.channel().isOpen()) { return; } if (channelHandler.syncManager.ready == false) - ctx.getChannel().disconnect(); + ctx.channel().disconnect(); } } } diff --git a/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncChannelHandler.java b/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncChannelHandler.java index 9ce4ecbb375e24949c093847b6d44c5f10942881..38ce819579c27f611e4049acd73d95d0b9f9ee9a 100644 --- a/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncChannelHandler.java +++ b/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncChannelHandler.java @@ -2,9 +2,8 @@ package org.sdnplatform.sync.internal.remote; import java.util.List; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelStateEvent; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; import org.sdnplatform.sync.Versioned; import org.sdnplatform.sync.error.AuthException; import org.sdnplatform.sync.error.SyncException; @@ -42,17 +41,17 @@ public class RemoteSyncChannelHandler extends AbstractRPCChannelHandler { // **************************** @Override - public void channelOpen(ChannelHandlerContext ctx, - ChannelStateEvent e) throws Exception { - syncManager.cg.add(ctx.getChannel()); + public void channelActive(ChannelHandlerContext ctx) throws Exception { + syncManager.cg.add(ctx.channel()); + super.channelActive(ctx); } @Override - public void channelDisconnected(ChannelHandlerContext ctx, - ChannelStateEvent e) throws Exception { + public void channelInactive(ChannelHandlerContext ctx) throws Exception { this.syncManager.channel = null; syncManager.ready = false; syncManager.channelDisconnected(null); + super.channelInactive(ctx); } // ****************************************** diff --git a/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncChannelInitializer.java b/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncChannelInitializer.java new file mode 100644 index 0000000000000000000000000000000000000000..bba84651d504a73698085bb1d9d9cea3e5745ef9 --- /dev/null +++ b/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncChannelInitializer.java @@ -0,0 +1,47 @@ +package org.sdnplatform.sync.internal.remote; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.util.Timer; + +import org.sdnplatform.sync.internal.rpc.SyncMessageDecoder; +import org.sdnplatform.sync.internal.rpc.SyncMessageEncoder; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Pipeline factory for the remote sync service + * @author readams + */ +public class RemoteSyncChannelInitializer extends ChannelInitializer<Channel> { + protected static final Logger logger = + LoggerFactory.getLogger(RemoteSyncChannelInitializer.class.getName()); + + private final RemoteSyncManager syncManager; + private final Timer timer; + + private static final int maxFrameSize = 1024 * 1024 * 10; + + public RemoteSyncChannelInitializer(Timer timer, RemoteSyncManager syncManager) { + super(); + this.syncManager = syncManager; + this.timer = timer; + } + + @Override + protected void initChannel(Channel ch) throws Exception { + RemoteSyncChannelHandler channelHandler = + new RemoteSyncChannelHandler(syncManager); + + ChannelPipeline pipeline = ch.pipeline(); + + pipeline.addLast("syncMessageDecoder", new SyncMessageDecoder(maxFrameSize)); + + pipeline.addLast("syncMessageEncoder", new SyncMessageEncoder()); + + pipeline.addLast("timeout", new RSHandshakeTimeoutHandler(channelHandler, timer, 3)); + + pipeline.addLast("handler", channelHandler); + } +} \ No newline at end of file diff --git a/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncManager.java b/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncManager.java index efcc3ec67d602d722420d0b1989a77b97045a720..23b8e7bc24b93d37d565081c5da17252be97a95f 100644 --- a/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncManager.java +++ b/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncManager.java @@ -5,19 +5,23 @@ import java.net.SocketAddress; import java.util.Collection; import java.util.Map; import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ExecutorService; -import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; import java.util.concurrent.atomic.AtomicInteger; -import org.jboss.netty.bootstrap.ClientBootstrap; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelFuture; -import org.jboss.netty.channel.group.ChannelGroup; -import org.jboss.netty.channel.group.DefaultChannelGroup; -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; +import io.netty.bootstrap.Bootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.group.ChannelGroup; +import io.netty.channel.group.DefaultChannelGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.HashedWheelTimer; +import io.netty.util.concurrent.GlobalEventExecutor; + import org.sdnplatform.sync.error.RemoteStoreException; import org.sdnplatform.sync.error.SyncException; import org.sdnplatform.sync.error.SyncRuntimeException; @@ -53,10 +57,9 @@ public class RemoteSyncManager extends AbstractSyncManager { /** * Channel group that will hold all our channels */ - final ChannelGroup cg = new DefaultChannelGroup("Internal RPC"); - RemoteSyncPipelineFactory pipelineFactory; - ExecutorService bossExecutor; - ExecutorService workerExecutor; + final ChannelGroup cg = new DefaultChannelGroup("Internal RPC", GlobalEventExecutor.INSTANCE); + RemoteSyncChannelInitializer pipelineFactory; + EventLoopGroup workerExecutor; /** * Active connection to server @@ -75,7 +78,7 @@ public class RemoteSyncManager extends AbstractSyncManager { /** * Client bootstrap */ - protected ClientBootstrap clientBootstrap; + protected Bootstrap clientBootstrap; /** * Transaction ID used in message headers in the RPC protocol @@ -92,6 +95,11 @@ public class RemoteSyncManager extends AbstractSyncManager { */ protected int port = 6642; + /** + * Timer for Netty + */ + private HashedWheelTimer timer; + protected AuthScheme authScheme; protected String keyStorePath; protected String keyStorePassword; @@ -153,19 +161,16 @@ public class RemoteSyncManager extends AbstractSyncManager { logger.debug("Failed to cleanly shut down remote sync"); return; } - if (clientBootstrap != null) { - clientBootstrap.releaseExternalResources(); - } clientBootstrap = null; - if (pipelineFactory != null) - pipelineFactory.releaseExternalResources(); pipelineFactory = null; - if (workerExecutor != null) - workerExecutor.shutdown(); - workerExecutor = null; - if (bossExecutor != null) - bossExecutor.shutdown(); - bossExecutor = null; + if (workerExecutor != null) { + workerExecutor.shutdownGracefully(); + workerExecutor = null; + } + if (timer != null) { + timer.stop(); + timer = null; + } } catch (InterruptedException e) { logger.debug("Interrupted while shutting down remote sync"); } @@ -195,24 +200,22 @@ public class RemoteSyncManager extends AbstractSyncManager { public void startUp(FloodlightModuleContext context) throws FloodlightModuleException { shutdown = false; - bossExecutor = Executors.newCachedThreadPool(); - workerExecutor = Executors.newCachedThreadPool(); + workerExecutor = new NioEventLoopGroup(); + timer = new HashedWheelTimer(); + + pipelineFactory = new RemoteSyncChannelInitializer(timer, this); + + final Bootstrap bootstrap = new Bootstrap() + .channel(NioSocketChannel.class) + .group(workerExecutor) + .option(ChannelOption.SO_REUSEADDR, true) + .option(ChannelOption.SO_KEEPALIVE, true) + .option(ChannelOption.TCP_NODELAY, true) + .option(ChannelOption.SO_SNDBUF, RPCService.SEND_BUFFER_SIZE) + .option(ChannelOption.SO_RCVBUF, RPCService.SEND_BUFFER_SIZE) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, RPCService.CONNECT_TIMEOUT) + .handler(pipelineFactory); - final ClientBootstrap bootstrap = - new ClientBootstrap( - new NioClientSocketChannelFactory(bossExecutor, - workerExecutor)); - bootstrap.setOption("child.reuseAddr", true); - bootstrap.setOption("child.keepAlive", true); - bootstrap.setOption("child.tcpNoDelay", true); - bootstrap.setOption("child.sendBufferSize", - RPCService.SEND_BUFFER_SIZE); - bootstrap.setOption("child.receiveBufferSize", - RPCService.SEND_BUFFER_SIZE); - bootstrap.setOption("child.connectTimeoutMillis", - RPCService.CONNECT_TIMEOUT); - pipelineFactory = new RemoteSyncPipelineFactory(this); - bootstrap.setPipelineFactory(pipelineFactory); clientBootstrap = bootstrap; } @@ -264,7 +267,7 @@ public class RemoteSyncManager extends AbstractSyncManager { } } } - channel.write(request); + channel.writeAndFlush(request); return future; } @@ -316,24 +319,24 @@ public class RemoteSyncManager extends AbstractSyncManager { protected boolean connect(String hostname, int port) { ready = false; - if (channel == null || !channel.isConnected()) { + if (channel == null || !channel.isActive()) { SocketAddress sa = new InetSocketAddress(hostname, port); ChannelFuture future = clientBootstrap.connect(sa); future.awaitUninterruptibly(); if (!future.isSuccess()) { logger.error("Could not connect to " + hostname + - ":" + port, future.getCause()); + ":" + port, future.cause()); return false; } - channel = future.getChannel(); + channel = future.channel(); } - while (!ready && channel != null && channel.isConnected()) { + while (!ready && channel != null && channel.isActive()) { try { Thread.sleep(10); } catch (InterruptedException e) { } } - if (!ready || channel == null || !channel.isConnected()) { + if (!ready || channel == null || !channel.isActive()) { logger.warn("Timed out connecting to {}:{}", hostname, port); return false; } diff --git a/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncPipelineFactory.java b/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncPipelineFactory.java deleted file mode 100644 index 4cf2d60bee6b1bcbe2958317d2ce0dda25530156..0000000000000000000000000000000000000000 --- a/src/main/java/org/sdnplatform/sync/internal/remote/RemoteSyncPipelineFactory.java +++ /dev/null @@ -1,56 +0,0 @@ -package org.sdnplatform.sync.internal.remote; - -import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.ChannelPipelineFactory; -import org.jboss.netty.channel.Channels; -import org.jboss.netty.util.ExternalResourceReleasable; -import org.jboss.netty.util.HashedWheelTimer; -import org.jboss.netty.util.Timer; -import org.sdnplatform.sync.internal.rpc.ThriftFrameDecoder; -import org.sdnplatform.sync.internal.rpc.ThriftFrameEncoder; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -/** - * Pipeline factory for the remote sync service - * @author readams - */ -public class RemoteSyncPipelineFactory - implements ChannelPipelineFactory, ExternalResourceReleasable { - protected static final Logger logger = - LoggerFactory.getLogger(RemoteSyncPipelineFactory.class.getName()); - - protected RemoteSyncManager syncManager; - protected Timer timer; - - private static final int maxFrameSize = 1024 * 1024 * 10; - - public RemoteSyncPipelineFactory(RemoteSyncManager syncManager) { - super(); - this.syncManager = syncManager; - this.timer = new HashedWheelTimer(); - } - - @Override - public ChannelPipeline getPipeline() throws Exception { - RemoteSyncChannelHandler channelHandler = - new RemoteSyncChannelHandler(syncManager); - ChannelPipeline pipeline = Channels.pipeline(); - - pipeline.addLast("frameDecoder", - new ThriftFrameDecoder(maxFrameSize)); - pipeline.addLast("frameEncoder", - new ThriftFrameEncoder()); - pipeline.addLast("timeout", - new RSHandshakeTimeoutHandler(channelHandler, - timer, 3)); - - pipeline.addLast("handler", channelHandler); - return pipeline; - } - - @Override - public void releaseExternalResources() { - timer.stop(); - } -} diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/AbstractRPCChannelHandler.java b/src/main/java/org/sdnplatform/sync/internal/rpc/AbstractRPCChannelHandler.java index 8fe90ff43552e1dbec4e9e9191039bddc89c8767..9d0f2b447908b3b413a2e837a5a4a71dddd38448 100644 --- a/src/main/java/org/sdnplatform/sync/internal/rpc/AbstractRPCChannelHandler.java +++ b/src/main/java/org/sdnplatform/sync/internal/rpc/AbstractRPCChannelHandler.java @@ -11,15 +11,12 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import javax.xml.bind.DatatypeConverter; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelStateEvent; -import org.jboss.netty.channel.Channels; -import org.jboss.netty.channel.ExceptionEvent; -import org.jboss.netty.channel.MessageEvent; -import org.jboss.netty.handler.timeout.IdleStateAwareChannelHandler; -import org.jboss.netty.handler.timeout.IdleStateEvent; -import org.jboss.netty.handler.timeout.ReadTimeoutException; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.handler.timeout.IdleStateEvent; +import io.netty.handler.timeout.ReadTimeoutException; + import org.sdnplatform.sync.error.AuthException; import org.sdnplatform.sync.error.HandshakeTimeoutException; import org.sdnplatform.sync.error.SyncException; @@ -61,8 +58,7 @@ import org.slf4j.LoggerFactory; * a {@link SyncMessage} which will provide specific type information. * @author readams */ -public abstract class AbstractRPCChannelHandler - extends IdleStateAwareChannelHandler { +public abstract class AbstractRPCChannelHandler extends ChannelInboundHandlerAdapter { protected static final Logger logger = LoggerFactory.getLogger(AbstractRPCChannelHandler.class); protected String currentChallenge; @@ -84,8 +80,7 @@ public abstract class AbstractRPCChannelHandler // **************************** @Override - public void channelConnected(ChannelHandlerContext ctx, - ChannelStateEvent e) throws Exception { + public void channelActive(ChannelHandlerContext ctx) throws Exception { channelState = ChannelState.CONNECTED; HelloMessage m = new HelloMessage(); @@ -110,12 +105,18 @@ public abstract class AbstractRPCChannelHandler } SyncMessage bsm = new SyncMessage(MessageType.HELLO); bsm.setHello(m); - ctx.getChannel().write(bsm); + ctx.channel().writeAndFlush(bsm); } - + @Override - public void channelIdle(ChannelHandlerContext ctx, - IdleStateEvent e) throws Exception { + public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception { + if (evt instanceof IdleStateEvent) { + channelIdle(ctx, (IdleStateEvent) evt); + } + super.userEventTriggered(ctx, evt); + } + + public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e) throws Exception { // send an echo request EchoRequestMessage m = new EchoRequestMessage(); AsyncMessageHeader header = new AsyncMessageHeader(); @@ -123,53 +124,50 @@ public abstract class AbstractRPCChannelHandler m.setHeader(header); SyncMessage bsm = new SyncMessage(MessageType.ECHO_REQUEST); bsm.setEchoRequest(m); - ctx.getChannel().write(bsm); + ctx.channel().writeAndFlush(bsm); } @Override - public void exceptionCaught(ChannelHandlerContext ctx, - ExceptionEvent e) throws Exception { - if (e.getCause() instanceof ReadTimeoutException) { + public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { + if (cause instanceof ReadTimeoutException) { // read timeout logger.error("[{}->{}] Disconnecting RPC node due to read timeout", getLocalNodeIdString(), getRemoteNodeIdString()); - ctx.getChannel().close(); - } else if (e.getCause() instanceof HandshakeTimeoutException) { + ctx.channel().close(); + } else if (cause instanceof HandshakeTimeoutException) { // read timeout logger.error("[{}->{}] Disconnecting RPC node due to " + "handshake timeout", getLocalNodeIdString(), getRemoteNodeIdString()); - ctx.getChannel().close(); - } else if (e.getCause() instanceof ConnectException || - e.getCause() instanceof IOException) { + ctx.channel().close(); + } else if (cause instanceof ConnectException || + cause instanceof IOException) { logger.debug("[{}->{}] {}: {}", new Object[] {getLocalNodeIdString(), getRemoteNodeIdString(), - e.getCause().getClass().getName(), - e.getCause().getMessage()}); + cause.getClass().getName(), + cause.getMessage()}); } else { logger.error("[{}->{}] An error occurred on RPC channel", new Object[]{getLocalNodeIdString(), getRemoteNodeIdString(), - e.getCause()}); - ctx.getChannel().close(); + cause}); + ctx.channel().close(); } } @Override - public void messageReceived(ChannelHandlerContext ctx, - MessageEvent e) throws Exception { - Object message = e.getMessage(); + public void channelRead(ChannelHandlerContext ctx, Object message) throws Exception { if (message instanceof SyncMessage) { - handleSyncMessage((SyncMessage)message, ctx.getChannel()); + handleSyncMessage((SyncMessage)message, ctx.channel()); } else if (message instanceof List) { for (Object i : (List<?>)message) { if (i instanceof SyncMessage) { try { handleSyncMessage((SyncMessage)i, - ctx.getChannel()); + ctx.channel()); } catch (Exception ex) { - Channels.fireExceptionCaught(ctx, ex); + ctx.fireExceptionCaught(ex); } } } @@ -323,7 +321,7 @@ public abstract class AbstractRPCChannelHandler new Object[]{getLocalNodeIdString(), getRemoteNodeIdString(), e.getMessage()}); - channel.write(getError(request.getHeader().getTransactionId(), + channel.writeAndFlush(getError(request.getHeader().getTransactionId(), e, MessageType.HELLO)); channel.close(); } @@ -355,7 +353,7 @@ public abstract class AbstractRPCChannelHandler AuthChallengeResponse reply = new AuthChallengeResponse(); reply.setResponse(generateResponse(cr.getChallenge())); m.setAuthChallengeResponse(reply); - channel.write(bsm); + channel.writeAndFlush(bsm); } else { throw new AuthException("No authentication data in " + "handshake message"); @@ -382,7 +380,7 @@ public abstract class AbstractRPCChannelHandler m.setHeader(header); SyncMessage bsm = new SyncMessage(MessageType.ECHO_REPLY); bsm.setEchoReply(m); - channel.write(bsm); + channel.writeAndFlush(bsm); } protected void handleGetRequest(GetRequestMessage request, @@ -548,7 +546,7 @@ public abstract class AbstractRPCChannelHandler new Object[]{getLocalNodeIdString(), getRemoteNodeIdString(), message}); - channel.write(getError(transactionId, + channel.writeAndFlush(getError(transactionId, new SyncException(message), type)); } diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/HandshakeTimeoutHandler.java b/src/main/java/org/sdnplatform/sync/internal/rpc/HandshakeTimeoutHandler.java index 2ba0e4bb9be944b6729ee23cf0bf31b70040e4d0..7fc9b7f9a59fa3d7f4fef2cdb35d6eafdb7809ee 100644 --- a/src/main/java/org/sdnplatform/sync/internal/rpc/HandshakeTimeoutHandler.java +++ b/src/main/java/org/sdnplatform/sync/internal/rpc/HandshakeTimeoutHandler.java @@ -19,23 +19,18 @@ package org.sdnplatform.sync.internal.rpc; import java.util.concurrent.TimeUnit; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelStateEvent; -import org.jboss.netty.channel.Channels; -import org.jboss.netty.channel.SimpleChannelUpstreamHandler; -import org.jboss.netty.util.ExternalResourceReleasable; -import org.jboss.netty.util.Timeout; -import org.jboss.netty.util.Timer; -import org.jboss.netty.util.TimerTask; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelInboundHandlerAdapter; +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; import org.sdnplatform.sync.error.HandshakeTimeoutException; /** * Trigger a timeout if a switch fails to complete handshake soon enough */ -public class HandshakeTimeoutHandler - extends SimpleChannelUpstreamHandler - implements ExternalResourceReleasable { +public class HandshakeTimeoutHandler extends ChannelInboundHandlerAdapter { static final HandshakeTimeoutException EXCEPTION = new HandshakeTimeoutException(); @@ -55,27 +50,23 @@ public class HandshakeTimeoutHandler } @Override - public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e) + public void channelActive(ChannelHandlerContext ctx) throws Exception { if (timeoutNanos > 0) { timeout = timer.newTimeout(new HandshakeTimeoutTask(ctx), timeoutNanos, TimeUnit.NANOSECONDS); } - ctx.sendUpstream(e); + ctx.fireChannelActive(); } @Override - public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e) + public void channelInactive(ChannelHandlerContext ctx) throws Exception { if (timeout != null) { timeout.cancel(); timeout = null; } - } - - @Override - public void releaseExternalResources() { - timer.stop(); + ctx.fireChannelInactive(); } private final class HandshakeTimeoutTask implements TimerTask { @@ -92,14 +83,14 @@ public class HandshakeTimeoutHandler return; } - if (!ctx.getChannel().isOpen()) { + if (!ctx.channel().isOpen()) { return; } if (!handler.isClientConnection && ((handler.remoteNode == null || !handler.rpcService.isConnected(handler.remoteNode. getNodeId())))) - Channels.fireExceptionCaught(ctx, EXCEPTION); + ctx.fireExceptionCaught(EXCEPTION); } } } diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/RPCChannelHandler.java b/src/main/java/org/sdnplatform/sync/internal/rpc/RPCChannelHandler.java index bb7f942cf14abf72bd7aefba05b6d46291f3756a..a1c4fd7af7e73f9f97f5d165866adff4cca9fd10 100644 --- a/src/main/java/org/sdnplatform/sync/internal/rpc/RPCChannelHandler.java +++ b/src/main/java/org/sdnplatform/sync/internal/rpc/RPCChannelHandler.java @@ -8,10 +8,9 @@ import java.util.Map.Entry; import net.floodlightcontroller.debugcounter.IDebugCounter; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelStateEvent; -import org.jboss.netty.channel.MessageEvent; +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; + import org.sdnplatform.sync.IClosableIterator; import org.sdnplatform.sync.IStoreClient; import org.sdnplatform.sync.IVersion; @@ -61,29 +60,23 @@ public class RPCChannelHandler extends AbstractRPCChannelHandler { // **************************** @Override - public void channelOpen(ChannelHandlerContext ctx, - ChannelStateEvent e) throws Exception { - rpcService.cg.add(ctx.getChannel()); + public void channelActive(ChannelHandlerContext ctx) throws Exception { + rpcService.getChannelGroup().add(ctx.channel()); + super.channelActive(ctx); } @Override - public void channelDisconnected(ChannelHandlerContext ctx, - ChannelStateEvent e) throws Exception { + public void channelInactive(ChannelHandlerContext ctx) throws Exception { if (remoteNode != null) { rpcService.disconnectNode(remoteNode.getNodeId()); } + super.channelInactive(ctx); } // ****************************************** // AbstractRPCChannelHandler message handlers // ****************************************** - @Override - public void messageReceived(ChannelHandlerContext ctx, - MessageEvent e) throws Exception { - super.messageReceived(ctx, e); - } - @Override protected void handleHello(HelloMessage hello, Channel channel) { if (!hello.isSetNodeId()) { @@ -109,7 +102,7 @@ public class RPCChannelHandler extends AbstractRPCChannelHandler { header.setTransactionId(getTransactionId()); srm.setHeader(header); SyncMessage bsm = new SyncMessage(MessageType.FULL_SYNC_REQUEST); - channel.write(bsm); + channel.writeAndFlush(bsm); // XXX - TODO - if last connection was longer ago than the tombstone // timeout, then we need to do a complete flush and reload of our @@ -142,9 +135,9 @@ public class RPCChannelHandler extends AbstractRPCChannelHandler { SyncMessage bsm = new SyncMessage(MessageType.GET_RESPONSE); bsm.setGetResponse(m); - channel.write(bsm); + channel.writeAndFlush(bsm); } catch (Exception e) { - channel.write(getError(request.getHeader().getTransactionId(), e, + channel.writeAndFlush(getError(request.getHeader().getTransactionId(), e, MessageType.GET_REQUEST)); } } @@ -187,9 +180,9 @@ public class RPCChannelHandler extends AbstractRPCChannelHandler { SyncMessage bsm = new SyncMessage(MessageType.PUT_RESPONSE); bsm.setPutResponse(m); - channel.write(bsm); + channel.writeAndFlush(bsm); } catch (Exception e) { - channel.write(getError(request.getHeader().getTransactionId(), e, + channel.writeAndFlush(getError(request.getHeader().getTransactionId(), e, MessageType.PUT_REQUEST)); } } @@ -226,9 +219,9 @@ public class RPCChannelHandler extends AbstractRPCChannelHandler { SyncMessage bsm = new SyncMessage(MessageType.DELETE_RESPONSE); bsm.setDeleteResponse(m); - channel.write(bsm); + channel.writeAndFlush(bsm); } catch (Exception e) { - channel.write(getError(request.getHeader().getTransactionId(), e, + channel.writeAndFlush(getError(request.getHeader().getTransactionId(), e, MessageType.DELETE_REQUEST)); } } @@ -268,9 +261,9 @@ public class RPCChannelHandler extends AbstractRPCChannelHandler { updateCounter(SyncManager.counterReceivedValues, request.getValuesSize()); - channel.write(bsm); + channel.writeAndFlush(bsm); } catch (Exception e) { - channel.write(getError(request.getHeader().getTransactionId(), e, + channel.writeAndFlush(getError(request.getHeader().getTransactionId(), e, MessageType.SYNC_VALUE)); } } @@ -313,10 +306,10 @@ public class RPCChannelHandler extends AbstractRPCChannelHandler { getRemoteNodeIdString(), srm.getKeysSize()}); } - channel.write(bsm); + channel.writeAndFlush(bsm); } catch (Exception e) { - channel.write(getError(request.getHeader().getTransactionId(), + channel.writeAndFlush(getError(request.getHeader().getTransactionId(), e, MessageType.SYNC_OFFER)); } } @@ -355,7 +348,7 @@ public class RPCChannelHandler extends AbstractRPCChannelHandler { bsm)); } } catch (Exception e) { - channel.write(getError(request.getHeader().getTransactionId(), e, + channel.writeAndFlush(getError(request.getHeader().getTransactionId(), e, MessageType.SYNC_REQUEST)); } } @@ -402,9 +395,9 @@ public class RPCChannelHandler extends AbstractRPCChannelHandler { SyncMessage bsm = new SyncMessage(MessageType.CURSOR_RESPONSE); bsm.setCursorResponse(m); - channel.write(bsm); + channel.writeAndFlush(bsm); } catch (Exception e) { - channel.write(getError(request.getHeader().getTransactionId(), + channel.writeAndFlush(getError(request.getHeader().getTransactionId(), e, MessageType.CURSOR_REQUEST)); } } @@ -426,9 +419,9 @@ public class RPCChannelHandler extends AbstractRPCChannelHandler { SyncMessage bsm = new SyncMessage(MessageType.REGISTER_RESPONSE); bsm.setRegisterResponse(m); - channel.write(bsm); + channel.writeAndFlush(bsm); } catch (Exception e) { - channel.write(getError(request.getHeader().getTransactionId(), e, + channel.writeAndFlush(getError(request.getHeader().getTransactionId(), e, MessageType.REGISTER_REQUEST)); } } @@ -511,9 +504,9 @@ public class RPCChannelHandler extends AbstractRPCChannelHandler { SyncMessage bsm = new SyncMessage(MessageType.CLUSTER_JOIN_RESPONSE); bsm.setClusterJoinResponse(cjrm); - channel.write(bsm); + channel.writeAndFlush(bsm); } catch (Exception e) { - channel.write(getError(request.getHeader().getTransactionId(), e, + channel.writeAndFlush(getError(request.getHeader().getTransactionId(), e, MessageType.CLUSTER_JOIN_REQUEST)); } } diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/RPCChannelInitializer.java b/src/main/java/org/sdnplatform/sync/internal/rpc/RPCChannelInitializer.java new file mode 100644 index 0000000000000000000000000000000000000000..28b96de13d119aa23146c7ca5b6c55023d8c15ef --- /dev/null +++ b/src/main/java/org/sdnplatform/sync/internal/rpc/RPCChannelInitializer.java @@ -0,0 +1,58 @@ +package org.sdnplatform.sync.internal.rpc; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelInitializer; +import io.netty.channel.ChannelPipeline; +import io.netty.handler.timeout.IdleStateHandler; +import io.netty.handler.timeout.ReadTimeoutHandler; +import io.netty.util.Timer; + +import org.sdnplatform.sync.internal.SyncManager; + + +/** + * Pipeline factory for the sync service. + * @see SyncManager + * @author readams + */ +public class RPCChannelInitializer extends ChannelInitializer<Channel> { + + protected SyncManager syncManager; + protected RPCService rpcService; + protected Timer timer; + + private static final int maxFrameSize = 512 * 1024; + + public RPCChannelInitializer(SyncManager syncManager, + RPCService rpcService, + Timer timer) { + super(); + this.syncManager = syncManager; + this.rpcService = rpcService; + this.timer = timer; + } + + @Override + protected void initChannel(Channel ch) throws Exception { + RPCChannelHandler channelHandler = + new RPCChannelHandler(syncManager, rpcService); + + IdleStateHandler idleHandler = + new IdleStateHandler(5, 10, 0); + ReadTimeoutHandler readTimeoutHandler = + new ReadTimeoutHandler(30); + + ChannelPipeline pipeline = ch.pipeline(); + pipeline.addLast("idle", idleHandler); + pipeline.addLast("timeout", readTimeoutHandler); + pipeline.addLast("handshaketimeout", + new HandshakeTimeoutHandler(channelHandler, timer, 10)); + + pipeline.addLast("syncMessageDecoder", + new SyncMessageDecoder(maxFrameSize)); + pipeline.addLast("syncMessageEncoder", + new SyncMessageEncoder()); + + pipeline.addLast("handler", channelHandler); + } +} diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/RPCPipelineFactory.java b/src/main/java/org/sdnplatform/sync/internal/rpc/RPCPipelineFactory.java deleted file mode 100644 index 4016711e57d8fe851f8a6aa5ad1288974f265e37..0000000000000000000000000000000000000000 --- a/src/main/java/org/sdnplatform/sync/internal/rpc/RPCPipelineFactory.java +++ /dev/null @@ -1,66 +0,0 @@ -package org.sdnplatform.sync.internal.rpc; - -import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.ChannelPipelineFactory; -import org.jboss.netty.channel.Channels; -import org.jboss.netty.handler.timeout.IdleStateHandler; -import org.jboss.netty.handler.timeout.ReadTimeoutHandler; -import org.jboss.netty.util.ExternalResourceReleasable; -import org.jboss.netty.util.HashedWheelTimer; -import org.jboss.netty.util.Timer; -import org.sdnplatform.sync.internal.SyncManager; - - -/** - * Pipeline factory for the sync service. - * @see SyncManager - * @author readams - */ -public class RPCPipelineFactory - implements ChannelPipelineFactory, ExternalResourceReleasable { - - protected SyncManager syncManager; - protected RPCService rpcService; - protected Timer timer; - - private static final int maxFrameSize = 512 * 1024; - - public RPCPipelineFactory(SyncManager syncManager, - RPCService rpcService) { - super(); - this.syncManager = syncManager; - this.rpcService = rpcService; - - this.timer = new HashedWheelTimer(); - } - - @Override - public ChannelPipeline getPipeline() throws Exception { - RPCChannelHandler channelHandler = - new RPCChannelHandler(syncManager, rpcService); - - IdleStateHandler idleHandler = - new IdleStateHandler(timer, 5, 10, 0); - ReadTimeoutHandler readTimeoutHandler = - new ReadTimeoutHandler(timer, 30); - - ChannelPipeline pipeline = Channels.pipeline(); - pipeline.addLast("idle", idleHandler); - pipeline.addLast("timeout", readTimeoutHandler); - pipeline.addLast("handshaketimeout", - new HandshakeTimeoutHandler(channelHandler, timer, 10)); - - pipeline.addLast("frameDecoder", - new ThriftFrameDecoder(maxFrameSize)); - pipeline.addLast("frameEncoder", - new ThriftFrameEncoder()); - - pipeline.addLast("handler", channelHandler); - return pipeline; - } - - @Override - public void releaseExternalResources() { - timer.stop(); - } -} diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/RPCService.java b/src/main/java/org/sdnplatform/sync/internal/rpc/RPCService.java index 60dbcbbc8e9089476db022de386e70c4bd51162f..962e355437fd946335f5f56c7a2c95977ca75670 100644 --- a/src/main/java/org/sdnplatform/sync/internal/rpc/RPCService.java +++ b/src/main/java/org/sdnplatform/sync/internal/rpc/RPCService.java @@ -18,17 +18,21 @@ import java.util.concurrent.LinkedTransferQueue; import net.floodlightcontroller.core.util.SingletonTask; import net.floodlightcontroller.debugcounter.IDebugCounterService; +import io.netty.bootstrap.Bootstrap; +import io.netty.bootstrap.ServerBootstrap; +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelFutureListener; +import io.netty.channel.ChannelOption; +import io.netty.channel.EventLoopGroup; +import io.netty.channel.group.ChannelGroup; +import io.netty.channel.group.DefaultChannelGroup; +import io.netty.channel.nio.NioEventLoopGroup; +import io.netty.channel.socket.nio.NioServerSocketChannel; +import io.netty.channel.socket.nio.NioSocketChannel; +import io.netty.util.Timer; +import io.netty.util.concurrent.GlobalEventExecutor; -import org.jboss.netty.bootstrap.ClientBootstrap; -import org.jboss.netty.bootstrap.ServerBootstrap; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelFuture; -import org.jboss.netty.channel.ChannelFutureListener; -import org.jboss.netty.channel.ChannelPipelineFactory; -import org.jboss.netty.channel.group.ChannelGroup; -import org.jboss.netty.channel.group.DefaultChannelGroup; -import org.jboss.netty.channel.socket.nio.NioClientSocketChannelFactory; -import org.jboss.netty.channel.socket.nio.NioServerSocketChannelFactory; import org.sdnplatform.sync.internal.SyncManager; import org.sdnplatform.sync.internal.config.Node; import org.sdnplatform.sync.internal.util.Pair; @@ -58,32 +62,27 @@ public class RPCService { /** * Channel group that will hold all our channels */ - final ChannelGroup cg = new DefaultChannelGroup("Internal RPC"); + private final ChannelGroup cg = new DefaultChannelGroup("Internal RPC", GlobalEventExecutor.INSTANCE); /** - * {@link ExecutorService} used for netty boss threads + * {@link EventLoopGroup} used for netty boss threads */ - protected ExecutorService bossExecutor; + protected EventLoopGroup bossGroup; /** - * {@link ExecutorService} used for netty worker threads + * {@link EventLoopGroup} used for netty worker threads */ - protected ExecutorService workerExecutor; + protected EventLoopGroup workerGroup; /** * Netty {@link ClientBootstrap} used for creating client connections */ - protected ClientBootstrap clientBootstrap; - - /** - * Netty {@link ServerBootstrap} used for creating server connections - */ - protected ServerBootstrap serverBootstrap; + protected Bootstrap clientBootstrap; /** - * {@link ChannelPipelineFactory} for creating connections + * {@link RPCChannelInitializer} for creating connections */ - protected RPCPipelineFactory pipelineFactory; + protected RPCChannelInitializer channelInitializer; /** * Node connections @@ -122,6 +121,11 @@ public class RPCService { */ protected SingletonTask reconnectTask; + /** + * Timer used for timeouts + */ + private final Timer timer; + /** * If we want to rate-limit certain types of messages, we can do * so by limiting the overall number of outstanding messages. @@ -162,10 +166,12 @@ public class RPCService { protected static final int MAX_PENDING_MESSAGES = 500; public RPCService(SyncManager syncManager, - IDebugCounterService debugCounter) { + IDebugCounterService debugCounter, + Timer timer) { super(); this.syncManager = syncManager; this.debugCounter = debugCounter; + this.timer = timer; messageWindows = new ConcurrentHashMap<Short, MessageWindow>(); } @@ -205,13 +211,13 @@ public class RPCService { } }; - bossExecutor = Executors.newCachedThreadPool(f2); - workerExecutor = Executors.newCachedThreadPool(f2); + bossGroup = new NioEventLoopGroup(0, f2); + workerGroup = new NioEventLoopGroup(0, f2); - pipelineFactory = new RPCPipelineFactory(syncManager, this); + channelInitializer = new RPCChannelInitializer(syncManager, this, timer); - startServer(pipelineFactory); - startClients(pipelineFactory); + startServer(channelInitializer); + startClients(channelInitializer); } /** @@ -224,21 +230,15 @@ public class RPCService { logger.warn("Failed to cleanly shut down RPC server"); return; } - if (clientBootstrap != null) - clientBootstrap.releaseExternalResources(); + clientBootstrap = null; - if (serverBootstrap != null) - serverBootstrap.releaseExternalResources(); - serverBootstrap = null; - if (pipelineFactory != null) - pipelineFactory.releaseExternalResources(); - pipelineFactory = null; - if (bossExecutor != null) - bossExecutor.shutdown(); - bossExecutor = null; - if (workerExecutor != null) - workerExecutor.shutdown(); - workerExecutor = null; + channelInitializer = null; + if (bossGroup != null) + bossGroup.shutdownGracefully(); + bossGroup = null; + if (workerGroup != null) + workerGroup.shutdownGracefully(); + workerGroup = null; } catch (InterruptedException e) { logger.warn("Interrupted while shutting down RPC server"); } @@ -268,7 +268,7 @@ public class RPCService { NodeConnection nc = connections.get(nodeId); if (nc != null && nc.state == NodeConnectionState.CONNECTED) { waitForMessageWindow(bsm.getType(), nodeId, 0); - nc.nodeChannel.write(bsm); + nc.nodeChannel.writeAndFlush(bsm); return true; } return false; @@ -422,19 +422,17 @@ public class RPCService { /** * Start listening sockets */ - protected void startServer(ChannelPipelineFactory pipelineFactory) { - final ServerBootstrap bootstrap = - new ServerBootstrap( - new NioServerSocketChannelFactory(bossExecutor, - workerExecutor)); - bootstrap.setOption("reuseAddr", true); - bootstrap.setOption("child.keepAlive", true); - bootstrap.setOption("child.tcpNoDelay", true); - bootstrap.setOption("child.sendBufferSize", SEND_BUFFER_SIZE); - bootstrap.setOption("child.receiveBufferSize", SEND_BUFFER_SIZE); - - bootstrap.setPipelineFactory(pipelineFactory); - serverBootstrap = bootstrap; + protected void startServer(RPCChannelInitializer channelInitializer) { + final ServerBootstrap bootstrap = new ServerBootstrap(); + bootstrap.group(bossGroup, workerGroup) + .channel(NioServerSocketChannel.class) + .option(ChannelOption.SO_REUSEADDR, true) + .option(ChannelOption.SO_KEEPALIVE, true) + .option(ChannelOption.TCP_NODELAY, true) + .option(ChannelOption.SO_SNDBUF, SEND_BUFFER_SIZE) + .option(ChannelOption.SO_RCVBUF, SEND_BUFFER_SIZE) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT) + .childHandler(channelInitializer); int port = syncManager.getClusterConfig().getNode().getPort(); InetSocketAddress sa; @@ -445,8 +443,9 @@ public class RPCService { else sa = new InetSocketAddress(port); - cg.add(bootstrap.bind(sa)); - + ChannelFuture bindFuture = bootstrap.bind(sa); + cg.add(bindFuture.channel()); + logger.info("Listening for internal floodlight RPC on {}", sa); } @@ -468,12 +467,12 @@ public class RPCService { synchronized (connections) { NodeConnection c = connections.remove(node.getNodeId()); if (c != null) c.nuke(); - cf.getChannel().close(); + cf.channel().close(); } String message = "[unknown error]"; if (cf.isCancelled()) message = "Timed out on connect"; - if (cf.getCause() != null) message = cf.getCause().getMessage(); + if (cf.cause() != null) message = cf.cause().getMessage(); logger.debug("[{}->{}] Could not connect to RPC " + "node: {}", new Object[]{syncManager.getLocalNodeId(), @@ -511,17 +510,16 @@ public class RPCService { * any nodes with a lower ID so that there will be a single connection * between each pair of nodes which we'll use symmetrically */ - protected void startClients(ChannelPipelineFactory pipelineFactory) { - final ClientBootstrap bootstrap = - new ClientBootstrap( - new NioClientSocketChannelFactory(bossExecutor, - workerExecutor)); - bootstrap.setOption("child.reuseAddr", true); - bootstrap.setOption("child.keepAlive", true); - bootstrap.setOption("child.tcpNoDelay", true); - bootstrap.setOption("child.sendBufferSize", SEND_BUFFER_SIZE); - bootstrap.setOption("child.connectTimeoutMillis", CONNECT_TIMEOUT); - bootstrap.setPipelineFactory(pipelineFactory); + protected void startClients(RPCChannelInitializer channelInitializer) { + final Bootstrap bootstrap = new Bootstrap(); + bootstrap.group(workerGroup) + .channel(NioSocketChannel.class) + .option(ChannelOption.SO_REUSEADDR, true) + .option(ChannelOption.SO_KEEPALIVE, true) + .option(ChannelOption.TCP_NODELAY, true) + .option(ChannelOption.SO_SNDBUF, SEND_BUFFER_SIZE) + .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, CONNECT_TIMEOUT) + .handler(channelInitializer); clientBootstrap = bootstrap; ScheduledExecutorService ses = @@ -576,6 +574,14 @@ public class RPCService { doNodeConnect(n); } } + + /** + * Retrieve the Netty ChannelGroup + * @return + */ + protected ChannelGroup getChannelGroup() { + return cg; + } /** * Periodically ensure that all the node connections are alive @@ -617,7 +623,7 @@ public class RPCService { protected void nuke() { state = NodeConnectionState.NONE; - if (pendingFuture != null) pendingFuture.cancel(); + if (pendingFuture != null) pendingFuture.cancel(false); if (nodeChannel != null) nodeChannel.close(); pendingFuture = null; nodeChannel = null; @@ -668,4 +674,4 @@ public class RPCService { } } } -} +} \ No newline at end of file diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/SyncMessageDecoder.java b/src/main/java/org/sdnplatform/sync/internal/rpc/SyncMessageDecoder.java new file mode 100644 index 0000000000000000000000000000000000000000..c5ac20eb50bbaec865048c9050b6c86c66ac6e6e --- /dev/null +++ b/src/main/java/org/sdnplatform/sync/internal/rpc/SyncMessageDecoder.java @@ -0,0 +1,18 @@ +package org.sdnplatform.sync.internal.rpc; + +import net.floodlightcontroller.core.util.ThriftFrameDecoder; + +import org.sdnplatform.sync.thrift.SyncMessage; + +public class SyncMessageDecoder extends ThriftFrameDecoder<SyncMessage> { + + public SyncMessageDecoder(int maxSize) { + super(maxSize); + } + + @Override + protected SyncMessage allocateMessage() { + return new SyncMessage(); + } + +} diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/SyncMessageEncoder.java b/src/main/java/org/sdnplatform/sync/internal/rpc/SyncMessageEncoder.java new file mode 100644 index 0000000000000000000000000000000000000000..21e625f097b06b80721d0ea8b730c3527debb9ce --- /dev/null +++ b/src/main/java/org/sdnplatform/sync/internal/rpc/SyncMessageEncoder.java @@ -0,0 +1,9 @@ +package org.sdnplatform.sync.internal.rpc; + +import org.sdnplatform.sync.thrift.SyncMessage; + +import net.floodlightcontroller.core.util.ThriftFrameEncoder; + +public class SyncMessageEncoder extends ThriftFrameEncoder<SyncMessage> { + +} \ No newline at end of file diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/ThriftFrameDecoder.java b/src/main/java/org/sdnplatform/sync/internal/rpc/ThriftFrameDecoder.java deleted file mode 100644 index 50ec9d0f81f6613ba33555ed7366634baac2f759..0000000000000000000000000000000000000000 --- a/src/main/java/org/sdnplatform/sync/internal/rpc/ThriftFrameDecoder.java +++ /dev/null @@ -1,49 +0,0 @@ -package org.sdnplatform.sync.internal.rpc; - -import java.util.ArrayList; -import java.util.List; - -import org.apache.thrift.protocol.TCompactProtocol; -import org.apache.thrift.transport.TIOStreamTransport; -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBufferInputStream; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.handler.codec.frame.LengthFieldBasedFrameDecoder; -import org.sdnplatform.sync.thrift.SyncMessage; - -/** - * Decode a {@link SyncMessage} from the channel - * @author readams - */ -public class ThriftFrameDecoder extends LengthFieldBasedFrameDecoder { - - public ThriftFrameDecoder(int maxSize) { - super(maxSize, 0, 4, 0, 4); - } - - @Override - protected Object decode(ChannelHandlerContext ctx, - Channel channel, - ChannelBuffer buffer) throws Exception { - List<SyncMessage> ms = null; - ChannelBuffer frame = null; - while (null != (frame = (ChannelBuffer) super.decode(ctx, channel, - buffer))) { - if (ms == null) ms = new ArrayList<SyncMessage>(); - ChannelBufferInputStream is = new ChannelBufferInputStream(frame); - TCompactProtocol thriftProtocol = - new TCompactProtocol(new TIOStreamTransport(is)); - SyncMessage bsm = new SyncMessage(); - bsm.read(thriftProtocol); - ms.add(bsm); - } - return ms; - } - - @Override - protected ChannelBuffer extractFrame(ChannelBuffer buffer, - int index, int length) { - return buffer.slice(index, length); - } -} diff --git a/src/main/java/org/sdnplatform/sync/internal/rpc/ThriftFrameEncoder.java b/src/main/java/org/sdnplatform/sync/internal/rpc/ThriftFrameEncoder.java deleted file mode 100644 index d71e8d2de8e51dd0285fa55b85c3d6e6c75bcc4e..0000000000000000000000000000000000000000 --- a/src/main/java/org/sdnplatform/sync/internal/rpc/ThriftFrameEncoder.java +++ /dev/null @@ -1,39 +0,0 @@ -package org.sdnplatform.sync.internal.rpc; - -import org.apache.thrift.protocol.TCompactProtocol; -import org.apache.thrift.transport.TIOStreamTransport; -import org.jboss.netty.buffer.ChannelBuffer; -import org.jboss.netty.buffer.ChannelBufferOutputStream; -import org.jboss.netty.buffer.ChannelBuffers; -import org.jboss.netty.buffer.DynamicChannelBuffer; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.handler.codec.oneone.OneToOneEncoder; -import org.sdnplatform.sync.thrift.SyncMessage; - - -/** - * Encode a {@link SyncMessage} into the channel - * @author readams - * - */ -public class ThriftFrameEncoder extends OneToOneEncoder { - - @Override - protected Object encode(ChannelHandlerContext ctx, Channel channel, - Object message) throws Exception { - if (message instanceof SyncMessage) { - ChannelBuffer buf = new DynamicChannelBuffer(512); - ChannelBufferOutputStream os = new ChannelBufferOutputStream(buf); - TCompactProtocol thriftProtocol = - new TCompactProtocol(new TIOStreamTransport(os)); - ((SyncMessage) message).write(thriftProtocol); - - ChannelBuffer len = ChannelBuffers.buffer(4); - len.writeInt(buf.readableBytes()); - return ChannelBuffers.wrappedBuffer(len, buf); - } - return message; - } - -} diff --git a/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule b/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule index e11880981126a0cdae3c604e5c9f4a0783f9db5c..b7dfe953e276c4fea382bdb2bd1ff63851d52f74 100644 --- a/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule +++ b/src/main/resources/META-INF/services/net.floodlightcontroller.core.module.IFloodlightModule @@ -26,4 +26,5 @@ net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl net.floodlightcontroller.firewall.Firewall net.floodlightcontroller.accesscontrollist.ACL net.floodlightcontroller.dhcpserver.DHCPServer -net.floodlightcontroller.learningswitch.LearningSwitch \ No newline at end of file +net.floodlightcontroller.learningswitch.LearningSwitch +net.floodlightcontroller.statistics.StatisticsCollector \ No newline at end of file diff --git a/src/main/resources/floodlightdefault.properties b/src/main/resources/floodlightdefault.properties index 5b909f849c42df0dfeb5012b465aaaa7fa3cd27f..89bf32381355331dd93a011d0e59d2a2581d4d5d 100644 --- a/src/main/resources/floodlightdefault.properties +++ b/src/main/resources/floodlightdefault.properties @@ -15,18 +15,19 @@ net.floodlightcontroller.ui.web.StaticWebRoutable,\ net.floodlightcontroller.loadbalancer.LoadBalancer,\ net.floodlightcontroller.firewall.Firewall,\ net.floodlightcontroller.devicemanager.internal.DeviceManagerImpl,\ -net.floodlightcontroller.accesscontrollist.ACL +net.floodlightcontroller.accesscontrollist.ACL,\ +net.floodlightcontroller.statistics.StatisticsCollector org.sdnplatform.sync.internal.SyncManager.authScheme=CHALLENGE_RESPONSE org.sdnplatform.sync.internal.SyncManager.keyStorePath=/etc/floodlight/auth_credentials.jceks org.sdnplatform.sync.internal.SyncManager.dbPath=/var/lib/floodlight/ org.sdnplatform.sync.internal.SyncManager.port=6642 net.floodlightcontroller.forwarding.Forwarding.match=vlan, mac, ip, transport net.floodlightcontroller.forwarding.Forwarding.flood-arp=NO -net.floodlightcontroller.core.internal.FloodlightProvider.openflowPort=6653 +net.floodlightcontroller.core.internal.FloodlightProvider.openFlowPort=6653 net.floodlightcontroller.core.internal.FloodlightProvider.role=ACTIVE net.floodlightcontroller.linkdiscovery.internal.LinkDiscoveryManager.latency-history-size=10 net.floodlightcontroller.linkdiscovery.internal.LinkDiscoveryManager.latency-update-threshold=0.5 -net.floodlightcontroller.core.internal.OFSwitchManager.defaultMaxTablesToReceiveTableMissFlow=2 +net.floodlightcontroller.core.internal.OFSwitchManager.defaultMaxTablesToReceiveTableMissFlow=1 net.floodlightcontroller.core.internal.OFSwitchManager.maxTablesToReceiveTableMissFlowPerDpid={"00:00:00:00:00:00:00:01":"1","2":"1"} net.floodlightcontroller.core.internal.OFSwitchManager.clearTablesOnInitialHandshakeAsMaster=YES net.floodlightcontroller.core.internal.OFSwitchManager.clearTablesOnEachTransitionToMaster=YES @@ -41,3 +42,5 @@ net.floodlightcontroller.restserver.RestApiServer.useHttps=NO net.floodlightcontroller.restserver.RestApiServer.useHttp=YES net.floodlightcontroller.restserver.RestApiServer.httpsPort=8081 net.floodlightcontroller.restserver.RestApiServer.httpPort=8080 +net.floodlightcontroller.statistics.StatisticsCollector.enable=FALSE +net.floodlightcontroller.statistics.StatisticsCollector.collectionIntervalPortStatsSeconds=10 \ No newline at end of file diff --git a/src/main/resources/logback-test.xml b/src/main/resources/logback-test.xml index b5e3bfd8093df991c049e5f86e873a9834d12747..8fa1e8f86065f20fa51ea0b42336af21b4e15cdc 100644 --- a/src/main/resources/logback-test.xml +++ b/src/main/resources/logback-test.xml @@ -11,6 +11,7 @@ <appender-ref ref="EV_WARN_ERR" /> </root> <logger name="org" level="WARN"/> + <logger name="io" level="INFO"></logger> <!-- Netty logging --> <logger name="LogService" level="DEBUG"></logger> <!-- Restlet access logging --> <logger name="net.floodlightcontroller" level="INFO"/> <logger name="org.sdnplatform" level="INFO"></logger> diff --git a/src/test/java/net/floodlightcontroller/core/internal/MockOFConnection.java b/src/test/java/net/floodlightcontroller/core/internal/MockOFConnection.java index c1eab8ba8baedd748aaddeb089504befb4cad6a0..ccf19f5b057e6f019803f8bab4a35e82bcda9317 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/MockOFConnection.java +++ b/src/test/java/net/floodlightcontroller/core/internal/MockOFConnection.java @@ -4,6 +4,8 @@ import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.util.ArrayList; +import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; @@ -72,13 +74,15 @@ public class MockOFConnection implements IOFConnectionBackend { } @Override - public void write(OFMessage m) { + public boolean write(OFMessage m) { messages.add(m); + return true; } @Override - public void write(Iterable<OFMessage> msglist) { + public Collection<OFMessage> write(Iterable<OFMessage> msglist) { Iterables.addAll(messages, msglist); + return Collections.emptyList(); } static class RequestAndFuture<R extends OFMessage> { @@ -124,11 +128,6 @@ public class MockOFConnection implements IOFConnectionBackend { return this.connectedSince; } - @Override - public void flush() { - // no op - } - @Override public DatapathId getDatapathId() { return this.id; diff --git a/src/test/java/net/floodlightcontroller/core/internal/MockOFSwitchImpl.java b/src/test/java/net/floodlightcontroller/core/internal/MockOFSwitchImpl.java index 846f4f7b82ad89a557f6b30aaf0b1ddacd6f1675..0fcbc88c19b217e6d67141acc9f44b5f093c8046 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/MockOFSwitchImpl.java +++ b/src/test/java/net/floodlightcontroller/core/internal/MockOFSwitchImpl.java @@ -7,7 +7,6 @@ import java.util.Set; import java.util.concurrent.TimeUnit; import org.easymock.EasyMock; -import net.floodlightcontroller.core.OFSwitch; import org.projectfloodlight.openflow.protocol.OFCapabilities; import org.projectfloodlight.openflow.protocol.OFFactories; import org.projectfloodlight.openflow.protocol.OFStatsReply; diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerVer10Test.java b/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerVer10Test.java index 96147b02eb52b4323be4f804ffad6014c552809b..39ec2714e9aa9170a2f115f46cef7533e6593af8 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerVer10Test.java +++ b/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerVer10Test.java @@ -23,23 +23,25 @@ import java.util.Set; import org.easymock.Capture; import org.easymock.CaptureType; import org.easymock.EasyMock; +import org.easymock.IAnswer; import org.hamcrest.CoreMatchers; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.ChannelStateEvent; -import org.jboss.netty.channel.ExceptionEvent; -import org.jboss.netty.channel.MessageEvent; -import org.jboss.netty.util.HashedWheelTimer; -import org.jboss.netty.util.Timer; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.ChannelPromise; +import io.netty.channel.DefaultChannelPromise; +import io.netty.util.HashedWheelTimer; +import io.netty.util.Timer; + import org.junit.After; import org.junit.Before; import org.junit.Test; import net.floodlightcontroller.core.IOFConnectionBackend; -import net.floodlightcontroller.core.OFConnectionCounters; -import net.floodlightcontroller.core.internal.OpenflowPipelineFactory.PipelineHandler; -import net.floodlightcontroller.core.internal.OpenflowPipelineFactory.PipelineHandshakeTimeout; +import net.floodlightcontroller.core.internal.OFChannelInitializer.PipelineHandler; +import net.floodlightcontroller.core.internal.OFChannelInitializer.PipelineHandshakeTimeout; +import net.floodlightcontroller.core.test.TestEventLoop; import net.floodlightcontroller.debugcounter.DebugCounterServiceImpl; import net.floodlightcontroller.debugcounter.IDebugCounterService; @@ -67,6 +69,7 @@ import org.projectfloodlight.openflow.types.DatapathId; import org.projectfloodlight.openflow.types.OFPort; import org.projectfloodlight.openflow.types.U32; +import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; @@ -78,13 +81,10 @@ public class OFChannelHandlerVer10Test { private Channel channel; private Timer timer; private ChannelHandlerContext ctx; - private MessageEvent messageEvent; - private ChannelStateEvent channelStateEvent; private ChannelPipeline pipeline; - // FIXME:LOJI: Currently only use OF 1.0 private final OFFactory factory = OFFactories.getFactory(OFVersion.OF_10); - private Capture<ExceptionEvent> exceptionEventCapture; + private Capture<Throwable> exceptionEventCapture; private Capture<List<OFMessage>> writeCapture; private OFPortDesc portDesc; @@ -94,6 +94,8 @@ public class OFChannelHandlerVer10Test { private INewOFConnectionListener newConnectionListener; private Capture<IOFConnectionBackend> newConnection; private Capture<OFFeaturesReply> newFeaturesReply; + + private TestEventLoop eventLoop; public void setUpFeaturesReply() { portDesc = factory.buildPortDesc() @@ -119,13 +121,12 @@ public class OFChannelHandlerVer10Test { newConnectionListener = createMock(INewOFConnectionListener.class); newConnection = new Capture<IOFConnectionBackend>(); newFeaturesReply = new Capture<OFFeaturesReply>(); + eventLoop = new TestEventLoop(); ctx = createMock(ChannelHandlerContext.class); - channelStateEvent = createMock(ChannelStateEvent.class); channel = createMock(Channel.class); timer = new HashedWheelTimer(); - messageEvent = createMock(MessageEvent.class); - exceptionEventCapture = new Capture<ExceptionEvent>(CaptureType.ALL); + exceptionEventCapture = new Capture<Throwable>(CaptureType.ALL); pipeline = createMock(ChannelPipeline.class); writeCapture = new Capture<List<OFMessage>>(CaptureType.ALL); seenXids = null; @@ -150,16 +151,14 @@ public class OFChannelHandlerVer10Test { replay(switchManager); // Mock ctx and channelStateEvent - expect(ctx.getChannel()).andReturn(channel).anyTimes(); - expect(channelStateEvent.getChannel()).andReturn(channel).anyTimes(); - replay(ctx, channelStateEvent); + expect(ctx.channel()).andReturn(channel).anyTimes(); + expect(ctx.fireExceptionCaught(capture(exceptionEventCapture))).andReturn(ctx).anyTimes(); + replay(ctx); /* Setup an exception event capture on the channel. Right now * we only expect exception events to be send up the channel. * However, it's easy to extend to other events if we need it */ - pipeline.sendUpstream(capture(exceptionEventCapture)); - expectLastCall().anyTimes(); expect(pipeline.get(OFMessageDecoder.class)).andReturn(new OFMessageDecoder()).anyTimes(); replay(pipeline); } @@ -168,40 +167,34 @@ public class OFChannelHandlerVer10Test { public void tearDown() { /* ensure no exception was thrown */ if (exceptionEventCapture.hasCaptured()) { - Throwable ex = exceptionEventCapture.getValue().getCause(); + Throwable ex = exceptionEventCapture.getValue(); ex.printStackTrace(); - throw new AssertionError("Unexpected exception: " + - ex.getClass().getName() + "(" + ex + ")"); + Throwables.propagate(ex); } assertFalse("Unexpected messages have been captured", writeCapture.hasCaptured()); // verify all mocks. verify(channel); - verify(messageEvent); verify(switchManager); verify(ctx); - verify(channelStateEvent); verify(pipeline); } /** Reset the channel mock and set basic method call expectations */ void resetChannel() { reset(channel); - expect(channel.getPipeline()).andReturn(pipeline).anyTimes(); - expect(channel.getRemoteAddress()).andReturn(InetSocketAddress.createUnresolved("1.1.1.1", 80)).anyTimes(); - } - - - /** reset, setup, and replay the messageEvent mock for the given - * messages - */ - void setupMessageEvent(List<OFMessage> messages) { - reset(messageEvent); - expect(messageEvent.getMessage()).andReturn(messages).atLeastOnce(); - replay(messageEvent); + expect(channel.newPromise()).andAnswer(new IAnswer<ChannelPromise>() { + @Override + public ChannelPromise answer() throws Throwable { + return new DefaultChannelPromise(channel); + } + }).anyTimes(); + eventLoop = new TestEventLoop(); + expect(channel.eventLoop()).andReturn(eventLoop).anyTimes(); + expect(channel.pipeline()).andReturn(pipeline).anyTimes(); + expect(channel.remoteAddress()).andReturn(InetSocketAddress.createUnresolved("1.1.1.1", 80)).anyTimes(); } - /** reset, setup, and replay the messageEvent mock for the given * messages, mock controller send message to channel handler * @@ -209,7 +202,6 @@ public class OFChannelHandlerVer10Test { */ void sendMessageToHandlerWithControllerReset(List<OFMessage> messages) throws Exception { - sendMessageToHandlerNoControllerReset(messages); } @@ -220,9 +212,7 @@ public class OFChannelHandlerVer10Test { */ void sendMessageToHandlerNoControllerReset(List<OFMessage> messages) throws Exception { - setupMessageEvent(messages); - - handler.messageReceived(ctx, messageEvent); + handler.channelRead(ctx, messages); } /** @@ -256,7 +246,7 @@ public class OFChannelHandlerVer10Test { Class<? extends Throwable> expectedExceptionClass) { assertTrue("Excpected exception not thrown", exceptionEventCapture.hasCaptured()); - Throwable caughtEx = exceptionEventCapture.getValue().getCause(); + Throwable caughtEx = exceptionEventCapture.getValue(); assertEquals(expectedExceptionClass, caughtEx.getClass()); exceptionEventCapture.reset(); } @@ -277,16 +267,21 @@ public class OFChannelHandlerVer10Test { seenXids.add(xid); } } - + + @Test + public void testNullMsg() throws Exception { + reset(ctx); + expect(ctx.fireChannelRead(null)).andReturn(ctx).once(); + replay(ctx, channel); + + // null message is not passed to the handler + handler.channelRead(ctx, null); + verify(channel, ctx); + } @Test public void testInitState() throws Exception { - // Message event needs to be list - expect(messageEvent.getMessage()).andReturn(null); - replay(channel, messageEvent); - handler.messageReceived(ctx, messageEvent); - verify(channel, messageEvent); - verifyExceptionCaptured(AssertionError.class); + replay(channel); // We don't expect to receive /any/ messages in init state since // channelConnected moves us to a different state @@ -302,13 +297,10 @@ public class OFChannelHandlerVer10Test { public void moveToWaitHello() throws Exception { resetChannel(); - channel.write(capture(writeCapture)); - expectLastCall().andReturn(null).once(); + expect(channel.writeAndFlush(capture(writeCapture))).andReturn(null).once(); replay(channel); - // replay unused mocks - replay(messageEvent); - handler.channelConnected(ctx, channelStateEvent); + handler.channelActive(ctx); List<OFMessage> msgs = getMessagesFromCapture(); assertEquals(1, msgs.size()); @@ -327,8 +319,7 @@ public class OFChannelHandlerVer10Test { moveToWaitHello(); resetChannel(); - channel.write(capture(writeCapture)); - expectLastCall().andReturn(null).atLeastOnce(); + expect(channel.writeAndFlush(capture(writeCapture))).andReturn(null).atLeastOnce(); replay(channel); OFMessage hello = factory.buildHello().build(); @@ -387,8 +378,7 @@ public class OFChannelHandlerVer10Test { newConnection.getValue().setListener(connectionListener); resetChannel(); - channel.write(capture(writeCapture)); - expectLastCall().andReturn(null).atLeastOnce(); + expect(channel.writeAndFlush(capture(writeCapture))).andReturn(null).atLeastOnce(); replay(channel); // Send echo request. expect reply diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerVer13Test.java b/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerVer13Test.java index 9b9b6ce897bf00c210326fed7d49e315500e11eb..643bc416ba1df288845efe8ce1debb161e96c13e 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerVer13Test.java +++ b/src/test/java/net/floodlightcontroller/core/internal/OFChannelHandlerVer13Test.java @@ -23,23 +23,25 @@ import java.util.Set; import org.easymock.Capture; import org.easymock.CaptureType; import org.easymock.EasyMock; +import org.easymock.IAnswer; import org.hamcrest.CoreMatchers; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.channel.ChannelHandlerContext; -import org.jboss.netty.channel.ChannelPipeline; -import org.jboss.netty.channel.ChannelStateEvent; -import org.jboss.netty.channel.ExceptionEvent; -import org.jboss.netty.channel.MessageEvent; -import org.jboss.netty.util.HashedWheelTimer; -import org.jboss.netty.util.Timer; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelHandlerContext; +import io.netty.channel.ChannelPipeline; +import io.netty.channel.ChannelPromise; +import io.netty.channel.DefaultChannelPromise; +import io.netty.util.HashedWheelTimer; +import io.netty.util.Timer; + import org.junit.After; import org.junit.Before; import org.junit.Test; import net.floodlightcontroller.core.IOFConnectionBackend; -import net.floodlightcontroller.core.OFConnectionCounters; -import net.floodlightcontroller.core.internal.OpenflowPipelineFactory.PipelineHandler; -import net.floodlightcontroller.core.internal.OpenflowPipelineFactory.PipelineHandshakeTimeout; +import net.floodlightcontroller.core.internal.OFChannelInitializer.PipelineHandler; +import net.floodlightcontroller.core.internal.OFChannelInitializer.PipelineHandshakeTimeout; +import net.floodlightcontroller.core.test.TestEventLoop; import net.floodlightcontroller.debugcounter.DebugCounterServiceImpl; import net.floodlightcontroller.debugcounter.IDebugCounterService; @@ -68,408 +70,396 @@ import org.projectfloodlight.openflow.types.OFAuxId; import org.projectfloodlight.openflow.types.OFPort; import org.projectfloodlight.openflow.types.U32; +import com.google.common.base.Throwables; import com.google.common.collect.ImmutableList; public class OFChannelHandlerVer13Test { - private static final DatapathId dpid = DatapathId.of(0x42L); - - private IOFSwitchManager switchManager; - private IOFConnectionListener connectionListener; - private INewOFConnectionListener newConnectionListener; - private IDebugCounterService debugCounterService; - private OFChannelHandler handler; - private Channel channel; - private Timer timer; - private ChannelHandlerContext ctx; - private MessageEvent messageEvent; - private ChannelStateEvent channelStateEvent; - private ChannelPipeline pipeline; - private final OFFactory factory = OFFactories.getFactory(OFVersion.OF_13); - - private Capture<ExceptionEvent> exceptionEventCapture; - private Capture<List<OFMessage>> writeCapture; - - private OFFeaturesReply featuresReply; - private OFPortDesc portDesc; - - private Set<Long> seenXids = null; - - private Capture<IOFConnectionBackend> newConnection; - - private Capture<OFFeaturesReply> newFeaturesReply; - - public void setUpFeaturesReply() { - portDesc = factory.buildPortDesc() - .setName("Eth1") - .setPortNo(OFPort.of(1)) - .build(); - featuresReply = factory.buildFeaturesReply() - .setDatapathId(dpid) - .setNBuffers(1) - .setNTables((short)1) - .setCapabilities(EnumSet.<OFCapabilities>of(OFCapabilities.FLOW_STATS, OFCapabilities.TABLE_STATS)) - .setAuxiliaryId(OFAuxId.MAIN) - .build(); - } - - @Before - public void setUp() throws Exception { - setUpFeaturesReply(); - switchManager = createMock(IOFSwitchManager.class); - connectionListener = createMock(IOFConnectionListener.class); - newConnectionListener = createMock(INewOFConnectionListener.class); - newConnection = new Capture<IOFConnectionBackend>(); - newFeaturesReply = new Capture<OFFeaturesReply>(); - - ctx = createMock(ChannelHandlerContext.class); - channelStateEvent = createMock(ChannelStateEvent.class); - channel = createMock(Channel.class); - timer = new HashedWheelTimer(); - messageEvent = createMock(MessageEvent.class); - exceptionEventCapture = new Capture<ExceptionEvent>(CaptureType.ALL); - pipeline = createMock(ChannelPipeline.class); - writeCapture = new Capture<List<OFMessage>>(CaptureType.ALL); - seenXids = null; - - - // TODO: should mock IDebugCounterService and make sure - // the expected counters are updated. - debugCounterService = new DebugCounterServiceImpl(); - debugCounterService.registerModule(OFConnectionCounters.COUNTER_MODULE); - SwitchManagerCounters counters = - new SwitchManagerCounters(debugCounterService); - expect(switchManager.getCounters()).andReturn(counters).anyTimes(); - replay(switchManager); - handler = new OFChannelHandler(switchManager, newConnectionListener, - pipeline, debugCounterService, /* 62 is OF versions 1.0 thru 1.4 in decimal */ - timer, Collections.singletonList(U32.of(62)), OFFactories.getFactory(OFVersion.OF_14)); - - verify(switchManager); - reset(switchManager); - - resetChannel(); - - // replay controller. Reset it if you need more specific behavior - replay(switchManager); - - // Mock ctx and channelStateEvent - expect(ctx.getChannel()).andReturn(channel).anyTimes(); - expect(channelStateEvent.getChannel()).andReturn(channel).anyTimes(); - replay(ctx, channelStateEvent); - - /* Setup an exception event capture on the channel. Right now - * we only expect exception events to be send up the channel. - * However, it's easy to extend to other events if we need it - */ - pipeline.sendUpstream(capture(exceptionEventCapture)); - expectLastCall().anyTimes(); - expect(pipeline.get(OFMessageDecoder.class)).andReturn(new OFMessageDecoder()).anyTimes(); - replay(pipeline); - } - - @After - public void tearDown() { - /* ensure no exception was thrown */ - if (exceptionEventCapture.hasCaptured()) { - Throwable ex = exceptionEventCapture.getValue().getCause(); - ex.printStackTrace(); - throw new AssertionError("Unexpected exception: " + - ex.getClass().getName() + "(" + ex + ")"); - } - assertFalse("Unexpected messages have been captured", - writeCapture.hasCaptured()); - // verify all mocks. - verify(channel); - verify(messageEvent); - verify(switchManager); - verify(ctx); - verify(channelStateEvent); - verify(pipeline); - } - - /** Reset the channel mock and set basic method call expectations */ - void resetChannel() { - reset(channel); - expect(channel.getPipeline()).andReturn(pipeline).anyTimes(); - expect(channel.getRemoteAddress()).andReturn(null).anyTimes(); - } - - - /** reset, setup, and replay the messageEvent mock for the given - * messages - */ - void setupMessageEvent(List<OFMessage> messages) { - reset(messageEvent); - expect(messageEvent.getMessage()).andReturn(messages).atLeastOnce(); - replay(messageEvent); - } - - - /** 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 - */ - void sendMessageToHandlerWithControllerReset(List<OFMessage> messages) - throws Exception { - - sendMessageToHandlerNoControllerReset(messages); - } - - /** 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 - */ - void sendMessageToHandlerNoControllerReset(List<OFMessage> messages) - throws Exception { - setupMessageEvent(messages); - - handler.messageReceived(ctx, messageEvent); - } - - /** - * 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 - * OFMessages. - * Resets the channelWriteCapture. - */ - List<OFMessage> getMessagesFromCapture() { - List<OFMessage> msgs = new ArrayList<OFMessage>(); - - assertTrue("No write on channel was captured", - writeCapture.hasCaptured()); - List<List<OFMessage>> capturedVals = writeCapture.getValues(); - - for (List<OFMessage> oneWriteList: capturedVals) - msgs.addAll(oneWriteList); - writeCapture.reset(); - return msgs; - } - - - /** - * Verify that the given exception event capture (as returned by - * getAndInitExceptionCapture) has thrown an exception of the given - * expectedExceptionClass. - * Resets the capture - */ - void verifyExceptionCaptured( - Class<? extends Throwable> expectedExceptionClass) { - assertTrue("Excpected exception not thrown", - exceptionEventCapture.hasCaptured()); - Throwable caughtEx = exceptionEventCapture.getValue().getCause(); - assertEquals(expectedExceptionClass, caughtEx.getClass()); - exceptionEventCapture.reset(); - } - - /** 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. - */ - void verifyUniqueXids(List<OFMessage> msgs) { - if (seenXids == null) - seenXids = new HashSet<Long>(); - for (OFMessage m: msgs) { - long xid = m.getXid(); - assertTrue("Xid in messags is 0", xid != 0); - assertFalse("Xid " + xid + " has already been used", - seenXids.contains(xid)); - seenXids.add(xid); - } - } - - - @Test - public void testInitState() throws Exception { - // Message event needs to be list - expect(messageEvent.getMessage()).andReturn(null); - replay(channel, messageEvent); - handler.messageReceived(ctx, messageEvent); - verify(channel, messageEvent); - verifyExceptionCaptured(AssertionError.class); - - // We don't expect to receive /any/ messages in init state since - // channelConnected moves us to a different state - OFMessage m = factory.buildHello().build(); - sendMessageToHandlerWithControllerReset(ImmutableList.<OFMessage>of(m)); - - verifyExceptionCaptured(SwitchStateException.class); - assertThat(handler.getStateForTesting(), CoreMatchers.instanceOf(OFChannelHandler.InitState.class)); - } - - /* Move the channel from scratch to WAIT_HELLO state */ - @Test - public void moveToWaitHello() throws Exception { - resetChannel(); - channel.write(capture(writeCapture)); - expectLastCall().andReturn(null).once(); - replay(channel); - // replay unused mocks - replay(messageEvent); - - handler.channelConnected(ctx, channelStateEvent); - - List<OFMessage> msgs = getMessagesFromCapture(); - assertEquals(1, msgs.size()); - assertEquals(OFType.HELLO, msgs.get(0).getType()); - assertThat(handler.getStateForTesting(), CoreMatchers.instanceOf(OFChannelHandler.WaitHelloState.class)); - verifyUniqueXids(msgs); - } - - /** Move the channel from scratch to WAIT_FEATURES_REPLY state - * Builds on moveToWaitHello() - * adds testing for WAIT_HELLO state - */ - @Test - public void moveToWaitFeaturesReply() throws Exception { - moveToWaitHello(); - resetChannel(); - channel.write(capture(writeCapture)); - expectLastCall().andReturn(null).atLeastOnce(); - replay(channel); - - OFMessage hello = factory.buildHello().build(); - sendMessageToHandlerWithControllerReset(ImmutableList.<OFMessage>of(hello)); - - List<OFMessage> msgs = getMessagesFromCapture(); - assertEquals(1, msgs.size()); - assertEquals(OFType.FEATURES_REQUEST, msgs.get(0).getType()); - verifyUniqueXids(msgs); - - assertThat(handler.getStateForTesting(), CoreMatchers.instanceOf(OFChannelHandler.WaitFeaturesReplyState.class)); - } - - - /** Move the channel from scratch to WAIT_FEATURES_REPLY state - * Builds on moveToWaitHello() - * adds testing for WAIT_HELLO state - */ - @Test - public void moveToComplete() throws Exception { - moveToWaitFeaturesReply(); - - reset(pipeline); - HandshakeTimeoutHandler newHandler = new HandshakeTimeoutHandler( - handler, - timer, - PipelineHandshakeTimeout.SWITCH); - - expect( - pipeline.replace(EasyMock.eq(PipelineHandler.CHANNEL_HANDSHAKE_TIMEOUT), - EasyMock.eq(PipelineHandler.SWITCH_HANDSHAKE_TIMEOUT), - EasyMock.anyObject(HandshakeTimeoutHandler.class))).andReturn(newHandler) - .once(); - - replay(pipeline); - - newConnectionListener.connectionOpened(capture(newConnection), capture(newFeaturesReply)); - expectLastCall().once(); - replay(newConnectionListener); - - sendMessageToHandlerWithControllerReset(Collections.<OFMessage>singletonList(featuresReply)); - - assertThat(handler.getStateForTesting(), CoreMatchers.instanceOf(OFChannelHandler.CompleteState.class)); - assertTrue("A connection has been created and set", handler.getConnectionForTesting() != null); - verify(newConnectionListener); - assertTrue(newConnection.hasCaptured()); - assertThat(newFeaturesReply.getValue(), equalTo(featuresReply)); - } - - /** - * Test dispatch of messages while in Complete state - */ - @Test - public void testMessageDispatchComplete() throws Exception { - moveToComplete(); - newConnection.getValue().setListener(connectionListener); - - resetChannel(); - channel.write(capture(writeCapture)); - expectLastCall().andReturn(null).atLeastOnce(); - replay(channel); + private static final DatapathId dpid = DatapathId.of(0x42L); + + private IOFSwitchManager switchManager; + private IOFConnectionListener connectionListener; + private INewOFConnectionListener newConnectionListener; + private IDebugCounterService debugCounterService; + private OFChannelHandler handler; + private Channel channel; + private Timer timer; + private ChannelHandlerContext ctx; + private ChannelPipeline pipeline; + private final OFFactory factory = OFFactories.getFactory(OFVersion.OF_13); + + private Capture<Throwable> exceptionEventCapture; + private Capture<List<OFMessage>> writeCapture; + + private OFFeaturesReply featuresReply; + private OFPortDesc portDesc; + + private Set<Long> seenXids = null; + + private Capture<IOFConnectionBackend> newConnection; + + private Capture<OFFeaturesReply> newFeaturesReply; + + private TestEventLoop eventLoop; + + public void setUpFeaturesReply() { + portDesc = factory.buildPortDesc() + .setName("Eth1") + .setPortNo(OFPort.of(1)) + .build(); + featuresReply = factory.buildFeaturesReply() + .setDatapathId(dpid) + .setNBuffers(1) + .setNTables((short)1) + .setCapabilities(EnumSet.<OFCapabilities>of(OFCapabilities.FLOW_STATS, OFCapabilities.TABLE_STATS)) + .setAuxiliaryId(OFAuxId.MAIN) + .build(); + } + + @Before + public void setUp() throws Exception { + setUpFeaturesReply(); + switchManager = createMock(IOFSwitchManager.class); + connectionListener = createMock(IOFConnectionListener.class); + newConnectionListener = createMock(INewOFConnectionListener.class); + newConnection = new Capture<IOFConnectionBackend>(); + newFeaturesReply = new Capture<OFFeaturesReply>(); + eventLoop = new TestEventLoop(); + + ctx = createMock(ChannelHandlerContext.class); + channel = createMock(Channel.class); + timer = new HashedWheelTimer(); + exceptionEventCapture = new Capture<Throwable>(CaptureType.ALL); + pipeline = createMock(ChannelPipeline.class); + writeCapture = new Capture<List<OFMessage>>(CaptureType.ALL); + seenXids = null; + + + // TODO: should mock IDebugCounterService and make sure + // the expected counters are updated. + debugCounterService = new DebugCounterServiceImpl(); + debugCounterService.registerModule(OFConnectionCounters.COUNTER_MODULE); + SwitchManagerCounters counters = + new SwitchManagerCounters(debugCounterService); + expect(switchManager.getCounters()).andReturn(counters).anyTimes(); + replay(switchManager); + handler = new OFChannelHandler(switchManager, newConnectionListener, + pipeline, debugCounterService, /* 62 is OF versions 1.0 thru 1.4 in decimal */ + timer, Collections.singletonList(U32.of(62)), OFFactories.getFactory(OFVersion.OF_14)); + + verify(switchManager); + reset(switchManager); + + resetChannel(); + + // replay controller. Reset it if you need more specific behavior + replay(switchManager); + + // Mock ctx and channelStateEvent + expect(ctx.channel()).andReturn(channel).anyTimes(); + expect(ctx.fireExceptionCaught(capture(exceptionEventCapture))).andReturn(ctx).anyTimes(); + replay(ctx); + + /* Setup an exception event capture on the channel. Right now + * we only expect exception events to be send up the channel. + * However, it's easy to extend to other events if we need it + */ + expect(pipeline.get(OFMessageDecoder.class)).andReturn(new OFMessageDecoder()).anyTimes(); + replay(pipeline); + } + + @After + public void tearDown() { + /* ensure no exception was thrown */ + if (exceptionEventCapture.hasCaptured()) { + Throwable ex = exceptionEventCapture.getValue(); + ex.printStackTrace(); + Throwables.propagate(ex); + } + assertFalse("Unexpected messages have been captured", + writeCapture.hasCaptured()); + // verify all mocks. + verify(channel); + verify(switchManager); + verify(ctx); + verify(pipeline); + } + + /** Reset the channel mock and set basic method call expectations */ + void resetChannel() { + reset(channel); + expect(channel.newPromise()).andAnswer(new IAnswer<ChannelPromise>() { + @Override + public ChannelPromise answer() throws Throwable { + return new DefaultChannelPromise(channel); + } + }).anyTimes(); + eventLoop = new TestEventLoop(); + expect(channel.eventLoop()).andReturn(eventLoop).anyTimes(); + expect(channel.pipeline()).andReturn(pipeline).anyTimes(); + expect(channel.remoteAddress()).andReturn(null).anyTimes(); + } + + /** 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 + */ + void sendMessageToHandlerWithControllerReset(List<OFMessage> messages) + throws Exception { + sendMessageToHandlerNoControllerReset(messages); + } + + /** 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 + */ + void sendMessageToHandlerNoControllerReset(List<OFMessage> messages) + throws Exception { + handler.channelRead(ctx, messages); + } + + /** + * 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 + * OFMessages. + * Resets the channelWriteCapture. + */ + List<OFMessage> getMessagesFromCapture() { + List<OFMessage> msgs = new ArrayList<OFMessage>(); + + assertTrue("No write on channel was captured", + writeCapture.hasCaptured()); + List<List<OFMessage>> capturedVals = writeCapture.getValues(); + + for (List<OFMessage> oneWriteList: capturedVals) + msgs.addAll(oneWriteList); + writeCapture.reset(); + return msgs; + } + + + /** + * Verify that the given exception event capture (as returned by + * getAndInitExceptionCapture) has thrown an exception of the given + * expectedExceptionClass. + * Resets the capture + */ + void verifyExceptionCaptured(Class<? extends Throwable> expectedExceptionClass) { + assertTrue("Excpected exception not thrown", exceptionEventCapture.hasCaptured()); + Throwable caughtEx = exceptionEventCapture.getValue(); + assertEquals(expectedExceptionClass, caughtEx.getClass()); + exceptionEventCapture.reset(); + } + + /** 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. + */ + void verifyUniqueXids(List<OFMessage> msgs) { + if (seenXids == null) + seenXids = new HashSet<Long>(); + for (OFMessage m: msgs) { + long xid = m.getXid(); + assertTrue("Xid in messags is 0", xid != 0); + assertFalse("Xid " + xid + " has already been used", + seenXids.contains(xid)); + seenXids.add(xid); + } + } + + @Test + public void testNullMsg() throws Exception { + reset(ctx); + expect(ctx.fireChannelRead(null)).andReturn(ctx).once(); + replay(ctx, channel); + + // null message is not passed to the handler + handler.channelRead(ctx, null); + verify(channel, ctx); + } + + @Test + public void testInitState() throws Exception { + replay(channel); + + // We don't expect to receive /any/ messages in init state since + // channelConnected moves us to a different state + OFMessage m = factory.buildHello().build(); + sendMessageToHandlerWithControllerReset(ImmutableList.<OFMessage>of(m)); + + verifyExceptionCaptured(SwitchStateException.class); + assertThat(handler.getStateForTesting(), CoreMatchers.instanceOf(OFChannelHandler.InitState.class)); + } + + /* Move the channel from scratch to WAIT_HELLO state */ + @Test + public void moveToWaitHello() throws Exception { + resetChannel(); + expect(channel.writeAndFlush(capture(writeCapture))).andReturn(null).once(); + replay(channel); + + handler.channelActive(ctx); + eventLoop.runTasks(); + + List<OFMessage> msgs = getMessagesFromCapture(); + assertEquals(1, msgs.size()); + assertEquals(OFType.HELLO, msgs.get(0).getType()); + assertThat(handler.getStateForTesting(), CoreMatchers.instanceOf(OFChannelHandler.WaitHelloState.class)); + verifyUniqueXids(msgs); + } + + /** Move the channel from scratch to WAIT_FEATURES_REPLY state + * Builds on moveToWaitHello() + * adds testing for WAIT_HELLO state + */ + @Test + public void moveToWaitFeaturesReply() throws Exception { + moveToWaitHello(); + resetChannel(); + expect(channel.writeAndFlush(capture(writeCapture))).andReturn(null).once(); + replay(channel); + + OFMessage hello = factory.buildHello().build(); + sendMessageToHandlerWithControllerReset(ImmutableList.<OFMessage>of(hello)); + + List<OFMessage> msgs = getMessagesFromCapture(); + assertEquals(1, msgs.size()); + assertEquals(OFType.FEATURES_REQUEST, msgs.get(0).getType()); + verifyUniqueXids(msgs); + + assertThat(handler.getStateForTesting(), CoreMatchers.instanceOf(OFChannelHandler.WaitFeaturesReplyState.class)); + } + + + /** Move the channel from scratch to WAIT_FEATURES_REPLY state + * Builds on moveToWaitHello() + * adds testing for WAIT_HELLO state + */ + @Test + public void moveToComplete() throws Exception { + moveToWaitFeaturesReply(); + + reset(pipeline); + HandshakeTimeoutHandler newHandler = new HandshakeTimeoutHandler( + handler, + timer, + PipelineHandshakeTimeout.SWITCH); + + expect( + pipeline.replace(EasyMock.eq(PipelineHandler.CHANNEL_HANDSHAKE_TIMEOUT), + EasyMock.eq(PipelineHandler.SWITCH_HANDSHAKE_TIMEOUT), + EasyMock.anyObject(HandshakeTimeoutHandler.class))).andReturn(newHandler) + .once(); + + replay(pipeline); + + newConnectionListener.connectionOpened(capture(newConnection), capture(newFeaturesReply)); + expectLastCall().once(); + replay(newConnectionListener); + + sendMessageToHandlerWithControllerReset(Collections.<OFMessage>singletonList(featuresReply)); + + assertThat(handler.getStateForTesting(), CoreMatchers.instanceOf(OFChannelHandler.CompleteState.class)); + assertTrue("A connection has been created and set", handler.getConnectionForTesting() != null); + verify(newConnectionListener); + assertTrue(newConnection.hasCaptured()); + assertThat(newFeaturesReply.getValue(), equalTo(featuresReply)); + } + + /** + * Test dispatch of messages while in Complete state + */ + @Test + public void testMessageDispatchComplete() throws Exception { + moveToComplete(); + newConnection.getValue().setListener(connectionListener); + + resetChannel(); + expect(channel.writeAndFlush(capture(writeCapture))).andReturn(null).once(); + replay(channel); - // Send echo request. expect reply - OFMessage echoRequest = factory.buildEchoRequest().build(); - sendMessageToHandlerWithControllerReset(ImmutableList.<OFMessage>of(echoRequest)); + // Send echo request. expect reply + OFMessage echoRequest = factory.buildEchoRequest().build(); + sendMessageToHandlerWithControllerReset(ImmutableList.<OFMessage>of(echoRequest)); - List<OFMessage> msgs = getMessagesFromCapture(); - assertEquals(1, msgs.size()); - assertEquals(OFType.ECHO_REPLY, msgs.get(0).getType()); + List<OFMessage> msgs = getMessagesFromCapture(); + assertEquals(1, msgs.size()); + assertEquals(OFType.ECHO_REPLY, msgs.get(0).getType()); - // Send barrier reply. expect dispatch - OFBarrierReply barrierReply = factory.buildBarrierReply() - .build(); + // Send barrier reply. expect dispatch + OFBarrierReply barrierReply = factory.buildBarrierReply() + .build(); - resetAndExpectConnectionListener(barrierReply); + resetAndExpectConnectionListener(barrierReply); - // Send packet in. expect dispatch - OFFlowRemoved flowRemoved = factory.buildFlowRemoved() - .build(); + // Send packet in. expect dispatch + OFFlowRemoved flowRemoved = factory.buildFlowRemoved() + .build(); - resetAndExpectConnectionListener(flowRemoved); + resetAndExpectConnectionListener(flowRemoved); - // Send get config reply. expect dispatch - OFGetConfigReply getConfigReply = factory.buildGetConfigReply() - .build(); + // Send get config reply. expect dispatch + OFGetConfigReply getConfigReply = factory.buildGetConfigReply() + .build(); - resetAndExpectConnectionListener(getConfigReply); + resetAndExpectConnectionListener(getConfigReply); - // Send packet in. expect dispatch - OFPacketIn pi = factory.buildPacketIn() - .setReason(OFPacketInReason.NO_MATCH) - .build(); + // Send packet in. expect dispatch + OFPacketIn pi = factory.buildPacketIn() + .setReason(OFPacketInReason.NO_MATCH) + .build(); - resetAndExpectConnectionListener(pi); + resetAndExpectConnectionListener(pi); - // Send port status. expect dispatch - OFPortStatus portStatus = factory.buildPortStatus() - .setReason(OFPortReason.DELETE) - .setDesc(portDesc) - .build(); + // Send port status. expect dispatch + OFPortStatus portStatus = factory.buildPortStatus() + .setReason(OFPortReason.DELETE) + .setDesc(portDesc) + .build(); - resetAndExpectConnectionListener(portStatus); + resetAndExpectConnectionListener(portStatus); - // Send queue reply. expect dispatch - OFQueueGetConfigReply queueReply = factory.buildQueueGetConfigReply() - .build(); + // Send queue reply. expect dispatch + OFQueueGetConfigReply queueReply = factory.buildQueueGetConfigReply() + .build(); - resetAndExpectConnectionListener(queueReply); + resetAndExpectConnectionListener(queueReply); - // Send stat reply. expect dispatch - OFFlowStatsReply statReply = factory.buildFlowStatsReply() - .build(); + // Send stat reply. expect dispatch + OFFlowStatsReply statReply = factory.buildFlowStatsReply() + .build(); - resetAndExpectConnectionListener(statReply); + resetAndExpectConnectionListener(statReply); - // Send role reply. expect dispatch - OFRoleReply roleReply = factory.buildRoleReply() - .setRole(OFControllerRole.ROLE_MASTER) - .build(); + // Send role reply. expect dispatch + OFRoleReply roleReply = factory.buildRoleReply() + .setRole(OFControllerRole.ROLE_MASTER) + .build(); - resetAndExpectConnectionListener(roleReply); + resetAndExpectConnectionListener(roleReply); - // Send experimenter. expect dispatch - OFBsnSetAuxCxnsReply auxReply = factory.buildBsnSetAuxCxnsReply() - .build(); + // Send experimenter. expect dispatch + OFBsnSetAuxCxnsReply auxReply = factory.buildBsnSetAuxCxnsReply() + .build(); - resetAndExpectConnectionListener(auxReply); + resetAndExpectConnectionListener(auxReply); - } + } - public void resetAndExpectConnectionListener(OFMessage m) throws Exception{ - reset(connectionListener); - connectionListener.messageReceived(handler.getConnectionForTesting(), m); - expectLastCall().once(); - replay(connectionListener); + public void resetAndExpectConnectionListener(OFMessage m) throws Exception{ + reset(connectionListener); + connectionListener.messageReceived(handler.getConnectionForTesting(), m); + expectLastCall().once(); + replay(connectionListener); - sendMessageToHandlerWithControllerReset(Collections.<OFMessage>singletonList(m)); + sendMessageToHandlerWithControllerReset(Collections.<OFMessage>singletonList(m)); - verify(connectionListener); - } + verify(connectionListener); + } } diff --git a/src/test/java/net/floodlightcontroller/core/OFConnectionTest.java b/src/test/java/net/floodlightcontroller/core/internal/OFConnectionTest.java similarity index 89% rename from src/test/java/net/floodlightcontroller/core/OFConnectionTest.java rename to src/test/java/net/floodlightcontroller/core/internal/OFConnectionTest.java index d65788b36a3cff928e09301fdeb1362f3f78121d..23230074aacb2c69d45801bda2c5bdaa4bddff1c 100644 --- a/src/test/java/net/floodlightcontroller/core/OFConnectionTest.java +++ b/src/test/java/net/floodlightcontroller/core/internal/OFConnectionTest.java @@ -1,4 +1,4 @@ -package net.floodlightcontroller.core; +package net.floodlightcontroller.core.internal; import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.expect; @@ -13,12 +13,20 @@ import org.easymock.Capture; import org.easymock.EasyMock; import org.hamcrest.CoreMatchers; import org.hamcrest.Matchers; -import org.jboss.netty.channel.Channel; -import org.jboss.netty.util.HashedWheelTimer; -import org.jboss.netty.util.Timer; + +import io.netty.channel.Channel; +import io.netty.util.HashedWheelTimer; +import io.netty.util.Timer; + +import org.junit.After; import org.junit.Before; import org.junit.Test; +import net.floodlightcontroller.core.SwitchDisconnectedException; +import net.floodlightcontroller.core.internal.OFConnection; +import net.floodlightcontroller.core.internal.OFConnectionCounters; +import net.floodlightcontroller.core.internal.OFErrorMsgException; +import net.floodlightcontroller.core.test.TestEventLoop; import net.floodlightcontroller.debugcounter.DebugCounterServiceImpl; import net.floodlightcontroller.debugcounter.IDebugCounterService; @@ -62,17 +70,28 @@ public class OFConnectionTest { private OFConnection conn; private DatapathId switchId; private Timer timer; + private TestEventLoop eventLoop; @Before public void setUp() throws Exception { factory = OFFactories.getFactory(OFVersion.OF_13); switchId = DatapathId.of(1); timer = new HashedWheelTimer(); - channel = EasyMock.createNiceMock(Channel.class); + channel = EasyMock.createMock(Channel.class); IDebugCounterService debugCounterService = new DebugCounterServiceImpl(); debugCounterService.registerModule(OFConnectionCounters.COUNTER_MODULE); conn = new OFConnection(switchId, factory, channel, OFAuxId.MAIN, debugCounterService, timer); + eventLoop = new TestEventLoop(); + + expect(channel.eventLoop()).andReturn(eventLoop).anyTimes(); + } + + @After + public void tearDown() throws Exception { + if (timer != null) { + timer.stop(); + } } @Test(timeout = 5000) @@ -84,6 +103,7 @@ public class OFConnectionTest { assertThat("Connection should have 1 pending request", conn.getPendingRequestIds().size(), equalTo(1)); + eventLoop.runTasks(); assertThat("Should have captured MsgList", cMsgList.getValue(), Matchers.<OFMessage> contains(echoRequest)); @@ -111,7 +131,8 @@ public class OFConnectionTest { ListenableFuture<List<OFFlowStatsReply>> future = conn.writeStatsRequest(flowStatsRequest); assertThat("Connection should have 1 pending request", conn.getPendingRequestIds().size(), equalTo(1)); - + + eventLoop.runTasks(); assertThat("Should have captured MsgList", cMsgList.getValue(), Matchers.<OFMessage> contains(flowStatsRequest)); @@ -142,9 +163,9 @@ public class OFConnectionTest { } private Capture<List<OFMessage>> prepareChannelForWriteList() { - EasyMock.expect(channel.isConnected()).andReturn(Boolean.TRUE).anyTimes(); + EasyMock.expect(channel.isActive()).andReturn(Boolean.TRUE).anyTimes(); Capture<List<OFMessage>> cMsgList = new Capture<>(); - expect(channel.write(capture(cMsgList))).andReturn(null).once(); + expect(channel.writeAndFlush(capture(cMsgList))).andReturn(null).once(); replay(channel); return cMsgList; } @@ -159,6 +180,7 @@ public class OFConnectionTest { assertThat("Connection should have 1 pending request", conn.getPendingRequestIds().size(), equalTo(1)); + eventLoop.runTasks(); assertThat("Should have captured MsgList", cMsgList.getValue(), Matchers.<OFMessage> contains(roleRequest)); @@ -181,7 +203,7 @@ public class OFConnectionTest { @Test(timeout = 5000) public void testWriteRequestNotConnectedFailure() throws InterruptedException, ExecutionException { - EasyMock.expect(channel.isConnected()).andReturn(Boolean.FALSE).anyTimes(); + EasyMock.expect(channel.isActive()).andReturn(Boolean.FALSE).anyTimes(); replay(channel); OFEchoRequest echoRequest = factory.echoRequest(new byte[] {}); @@ -216,7 +238,7 @@ public class OFConnectionTest { conn.getPendingRequestIds().isEmpty(), equalTo(true)); } - /** write a packetOut, which is buffered */ + /** write a packetOut, which is not buffered */ @Test(timeout = 5000) public void testSingleMessageWrite() throws InterruptedException, ExecutionException { Capture<List<OFMessage>> cMsgList = prepareChannelForWriteList(); @@ -227,6 +249,7 @@ public class OFConnectionTest { .build(); conn.write(packetOut); + eventLoop.runTasks(); assertThat("Write should have been flushed", cMsgList.hasCaptured(), equalTo(true)); List<OFMessage> value = cMsgList.getValue(); @@ -247,7 +270,7 @@ public class OFConnectionTest { .build(); conn.write(ImmutableList.of(hello, packetOut)); - + eventLoop.runTasks(); assertThat("Write should have been written", cMsgList.hasCaptured(), equalTo(true)); List<OFMessage> value = cMsgList.getValue(); logger.info("Captured channel write: "+value); diff --git a/src/test/java/net/floodlightcontroller/core/OFSwitchBaseTest.java b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchBaseTest.java similarity index 95% rename from src/test/java/net/floodlightcontroller/core/OFSwitchBaseTest.java rename to src/test/java/net/floodlightcontroller/core/internal/OFSwitchBaseTest.java index 24db287af6da5c28214280f1a240991dafc27e55..5c067f060310082d3f64a9985f892111f0eb5f5e 100644 --- a/src/test/java/net/floodlightcontroller/core/OFSwitchBaseTest.java +++ b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchBaseTest.java @@ -14,7 +14,7 @@ * under the License. **/ -package net.floodlightcontroller.core; +package net.floodlightcontroller.core.internal; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.expect; @@ -41,7 +41,17 @@ import org.easymock.EasyMock; import org.junit.Before; import org.junit.Test; +import net.floodlightcontroller.core.IOFConnectionBackend; +import net.floodlightcontroller.core.IOFSwitchBackend; +import net.floodlightcontroller.core.LogicalOFMessageCategory; +import net.floodlightcontroller.core.PortChangeEvent; +import net.floodlightcontroller.core.PortChangeType; +import net.floodlightcontroller.core.SwitchDescription; +import net.floodlightcontroller.core.SwitchDriverSubHandshakeAlreadyStarted; +import net.floodlightcontroller.core.SwitchDriverSubHandshakeCompleted; +import net.floodlightcontroller.core.SwitchDriverSubHandshakeNotStarted; import net.floodlightcontroller.core.internal.IOFSwitchManager; +import net.floodlightcontroller.core.internal.OFSwitch; import net.floodlightcontroller.core.internal.SwitchManagerCounters; import net.floodlightcontroller.debugcounter.DebugCounterServiceImpl; import net.floodlightcontroller.debugcounter.IDebugCounterService; @@ -49,6 +59,8 @@ import net.floodlightcontroller.debugcounter.IDebugCounterService; import org.projectfloodlight.openflow.protocol.OFControllerRole; import org.projectfloodlight.openflow.protocol.OFFactories; import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFFlowAdd; +import org.projectfloodlight.openflow.protocol.OFFlowStatsRequest; import org.projectfloodlight.openflow.protocol.OFMessage; import org.projectfloodlight.openflow.protocol.OFPortConfig; import org.projectfloodlight.openflow.protocol.OFPortDesc; @@ -112,7 +124,7 @@ public class OFSwitchBaseTest { new PortChangeEvent(portBar1, PortChangeType.DELETE); private final PortChangeEvent portBar2Del = new PortChangeEvent(portBar2, PortChangeType.DELETE); - private Capture<OFMessage> capturedMessage; + private Capture<Iterable<OFMessage>> capturedMessage; private OFFactory factory; @Before @@ -134,9 +146,8 @@ public class OFSwitchBaseTest { .build(); IOFConnectionBackend conn = EasyMock.createNiceMock(IOFConnectionBackend.class); - capturedMessage = new Capture<OFMessage>(); - conn.write(EasyMock.capture(capturedMessage)); - expectLastCall().anyTimes(); + capturedMessage = new Capture<Iterable<OFMessage>>(); + expect(conn.write(EasyMock.capture(capturedMessage))).andReturn(Collections.<OFMessage>emptyList()).atLeastOnce(); expect(conn.getOFFactory()).andReturn(factory).anyTimes(); expect(conn.getAuxId()).andReturn(OFAuxId.MAIN).anyTimes(); EasyMock.replay(conn); @@ -144,6 +155,7 @@ public class OFSwitchBaseTest { IOFConnectionBackend auxConn = EasyMock.createNiceMock(IOFConnectionBackend.class); expect(auxConn.getOFFactory()).andReturn(factory).anyTimes(); expect(auxConn.getAuxId()).andReturn(OFAuxId.of(1)).anyTimes(); + expect(auxConn.write(EasyMock.capture(capturedMessage))).andReturn(Collections.<OFMessage>emptyList()).once(); EasyMock.replay(auxConn); sw = new OFSwitchTest(conn, switchManager); @@ -1395,12 +1407,42 @@ public class OFSwitchBaseTest { reset(switchManager); expect(switchManager.isCategoryRegistered(category)).andReturn(true); switchManager.handleOutgoingMessage(sw, testMessage); - expectLastCall(); + expectLastCall().once(); replay(switchManager); - + sw.write(testMessage, category); verify(switchManager); } -} + + @Test + public void testMasterSlaveWrites() { + OFFactory factory = OFFactories.getFactory(OFVersion.OF_13); + OFFlowAdd fa = factory.buildFlowAdd().build(); + OFFlowStatsRequest fsr = factory.buildFlowStatsRequest().build(); + List<OFMessage> msgList = new ArrayList<OFMessage>(); + msgList.add(fa); + msgList.add(fsr); + + reset(switchManager); + expect(switchManager.isCategoryRegistered(LogicalOFMessageCategory.MAIN)).andReturn(true).times(6); + switchManager.handleOutgoingMessage(sw, fa); + expectLastCall().times(2); + switchManager.handleOutgoingMessage(sw, fsr); + expectLastCall().times(4); + replay(switchManager); + + /* test master -- both messages should be written */ + sw.setControllerRole(OFControllerRole.ROLE_MASTER); + assertTrue(sw.write(fa)); + assertTrue(sw.write(fsr)); + assertEquals(Collections.<OFMessage>emptyList(), sw.write(msgList)); + + /* test slave -- flow-add (mod op) should fail each time; flow stats (read op) should pass */ + sw.setControllerRole(OFControllerRole.ROLE_SLAVE); + assertFalse(sw.write(fa)); /* flow-add should be stopped (mod op) */ + assertTrue(sw.write(fsr)); /* stats request makes it (read op) */ + assertEquals(Collections.<OFMessage>singletonList(fa), sw.write(msgList)); /* return bad flow-add */ + } +} \ No newline at end of file diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandlerTestBase.java b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandlerTestBase.java index 675873a5eeac46c7e8db6cef30e31ea02680fd08..9f254d658bbe130ac27394707bd8fd619e825218 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandlerTestBase.java +++ b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandlerTestBase.java @@ -27,9 +27,11 @@ import java.util.concurrent.TimeUnit; import org.easymock.EasyMock; import org.hamcrest.CoreMatchers; import org.hamcrest.Matchers; -import org.jboss.netty.util.Timeout; -import org.jboss.netty.util.Timer; -import org.jboss.netty.util.TimerTask; + +import io.netty.util.Timeout; +import io.netty.util.Timer; +import io.netty.util.TimerTask; + import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -521,10 +523,8 @@ public abstract class OFSwitchHandlerTestBase { // TODO: hmmm. While it's not incorrect that we set the attribute // again it looks odd. Maybe change expect(sw.getOFFactory()).andReturn(factory).anyTimes(); - sw.write(anyObject(OFMessage.class)); - expectLastCall().anyTimes(); - sw.write(anyObject(Iterable.class)); - expectLastCall().anyTimes(); + expect(sw.write(anyObject(OFMessage.class))).andReturn(true).anyTimes(); + expect(sw.write(anyObject(Iterable.class))).andReturn(Collections.EMPTY_LIST).anyTimes(); expect(sw.getNumTables()).andStubReturn((short)0); sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, supportsNxRole); expectLastCall().anyTimes(); @@ -599,10 +599,8 @@ public abstract class OFSwitchHandlerTestBase { // prepare mocks and inject the role reply message reset(sw); expect(sw.getOFFactory()).andReturn(factory).anyTimes(); - sw.write(anyObject(OFMessage.class)); - expectLastCall().anyTimes(); - sw.write(anyObject(Iterable.class)); - expectLastCall().anyTimes(); + expect(sw.write(anyObject(OFMessage.class))).andReturn(true).anyTimes(); + expect(sw.write(anyObject(Iterable.class))).andReturn(Collections.EMPTY_LIST).anyTimes(); expect(sw.getTables()).andStubReturn(Collections.EMPTY_LIST); expect(sw.getNumTables()).andStubReturn((short) 0); sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true); @@ -695,10 +693,8 @@ public abstract class OFSwitchHandlerTestBase { // prepare mocks and inject the role reply message reset(sw); expect(sw.getOFFactory()).andReturn(factory).anyTimes(); - sw.write(anyObject(OFMessage.class)); - expectLastCall().anyTimes(); - sw.write(anyObject(Iterable.class)); - expectLastCall().anyTimes(); + expect(sw.write(anyObject(OFMessage.class))).andReturn(true).anyTimes(); + expect(sw.write(anyObject(Iterable.class))).andReturn(Collections.EMPTY_LIST).anyTimes(); expect(sw.getNumTables()).andStubReturn((short)0); sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false); expectLastCall().once(); @@ -758,10 +754,8 @@ public abstract class OFSwitchHandlerTestBase { // prepare mocks and inject the role reply message reset(sw); expect(sw.getOFFactory()).andReturn(factory).anyTimes(); - sw.write(anyObject(OFMessage.class)); - expectLastCall().anyTimes(); - sw.write(anyObject(Iterable.class)); - expectLastCall().anyTimes(); + expect(sw.write(anyObject(OFMessage.class))).andReturn(true).anyTimes(); + expect(sw.write(anyObject(Iterable.class))).andReturn(Collections.EMPTY_LIST).anyTimes(); expect(sw.getNumTables()).andStubReturn((short)0); sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, false); expectLastCall().once(); @@ -922,10 +916,8 @@ public abstract class OFSwitchHandlerTestBase { // prepare mocks and inject the role reply message reset(sw); expect(sw.getOFFactory()).andReturn(factory).anyTimes(); - sw.write(anyObject(OFMessage.class)); - expectLastCall().anyTimes(); - sw.write(anyObject(Iterable.class)); - expectLastCall().anyTimes(); + expect(sw.write(anyObject(OFMessage.class))).andReturn(true).anyTimes(); + expect(sw.write(anyObject(Iterable.class))).andReturn(Collections.EMPTY_LIST).anyTimes(); expect(sw.getNumTables()).andStubReturn((short)0); sw.setAttribute(IOFSwitch.SWITCH_SUPPORTS_NX_ROLE, true); expectLastCall().once(); diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandlerVer10Test.java b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandlerVer10Test.java index be752d0246f9075751fae5d2f321a7ee4af85ac7..d940ed4d55ee6da5e922d871a8319a6ce2437457 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandlerVer10Test.java +++ b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandlerVer10Test.java @@ -16,11 +16,12 @@ import java.util.EnumSet; import org.hamcrest.CoreMatchers; import org.hamcrest.Matchers; import org.junit.Test; + import net.floodlightcontroller.core.IOFSwitchBackend; -import net.floodlightcontroller.core.OFConnection; import net.floodlightcontroller.core.SwitchDescription; import net.floodlightcontroller.core.internal.OFSwitchAppHandshakePlugin.PluginResultType; import net.floodlightcontroller.core.internal.OFSwitchHandshakeHandler.WaitAppHandshakeState; + import org.projectfloodlight.openflow.protocol.OFActionType; import org.projectfloodlight.openflow.protocol.OFCapabilities; import org.projectfloodlight.openflow.protocol.OFControllerRole; diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandlerVer13Test.java b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandlerVer13Test.java index 9cb4c82167651f09f48588bc085e460103220e23..7f8393f65b704c26e3b865c93a2518494439e7f3 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandlerVer13Test.java +++ b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchHandshakeHandlerVer13Test.java @@ -18,7 +18,6 @@ import org.hamcrest.Matchers; import org.junit.Test; import net.floodlightcontroller.core.IOFSwitchBackend; -import net.floodlightcontroller.core.OFConnection; import net.floodlightcontroller.core.SwitchDescription; import net.floodlightcontroller.core.internal.OFSwitchHandshakeHandler.WaitAppHandshakeState; import net.floodlightcontroller.core.internal.OFSwitchHandshakeHandler.WaitTableFeaturesReplyState; diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchManagerTest.java b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchManagerTest.java index 62d43ab96a1305cb89e0df40ae14df5ee9f89fd2..6f5312ae2670344d91791bd4b87292a4ee5078e1 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchManagerTest.java +++ b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchManagerTest.java @@ -35,7 +35,7 @@ import static org.junit.Assert.fail; import java.util.List; -import org.jboss.netty.util.Timer; +import io.netty.util.Timer; import org.junit.After; import org.junit.Before; import org.junit.Test; @@ -50,8 +50,6 @@ import net.floodlightcontroller.core.IOFSwitchListener; import net.floodlightcontroller.core.IShutdownListener; import net.floodlightcontroller.core.IShutdownService; import net.floodlightcontroller.core.LogicalOFMessageCategory; -import net.floodlightcontroller.core.NullConnection; -import net.floodlightcontroller.core.OFSwitch; import net.floodlightcontroller.core.PortChangeType; import net.floodlightcontroller.core.SwitchDescription; import net.floodlightcontroller.core.module.FloodlightModuleContext; diff --git a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchTest.java b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchTest.java index b5ca01ed6884656e520c40ccd5cc284e88eae5f9..483aab250f55cf2b5b3b7445ee75c452eda94e87 100644 --- a/src/test/java/net/floodlightcontroller/core/internal/OFSwitchTest.java +++ b/src/test/java/net/floodlightcontroller/core/internal/OFSwitchTest.java @@ -29,12 +29,13 @@ import java.util.List; import org.easymock.EasyMock; import org.junit.Before; import org.junit.Test; + import net.floodlightcontroller.core.IOFSwitchBackend; -import net.floodlightcontroller.core.OFSwitch; import net.floodlightcontroller.core.SwitchDriverSubHandshakeAlreadyStarted; import net.floodlightcontroller.core.SwitchDriverSubHandshakeCompleted; import net.floodlightcontroller.core.SwitchDriverSubHandshakeNotStarted; import net.floodlightcontroller.core.util.URIUtil; + import org.projectfloodlight.openflow.protocol.OFBsnControllerConnection; import org.projectfloodlight.openflow.protocol.OFBsnControllerConnectionState; import org.projectfloodlight.openflow.protocol.OFBsnControllerConnectionsReply; @@ -48,116 +49,116 @@ import org.projectfloodlight.openflow.types.DatapathId; import org.projectfloodlight.openflow.types.OFAuxId; public class OFSwitchTest { - protected OFSwitch sw; - protected OFFactory factory = OFFactories.getFactory(OFVersion.OF_13); - - @Before - public void setUp() throws Exception { - MockOFConnection mockConnection = new MockOFConnection(DatapathId.of(1), OFAuxId.MAIN); - sw = new OFSwitch(mockConnection, OFFactories.getFactory(OFVersion.OF_10), - EasyMock.createMock(IOFSwitchManager.class), DatapathId.of(1)); - } - - @Test - public void testSetHARoleReply() { - sw.setControllerRole(OFControllerRole.ROLE_MASTER); - assertEquals(OFControllerRole.ROLE_MASTER, sw.getControllerRole()); - - sw.setControllerRole(OFControllerRole.ROLE_EQUAL); - assertEquals(OFControllerRole.ROLE_EQUAL, sw.getControllerRole()); - - sw.setControllerRole(OFControllerRole.ROLE_SLAVE); - assertEquals(OFControllerRole.ROLE_SLAVE, sw.getControllerRole()); - } - - @Test - public void testSubHandshake() { - OFFactory factory = OFFactories.getFactory(OFVersion.OF_10); - OFMessage m = factory.buildNiciraControllerRoleReply() - .setXid(1) - .setRole(OFNiciraControllerRole.ROLE_MASTER) - .build(); - // test exceptions before handshake is started - try { - sw.processDriverHandshakeMessage(m); - fail("expected exception not thrown"); - } catch (SwitchDriverSubHandshakeNotStarted e) { /* expected */ } - try { - sw.isDriverHandshakeComplete(); - fail("expected exception not thrown"); - } catch (SwitchDriverSubHandshakeNotStarted e) { /* expected */ } - - // start the handshake -- it should immediately complete - sw.startDriverHandshake(); - assertTrue("Handshake should be complete", - sw.isDriverHandshakeComplete()); - - // test exceptions after handshake is completed - try { - sw.processDriverHandshakeMessage(m); - fail("expected exception not thrown"); - } catch (SwitchDriverSubHandshakeCompleted e) { /* expected */ } - try { - sw.startDriverHandshake(); - fail("Expected exception not thrown"); - } catch (SwitchDriverSubHandshakeAlreadyStarted e) { /* expected */ } - } - - /** - * Helper to load controller connection messages into a switch for testing. - * @param sw the switch to insert the message on - * @param role the role for the controller connection message - * @param state the state for the controller connection message - * @param uri the URI for the controller connection message - */ - public void updateControllerConnections(IOFSwitchBackend sw, OFControllerRole role1, OFBsnControllerConnectionState state1, String uri1 - , OFControllerRole role2, OFBsnControllerConnectionState state2, String uri2) { - OFBsnControllerConnection connection1 = factory.buildBsnControllerConnection() - .setAuxiliaryId(OFAuxId.MAIN) - .setRole(role1) - .setState(state1) - .setUri(uri1) - .build(); - - OFBsnControllerConnection connection2 = factory.buildBsnControllerConnection() - .setAuxiliaryId(OFAuxId.MAIN) - .setRole(role2) - .setState(state2) - .setUri(uri2) - .build(); - - List<OFBsnControllerConnection> connections = new ArrayList<OFBsnControllerConnection>(); - connections.add(connection1); - connections.add(connection2); - - OFBsnControllerConnectionsReply reply = factory.buildBsnControllerConnectionsReply() - .setConnections(connections) - .build(); - - sw.updateControllerConnections(reply); - } - - /** - * This test ensures that the switch accurately determined if another master - * exists in the cluster by examining the controller connections it has. - */ - @Test - public void testHasAnotherMaster() { - URI cokeUri = URIUtil.createURI("1.2.3.4", 6653); - InetSocketAddress address = (InetSocketAddress) sw.getConnection(OFAuxId.MAIN).getLocalInetAddress(); - URI pepsiUri = URIUtil.createURI(address.getHostName(), address.getPort()); - - updateControllerConnections(sw, OFControllerRole.ROLE_SLAVE, OFBsnControllerConnectionState.BSN_CONTROLLER_CONNECTION_STATE_CONNECTED, cokeUri.toString(), - OFControllerRole.ROLE_MASTER, OFBsnControllerConnectionState.BSN_CONTROLLER_CONNECTION_STATE_CONNECTED, pepsiUri.toString()); - - // From the perspective of pepsi, the cluster currently does NOT have another master controller - assertFalse(sw.hasAnotherMaster()); - - // Switch the controller connections so that pepsi is no longer master - updateControllerConnections(sw, OFControllerRole.ROLE_MASTER, OFBsnControllerConnectionState.BSN_CONTROLLER_CONNECTION_STATE_CONNECTED, cokeUri.toString(), - OFControllerRole.ROLE_SLAVE, OFBsnControllerConnectionState.BSN_CONTROLLER_CONNECTION_STATE_CONNECTED, pepsiUri.toString()); - - // From the perspective of pepsi, the cluster currently has another master controller - assertTrue(sw.hasAnotherMaster()); - } + protected OFSwitch sw; + protected OFFactory factory = OFFactories.getFactory(OFVersion.OF_13); + + @Before + public void setUp() throws Exception { + MockOFConnection mockConnection = new MockOFConnection(DatapathId.of(1), OFAuxId.MAIN); + sw = new OFSwitch(mockConnection, OFFactories.getFactory(OFVersion.OF_10), + EasyMock.createMock(IOFSwitchManager.class), DatapathId.of(1)); + } + + @Test + public void testSetHARoleReply() { + sw.setControllerRole(OFControllerRole.ROLE_MASTER); + assertEquals(OFControllerRole.ROLE_MASTER, sw.getControllerRole()); + + sw.setControllerRole(OFControllerRole.ROLE_EQUAL); + assertEquals(OFControllerRole.ROLE_EQUAL, sw.getControllerRole()); + + sw.setControllerRole(OFControllerRole.ROLE_SLAVE); + assertEquals(OFControllerRole.ROLE_SLAVE, sw.getControllerRole()); + } + + @Test + public void testSubHandshake() { + OFFactory factory = OFFactories.getFactory(OFVersion.OF_10); + OFMessage m = factory.buildNiciraControllerRoleReply() + .setXid(1) + .setRole(OFNiciraControllerRole.ROLE_MASTER) + .build(); + // test exceptions before handshake is started + try { + sw.processDriverHandshakeMessage(m); + fail("expected exception not thrown"); + } catch (SwitchDriverSubHandshakeNotStarted e) { /* expected */ } + try { + sw.isDriverHandshakeComplete(); + fail("expected exception not thrown"); + } catch (SwitchDriverSubHandshakeNotStarted e) { /* expected */ } + + // start the handshake -- it should immediately complete + sw.startDriverHandshake(); + assertTrue("Handshake should be complete", + sw.isDriverHandshakeComplete()); + + // test exceptions after handshake is completed + try { + sw.processDriverHandshakeMessage(m); + fail("expected exception not thrown"); + } catch (SwitchDriverSubHandshakeCompleted e) { /* expected */ } + try { + sw.startDriverHandshake(); + fail("Expected exception not thrown"); + } catch (SwitchDriverSubHandshakeAlreadyStarted e) { /* expected */ } + } + + /** + * Helper to load controller connection messages into a switch for testing. + * @param sw the switch to insert the message on + * @param role the role for the controller connection message + * @param state the state for the controller connection message + * @param uri the URI for the controller connection message + */ + public void updateControllerConnections(IOFSwitchBackend sw, OFControllerRole role1, OFBsnControllerConnectionState state1, String uri1 + , OFControllerRole role2, OFBsnControllerConnectionState state2, String uri2) { + OFBsnControllerConnection connection1 = factory.buildBsnControllerConnection() + .setAuxiliaryId(OFAuxId.MAIN) + .setRole(role1) + .setState(state1) + .setUri(uri1) + .build(); + + OFBsnControllerConnection connection2 = factory.buildBsnControllerConnection() + .setAuxiliaryId(OFAuxId.MAIN) + .setRole(role2) + .setState(state2) + .setUri(uri2) + .build(); + + List<OFBsnControllerConnection> connections = new ArrayList<OFBsnControllerConnection>(); + connections.add(connection1); + connections.add(connection2); + + OFBsnControllerConnectionsReply reply = factory.buildBsnControllerConnectionsReply() + .setConnections(connections) + .build(); + + sw.updateControllerConnections(reply); + } + + /** + * This test ensures that the switch accurately determined if another master + * exists in the cluster by examining the controller connections it has. + */ + @Test + public void testHasAnotherMaster() { + URI cokeUri = URIUtil.createURI("1.2.3.4", 6653); + InetSocketAddress address = (InetSocketAddress) sw.getConnection(OFAuxId.MAIN).getLocalInetAddress(); + URI pepsiUri = URIUtil.createURI(address.getHostName(), address.getPort()); + + updateControllerConnections(sw, OFControllerRole.ROLE_SLAVE, OFBsnControllerConnectionState.BSN_CONTROLLER_CONNECTION_STATE_CONNECTED, cokeUri.toString(), + OFControllerRole.ROLE_MASTER, OFBsnControllerConnectionState.BSN_CONTROLLER_CONNECTION_STATE_CONNECTED, pepsiUri.toString()); + + // From the perspective of pepsi, the cluster currently does NOT have another master controller + assertFalse(sw.hasAnotherMaster()); + + // Switch the controller connections so that pepsi is no longer master + updateControllerConnections(sw, OFControllerRole.ROLE_MASTER, OFBsnControllerConnectionState.BSN_CONTROLLER_CONNECTION_STATE_CONNECTED, cokeUri.toString(), + OFControllerRole.ROLE_SLAVE, OFBsnControllerConnectionState.BSN_CONTROLLER_CONNECTION_STATE_CONNECTED, pepsiUri.toString()); + + // From the perspective of pepsi, the cluster currently has another master controller + assertTrue(sw.hasAnotherMaster()); + } } diff --git a/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java b/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java index 4f36d16ef79b20b013c883b56fc8b97655119402..d019d624a3f638ecd17b4b62c1c4e8af9b4910be 100644 --- a/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java +++ b/src/test/java/net/floodlightcontroller/core/test/MockFloodlightProvider.java @@ -36,8 +36,7 @@ import java.util.concurrent.Executors; import java.util.concurrent.Future; import java.util.concurrent.TimeUnit; -import org.jboss.netty.util.Timer; - +import io.netty.util.Timer; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.HAListenerTypeMarker; import net.floodlightcontroller.core.HARole; @@ -61,6 +60,8 @@ import net.floodlightcontroller.core.util.ListenerDispatcher; import org.projectfloodlight.openflow.protocol.OFMessage; import org.projectfloodlight.openflow.protocol.OFPacketIn; import org.projectfloodlight.openflow.protocol.OFType; +import org.projectfloodlight.openflow.types.IPv4Address; +import org.projectfloodlight.openflow.types.TransportPort; import net.floodlightcontroller.packet.Ethernet; @@ -76,8 +77,8 @@ public class MockFloodlightProvider implements IFloodlightModule, IFloodlightPro protected ConcurrentMap<OFType, ListenerDispatcher<OFType,IOFMessageListener>> listeners; protected ListenerDispatcher<HAListenerTypeMarker, IHAListener> haListeners; private HARole role; - private final String openFlowHostname = "127.0.0.1"; - private final int openFlowPort = 6653; + private final Set<IPv4Address> openFlowHostname = Collections.singleton(IPv4Address.of("127.0.0.1")); + private final TransportPort openFlowPort = TransportPort.of(6653); private final boolean useAsyncUpdates; private volatile ExecutorService executorService; private volatile Future<?> mostRecentUpdateFuture; @@ -385,12 +386,12 @@ public class MockFloodlightProvider implements IFloodlightModule, IFloodlightPro } @Override - public String getOFHostname() { + public Set<IPv4Address> getOFAddresses() { return openFlowHostname; } @Override - public int getOFPort() { + public TransportPort getOFPort() { return openFlowPort; } diff --git a/src/test/java/net/floodlightcontroller/core/test/TestEventLoop.java b/src/test/java/net/floodlightcontroller/core/test/TestEventLoop.java new file mode 100644 index 0000000000000000000000000000000000000000..b31725e6762a47a4b35ce67f059ac27979b46da4 --- /dev/null +++ b/src/test/java/net/floodlightcontroller/core/test/TestEventLoop.java @@ -0,0 +1,134 @@ +package net.floodlightcontroller.core.test; + +import java.util.ArrayDeque; +import java.util.Queue; +import java.util.concurrent.TimeUnit; + +import com.google.common.collect.ImmutableList; + +import io.netty.channel.Channel; +import io.netty.channel.ChannelFuture; +import io.netty.channel.ChannelPromise; +import io.netty.channel.DefaultChannelPromise; +import io.netty.channel.EventLoop; +import io.netty.channel.EventLoopGroup; +import io.netty.util.concurrent.AbstractScheduledEventExecutor; +import io.netty.util.concurrent.Future; + +public final class TestEventLoop extends AbstractScheduledEventExecutor implements EventLoop { + + private final Queue<Runnable> tasks = new ArrayDeque<Runnable>(2); + + @Override + public void execute(Runnable command) { + if (command == null) { + throw new NullPointerException("command"); + } + tasks.add(command); + } + + public void runTasks() { + for (;;) { + Runnable task = tasks.poll(); + if (task == null) { + break; + } + + task.run(); + } + } + + long runScheduledTasks() { + long time = AbstractScheduledEventExecutor.nanoTime(); + for (;;) { + Runnable task = pollScheduledTask(time); + if (task == null) { + return nextScheduledTaskNano(); + } + + task.run(); + } + } + + long nextScheduledTask() { + return nextScheduledTaskNano(); + } + + @Override + protected void cancelScheduledTasks() { + super.cancelScheduledTasks(); + } + + @Override + public Future<?> shutdownGracefully(long quietPeriod, long timeout, TimeUnit unit) { + throw new UnsupportedOperationException(); + } + + @Override + public Future<?> terminationFuture() { + throw new UnsupportedOperationException(); + } + + @Override + @Deprecated + public void shutdown() { + throw new UnsupportedOperationException(); + } + + @Override + public boolean isShuttingDown() { + return false; + } + + @Override + public boolean isShutdown() { + return false; + } + + @Override + public boolean isTerminated() { + return false; + } + + @Override + public boolean awaitTermination(long timeout, TimeUnit unit) + throws InterruptedException { + Thread.sleep(unit.toMillis(timeout)); + return false; + } + + @Override + public ChannelFuture register(Channel channel) { + return register(channel, new DefaultChannelPromise(channel, this)); + } + + @Override + public ChannelFuture register(Channel channel, ChannelPromise promise) { + channel.unsafe().register(this, promise); + return promise; + } + + @Override + public boolean inEventLoop() { + return true; + } + + @Override + public boolean inEventLoop(Thread thread) { + return true; + } + + @Override + public EventLoop next() { + return this; + } + + @Override + public EventLoopGroup parent() { + return this; + } + + public ImmutableList<Runnable> getQueuedTasks() { + return ImmutableList.copyOf(tasks); + } +} \ No newline at end of file diff --git a/src/test/java/net/floodlightcontroller/debugcounter/OFConnectionCountersTest.java b/src/test/java/net/floodlightcontroller/debugcounter/OFConnectionCountersTest.java index c82eb132c050eef6b9acedc0cde5936dd2545154..46984522e60e9af43a471a42e8cc5c6f759c3deb 100644 --- a/src/test/java/net/floodlightcontroller/debugcounter/OFConnectionCountersTest.java +++ b/src/test/java/net/floodlightcontroller/debugcounter/OFConnectionCountersTest.java @@ -7,7 +7,7 @@ import java.util.ArrayList; import org.junit.Before; import org.junit.Test; -import net.floodlightcontroller.core.OFConnectionCounters; +import net.floodlightcontroller.core.internal.OFConnectionCounters; import org.projectfloodlight.openflow.protocol.OFAsyncGetReply; import org.projectfloodlight.openflow.protocol.OFAsyncGetRequest; diff --git a/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java b/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java index 8c84298cd564b869a1a156a13d2274d328795464..fb1798028b835d52a8577b94ac736d25b1c2353d 100644 --- a/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java +++ b/src/test/java/net/floodlightcontroller/forwarding/ForwardingTest.java @@ -31,6 +31,7 @@ import java.util.Set; import net.floodlightcontroller.core.FloodlightContext; import net.floodlightcontroller.core.IFloodlightProviderService; import net.floodlightcontroller.core.IOFSwitch; +import net.floodlightcontroller.core.SwitchDescription; import net.floodlightcontroller.core.internal.IOFSwitchService; import net.floodlightcontroller.core.module.FloodlightModuleContext; import net.floodlightcontroller.core.test.MockThreadPoolService; @@ -67,6 +68,7 @@ import org.projectfloodlight.openflow.protocol.OFFeaturesReply; import org.projectfloodlight.openflow.protocol.OFFlowMod; import org.projectfloodlight.openflow.protocol.match.Match; import org.projectfloodlight.openflow.protocol.match.MatchField; +import org.projectfloodlight.openflow.protocol.OFDescStatsReply; import org.projectfloodlight.openflow.protocol.OFFactories; import org.projectfloodlight.openflow.protocol.OFFactory; import org.projectfloodlight.openflow.protocol.OFMessage; @@ -99,6 +101,7 @@ public class ForwardingTest extends FloodlightTestCase { protected MockThreadPoolService threadPool; protected IOFSwitch sw1, sw2; protected OFFeaturesReply swFeatures; + protected OFDescStatsReply swDescription; protected IDevice srcDevice, dstDevice1, dstDevice2; /* reuse for IPv4 and IPv6 */ protected OFPacketIn packetIn; protected OFPacketIn packetInIPv6; @@ -162,6 +165,7 @@ public class ForwardingTest extends FloodlightTestCase { entityClassifier.startUp(fmc); verify(topology); + swDescription = factory.buildDescStatsReply().build(); swFeatures = factory.buildFeaturesReply().setNBuffers(1000).build(); // Mock switches sw1 = EasyMock.createMock(IOFSwitch.class); @@ -177,6 +181,12 @@ public class ForwardingTest extends FloodlightTestCase { expect(sw1.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_TABLE)).andReturn(true).anyTimes(); expect(sw2.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_TABLE)).andReturn(true).anyTimes(); + + expect(sw1.getSwitchDescription()).andReturn(new SwitchDescription(swDescription)).anyTimes(); + expect(sw2.getSwitchDescription()).andReturn(new SwitchDescription(swDescription)).anyTimes(); + + expect(sw1.isActive()).andReturn(true).anyTimes(); + expect(sw2.isActive()).andReturn(true).anyTimes(); // Load the switch map Map<DatapathId, IOFSwitch> switches = new HashMap<DatapathId, IOFSwitch>(); @@ -450,10 +460,8 @@ public class ForwardingTest extends FloodlightTestCase { .build(); OFFlowMod fm2 = fm1.createBuilder().build(); - sw1.write(capture(wc1)); - expectLastCall().anyTimes(); - sw2.write(capture(wc2)); - expectLastCall().anyTimes(); + expect(sw1.write(capture(wc1))).andReturn(true).anyTimes(); + expect(sw2.write(capture(wc2))).andReturn(true).anyTimes(); reset(topology); expect(topology.getOpenflowDomainId(DatapathId.of(1L))).andReturn(DatapathId.of(1L)).anyTimes(); @@ -522,10 +530,8 @@ public class ForwardingTest extends FloodlightTestCase { .build(); OFFlowMod fm2 = fm1.createBuilder().build(); - sw1.write(capture(wc1)); - expectLastCall().anyTimes(); - sw2.write(capture(wc2)); - expectLastCall().anyTimes(); + expect(sw1.write(capture(wc1))).andReturn(true).anyTimes(); + expect(sw2.write(capture(wc2))).andReturn(true).anyTimes(); reset(topology); expect(topology.getOpenflowDomainId(DatapathId.of(1L))).andReturn(DatapathId.of(1L)).anyTimes(); @@ -590,10 +596,8 @@ public class ForwardingTest extends FloodlightTestCase { .build(); // Record expected packet-outs/flow-mods - sw1.write(capture(wc1)); - expectLastCall().once(); - sw1.write(capture(wc2)); - expectLastCall().once(); + expect(sw1.write(capture(wc1))).andReturn(true).once(); + expect(sw1.write(capture(wc2))).andReturn(true).once(); reset(topology); expect(topology.isIncomingBroadcastAllowed(DatapathId.of(anyLong()), OFPort.of(anyShort()))).andReturn(true).anyTimes(); @@ -646,10 +650,8 @@ public class ForwardingTest extends FloodlightTestCase { .build(); // Record expected packet-outs/flow-mods - sw1.write(capture(wc1)); - expectLastCall().once(); - sw1.write(capture(wc2)); - expectLastCall().once(); + expect(sw1.write(capture(wc1))).andReturn(true).once(); + expect(sw1.write(capture(wc2))).andReturn(true).once(); reset(topology); expect(topology.isIncomingBroadcastAllowed(DatapathId.of(anyLong()), OFPort.of(anyShort()))).andReturn(true).anyTimes(); @@ -751,8 +753,7 @@ public class ForwardingTest extends FloodlightTestCase { .andReturn(true) .anyTimes(); expect(sw1.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_FLOOD)).andReturn(true).anyTimes(); - sw1.write(capture(wc1)); - expectLastCall().once(); + expect(sw1.write(capture(wc1))).andReturn(true).once(); replay(sw1, sw2, routingEngine, topology); forwarding.receive(sw1, this.packetIn, cntx); verify(sw1, sw2, routingEngine); @@ -785,8 +786,7 @@ public class ForwardingTest extends FloodlightTestCase { expect(sw1.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_FLOOD)) .andReturn(true).anyTimes(); // Reset XID to expected (dependent on prior unit tests) - sw1.write(capture(wc1)); - expectLastCall().once(); + expect(sw1.write(capture(wc1))).andReturn(true).once(); replay(sw1, sw2, routingEngine, topology); forwarding.receive(sw1, this.packetInIPv6, cntx); verify(sw1, sw2, routingEngine); diff --git a/src/test/java/net/floodlightcontroller/hub/HubTest.java b/src/test/java/net/floodlightcontroller/hub/HubTest.java index 6a0a2dde83c80d9baf104afed0b176f3a00d546c..6bc1c98984d123ef3342de16dac4021bfca7a263 100644 --- a/src/test/java/net/floodlightcontroller/hub/HubTest.java +++ b/src/test/java/net/floodlightcontroller/hub/HubTest.java @@ -19,6 +19,7 @@ package net.floodlightcontroller.hub; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.replay; +import static org.easymock.EasyMock.expect; import static org.easymock.EasyMock.verify; import static org.easymock.EasyMock.capture; import static org.junit.Assert.*; @@ -122,7 +123,7 @@ public class HubTest extends FloodlightTestCase { Capture<OFMessage> wc1 = new Capture<OFMessage>(CaptureType.ALL); - mockSwitch.write(capture(wc1)); + expect(mockSwitch.write(capture(wc1))).andReturn(true).anyTimes(); // Start recording the replay on the mocks replay(mockSwitch); @@ -163,7 +164,7 @@ public class HubTest extends FloodlightTestCase { IOFSwitch mockSwitch = createMock(IOFSwitch.class); EasyMock.expect(mockSwitch.getOFFactory()).andReturn(OFFactories.getFactory(OFVersion.OF_13)).anyTimes(); Capture<OFPacketOut> wc1 = new Capture<OFPacketOut>(CaptureType.ALL); - mockSwitch.write(capture(wc1)); + expect(mockSwitch.write(capture(wc1))).andReturn(true).anyTimes(); // Start recording the replay on the mocks replay(mockSwitch); diff --git a/src/test/java/net/floodlightcontroller/learningswitch/LearningSwitchTest.java b/src/test/java/net/floodlightcontroller/learningswitch/LearningSwitchTest.java index b2754b2ea73b0b14743c7432648c45c5c7655aae..fd397c461a006fb0ce65d9854977a633fbc8fca8 100644 --- a/src/test/java/net/floodlightcontroller/learningswitch/LearningSwitchTest.java +++ b/src/test/java/net/floodlightcontroller/learningswitch/LearningSwitchTest.java @@ -190,8 +190,7 @@ public class LearningSwitchTest extends FloodlightTestCase { IOFSwitch mockSwitch = createMock(IOFSwitch.class); expect(mockSwitch.getId()).andReturn(DatapathId.of("00:11:22:33:44:55:66:77")).anyTimes(); expect(mockSwitch.getOFFactory()).andReturn(factory).anyTimes(); - mockSwitch.write(EasyMock.capture(wc1)); // expect po - EasyMock.expectLastCall().once(); + expect(mockSwitch.write(EasyMock.capture(wc1))).andReturn(true).once(); // expect po // Start recording the replay on the mocks replay(mockSwitch); @@ -269,12 +268,9 @@ public class LearningSwitchTest extends FloodlightTestCase { expect(mockSwitch.getBuffers()).andReturn((long)100).anyTimes(); expect(mockSwitch.getOFFactory()).andReturn(factory).anyTimes(); - mockSwitch.write(EasyMock.capture(wc1)); // expect packetOut - EasyMock.expectLastCall().once(); - mockSwitch.write(EasyMock.capture(wc2)); // expect fm1 - EasyMock.expectLastCall().once(); - mockSwitch.write(EasyMock.capture(wc3)); // expect fm2 - EasyMock.expectLastCall().once(); + expect(mockSwitch.write(EasyMock.capture(wc1))).andReturn(true).once(); // expect packetOut + expect(mockSwitch.write(EasyMock.capture(wc2))).andReturn(true).once(); // expect fm1 + expect(mockSwitch.write(EasyMock.capture(wc3))).andReturn(true).once(); // expect fm2 // Start recording the replay on the mocks replay(mockSwitch); diff --git a/src/test/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManagerTest.java b/src/test/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManagerTest.java index a4c8d244785fcccf952d5db5133bd7e01e72264f..89638f378e66be3bb5c21d6ea7b95629ff7416e1 100644 --- a/src/test/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManagerTest.java +++ b/src/test/java/net/floodlightcontroller/linkdiscovery/internal/LinkDiscoveryManagerTest.java @@ -21,7 +21,6 @@ import static org.easymock.EasyMock.capture; import static org.easymock.EasyMock.createMock; import static org.easymock.EasyMock.createNiceMock; import static org.easymock.EasyMock.expect; -import static org.easymock.EasyMock.expectLastCall; import static org.easymock.EasyMock.replay; import static org.easymock.EasyMock.verify; import static org.junit.Assert.*; @@ -497,8 +496,7 @@ public class LinkDiscoveryManagerTest extends FloodlightTestCase { expect(sw1.getPort(OFPort.of(EasyMock.anyInt()))).andReturn(ofpp).anyTimes(); expect(sw1.getOFFactory()).andReturn(OFFactories.getFactory(OFVersion.OF_13)).anyTimes(); expect(sw1.getLatency()).andReturn(U64.ZERO).anyTimes(); - sw1.write(capture(wc)); - expectLastCall().anyTimes(); + expect(sw1.write(capture(wc))).andReturn(true).anyTimes(); replay(sw1); linkDiscovery.switchActivated(sw1.getId()); diff --git a/src/test/java/net/floodlightcontroller/loadbalancer/LoadBalancerTest.java b/src/test/java/net/floodlightcontroller/loadbalancer/LoadBalancerTest.java index f19ce3bff93ff051ce38c8853c1b375246e0a1a2..f8c0101af6b2b5e534f232c0f566d4984756d858 100644 --- a/src/test/java/net/floodlightcontroller/loadbalancer/LoadBalancerTest.java +++ b/src/test/java/net/floodlightcontroller/loadbalancer/LoadBalancerTest.java @@ -446,10 +446,7 @@ public class LoadBalancerTest extends FloodlightTestCase { expect(sw1.getId()).andReturn(DatapathId.of(1L)).anyTimes(); expect(sw1.hasAttribute(IOFSwitch.PROP_SUPPORTS_OFPP_TABLE)).andReturn(true).anyTimes(); expect(sw1.getOFFactory()).andReturn(factory).anyTimes(); - sw1.write(capture(wc1)); - expectLastCall().anyTimes(); - sw1.flush(); - expectLastCall().anyTimes(); + expect(sw1.write(capture(wc1))).andReturn(true).anyTimes(); replay(sw1); sfp.switchAdded(DatapathId.of(1L)); diff --git a/src/test/java/net/floodlightcontroller/staticflowentry/StaticFlowTests.java b/src/test/java/net/floodlightcontroller/staticflowentry/StaticFlowTests.java index 51b2319f19053890ae402247eaf4b05a83f6bebe..25f8f4ea1ade6578f2ee1361f641f1ded33d3fdb 100644 --- a/src/test/java/net/floodlightcontroller/staticflowentry/StaticFlowTests.java +++ b/src/test/java/net/floodlightcontroller/staticflowentry/StaticFlowTests.java @@ -203,12 +203,8 @@ public class StaticFlowTests extends FloodlightTestCase { writeCapture = new Capture<OFMessage>(CaptureType.ALL); writeCaptureList = new Capture<List<OFMessage>>(CaptureType.ALL); - mockSwitch.write(capture(writeCapture)); - expectLastCall().anyTimes(); - mockSwitch.write(capture(writeCaptureList)); - expectLastCall().anyTimes(); - mockSwitch.flush(); - expectLastCall().anyTimes(); + expect(mockSwitch.write(capture(writeCapture))).andReturn(true).anyTimes(); + expect(mockSwitch.write(capture(writeCaptureList))).andReturn(Collections.<OFMessage>emptyList()).anyTimes(); expect(mockSwitch.getOFFactory()).andReturn(factory).anyTimes(); replay(mockSwitch); @@ -249,12 +245,8 @@ public class StaticFlowTests extends FloodlightTestCase { // if someone calls getId(), return this dpid instead resetToNice(mockSwitch); - mockSwitch.write(capture(writeCapture)); - expectLastCall().anyTimes(); - mockSwitch.write(capture(writeCaptureList)); - expectLastCall().anyTimes(); - mockSwitch.flush(); - expectLastCall().anyTimes(); + expect(mockSwitch.write(capture(writeCapture))).andReturn(true).anyTimes(); + expect(mockSwitch.write(capture(writeCaptureList))).andReturn(Collections.<OFMessage> emptyList()).anyTimes(); expect(mockSwitch.getOFFactory()).andReturn(factory).anyTimes(); expect(mockSwitch.getId()).andReturn(DatapathId.of(dpid)).anyTimes(); replay(mockSwitch); diff --git a/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java b/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java index c4005b32aaec719cfd0f4f43732bacc941f75bb3..39569240cc8603be553fd5507e638d889a5c3429 100644 --- a/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java +++ b/src/test/java/net/floodlightcontroller/util/OFMessageDamperMockSwitch.java @@ -20,6 +20,7 @@ import static org.junit.Assert.*; import java.net.SocketAddress; import java.util.Collection; +import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Map; @@ -28,8 +29,8 @@ import java.util.Set; import net.floodlightcontroller.core.IOFConnection; import net.floodlightcontroller.core.IOFSwitch; import net.floodlightcontroller.core.LogicalOFMessageCategory; -import net.floodlightcontroller.core.OFConnection; import net.floodlightcontroller.core.SwitchDescription; +import net.floodlightcontroller.core.internal.OFConnection; import net.floodlightcontroller.core.internal.TableFeatures; import org.projectfloodlight.openflow.protocol.OFActionType; @@ -160,11 +161,6 @@ public class OFMessageDamperMockSwitch implements IOFSwitch { return null; } - @Override - public void flush() { - assertTrue("Unexpected method call", false); - } - @Override public long getBuffers() { fail("Unexpected method call"); @@ -208,14 +204,14 @@ public class OFMessageDamperMockSwitch implements IOFSwitch { } @Override - public void write(OFMessage m) { + public boolean write(OFMessage m) { writtenMessage = m; - // TODO Auto-generated method stub + return true; } @Override - public void write(Iterable<OFMessage> msglist) { - // TODO Auto-generated method stub + public Collection<OFMessage> write(Iterable<OFMessage> msgList) { + return Collections.emptyList(); } @Override @@ -287,16 +283,14 @@ public class OFMessageDamperMockSwitch implements IOFSwitch { } @Override - public void write(OFMessage m, LogicalOFMessageCategory category) { - // TODO Auto-generated method stub - + public boolean write(OFMessage m, LogicalOFMessageCategory category) { + return true; } @Override - public void write(Iterable<OFMessage> msglist, + public Collection<OFMessage> write(Iterable<OFMessage> msgList, LogicalOFMessageCategory category) { - // TODO Auto-generated method stub - + return Collections.emptyList(); } @Override diff --git a/src/test/java/org/sdnplatform/sync/internal/BootstrapTest.java b/src/test/java/org/sdnplatform/sync/internal/BootstrapTest.java index 4aeb57ac9fd692fa07b8ab2dbe3659c47db2933a..0bd1764f7290728602cf430c2d15fbe9733687f6 100644 --- a/src/test/java/org/sdnplatform/sync/internal/BootstrapTest.java +++ b/src/test/java/org/sdnplatform/sync/internal/BootstrapTest.java @@ -110,7 +110,7 @@ public class BootstrapTest { unsyncStore.put("seeds", curSeed); waitForValue(unsyncStore, "localNodeId", null, - 3000, "unsyncStore" + i); + 10000, "unsyncStore" + i); short nodeId = Short.parseShort(unsyncStore.getValue("localNodeId")); Node node = nodeStore.getValue(nodeId);