aboutsummaryrefslogtreecommitdiffstats
path: root/framework/src/onos/protocols/openflow/ctl/src
diff options
context:
space:
mode:
Diffstat (limited to 'framework/src/onos/protocols/openflow/ctl/src')
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/Controller.java328
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/HandshakeTimeoutException.java28
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/HandshakeTimeoutHandler.java93
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFChannelHandler.java1320
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFMessageDecoder.java55
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFMessageEncoder.java59
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImpl.java633
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OpenflowPipelineFactory.java93
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/RoleManager.java406
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/package-info.java20
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/ChannelAdapter.java159
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/ChannelHandlerContextAdapter.java77
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/DriverAdapter.java104
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/DriverServiceAdapter.java59
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/ExecutorServiceAdapter.java99
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/MockOfFeaturesReply.java81
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/MockOfPacketIn.java84
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/MockOfPortStatus.java45
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OFDescStatsReplyAdapter.java97
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OfMessageAdapter.java62
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OpenFlowSwitchListenerAdapter.java77
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OpenflowSwitchDriverAdapter.java302
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/ControllerTest.java219
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OFMessageDecoderTest.java84
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OFMessageEncoderTest.java90
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImplPacketsTest.java167
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImplTest.java283
-rw-r--r--framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/RoleManagerTest.java130
28 files changed, 5254 insertions, 0 deletions
diff --git a/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/Controller.java b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/Controller.java
new file mode 100644
index 00000000..56b3a99c
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/Controller.java
@@ -0,0 +1,328 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow.controller.impl;
+
+import com.google.common.base.Strings;
+import com.google.common.collect.ImmutableList;
+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 org.onlab.util.ItemNotFoundException;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.driver.DefaultDriverData;
+import org.onosproject.net.driver.DefaultDriverHandler;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.openflow.controller.Dpid;
+import org.onosproject.openflow.controller.driver.OpenFlowAgent;
+import org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver;
+import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
+import org.projectfloodlight.openflow.protocol.OFFactories;
+import org.projectfloodlight.openflow.protocol.OFFactory;
+import org.projectfloodlight.openflow.protocol.OFVersion;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.KeyManagerFactory;
+import javax.net.ssl.SSLContext;
+import javax.net.ssl.SSLEngine;
+import javax.net.ssl.TrustManagerFactory;
+import java.io.FileInputStream;
+import java.lang.management.ManagementFactory;
+import java.lang.management.RuntimeMXBean;
+import java.net.InetSocketAddress;
+import java.security.KeyStore;
+import java.util.Dictionary;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.Executors;
+import java.util.stream.Collectors;
+import java.util.stream.Stream;
+
+import static org.onlab.util.Tools.get;
+import static org.onlab.util.Tools.groupedThreads;
+import static org.onosproject.net.DeviceId.deviceId;
+import static org.onosproject.openflow.controller.Dpid.uri;
+
+
+/**
+ * The main controller class. Handles all setup and network listeners
+ * - Distributed ownership control of switch through IControllerRegistryService
+ */
+public class Controller {
+
+ protected static final Logger log = LoggerFactory.getLogger(Controller.class);
+
+ protected static final OFFactory FACTORY13 = OFFactories.getFactory(OFVersion.OF_13);
+ protected static final OFFactory FACTORY10 = OFFactories.getFactory(OFVersion.OF_10);
+ private static final boolean TLS_DISABLED = false;
+ private static final short MIN_KS_LENGTH = 6;
+
+ protected HashMap<String, String> controllerNodeIPsCache;
+
+ private ChannelGroup cg;
+
+ // Configuration options
+ protected List<Integer> openFlowPorts = ImmutableList.of(6633, 6653);
+ protected int workerThreads = 16;
+
+ // Start time of the controller
+ protected long systemStartTime;
+
+ private OpenFlowAgent agent;
+
+ private NioServerSocketChannelFactory execFactory;
+
+ protected String ksLocation;
+ protected String tsLocation;
+ protected char[] ksPwd;
+ protected char[] tsPwd;
+ protected SSLEngine serverSslEngine;
+
+ // Perf. related configuration
+ protected static final int SEND_BUFFER_SIZE = 4 * 1024 * 1024;
+ private DriverService driverService;
+ private boolean enableOfTls = TLS_DISABLED;
+
+ // ***************
+ // Getters/Setters
+ // ***************
+
+ public OFFactory getOFMessageFactory10() {
+ return FACTORY10;
+ }
+
+
+ public OFFactory getOFMessageFactory13() {
+ return FACTORY13;
+ }
+
+ // **************
+ // Initialization
+ // **************
+
+ /**
+ * Tell controller that we're ready to accept switches loop.
+ */
+ public void run() {
+
+ 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 =
+ new OpenflowPipelineFactory(this, null, serverSslEngine);
+ bootstrap.setPipelineFactory(pfact);
+ cg = new DefaultChannelGroup();
+ openFlowPorts.forEach(port -> {
+ InetSocketAddress sa = new InetSocketAddress(port);
+ cg.add(bootstrap.bind(sa));
+ log.info("Listening for switch connections on {}", sa);
+ });
+
+ } catch (Exception e) {
+ throw new RuntimeException(e);
+ }
+
+ }
+
+ private ServerBootstrap createServerBootStrap() {
+
+ if (workerThreads == 0) {
+ execFactory = new NioServerSocketChannelFactory(
+ Executors.newCachedThreadPool(groupedThreads("onos/of", "boss-%d")),
+ Executors.newCachedThreadPool(groupedThreads("onos/of", "worker-%d")));
+ return new ServerBootstrap(execFactory);
+ } else {
+ execFactory = new NioServerSocketChannelFactory(
+ Executors.newCachedThreadPool(groupedThreads("onos/of", "boss-%d")),
+ Executors.newCachedThreadPool(groupedThreads("onos/of", "worker-%d")), workerThreads);
+ return new ServerBootstrap(execFactory);
+ }
+ }
+
+ public void setConfigParams(Dictionary<?, ?> properties) {
+ String ports = get(properties, "openflowPorts");
+ if (!Strings.isNullOrEmpty(ports)) {
+ this.openFlowPorts = Stream.of(ports.split(","))
+ .map(s -> Integer.parseInt(s))
+ .collect(Collectors.toList());
+ }
+ log.debug("OpenFlow ports set to {}", this.openFlowPorts);
+
+ String threads = get(properties, "workerThreads");
+ if (!Strings.isNullOrEmpty(threads)) {
+ this.workerThreads = Integer.parseInt(threads);
+ }
+ log.debug("Number of worker threads set to {}", this.workerThreads);
+ }
+
+ /**
+ * Initialize internal data structures.
+ */
+ public void init() {
+ // These data structures are initialized here because other
+ // module's startUp() might be called before ours
+ this.controllerNodeIPsCache = new HashMap<>();
+
+ this.systemStartTime = System.currentTimeMillis();
+
+ try {
+ getTlsParameters();
+ if (enableOfTls) {
+ initSsl();
+ }
+ } catch (Exception ex) {
+ log.error("SSL init failed: {}", ex.getMessage());
+ }
+
+ }
+
+ private void getTlsParameters() {
+ String tempString = System.getProperty("enableOFTLS");
+ enableOfTls = Strings.isNullOrEmpty(tempString) ? TLS_DISABLED : Boolean.parseBoolean(tempString);
+ log.info("OpenFlow Security is {}", enableOfTls ? "enabled" : "disabled");
+ if (enableOfTls) {
+ ksLocation = System.getProperty("javax.net.ssl.keyStore");
+ if (Strings.isNullOrEmpty(ksLocation)) {
+ enableOfTls = TLS_DISABLED;
+ return;
+ }
+ tsLocation = System.getProperty("javax.net.ssl.trustStore");
+ if (Strings.isNullOrEmpty(tsLocation)) {
+ enableOfTls = TLS_DISABLED;
+ return;
+ }
+ ksPwd = System.getProperty("javax.net.ssl.keyStorePassword").toCharArray();
+ if (MIN_KS_LENGTH > ksPwd.length) {
+ enableOfTls = TLS_DISABLED;
+ return;
+ }
+ tsPwd = System.getProperty("javax.net.ssl.trustStorePassword").toCharArray();
+ if (MIN_KS_LENGTH > tsPwd.length) {
+ enableOfTls = TLS_DISABLED;
+ return;
+ }
+ }
+ }
+
+ private void initSsl() throws Exception {
+
+ TrustManagerFactory tmFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
+ KeyStore ts = KeyStore.getInstance("JKS");
+ ts.load(new FileInputStream(tsLocation), tsPwd);
+ tmFactory.init(ts);
+
+ KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
+ KeyStore ks = KeyStore.getInstance("JKS");
+ ks.load(new FileInputStream(ksLocation), ksPwd);
+ kmf.init(ks, ksPwd);
+
+ SSLContext serverContext = SSLContext.getInstance("TLS");
+ serverContext.init(kmf.getKeyManagers(), tmFactory.getTrustManagers(), null);
+
+ serverSslEngine = serverContext.createSSLEngine();
+
+ serverSslEngine.setNeedClientAuth(true);
+ serverSslEngine.setUseClientMode(false);
+ serverSslEngine.setEnabledProtocols(serverSslEngine.getSupportedProtocols());
+ serverSslEngine.setEnabledCipherSuites(serverSslEngine.getSupportedCipherSuites());
+ serverSslEngine.setEnableSessionCreation(true);
+ }
+
+ // **************
+ // Utility methods
+ // **************
+
+ public Map<String, Long> getMemory() {
+ Map<String, Long> m = new HashMap<>();
+ Runtime runtime = Runtime.getRuntime();
+ m.put("total", runtime.totalMemory());
+ m.put("free", runtime.freeMemory());
+ return m;
+ }
+
+
+ public Long getSystemUptime() {
+ RuntimeMXBean rb = ManagementFactory.getRuntimeMXBean();
+ return rb.getUptime();
+ }
+
+ public long getSystemStartTime() {
+ return (this.systemStartTime);
+ }
+
+ /**
+ * Forward to the driver-manager to get an IOFSwitch instance.
+ *
+ * @param dpid data path id
+ * @param desc switch description
+ * @param ofv OpenFlow version
+ * @return switch instance
+ */
+ protected OpenFlowSwitchDriver getOFSwitchInstance(long dpid,
+ OFDescStatsReply desc,
+ OFVersion ofv) {
+ Dpid dpidObj = new Dpid(dpid);
+
+ Driver driver;
+ try {
+ driver = driverService.getDriver(DeviceId.deviceId(Dpid.uri(dpidObj)));
+ } catch (ItemNotFoundException e) {
+ driver = driverService.getDriver(desc.getMfrDesc(), desc.getHwDesc(), desc.getSwDesc());
+ }
+
+ if (driver != null && driver.hasBehaviour(OpenFlowSwitchDriver.class)) {
+ Dpid did = new Dpid(dpid);
+ DefaultDriverHandler handler =
+ new DefaultDriverHandler(new DefaultDriverData(driver, deviceId(uri(did))));
+ OpenFlowSwitchDriver ofSwitchDriver =
+ driver.createBehaviour(handler, OpenFlowSwitchDriver.class);
+ ofSwitchDriver.init(did, desc, ofv);
+ ofSwitchDriver.setAgent(agent);
+ ofSwitchDriver.setRoleHandler(new RoleManager(ofSwitchDriver));
+ log.info("OpenFlow handshaker found for device {}: {}", dpid, ofSwitchDriver);
+ return ofSwitchDriver;
+ }
+ log.error("No OpenFlow driver for {} : {}", dpid, desc);
+ return null;
+
+ }
+
+ public void start(OpenFlowAgent ag, DriverService driverService) {
+ log.info("Starting OpenFlow IO");
+ this.agent = ag;
+ this.driverService = driverService;
+ this.init();
+ this.run();
+ }
+
+
+ public void stop() {
+ log.info("Stopping OpenFlow IO");
+ cg.close();
+ execFactory.shutdown();
+ }
+
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/HandshakeTimeoutException.java b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/HandshakeTimeoutException.java
new file mode 100644
index 00000000..bbe307be
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/HandshakeTimeoutException.java
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow.controller.impl;
+
+/**
+ * Exception is thrown when the handshake fails to complete.
+ * before a specified time
+ *
+ */
+public class HandshakeTimeoutException extends Exception {
+
+ private static final long serialVersionUID = 6859880268940337312L;
+
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/HandshakeTimeoutHandler.java b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/HandshakeTimeoutHandler.java
new file mode 100644
index 00000000..fbbe3428
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/HandshakeTimeoutHandler.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow.controller.impl;
+
+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;
+
+/**
+ * Trigger a timeout if a switch fails to complete handshake soon enough.
+ */
+public class HandshakeTimeoutHandler
+ extends SimpleChannelUpstreamHandler {
+ static final HandshakeTimeoutException EXCEPTION =
+ new HandshakeTimeoutException();
+
+ final OFChannelHandler channelHandler;
+ final Timer timer;
+ final long timeoutNanos;
+ volatile Timeout timeout;
+
+ public HandshakeTimeoutHandler(OFChannelHandler channelHandler,
+ Timer timer,
+ long timeoutSeconds) {
+ super();
+ this.channelHandler = channelHandler;
+ this.timer = timer;
+ this.timeoutNanos = TimeUnit.SECONDS.toNanos(timeoutSeconds);
+
+ }
+
+ @Override
+ public void channelOpen(ChannelHandlerContext ctx, ChannelStateEvent e)
+ throws Exception {
+ if (timeoutNanos > 0) {
+ timeout = timer.newTimeout(new HandshakeTimeoutTask(ctx),
+ timeoutNanos, TimeUnit.NANOSECONDS);
+ }
+ ctx.sendUpstream(e);
+ }
+
+ @Override
+ public void channelClosed(ChannelHandlerContext ctx, ChannelStateEvent e)
+ throws Exception {
+ if (timeout != null) {
+ timeout.cancel();
+ timeout = null;
+ }
+ }
+
+ private final class HandshakeTimeoutTask implements TimerTask {
+
+ private final ChannelHandlerContext ctx;
+
+ HandshakeTimeoutTask(ChannelHandlerContext ctx) {
+ this.ctx = ctx;
+ }
+
+ @Override
+ public void run(Timeout t) throws Exception {
+ if (t.isCancelled()) {
+ return;
+ }
+
+ if (!ctx.getChannel().isOpen()) {
+ return;
+ }
+ if (!channelHandler.isHandshakeComplete()) {
+ Channels.fireExceptionCaught(ctx, EXCEPTION);
+ }
+ }
+ }
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFChannelHandler.java b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFChannelHandler.java
new file mode 100644
index 00000000..ff92b77e
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFChannelHandler.java
@@ -0,0 +1,1320 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+//CHECKSTYLE:OFF
+package org.onosproject.openflow.controller.impl;
+
+import java.io.IOException;
+import java.nio.channels.ClosedChannelException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.concurrent.CopyOnWriteArrayList;
+import java.util.concurrent.RejectedExecutionException;
+
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelStateEvent;
+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 org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver;
+import org.onosproject.openflow.controller.driver.SwitchStateException;
+import org.projectfloodlight.openflow.exceptions.OFParseError;
+import org.projectfloodlight.openflow.protocol.OFAsyncGetReply;
+import org.projectfloodlight.openflow.protocol.OFBadRequestCode;
+import org.projectfloodlight.openflow.protocol.OFBarrierReply;
+import org.projectfloodlight.openflow.protocol.OFBarrierRequest;
+import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
+import org.projectfloodlight.openflow.protocol.OFDescStatsRequest;
+import org.projectfloodlight.openflow.protocol.OFEchoReply;
+import org.projectfloodlight.openflow.protocol.OFEchoRequest;
+import org.projectfloodlight.openflow.protocol.OFErrorMsg;
+import org.projectfloodlight.openflow.protocol.OFErrorType;
+import org.projectfloodlight.openflow.protocol.OFExperimenter;
+import org.projectfloodlight.openflow.protocol.OFFactory;
+import org.projectfloodlight.openflow.protocol.OFFeaturesReply;
+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.OFHello;
+import org.projectfloodlight.openflow.protocol.OFHelloElem;
+import org.projectfloodlight.openflow.protocol.OFMessage;
+import org.projectfloodlight.openflow.protocol.OFPacketIn;
+import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply;
+import org.projectfloodlight.openflow.protocol.OFPortDescStatsRequest;
+import org.projectfloodlight.openflow.protocol.OFPortStatus;
+import org.projectfloodlight.openflow.protocol.OFQueueGetConfigReply;
+import org.projectfloodlight.openflow.protocol.OFRoleReply;
+import org.projectfloodlight.openflow.protocol.OFSetConfig;
+import org.projectfloodlight.openflow.protocol.OFStatsReply;
+import org.projectfloodlight.openflow.protocol.OFStatsReplyFlags;
+import org.projectfloodlight.openflow.protocol.OFStatsType;
+import org.projectfloodlight.openflow.protocol.OFType;
+import org.projectfloodlight.openflow.protocol.OFVersion;
+import org.projectfloodlight.openflow.protocol.errormsg.OFBadRequestErrorMsg;
+import org.projectfloodlight.openflow.protocol.errormsg.OFFlowModFailedErrorMsg;
+import org.projectfloodlight.openflow.types.U32;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+/**
+ * Channel handler deals with the switch connection and dispatches
+ * switch messages to the appropriate locations.
+ */
+class OFChannelHandler extends IdleStateAwareChannelHandler {
+ private static final Logger log = LoggerFactory.getLogger(OFChannelHandler.class);
+
+ private static final String RESET_BY_PEER = "Connection reset by peer";
+ private static final String BROKEN_PIPE = "Broken pipe";
+
+ private final Controller controller;
+ private OpenFlowSwitchDriver sw;
+ private long thisdpid; // channelHandler cached value of connected switch id
+ private Channel channel;
+ // State needs to be volatile because the HandshakeTimeoutHandler
+ // needs to check if the handshake is complete
+ private volatile ChannelState state;
+
+ // When a switch with a duplicate dpid is found (i.e we already have a
+ // connected switch with the same dpid), the new switch is immediately
+ // disconnected. At that point netty callsback channelDisconnected() which
+ // proceeds to cleaup switch state - we need to ensure that it does not cleanup
+ // switch state for the older (still connected) switch
+ private volatile Boolean duplicateDpidFound;
+
+ // Temporary storage for switch-features and port-description
+ private OFFeaturesReply featuresReply;
+ private List<OFPortDescStatsReply> portDescReplies;
+ //private OFPortDescStatsReply portDescReply;
+ // a concurrent ArrayList to temporarily store port status messages
+ // before we are ready to deal with them
+ private final CopyOnWriteArrayList<OFPortStatus> pendingPortStatusMsg;
+
+ //Indicates the openflow version used by this switch
+ protected OFVersion ofVersion;
+ protected OFFactory factory13;
+ protected OFFactory factory10;
+
+ /** transaction Ids to use during handshake. Since only one thread
+ * calls into an OFChannelHandler instance, we don't need atomic.
+ * We will count down
+ */
+ private int handshakeTransactionIds = -1;
+
+ /**
+ * Create a new unconnected OFChannelHandler.
+ * @param controller parent controller
+ */
+ OFChannelHandler(Controller controller) {
+ this.controller = controller;
+ this.state = ChannelState.INIT;
+ this.pendingPortStatusMsg = new CopyOnWriteArrayList<OFPortStatus>();
+ this.portDescReplies = new ArrayList<OFPortDescStatsReply>();
+ factory13 = controller.getOFMessageFactory13();
+ factory10 = controller.getOFMessageFactory10();
+ duplicateDpidFound = Boolean.FALSE;
+ }
+
+
+
+ // XXX S consider if necessary
+ public void disconnectSwitch() {
+ sw.disconnectSwitch();
+ }
+
+
+
+ //*************************
+ // Channel State Machine
+ //*************************
+
+ /**
+ * The state machine for handling the switch/channel state. All state
+ * transitions should happen from within the state machine (and not from other
+ * parts of the code)
+ */
+ enum ChannelState {
+ /**
+ * Initial state before channel is connected.
+ */
+ INIT(false) {
+ @Override
+ void processOFMessage(OFChannelHandler h, OFMessage m)
+ throws IOException, SwitchStateException {
+ illegalMessageReceived(h, m);
+ }
+
+ @Override
+ void processOFError(OFChannelHandler h, OFErrorMsg m)
+ throws IOException {
+ // need to implement since its abstract but it will never
+ // be called
+ }
+
+ @Override
+ void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException {
+ unhandledMessageReceived(h, m);
+ }
+ },
+
+ /**
+ * We send a OF 1.3 HELLO to the switch and wait for a Hello from the switch.
+ * Once we receive the reply, we decide on OF 1.3 or 1.0 switch - no other
+ * protocol version is accepted.
+ * We send an OFFeaturesRequest depending on the protocol version selected
+ * Next state is WAIT_FEATURES_REPLY
+ */
+ WAIT_HELLO(false) {
+ @Override
+ void processOFHello(OFChannelHandler h, OFHello m)
+ throws IOException {
+ // TODO We could check for the optional bitmap, but for now
+ // we are just checking the version number.
+ if (m.getVersion().getWireVersion() >= OFVersion.OF_13.getWireVersion()) {
+ log.debug("Received {} Hello from {} - switching to OF "
+ + "version 1.3", m.getVersion(),
+ h.channel.getRemoteAddress());
+ h.sendHandshakeHelloMessage();
+ h.ofVersion = OFVersion.OF_13;
+ } else if (m.getVersion().getWireVersion() >= OFVersion.OF_10.getWireVersion()) {
+ log.debug("Received {} Hello from {} - switching to OF "
+ + "version 1.0", m.getVersion(),
+ h.channel.getRemoteAddress());
+ OFHello hi =
+ h.factory10.buildHello()
+ .setXid(h.handshakeTransactionIds--)
+ .build();
+ h.channel.write(Collections.singletonList(hi));
+ h.ofVersion = OFVersion.OF_10;
+ } else {
+ log.error("Received Hello of version {} from switch at {}. "
+ + "This controller works with OF1.0 and OF1.3 "
+ + "switches. Disconnecting switch ...",
+ m.getVersion(), h.channel.getRemoteAddress());
+ h.channel.disconnect();
+ return;
+ }
+ h.sendHandshakeFeaturesRequestMessage();
+ h.setState(WAIT_FEATURES_REPLY);
+ }
+ @Override
+ void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m)
+ throws IOException, SwitchStateException {
+ illegalMessageReceived(h, m);
+ }
+ @Override
+ void processOFStatisticsReply(OFChannelHandler h,
+ OFStatsReply m)
+ throws IOException, SwitchStateException {
+ illegalMessageReceived(h, m);
+ }
+ @Override
+ void processOFError(OFChannelHandler h, OFErrorMsg m) {
+ logErrorDisconnect(h, m);
+ }
+
+ @Override
+ void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException {
+ unhandledMessageReceived(h, m);
+ }
+ },
+
+
+ /**
+ * We are waiting for a features reply message. Once we receive it, the
+ * behavior depends on whether this is a 1.0 or 1.3 switch. For 1.0,
+ * we send a SetConfig request, barrier, and GetConfig request and the
+ * next state is WAIT_CONFIG_REPLY. For 1.3, we send a Port description
+ * request and the next state is WAIT_PORT_DESC_REPLY.
+ */
+ WAIT_FEATURES_REPLY(false) {
+ @Override
+ void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m)
+ throws IOException {
+ h.thisdpid = m.getDatapathId().getLong();
+ log.debug("Received features reply for switch at {} with dpid {}",
+ h.getSwitchInfoString(), h.thisdpid);
+
+ h.featuresReply = m; //temp store
+ if (h.ofVersion == OFVersion.OF_10) {
+ h.sendHandshakeSetConfig();
+ h.setState(WAIT_CONFIG_REPLY);
+ } else {
+ //version is 1.3, must get switchport information
+ h.sendHandshakeOFPortDescRequest();
+ h.setState(WAIT_PORT_DESC_REPLY);
+ }
+ }
+ @Override
+ void processOFStatisticsReply(OFChannelHandler h,
+ OFStatsReply m)
+ throws IOException, SwitchStateException {
+ illegalMessageReceived(h, m);
+ }
+ @Override
+ void processOFError(OFChannelHandler h, OFErrorMsg m) {
+ logErrorDisconnect(h, m);
+ }
+
+ @Override
+ void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException {
+ h.pendingPortStatusMsg.add(m);
+ }
+ },
+
+ /**
+ * We are waiting for a description of the 1.3 switch ports.
+ * Once received, we send a SetConfig request
+ * Next State is WAIT_CONFIG_REPLY
+ */
+ WAIT_PORT_DESC_REPLY(false) {
+
+ @Override
+ void processOFStatisticsReply(OFChannelHandler h, OFStatsReply m)
+ throws SwitchStateException {
+ // Read port description
+ if (m.getStatsType() != OFStatsType.PORT_DESC) {
+ log.warn("Expecting port description stats but received stats "
+ + "type {} from {}. Ignoring ...", m.getStatsType(),
+ h.channel.getRemoteAddress());
+ return;
+ }
+ if (m.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) {
+ log.debug("Stats reply indicates more stats from sw {} for "
+ + "port description",
+ h.getSwitchInfoString());
+ h.portDescReplies.add((OFPortDescStatsReply)m);
+ return;
+ }
+ else {
+ h.portDescReplies.add((OFPortDescStatsReply)m);
+ }
+ //h.portDescReply = (OFPortDescStatsReply) m; // temp store
+ log.info("Received port desc reply for switch at {}",
+ h.getSwitchInfoString());
+ try {
+ h.sendHandshakeSetConfig();
+ } catch (IOException e) {
+ log.error("Unable to send setConfig after PortDescReply. "
+ + "Error: {}", e.getMessage());
+ }
+ h.setState(WAIT_CONFIG_REPLY);
+ }
+
+ @Override
+ void processOFError(OFChannelHandler h, OFErrorMsg m)
+ throws IOException, SwitchStateException {
+ logErrorDisconnect(h, m);
+
+ }
+
+ @Override
+ void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException, SwitchStateException {
+ h.pendingPortStatusMsg.add(m);
+
+ }
+ },
+
+ /**
+ * We are waiting for a config reply message. Once we receive it
+ * we send a DescriptionStatsRequest to the switch.
+ * Next state: WAIT_DESCRIPTION_STAT_REPLY
+ */
+ WAIT_CONFIG_REPLY(false) {
+ @Override
+ void processOFGetConfigReply(OFChannelHandler h, OFGetConfigReply m)
+ throws IOException {
+ if (m.getMissSendLen() == 0xffff) {
+ log.trace("Config Reply from switch {} confirms "
+ + "miss length set to 0xffff",
+ h.getSwitchInfoString());
+ } else {
+ // FIXME: we can't really deal with switches that don't send
+ // full packets. Shouldn't we drop the connection here?
+ log.warn("Config Reply from switch {} has"
+ + "miss length set to {}",
+ h.getSwitchInfoString(),
+ m.getMissSendLen());
+ }
+ h.sendHandshakeDescriptionStatsRequest();
+ h.setState(WAIT_DESCRIPTION_STAT_REPLY);
+ }
+
+ @Override
+ void processOFBarrierReply(OFChannelHandler h, OFBarrierReply m) {
+ // do nothing;
+ }
+
+ @Override
+ void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m)
+ throws IOException, SwitchStateException {
+ illegalMessageReceived(h, m);
+ }
+ @Override
+ void processOFStatisticsReply(OFChannelHandler h,
+ OFStatsReply m)
+ throws IOException, SwitchStateException {
+ log.error("Received multipart(stats) message sub-type {}",
+ m.getStatsType());
+ illegalMessageReceived(h, m);
+ }
+
+ @Override
+ void processOFError(OFChannelHandler h, OFErrorMsg m) {
+ logErrorDisconnect(h, m);
+ }
+
+ @Override
+ void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException {
+ h.pendingPortStatusMsg.add(m);
+ }
+ },
+
+
+ /**
+ * We are waiting for a OFDescriptionStat message from the switch.
+ * Once we receive any stat message we try to parse it. If it's not
+ * a description stats message we disconnect. If its the expected
+ * description stats message, we:
+ * - use the switch driver to bind the switch and get an IOFSwitch instance
+ * - setup the IOFSwitch instance
+ * - add switch controller and send the initial role
+ * request to the switch.
+ * Next state: WAIT_INITIAL_ROLE
+ * In the typical case, where switches support role request messages
+ * the next state is where we expect the role reply message.
+ * In the special case that where the switch does not support any kind
+ * of role request messages, we don't send a role message, but we do
+ * request mastership from the registry service. This controller
+ * should become master once we hear back from the registry service.
+ * All following states will have a h.sw instance!
+ */
+ WAIT_DESCRIPTION_STAT_REPLY(false) {
+ @Override
+ void processOFStatisticsReply(OFChannelHandler h, OFStatsReply m)
+ throws SwitchStateException {
+ // Read description, if it has been updated
+ if (m.getStatsType() != OFStatsType.DESC) {
+ log.warn("Expecting Description stats but received stats "
+ + "type {} from {}. Ignoring ...", m.getStatsType(),
+ h.channel.getRemoteAddress());
+ return;
+ }
+ OFDescStatsReply drep = (OFDescStatsReply) m;
+ log.info("Received switch description reply {} from switch at {}",
+ drep, h.channel.getRemoteAddress());
+ // Here is where we differentiate between different kinds of switches
+ h.sw = h.controller.getOFSwitchInstance(h.thisdpid, drep, h.ofVersion);
+
+ h.sw.setOFVersion(h.ofVersion);
+ h.sw.setFeaturesReply(h.featuresReply);
+ //h.sw.setPortDescReply(h.portDescReply);
+ h.sw.setPortDescReplies(h.portDescReplies);
+ h.sw.setConnected(true);
+ h.sw.setChannel(h.channel);
+// boolean success = h.sw.connectSwitch();
+//
+// if (!success) {
+// disconnectDuplicate(h);
+// return;
+// }
+ // set switch information
+
+
+
+ log.debug("Switch {} bound to class {}, description {}",
+ h.sw, h.sw.getClass(), drep);
+ //Put switch in EQUAL mode until we hear back from the global registry
+ //log.debug("Setting new switch {} to EQUAL and sending Role request",
+ // h.sw.getStringId());
+ //h.sw.activateEqualSwitch();
+ //h.setSwitchRole(RoleState.EQUAL);
+
+ h.sw.startDriverHandshake();
+ if (h.sw.isDriverHandshakeComplete()) {
+ if (!h.sw.connectSwitch()) {
+ disconnectDuplicate(h);
+ }
+ handlePendingPortStatusMessages(h);
+ h.setState(ACTIVE);
+ } else {
+ h.setState(WAIT_SWITCH_DRIVER_SUB_HANDSHAKE);
+ }
+
+ }
+
+ @Override
+ void processOFError(OFChannelHandler h, OFErrorMsg m) {
+ logErrorDisconnect(h, m);
+ }
+
+ @Override
+ void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m)
+ throws IOException, SwitchStateException {
+ illegalMessageReceived(h, m);
+ }
+
+ @Override
+ void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException {
+ h.pendingPortStatusMsg.add(m);
+ }
+ },
+
+
+ /**
+ * We are waiting for the respective switch driver to complete its
+ * configuration. Notice that we do not consider this to be part of the main
+ * switch-controller handshake. But we do consider it as a step that comes
+ * before we declare the switch as available to the controller.
+ * Next State: depends on the role of this controller for this switch - either
+ * MASTER or EQUAL.
+ */
+ WAIT_SWITCH_DRIVER_SUB_HANDSHAKE(true) {
+
+ @Override
+ void processOFError(OFChannelHandler h, OFErrorMsg m)
+ throws IOException {
+ // will never be called. We override processOFMessage
+ }
+
+
+
+ @Override
+ void processOFMessage(OFChannelHandler h, OFMessage m)
+ throws IOException, SwitchStateException {
+
+ if (h.sw.isDriverHandshakeComplete()) {
+ moveToActive(h);
+ h.state.processOFMessage(h, m);
+ return;
+
+ }
+
+ if (m.getType() == OFType.ECHO_REQUEST) {
+ processOFEchoRequest(h, (OFEchoRequest) m);
+ } else if (m.getType() == OFType.ECHO_REPLY) {
+ processOFEchoReply(h, (OFEchoReply) m);
+ } else if (m.getType() == OFType.ROLE_REPLY) {
+ h.sw.handleRole(m);
+ } else if (m.getType() == OFType.ERROR) {
+ if (!h.sw.handleRoleError((OFErrorMsg)m)) {
+ h.sw.processDriverHandshakeMessage(m);
+ if (h.sw.isDriverHandshakeComplete()) {
+ moveToActive(h);
+ }
+ }
+ } else {
+ if (m.getType() == OFType.EXPERIMENTER &&
+ ((OFExperimenter) m).getExperimenter() ==
+ RoleManager.NICIRA_EXPERIMENTER) {
+ h.sw.handleNiciraRole(m);
+ } else {
+ h.sw.processDriverHandshakeMessage(m);
+ if (h.sw.isDriverHandshakeComplete()) {
+ moveToActive(h);
+ }
+ }
+ }
+ }
+
+ @Override
+ void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException, SwitchStateException {
+ h.pendingPortStatusMsg.add(m);
+ }
+
+ private void moveToActive(OFChannelHandler h) {
+ boolean success = h.sw.connectSwitch();
+ handlePendingPortStatusMessages(h);
+ h.setState(ACTIVE);
+ if (!success) {
+ disconnectDuplicate(h);
+ }
+ }
+
+ },
+
+
+ /**
+ * This controller is in MASTER role for this switch. We enter this state
+ * after requesting and winning control from the global registry.
+ * The main handshake as well as the switch-driver sub-handshake
+ * is complete at this point.
+ * // XXX S reconsider below
+ * In the (near) future we may deterministically assign controllers to
+ * switches at startup.
+ * We only leave this state if the switch disconnects or
+ * if we send a role request for SLAVE /and/ receive the role reply for
+ * SLAVE.
+ */
+ ACTIVE(true) {
+ @Override
+ void processOFError(OFChannelHandler h, OFErrorMsg m)
+ throws IOException, SwitchStateException {
+ // if we get here, then the error message is for something else
+ if (m.getErrType() == OFErrorType.BAD_REQUEST &&
+ ((OFBadRequestErrorMsg) m).getCode() ==
+ OFBadRequestCode.EPERM) {
+ // We are the master controller and the switch returned
+ // a permission error. This is a likely indicator that
+ // the switch thinks we are slave. Reassert our
+ // role
+ // FIXME: this could be really bad during role transitions
+ // if two controllers are master (even if its only for
+ // a brief period). We might need to see if these errors
+ // persist before we reassert
+
+ h.sw.reassertRole();
+ } else if (m.getErrType() == OFErrorType.FLOW_MOD_FAILED &&
+ ((OFFlowModFailedErrorMsg) m).getCode() ==
+ OFFlowModFailedCode.ALL_TABLES_FULL) {
+ h.sw.setTableFull(true);
+ } else {
+ logError(h, m);
+ }
+ h.dispatchMessage(m);
+ }
+
+ @Override
+ void processOFStatisticsReply(OFChannelHandler h,
+ OFStatsReply m) {
+ if (m.getStatsType().equals(OFStatsType.PORT_DESC)) {
+ h.sw.setPortDescReply((OFPortDescStatsReply) m);
+ }
+ h.dispatchMessage(m);
+ }
+
+ @Override
+ void processOFExperimenter(OFChannelHandler h, OFExperimenter m)
+ throws SwitchStateException {
+ h.sw.handleNiciraRole(m);
+ }
+
+ @Override
+ void processOFRoleReply(OFChannelHandler h, OFRoleReply m)
+ throws SwitchStateException {
+ h.sw.handleRole(m);
+ }
+
+ @Override
+ void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws SwitchStateException {
+ handlePortStatusMessage(h, m, true);
+ //h.dispatchMessage(m);
+ }
+
+ @Override
+ void processOFPacketIn(OFChannelHandler h, OFPacketIn m) {
+// OFPacketOut out =
+// h.sw.factory().buildPacketOut()
+// .setXid(m.getXid())
+// .setBufferId(m.getBufferId()).build();
+// h.sw.sendMsg(out);
+ h.dispatchMessage(m);
+ }
+
+ @Override
+ void processOFFlowRemoved(OFChannelHandler h,
+ OFFlowRemoved m) {
+ h.dispatchMessage(m);
+ }
+
+ @Override
+ void processOFBarrierReply(OFChannelHandler h, OFBarrierReply m) {
+ h.dispatchMessage(m);
+ }
+
+ @Override
+ void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m) {
+ h.sw.setFeaturesReply(m);
+ h.dispatchMessage(m);
+ }
+
+ };
+
+ private final boolean handshakeComplete;
+ ChannelState(boolean handshakeComplete) {
+ this.handshakeComplete = handshakeComplete;
+ }
+
+ /**
+ * Is this a state in which the handshake has completed?
+ * @return true if the handshake is complete
+ */
+ public boolean isHandshakeComplete() {
+ return handshakeComplete;
+ }
+
+ /**
+ * Get a string specifying the switch connection, state, and
+ * message received. To be used as message for SwitchStateException
+ * or log messages
+ * @param h The channel handler (to get switch information_
+ * @param m The OFMessage that has just been received
+ * @param details A string giving more details about the exact nature
+ * of the problem.
+ * @return display string
+ */
+ // needs to be protected because enum members are actually subclasses
+ protected String getSwitchStateMessage(OFChannelHandler h,
+ OFMessage m,
+ String details) {
+ return String.format("Switch: [%s], State: [%s], received: [%s]"
+ + ", details: %s",
+ h.getSwitchInfoString(),
+ this.toString(),
+ m.getType().toString(),
+ details);
+ }
+
+ /**
+ * We have an OFMessage we didn't expect given the current state and
+ * we want to treat this as an error.
+ * We currently throw an exception that will terminate the connection
+ * However, we could be more forgiving
+ * @param h the channel handler that received the message
+ * @param m the message
+ * @throws SwitchStateException we always throw the exception
+ */
+ // needs to be protected because enum members are actually subclasses
+ protected void illegalMessageReceived(OFChannelHandler h, OFMessage m)
+ throws SwitchStateException {
+ String msg = getSwitchStateMessage(h, m,
+ "Switch should never send this message in the current state");
+ throw new SwitchStateException(msg);
+
+ }
+
+ /**
+ * We have an OFMessage we didn't expect given the current state and
+ * we want to ignore the message.
+ * @param h the channel handler the received the message
+ * @param m the message
+ */
+ protected void unhandledMessageReceived(OFChannelHandler h,
+ OFMessage m) {
+ if (log.isDebugEnabled()) {
+ String msg = getSwitchStateMessage(h, m,
+ "Ignoring unexpected message");
+ log.debug(msg);
+ }
+ }
+
+ /**
+ * Log an OpenFlow error message from a switch.
+ * @param h The switch that sent the error
+ * @param error The error message
+ */
+ protected void logError(OFChannelHandler h, OFErrorMsg error) {
+ log.error("{} from switch {} in state {}",
+ error,
+ h.getSwitchInfoString(),
+ this.toString());
+ }
+
+ /**
+ * Log an OpenFlow error message from a switch and disconnect the
+ * channel.
+ *
+ * @param h the IO channel for this switch.
+ * @param error The error message
+ */
+ protected void logErrorDisconnect(OFChannelHandler h, OFErrorMsg error) {
+ logError(h, error);
+ h.channel.disconnect();
+ }
+
+ /**
+ * log an error message for a duplicate dpid and disconnect this channel.
+ * @param h the IO channel for this switch.
+ */
+ protected void disconnectDuplicate(OFChannelHandler h) {
+ log.error("Duplicated dpid or incompleted cleanup - "
+ + "disconnecting channel {}", h.getSwitchInfoString());
+ h.duplicateDpidFound = Boolean.TRUE;
+ h.channel.disconnect();
+ }
+
+
+
+ /**
+ * Handles all pending port status messages before a switch is declared
+ * activated in MASTER or EQUAL role. Note that since this handling
+ * precedes the activation (and therefore notification to IOFSwitchListerners)
+ * the changes to ports will already be visible once the switch is
+ * activated. As a result, no notifications are sent out for these
+ * pending portStatus messages.
+ *
+ * @param h the channel handler that received the message
+ */
+ protected void handlePendingPortStatusMessages(OFChannelHandler h) {
+ try {
+ handlePendingPortStatusMessages(h, 0);
+ } catch (SwitchStateException e) {
+ log.error(e.getMessage());
+ }
+ }
+
+ private void handlePendingPortStatusMessages(OFChannelHandler h, int index)
+ throws SwitchStateException {
+ if (h.sw == null) {
+ String msg = "State machine error: switch is null. Should never " +
+ "happen";
+ throw new SwitchStateException(msg);
+ }
+ log.info("Processing {} pending port status messages for {}",
+ h.pendingPortStatusMsg.size(), h.sw.getStringId());
+
+ ArrayList<OFPortStatus> temp = new ArrayList<OFPortStatus>();
+ for (OFPortStatus ps: h.pendingPortStatusMsg) {
+ temp.add(ps);
+ handlePortStatusMessage(h, ps, false);
+ }
+ // expensive but ok - we don't expect too many port-status messages
+ // note that we cannot use clear(), because of the reasons below
+ h.pendingPortStatusMsg.removeAll(temp);
+ temp.clear();
+ // the iterator above takes a snapshot of the list - so while we were
+ // dealing with the pending port-status messages, we could have received
+ // newer ones. Handle them recursively, but break the recursion after
+ // five steps to avoid an attack.
+ if (!h.pendingPortStatusMsg.isEmpty() && ++index < 5) {
+ handlePendingPortStatusMessages(h, index);
+ }
+ }
+
+ /**
+ * Handle a port status message.
+ *
+ * Handle a port status message by updating the port maps in the
+ * IOFSwitch instance and notifying Controller about the change so
+ * it can dispatch a switch update.
+ *
+ * @param h The OFChannelHhandler that received the message
+ * @param m The PortStatus message we received
+ * @param doNotify if true switch port changed events will be
+ * dispatched
+ * @throws SwitchStateException if the switch is not bound to the channel
+ *
+ */
+ protected void handlePortStatusMessage(OFChannelHandler h, OFPortStatus m,
+ boolean doNotify) throws SwitchStateException {
+ if (h.sw == null) {
+ String msg = getSwitchStateMessage(h, m,
+ "State machine error: switch is null. Should never " +
+ "happen");
+ throw new SwitchStateException(msg);
+ }
+
+ h.sw.handleMessage(m);
+ }
+
+
+ /**
+ * Process an OF message received on the channel and
+ * update state accordingly.
+ *
+ * The main "event" of the state machine. Process the received message,
+ * send follow up message if required and update state if required.
+ *
+ * Switches on the message type and calls more specific event handlers
+ * for each individual OF message type. If we receive a message that
+ * is supposed to be sent from a controller to a switch we throw
+ * a SwitchStateExeption.
+ *
+ * The more specific handlers can also throw SwitchStateExceptions
+ *
+ * @param h The OFChannelHandler that received the message
+ * @param m The message we received.
+ * @throws SwitchStateException if the switch is not bound to the channel
+ * @throws IOException if unable to send message back to the switch
+ */
+ void processOFMessage(OFChannelHandler h, OFMessage m)
+ throws IOException, SwitchStateException {
+ switch(m.getType()) {
+ case HELLO:
+ processOFHello(h, (OFHello) m);
+ break;
+ case BARRIER_REPLY:
+ processOFBarrierReply(h, (OFBarrierReply) m);
+ break;
+ case ECHO_REPLY:
+ processOFEchoReply(h, (OFEchoReply) m);
+ break;
+ case ECHO_REQUEST:
+ processOFEchoRequest(h, (OFEchoRequest) m);
+ break;
+ case ERROR:
+ processOFError(h, (OFErrorMsg) m);
+ break;
+ case FEATURES_REPLY:
+ processOFFeaturesReply(h, (OFFeaturesReply) m);
+ break;
+ case FLOW_REMOVED:
+ processOFFlowRemoved(h, (OFFlowRemoved) m);
+ break;
+ case GET_CONFIG_REPLY:
+ processOFGetConfigReply(h, (OFGetConfigReply) m);
+ break;
+ case PACKET_IN:
+ processOFPacketIn(h, (OFPacketIn) m);
+ break;
+ case PORT_STATUS:
+ processOFPortStatus(h, (OFPortStatus) m);
+ break;
+ case QUEUE_GET_CONFIG_REPLY:
+ processOFQueueGetConfigReply(h, (OFQueueGetConfigReply) m);
+ break;
+ case STATS_REPLY: // multipart_reply in 1.3
+ processOFStatisticsReply(h, (OFStatsReply) m);
+ break;
+ case EXPERIMENTER:
+ processOFExperimenter(h, (OFExperimenter) m);
+ break;
+ case ROLE_REPLY:
+ processOFRoleReply(h, (OFRoleReply) m);
+ break;
+ case GET_ASYNC_REPLY:
+ processOFGetAsyncReply(h, (OFAsyncGetReply) m);
+ break;
+
+ // The following messages are sent to switches. The controller
+ // should never receive them
+ case SET_CONFIG:
+ case GET_CONFIG_REQUEST:
+ case PACKET_OUT:
+ case PORT_MOD:
+ case QUEUE_GET_CONFIG_REQUEST:
+ case BARRIER_REQUEST:
+ case STATS_REQUEST: // multipart request in 1.3
+ case FEATURES_REQUEST:
+ case FLOW_MOD:
+ case GROUP_MOD:
+ case TABLE_MOD:
+ case GET_ASYNC_REQUEST:
+ case SET_ASYNC:
+ case METER_MOD:
+ default:
+ illegalMessageReceived(h, m);
+ break;
+ }
+ }
+
+ /*-----------------------------------------------------------------
+ * Default implementation for message handlers in any state.
+ *
+ * Individual states must override these if they want a behavior
+ * that differs from the default.
+ *
+ * In general, these handlers simply ignore the message and do
+ * nothing.
+ *
+ * There are some exceptions though, since some messages really
+ * are handled the same way in every state (e.g., ECHO_REQUST) or
+ * that are only valid in a single state (e.g., HELLO, GET_CONFIG_REPLY
+ -----------------------------------------------------------------*/
+
+ void processOFHello(OFChannelHandler h, OFHello m)
+ throws IOException, SwitchStateException {
+ // we only expect hello in the WAIT_HELLO state
+ log.warn("Received Hello outside WAIT_HELLO state; switch {} is not complaint.",
+ h.channel.getRemoteAddress());
+ }
+
+ void processOFBarrierReply(OFChannelHandler h, OFBarrierReply m)
+ throws IOException {
+ // Silently ignore.
+ }
+
+ void processOFEchoRequest(OFChannelHandler h, OFEchoRequest m)
+ throws IOException {
+ if (h.ofVersion == null) {
+ log.error("No OF version set for {}. Not sending Echo REPLY",
+ h.channel.getRemoteAddress());
+ return;
+ }
+ OFFactory factory = (h.ofVersion == OFVersion.OF_13) ?
+ h.controller.getOFMessageFactory13() : h.controller.getOFMessageFactory10();
+ OFEchoReply reply = factory
+ .buildEchoReply()
+ .setXid(m.getXid())
+ .setData(m.getData())
+ .build();
+ h.channel.write(Collections.singletonList(reply));
+ }
+
+ void processOFEchoReply(OFChannelHandler h, OFEchoReply m)
+ throws IOException {
+ // Do nothing with EchoReplies !!
+ }
+
+ // no default implementation for OFError
+ // every state must override it
+ abstract void processOFError(OFChannelHandler h, OFErrorMsg m)
+ throws IOException, SwitchStateException;
+
+
+ void processOFFeaturesReply(OFChannelHandler h, OFFeaturesReply m)
+ throws IOException, SwitchStateException {
+ unhandledMessageReceived(h, m);
+ }
+
+ void processOFFlowRemoved(OFChannelHandler h, OFFlowRemoved m)
+ throws IOException {
+ unhandledMessageReceived(h, m);
+ }
+
+ void processOFGetConfigReply(OFChannelHandler h, OFGetConfigReply m)
+ throws IOException, SwitchStateException {
+ // we only expect config replies in the WAIT_CONFIG_REPLY state
+ illegalMessageReceived(h, m);
+ }
+
+ void processOFPacketIn(OFChannelHandler h, OFPacketIn m)
+ throws IOException {
+ unhandledMessageReceived(h, m);
+ }
+
+ // no default implementation. Every state needs to handle it.
+ abstract void processOFPortStatus(OFChannelHandler h, OFPortStatus m)
+ throws IOException, SwitchStateException;
+
+ void processOFQueueGetConfigReply(OFChannelHandler h,
+ OFQueueGetConfigReply m)
+ throws IOException {
+ unhandledMessageReceived(h, m);
+ }
+
+ void processOFStatisticsReply(OFChannelHandler h, OFStatsReply m)
+ throws IOException, SwitchStateException {
+ unhandledMessageReceived(h, m);
+ }
+
+ void processOFExperimenter(OFChannelHandler h, OFExperimenter m)
+ throws IOException, SwitchStateException {
+ // TODO: it might make sense to parse the vendor message here
+ // into the known vendor messages we support and then call more
+ // specific event handlers
+ unhandledMessageReceived(h, m);
+ }
+
+ void processOFRoleReply(OFChannelHandler h, OFRoleReply m)
+ throws SwitchStateException, IOException {
+ unhandledMessageReceived(h, m);
+ }
+
+ void processOFGetAsyncReply(OFChannelHandler h,
+ OFAsyncGetReply m) {
+ unhandledMessageReceived(h, m);
+ }
+
+ }
+
+
+
+ //*************************
+ // Channel handler methods
+ //*************************
+
+ @Override
+ public void channelConnected(ChannelHandlerContext ctx,
+ ChannelStateEvent e) throws Exception {
+ channel = e.getChannel();
+ log.info("New switch connection from {}",
+ channel.getRemoteAddress());
+ /*
+ hack to wait for the switch to tell us what it's
+ max version is. This is not spec compliant and should
+ be removed as soon as switches behave better.
+ */
+ //sendHandshakeHelloMessage();
+ setState(ChannelState.WAIT_HELLO);
+ }
+
+ @Override
+ public void channelDisconnected(ChannelHandlerContext ctx,
+ ChannelStateEvent e) throws Exception {
+ log.info("Switch disconnected callback for sw:{}. Cleaning up ...",
+ getSwitchInfoString());
+ if (thisdpid != 0) {
+ if (!duplicateDpidFound) {
+ // if the disconnected switch (on this ChannelHandler)
+ // was not one with a duplicate-dpid, it is safe to remove all
+ // state for it at the controller. Notice that if the disconnected
+ // switch was a duplicate-dpid, calling the method below would clear
+ // all state for the original switch (with the same dpid),
+ // which we obviously don't want.
+ log.info("{}:removal called", getSwitchInfoString());
+ if (sw != null) {
+ sw.removeConnectedSwitch();
+ }
+ } else {
+ // A duplicate was disconnected on this ChannelHandler,
+ // this is the same switch reconnecting, but the original state was
+ // not cleaned up - XXX check liveness of original ChannelHandler
+ log.info("{}:duplicate found", getSwitchInfoString());
+ duplicateDpidFound = Boolean.FALSE;
+ }
+ } else {
+ log.warn("no dpid in channelHandler registered for "
+ + "disconnected switch {}", getSwitchInfoString());
+ }
+ }
+
+ @Override
+ public void exceptionCaught(ChannelHandlerContext ctx, ExceptionEvent e)
+ throws Exception {
+ if (e.getCause() instanceof ReadTimeoutException) {
+ // switch timeout
+ log.error("Disconnecting switch {} due to read timeout",
+ getSwitchInfoString());
+ ctx.getChannel().close();
+ } else if (e.getCause() instanceof HandshakeTimeoutException) {
+ log.error("Disconnecting switch {}: failed to complete handshake",
+ getSwitchInfoString());
+ ctx.getChannel().close();
+ } else if (e.getCause() instanceof ClosedChannelException) {
+ log.debug("Channel for sw {} already closed", getSwitchInfoString());
+ } else if (e.getCause() instanceof IOException) {
+ if (!e.getCause().getMessage().equals(RESET_BY_PEER) &&
+ !e.getCause().getMessage().equals(BROKEN_PIPE)) {
+ log.error("Disconnecting switch {} due to IO Error: {}",
+ getSwitchInfoString(), e.getCause().getMessage());
+ if (log.isDebugEnabled()) {
+ // still print stack trace if debug is enabled
+ log.debug("StackTrace for previous Exception: ", e.getCause());
+ }
+ }
+ ctx.getChannel().close();
+ } else if (e.getCause() instanceof SwitchStateException) {
+ log.error("Disconnecting switch {} due to switch state error: {}",
+ getSwitchInfoString(), e.getCause().getMessage());
+ if (log.isDebugEnabled()) {
+ // still print stack trace if debug is enabled
+ log.debug("StackTrace for previous Exception: ", e.getCause());
+ }
+ ctx.getChannel().close();
+ } else if (e.getCause() instanceof OFParseError) {
+ log.error("Disconnecting switch "
+ + getSwitchInfoString() +
+ " due to message parse failure",
+ e.getCause());
+ ctx.getChannel().close();
+ } else if (e.getCause() instanceof RejectedExecutionException) {
+ log.warn("Could not process message: queue full");
+ } else {
+ log.error("Error while processing message from switch "
+ + getSwitchInfoString()
+ + "state " + this.state, e.getCause());
+ ctx.getChannel().close();
+ }
+ }
+
+ @Override
+ public String toString() {
+ return getSwitchInfoString();
+ }
+
+ @Override
+ public void channelIdle(ChannelHandlerContext ctx, IdleStateEvent e)
+ throws Exception {
+ OFFactory factory = (ofVersion == OFVersion.OF_13) ? factory13 : factory10;
+ OFMessage m = factory.buildEchoRequest().build();
+ log.debug("Sending Echo Request on idle channel: {}",
+ e.getChannel().getPipeline().getLast().toString());
+ e.getChannel().write(Collections.singletonList(m));
+ // XXX S some problems here -- echo request has no transaction id, and
+ // echo reply is not correlated to the echo request.
+ }
+
+ @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) {
+ // Do the actual packet processing
+ state.processOFMessage(this, ofm);
+ }
+ } else {
+ state.processOFMessage(this, (OFMessage) e.getMessage());
+ }
+ }
+
+
+
+ //*************************
+ // Channel utility methods
+ //*************************
+
+ /**
+ * Is this a state in which the handshake has completed?
+ * @return true if the handshake is complete
+ */
+ public boolean isHandshakeComplete() {
+ return this.state.isHandshakeComplete();
+ }
+
+ private void dispatchMessage(OFMessage m) {
+ sw.handleMessage(m);
+ }
+
+ /**
+ * Return a string describing this switch based on the already available
+ * information (DPID and/or remote socket).
+ * @return display string
+ */
+ private String getSwitchInfoString() {
+ if (sw != null) {
+ return sw.toString();
+ }
+ String channelString;
+ if (channel == null || channel.getRemoteAddress() == null) {
+ channelString = "?";
+ } else {
+ channelString = channel.getRemoteAddress().toString();
+ }
+ String dpidString;
+ if (featuresReply == null) {
+ dpidString = "?";
+ } else {
+ dpidString = featuresReply.getDatapathId().toString();
+ }
+ return String.format("[%s DPID[%s]]", channelString, dpidString);
+ }
+
+ /**
+ * Update the channels state. Only called from the state machine.
+ * TODO: enforce restricted state transitions
+ * @param state
+ */
+ private void setState(ChannelState state) {
+ this.state = state;
+ }
+
+ /**
+ * Send hello message to the switch using the handshake transactions ids.
+ * @throws IOException
+ */
+ private void sendHandshakeHelloMessage() throws IOException {
+ // The OF protocol requires us to start things off by sending the highest
+ // version of the protocol supported.
+
+ // bitmap represents OF1.0 (ofp_version=0x01) and OF1.3 (ofp_version=0x04)
+ // see Sec. 7.5.1 of the OF1.3.4 spec
+ U32 bitmap = U32.ofRaw(0x00000012);
+ OFHelloElem hem = factory13.buildHelloElemVersionbitmap()
+ .setBitmaps(Collections.singletonList(bitmap))
+ .build();
+ OFMessage.Builder mb = factory13.buildHello()
+ .setXid(this.handshakeTransactionIds--)
+ .setElements(Collections.singletonList(hem));
+ log.info("Sending OF_13 Hello to {}", channel.getRemoteAddress());
+ channel.write(Collections.singletonList(mb.build()));
+ }
+
+ /**
+ * Send featuresRequest msg to the switch using the handshake transactions ids.
+ * @throws IOException
+ */
+ private void sendHandshakeFeaturesRequestMessage() throws IOException {
+ OFFactory factory = (ofVersion == OFVersion.OF_13) ? factory13 : factory10;
+ OFMessage m = factory.buildFeaturesRequest()
+ .setXid(this.handshakeTransactionIds--)
+ .build();
+ channel.write(Collections.singletonList(m));
+ }
+
+ /**
+ * Send the configuration requests to tell the switch we want full
+ * packets.
+ * @throws IOException
+ */
+ private void sendHandshakeSetConfig() throws IOException {
+ OFFactory factory = (ofVersion == OFVersion.OF_13) ? factory13 : factory10;
+ //log.debug("Sending CONFIG_REQUEST to {}", channel.getRemoteAddress());
+ List<OFMessage> msglist = new ArrayList<OFMessage>(3);
+
+ // Ensure we receive the full packet via PacketIn
+ // FIXME: We don't set the reassembly flags.
+ // Only send config to switches to send full packets, if they have a buffer.
+ // Saves a packet & OFSetConfig can't be handled by certain switches.
+ if(this.featuresReply.getNBuffers() > 0) {
+ OFSetConfig sc = factory
+ .buildSetConfig()
+ .setMissSendLen((short) 0xffff)
+ .setXid(this.handshakeTransactionIds--)
+ .build();
+ msglist.add(sc);
+ }
+
+ // Barrier
+ OFBarrierRequest br = factory
+ .buildBarrierRequest()
+ .setXid(this.handshakeTransactionIds--)
+ .build();
+ msglist.add(br);
+
+ // Verify (need barrier?)
+ OFGetConfigRequest gcr = factory
+ .buildGetConfigRequest()
+ .setXid(this.handshakeTransactionIds--)
+ .build();
+ msglist.add(gcr);
+ channel.write(msglist);
+ }
+
+ /**
+ * send a description state request.
+ * @throws IOException
+ */
+ private void sendHandshakeDescriptionStatsRequest() throws IOException {
+ // Get Description to set switch-specific flags
+ OFFactory factory = (ofVersion == OFVersion.OF_13) ? factory13 : factory10;
+ OFDescStatsRequest dreq = factory
+ .buildDescStatsRequest()
+ .setXid(handshakeTransactionIds--)
+ .build();
+ channel.write(Collections.singletonList(dreq));
+ }
+
+ private void sendHandshakeOFPortDescRequest() throws IOException {
+ // Get port description for 1.3 switch
+ OFPortDescStatsRequest preq = factory13
+ .buildPortDescStatsRequest()
+ .setXid(handshakeTransactionIds--)
+ .build();
+ channel.write(Collections.singletonList(preq));
+ }
+
+ ChannelState getStateForTesting() {
+ return state;
+ }
+
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFMessageDecoder.java b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFMessageDecoder.java
new file mode 100644
index 00000000..f52d27e5
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFMessageDecoder.java
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow.controller.impl;
+
+
+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 org.projectfloodlight.openflow.protocol.OFFactories;
+import org.projectfloodlight.openflow.protocol.OFMessage;
+import org.projectfloodlight.openflow.protocol.OFMessageReader;
+
+/**
+ * Decode an openflow message from a Channel, for use in a netty pipeline.
+ */
+public class OFMessageDecoder extends FrameDecoder {
+
+ @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;
+ }
+
+ // Note that a single call to decode results in reading a single
+ // OFMessage from the channel buffer, which is passed on to, and processed
+ // by, the controller (in OFChannelHandler).
+ // This is different from earlier behavior (with the original openflowj),
+ // where we parsed all the messages in the buffer, before passing on
+ // a list of the parsed messages to the controller.
+ // The performance *may or may not* not be as good as before.
+ OFMessageReader<OFMessage> reader = OFFactories.getGenericReader();
+ OFMessage message = reader.readFrom(buffer);
+
+ return message;
+ }
+
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFMessageEncoder.java b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFMessageEncoder.java
new file mode 100644
index 00000000..4c1b16fe
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFMessageEncoder.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow.controller.impl;
+
+import java.util.List;
+
+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;
+
+/**
+ * Encode an openflow message for output into a ChannelBuffer, for use in a
+ * netty pipeline.
+ */
+public class OFMessageEncoder extends OneToOneEncoder {
+
+ @Override
+ protected Object encode(ChannelHandlerContext ctx, Channel channel,
+ Object msg) throws Exception {
+ if (!(msg instanceof List)) {
+ return msg;
+ }
+
+ @SuppressWarnings("unchecked")
+ List<OFMessage> msglist = (List<OFMessage>) msg;
+ /* XXX S can't get length of OFMessage in loxigen's openflowj??
+ int size = 0;
+ for (OFMessage ofm : msglist) {
+ size += ofm.getLengthU();
+ }*/
+
+ ChannelBuffer buf = ChannelBuffers.dynamicBuffer();
+
+ for (OFMessage ofm : msglist) {
+ if (ofm != null) {
+ ofm.writeTo(buf);
+ }
+ }
+ return buf;
+ }
+
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImpl.java b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImpl.java
new file mode 100644
index 00000000..b97c3362
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImpl.java
@@ -0,0 +1,633 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow.controller.impl;
+
+import com.google.common.collect.ArrayListMultimap;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Multimap;
+import org.apache.felix.scr.annotations.Activate;
+import org.apache.felix.scr.annotations.Component;
+import org.apache.felix.scr.annotations.Deactivate;
+import org.apache.felix.scr.annotations.Modified;
+import org.apache.felix.scr.annotations.Property;
+import org.apache.felix.scr.annotations.Reference;
+import org.apache.felix.scr.annotations.ReferenceCardinality;
+import org.apache.felix.scr.annotations.Service;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.net.driver.DefaultDriverProviderService;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.openflow.controller.DefaultOpenFlowPacketContext;
+import org.onosproject.openflow.controller.Dpid;
+import org.onosproject.openflow.controller.OpenFlowController;
+import org.onosproject.openflow.controller.OpenFlowEventListener;
+import org.onosproject.openflow.controller.OpenFlowPacketContext;
+import org.onosproject.openflow.controller.OpenFlowSwitch;
+import org.onosproject.openflow.controller.OpenFlowSwitchListener;
+import org.onosproject.openflow.controller.PacketListener;
+import org.onosproject.openflow.controller.RoleState;
+import org.onosproject.openflow.controller.driver.OpenFlowAgent;
+import org.osgi.service.component.ComponentContext;
+import org.projectfloodlight.openflow.protocol.OFCalientFlowStatsEntry;
+import org.projectfloodlight.openflow.protocol.OFCalientFlowStatsReply;
+import org.projectfloodlight.openflow.protocol.OFCircuitPortStatus;
+import org.projectfloodlight.openflow.protocol.OFExperimenter;
+import org.projectfloodlight.openflow.protocol.OFFactories;
+import org.projectfloodlight.openflow.protocol.OFFlowStatsEntry;
+import org.projectfloodlight.openflow.protocol.OFFlowStatsReply;
+import org.projectfloodlight.openflow.protocol.OFTableStatsEntry;
+import org.projectfloodlight.openflow.protocol.OFTableStatsReply;
+import org.projectfloodlight.openflow.protocol.OFGroupDescStatsEntry;
+import org.projectfloodlight.openflow.protocol.OFGroupDescStatsReply;
+import org.projectfloodlight.openflow.protocol.OFGroupStatsEntry;
+import org.projectfloodlight.openflow.protocol.OFGroupStatsReply;
+import org.projectfloodlight.openflow.protocol.OFMessage;
+import org.projectfloodlight.openflow.protocol.OFPacketIn;
+import org.projectfloodlight.openflow.protocol.OFPortDesc;
+import org.projectfloodlight.openflow.protocol.OFPortStatsEntry;
+import org.projectfloodlight.openflow.protocol.OFPortStatsReply;
+import org.projectfloodlight.openflow.protocol.OFPortStatus;
+import org.projectfloodlight.openflow.protocol.OFStatsReply;
+import org.projectfloodlight.openflow.protocol.OFStatsReplyFlags;
+import org.projectfloodlight.openflow.protocol.action.OFActionOutput;
+import org.projectfloodlight.openflow.protocol.instruction.OFInstruction;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.util.Collection;
+import java.util.Collections;
+import java.util.LinkedList;
+import java.util.List;
+import java.util.Set;
+import java.util.concurrent.ConcurrentHashMap;
+import java.util.concurrent.CopyOnWriteArraySet;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import static org.onlab.util.Tools.groupedThreads;
+
+@Component(immediate = true)
+@Service
+public class OpenFlowControllerImpl implements OpenFlowController {
+ private static final String DEFAULT_OFPORT = "6633,6653";
+ private static final int DEFAULT_WORKER_THREADS = 16;
+
+ private static final Logger log =
+ LoggerFactory.getLogger(OpenFlowControllerImpl.class);
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DriverService driverService;
+
+ // References exists merely for sequencing purpose to assure drivers are loaded
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DefaultDriverProviderService defaultDriverProviderService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected ComponentConfigService cfgService;
+
+ @Property(name = "openflowPorts", value = DEFAULT_OFPORT,
+ label = "Port numbers (comma separated) used by OpenFlow protocol; default is 6633,6653")
+ private String openflowPorts = DEFAULT_OFPORT;
+
+ @Property(name = "workerThreads", intValue = DEFAULT_WORKER_THREADS,
+ label = "Number of controller worker threads; default is 16")
+ private int workerThreads = DEFAULT_WORKER_THREADS;
+
+ protected ExecutorService executorMsgs =
+ Executors.newFixedThreadPool(32, groupedThreads("onos/of", "event-stats-%d"));
+
+ private final ExecutorService executorBarrier =
+ Executors.newFixedThreadPool(4, groupedThreads("onos/of", "event-barrier-%d"));
+
+ protected ConcurrentHashMap<Dpid, OpenFlowSwitch> connectedSwitches =
+ new ConcurrentHashMap<>();
+ protected ConcurrentHashMap<Dpid, OpenFlowSwitch> activeMasterSwitches =
+ new ConcurrentHashMap<>();
+ protected ConcurrentHashMap<Dpid, OpenFlowSwitch> activeEqualSwitches =
+ new ConcurrentHashMap<>();
+
+ protected OpenFlowSwitchAgent agent = new OpenFlowSwitchAgent();
+ protected Set<OpenFlowSwitchListener> ofSwitchListener = new CopyOnWriteArraySet<>();
+
+ protected Multimap<Integer, PacketListener> ofPacketListener =
+ ArrayListMultimap.create();
+
+ protected Set<OpenFlowEventListener> ofEventListener = new CopyOnWriteArraySet<>();
+
+ protected Multimap<Dpid, OFFlowStatsEntry> fullFlowStats =
+ ArrayListMultimap.create();
+
+ protected Multimap<Dpid, OFTableStatsEntry> fullTableStats =
+ ArrayListMultimap.create();
+
+ protected Multimap<Dpid, OFGroupStatsEntry> fullGroupStats =
+ ArrayListMultimap.create();
+
+ protected Multimap<Dpid, OFGroupDescStatsEntry> fullGroupDescStats =
+ ArrayListMultimap.create();
+
+ protected Multimap<Dpid, OFPortStatsEntry> fullPortStats =
+ ArrayListMultimap.create();
+
+ private final Controller ctrl = new Controller();
+
+ @Activate
+ public void activate(ComponentContext context) {
+ cfgService.registerProperties(getClass());
+ ctrl.setConfigParams(context.getProperties());
+ ctrl.start(agent, driverService);
+ }
+
+ @Deactivate
+ public void deactivate() {
+ cfgService.unregisterProperties(getClass(), false);
+ ctrl.stop();
+ }
+
+ @Modified
+ public void modified(ComponentContext context) {
+ ctrl.stop();
+ ctrl.setConfigParams(context.getProperties());
+ ctrl.start(agent, driverService);
+ }
+
+ @Override
+ public Iterable<OpenFlowSwitch> getSwitches() {
+ return connectedSwitches.values();
+ }
+
+ @Override
+ public Iterable<OpenFlowSwitch> getMasterSwitches() {
+ return activeMasterSwitches.values();
+ }
+
+ @Override
+ public Iterable<OpenFlowSwitch> getEqualSwitches() {
+ return activeEqualSwitches.values();
+ }
+
+ @Override
+ public OpenFlowSwitch getSwitch(Dpid dpid) {
+ return connectedSwitches.get(dpid);
+ }
+
+ @Override
+ public OpenFlowSwitch getMasterSwitch(Dpid dpid) {
+ return activeMasterSwitches.get(dpid);
+ }
+
+ @Override
+ public OpenFlowSwitch getEqualSwitch(Dpid dpid) {
+ return activeEqualSwitches.get(dpid);
+ }
+
+ @Override
+ public void addListener(OpenFlowSwitchListener listener) {
+ if (!ofSwitchListener.contains(listener)) {
+ this.ofSwitchListener.add(listener);
+ }
+ }
+
+ @Override
+ public void removeListener(OpenFlowSwitchListener listener) {
+ this.ofSwitchListener.remove(listener);
+ }
+
+ @Override
+ public void addPacketListener(int priority, PacketListener listener) {
+ ofPacketListener.put(priority, listener);
+ }
+
+ @Override
+ public void removePacketListener(PacketListener listener) {
+ ofPacketListener.values().remove(listener);
+ }
+
+ @Override
+ public void addEventListener(OpenFlowEventListener listener) {
+ ofEventListener.add(listener);
+ }
+
+ @Override
+ public void removeEventListener(OpenFlowEventListener listener) {
+ ofEventListener.remove(listener);
+ }
+
+ @Override
+ public void write(Dpid dpid, OFMessage msg) {
+ this.getSwitch(dpid).sendMsg(msg);
+ }
+
+ @Override
+ public void processPacket(Dpid dpid, OFMessage msg) {
+ Collection<OFFlowStatsEntry> flowStats;
+ Collection<OFTableStatsEntry> tableStats;
+ Collection<OFGroupStatsEntry> groupStats;
+ Collection<OFGroupDescStatsEntry> groupDescStats;
+ Collection<OFPortStatsEntry> portStats;
+
+ switch (msg.getType()) {
+ case PORT_STATUS:
+ for (OpenFlowSwitchListener l : ofSwitchListener) {
+ l.portChanged(dpid, (OFPortStatus) msg);
+ }
+ break;
+ case FEATURES_REPLY:
+ for (OpenFlowSwitchListener l : ofSwitchListener) {
+ l.switchChanged(dpid);
+ }
+ break;
+ case PACKET_IN:
+ OpenFlowPacketContext pktCtx = DefaultOpenFlowPacketContext
+ .packetContextFromPacketIn(this.getSwitch(dpid),
+ (OFPacketIn) msg);
+ for (PacketListener p : ofPacketListener.values()) {
+ p.handlePacket(pktCtx);
+ }
+ break;
+ // TODO: Consider using separate threadpool for sensitive messages.
+ // ie. Back to back error could cause us to starve.
+ case FLOW_REMOVED:
+ case ERROR:
+ executorMsgs.submit(new OFMessageHandler(dpid, msg));
+ break;
+ case STATS_REPLY:
+ OFStatsReply reply = (OFStatsReply) msg;
+ switch (reply.getStatsType()) {
+ case PORT_DESC:
+ for (OpenFlowSwitchListener l : ofSwitchListener) {
+ l.switchChanged(dpid);
+ }
+ break;
+ case FLOW:
+ flowStats = publishFlowStats(dpid, (OFFlowStatsReply) reply);
+ if (flowStats != null) {
+ OFFlowStatsReply.Builder rep =
+ OFFactories.getFactory(msg.getVersion()).buildFlowStatsReply();
+ rep.setEntries(Lists.newLinkedList(flowStats));
+ rep.setXid(reply.getXid());
+ executorMsgs.submit(new OFMessageHandler(dpid, rep.build()));
+ }
+ break;
+ case TABLE:
+ tableStats = publishTableStats(dpid, (OFTableStatsReply) reply);
+ if (tableStats != null) {
+ OFTableStatsReply.Builder rep =
+ OFFactories.getFactory(msg.getVersion()).buildTableStatsReply();
+ rep.setEntries(Lists.newLinkedList(tableStats));
+ executorMsgs.submit(new OFMessageHandler(dpid, rep.build()));
+ }
+ break;
+ case GROUP:
+ groupStats = publishGroupStats(dpid, (OFGroupStatsReply) reply);
+ if (groupStats != null) {
+ OFGroupStatsReply.Builder rep =
+ OFFactories.getFactory(msg.getVersion()).buildGroupStatsReply();
+ rep.setEntries(Lists.newLinkedList(groupStats));
+ rep.setXid(reply.getXid());
+ executorMsgs.submit(new OFMessageHandler(dpid, rep.build()));
+ }
+ break;
+ case GROUP_DESC:
+ groupDescStats = publishGroupDescStats(dpid,
+ (OFGroupDescStatsReply) reply);
+ if (groupDescStats != null) {
+ OFGroupDescStatsReply.Builder rep =
+ OFFactories.getFactory(msg.getVersion()).buildGroupDescStatsReply();
+ rep.setEntries(Lists.newLinkedList(groupDescStats));
+ rep.setXid(reply.getXid());
+ executorMsgs.submit(new OFMessageHandler(dpid, rep.build()));
+ }
+ break;
+ case PORT:
+ executorMsgs.submit(new OFMessageHandler(dpid, reply));
+ break;
+ case METER:
+ executorMsgs.submit(new OFMessageHandler(dpid, reply));
+ break;
+ case EXPERIMENTER:
+ if (reply instanceof OFCalientFlowStatsReply) {
+ // Convert Calient flow statistics to regular flow stats
+ // TODO: parse remaining fields such as power levels etc. when we have proper monitoring API
+ OFFlowStatsReply.Builder fsr = getSwitch(dpid).factory().buildFlowStatsReply();
+ List<OFFlowStatsEntry> entries = new LinkedList<>();
+ for (OFCalientFlowStatsEntry entry : ((OFCalientFlowStatsReply) msg).getEntries()) {
+
+ // Single instruction, i.e., output to port
+ OFActionOutput action = OFFactories
+ .getFactory(msg.getVersion())
+ .actions()
+ .buildOutput()
+ .setPort(entry.getOutPort())
+ .build();
+ OFInstruction instruction = OFFactories
+ .getFactory(msg.getVersion())
+ .instructions()
+ .applyActions(Collections.singletonList(action));
+ OFFlowStatsEntry fs = getSwitch(dpid).factory().buildFlowStatsEntry()
+ .setMatch(entry.getMatch())
+ .setTableId(entry.getTableId())
+ .setDurationSec(entry.getDurationSec())
+ .setDurationNsec(entry.getDurationNsec())
+ .setPriority(entry.getPriority())
+ .setIdleTimeout(entry.getIdleTimeout())
+ .setHardTimeout(entry.getHardTimeout())
+ .setFlags(entry.getFlags())
+ .setCookie(entry.getCookie())
+ .setInstructions(Collections.singletonList(instruction))
+ .build();
+ entries.add(fs);
+ }
+ fsr.setEntries(entries);
+
+ flowStats = publishFlowStats(dpid, fsr.build());
+ if (flowStats != null) {
+ OFFlowStatsReply.Builder rep =
+ OFFactories.getFactory(msg.getVersion()).buildFlowStatsReply();
+ rep.setEntries(Lists.newLinkedList(flowStats));
+ executorMsgs.submit(new OFMessageHandler(dpid, rep.build()));
+ }
+ } else {
+ executorMsgs.submit(new OFMessageHandler(dpid, reply));
+ }
+ break;
+ default:
+ log.warn("Discarding unknown stats reply type {}", reply.getStatsType());
+ break;
+ }
+ break;
+ case BARRIER_REPLY:
+ executorBarrier.submit(new OFMessageHandler(dpid, msg));
+ break;
+ case EXPERIMENTER:
+ long experimenter = ((OFExperimenter) msg).getExperimenter();
+ if (experimenter == 0x748771) {
+ // LINC-OE port stats
+ OFCircuitPortStatus circuitPortStatus = (OFCircuitPortStatus) msg;
+ OFPortStatus.Builder portStatus = this.getSwitch(dpid).factory().buildPortStatus();
+ OFPortDesc.Builder portDesc = this.getSwitch(dpid).factory().buildPortDesc();
+ portDesc.setPortNo(circuitPortStatus.getPortNo())
+ .setHwAddr(circuitPortStatus.getHwAddr())
+ .setName(circuitPortStatus.getName())
+ .setConfig(circuitPortStatus.getConfig())
+ .setState(circuitPortStatus.getState());
+ portStatus.setReason(circuitPortStatus.getReason()).setDesc(portDesc.build());
+ for (OpenFlowSwitchListener l : ofSwitchListener) {
+ l.portChanged(dpid, portStatus.build());
+ }
+ } else {
+ log.warn("Handling experimenter type {} not yet implemented",
+ ((OFExperimenter) msg).getExperimenter(), msg);
+ }
+ break;
+ default:
+ log.warn("Handling message type {} not yet implemented {}",
+ msg.getType(), msg);
+ }
+ }
+
+ private synchronized Collection<OFFlowStatsEntry> publishFlowStats(Dpid dpid,
+ OFFlowStatsReply reply) {
+ //TODO: Get rid of synchronized
+ fullFlowStats.putAll(dpid, reply.getEntries());
+ if (!reply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) {
+ return fullFlowStats.removeAll(dpid);
+ }
+ return null;
+ }
+
+ private synchronized Collection<OFTableStatsEntry> publishTableStats(Dpid dpid,
+ OFTableStatsReply reply) {
+ //TODO: Get rid of synchronized
+ fullTableStats.putAll(dpid, reply.getEntries());
+ if (!reply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) {
+ return fullTableStats.removeAll(dpid);
+ }
+ return null;
+ }
+
+ private synchronized Collection<OFGroupStatsEntry> publishGroupStats(Dpid dpid,
+ OFGroupStatsReply reply) {
+ //TODO: Get rid of synchronized
+ fullGroupStats.putAll(dpid, reply.getEntries());
+ if (!reply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) {
+ return fullGroupStats.removeAll(dpid);
+ }
+ return null;
+ }
+
+ private synchronized Collection<OFGroupDescStatsEntry> publishGroupDescStats(Dpid dpid,
+ OFGroupDescStatsReply reply) {
+ //TODO: Get rid of synchronized
+ fullGroupDescStats.putAll(dpid, reply.getEntries());
+ if (!reply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) {
+ return fullGroupDescStats.removeAll(dpid);
+ }
+ return null;
+ }
+
+ private synchronized Collection<OFPortStatsEntry> publishPortStats(Dpid dpid,
+ OFPortStatsReply reply) {
+ fullPortStats.putAll(dpid, reply.getEntries());
+ if (!reply.getFlags().contains(OFStatsReplyFlags.REPLY_MORE)) {
+ return fullPortStats.removeAll(dpid);
+ }
+ return null;
+ }
+
+ @Override
+ public void setRole(Dpid dpid, RoleState role) {
+ final OpenFlowSwitch sw = getSwitch(dpid);
+ if (sw == null) {
+ log.debug("Switch not connected. Ignoring setRole({}, {})", dpid, role);
+ return;
+ }
+ sw.setRole(role);
+ }
+
+ /**
+ * Implementation of an OpenFlow Agent which is responsible for
+ * keeping track of connected switches and the state in which
+ * they are.
+ */
+ public class OpenFlowSwitchAgent implements OpenFlowAgent {
+
+ private final Logger log = LoggerFactory.getLogger(OpenFlowSwitchAgent.class);
+ private final Lock switchLock = new ReentrantLock();
+
+ @Override
+ public boolean addConnectedSwitch(Dpid dpid, OpenFlowSwitch sw) {
+
+ if (connectedSwitches.get(dpid) != null) {
+ log.error("Trying to add connectedSwitch but found a previous "
+ + "value for dpid: {}", dpid);
+ return false;
+ } else {
+ log.info("Added switch {}", dpid);
+ connectedSwitches.put(dpid, sw);
+ for (OpenFlowSwitchListener l : ofSwitchListener) {
+ l.switchAdded(dpid);
+ }
+ return true;
+ }
+ }
+
+ @Override
+ public boolean validActivation(Dpid dpid) {
+ if (connectedSwitches.get(dpid) == null) {
+ log.error("Trying to activate switch but is not in "
+ + "connected switches: dpid {}. Aborting ..",
+ dpid);
+ return false;
+ }
+ if (activeMasterSwitches.get(dpid) != null ||
+ activeEqualSwitches.get(dpid) != null) {
+ log.error("Trying to activate switch but it is already "
+ + "activated: dpid {}. Found in activeMaster: {} "
+ + "Found in activeEqual: {}. Aborting ..",
+ dpid,
+ (activeMasterSwitches.get(dpid) == null) ? 'N' : 'Y',
+ (activeEqualSwitches.get(dpid) == null) ? 'N' : 'Y');
+ return false;
+ }
+ return true;
+ }
+
+
+ @Override
+ public boolean addActivatedMasterSwitch(Dpid dpid, OpenFlowSwitch sw) {
+ switchLock.lock();
+ try {
+ if (!validActivation(dpid)) {
+ return false;
+ }
+ activeMasterSwitches.put(dpid, sw);
+ return true;
+ } finally {
+ switchLock.unlock();
+ }
+ }
+
+ @Override
+ public boolean addActivatedEqualSwitch(Dpid dpid, OpenFlowSwitch sw) {
+ switchLock.lock();
+ try {
+ if (!validActivation(dpid)) {
+ return false;
+ }
+ activeEqualSwitches.put(dpid, sw);
+ log.info("Added Activated EQUAL Switch {}", dpid);
+ return true;
+ } finally {
+ switchLock.unlock();
+ }
+ }
+
+ @Override
+ public void transitionToMasterSwitch(Dpid dpid) {
+ switchLock.lock();
+ try {
+ if (activeMasterSwitches.containsKey(dpid)) {
+ return;
+ }
+ OpenFlowSwitch sw = activeEqualSwitches.remove(dpid);
+ if (sw == null) {
+ sw = getSwitch(dpid);
+ if (sw == null) {
+ log.error("Transition to master called on sw {}, but switch "
+ + "was not found in controller-cache", dpid);
+ return;
+ }
+ }
+ log.info("Transitioned switch {} to MASTER", dpid);
+ activeMasterSwitches.put(dpid, sw);
+ } finally {
+ switchLock.unlock();
+ }
+ }
+
+
+ @Override
+ public void transitionToEqualSwitch(Dpid dpid) {
+ switchLock.lock();
+ try {
+ if (activeEqualSwitches.containsKey(dpid)) {
+ return;
+ }
+ OpenFlowSwitch sw = activeMasterSwitches.remove(dpid);
+ if (sw == null) {
+ sw = getSwitch(dpid);
+ if (sw == null) {
+ log.error("Transition to equal called on sw {}, but switch "
+ + "was not found in controller-cache", dpid);
+ return;
+ }
+ }
+ log.info("Transitioned switch {} to EQUAL", dpid);
+ activeEqualSwitches.put(dpid, sw);
+ } finally {
+ switchLock.unlock();
+ }
+
+ }
+
+ @Override
+ public void removeConnectedSwitch(Dpid dpid) {
+ connectedSwitches.remove(dpid);
+ OpenFlowSwitch sw = activeMasterSwitches.remove(dpid);
+ if (sw == null) {
+ log.debug("sw was null for {}", dpid);
+ sw = activeEqualSwitches.remove(dpid);
+ }
+ for (OpenFlowSwitchListener l : ofSwitchListener) {
+ l.switchRemoved(dpid);
+ }
+ }
+
+ @Override
+ public void processMessage(Dpid dpid, OFMessage m) {
+ processPacket(dpid, m);
+ }
+
+ @Override
+ public void returnRoleReply(Dpid dpid, RoleState requested, RoleState response) {
+ for (OpenFlowSwitchListener l : ofSwitchListener) {
+ l.receivedRoleReply(dpid, requested, response);
+ }
+ }
+ }
+
+ protected final class OFMessageHandler implements Runnable {
+
+ protected final OFMessage msg;
+ protected final Dpid dpid;
+
+ public OFMessageHandler(Dpid dpid, OFMessage msg) {
+ this.msg = msg;
+ this.dpid = dpid;
+ }
+
+ @Override
+ public void run() {
+ for (OpenFlowEventListener listener : ofEventListener) {
+ listener.handleMessage(dpid, msg);
+ }
+ }
+
+ }
+
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OpenflowPipelineFactory.java b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OpenflowPipelineFactory.java
new file mode 100644
index 00000000..1467520d
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OpenflowPipelineFactory.java
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow.controller.impl;
+
+import java.util.concurrent.ThreadPoolExecutor;
+
+import org.jboss.netty.channel.ChannelPipeline;
+import org.jboss.netty.channel.ChannelPipelineFactory;
+import org.jboss.netty.channel.Channels;
+import org.jboss.netty.handler.execution.ExecutionHandler;
+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.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import javax.net.ssl.SSLEngine;
+
+/**
+ * Creates a ChannelPipeline for a server-side openflow channel.
+ */
+public class OpenflowPipelineFactory
+ implements ChannelPipelineFactory, ExternalResourceReleasable {
+
+ private final Logger log = LoggerFactory.getLogger(getClass());
+
+ private final SSLEngine sslEngine;
+ protected Controller controller;
+ protected ThreadPoolExecutor pipelineExecutor;
+ protected Timer timer;
+ protected IdleStateHandler idleHandler;
+ protected ReadTimeoutHandler readTimeoutHandler;
+
+ public OpenflowPipelineFactory(Controller controller,
+ ThreadPoolExecutor pipelineExecutor,
+ SSLEngine sslEngine) {
+ super();
+ this.controller = controller;
+ this.pipelineExecutor = pipelineExecutor;
+ this.timer = new HashedWheelTimer();
+ this.idleHandler = new IdleStateHandler(timer, 20, 25, 0);
+ this.readTimeoutHandler = new ReadTimeoutHandler(timer, 30);
+ this.sslEngine = sslEngine;
+ }
+
+ @Override
+ public ChannelPipeline getPipeline() throws Exception {
+ OFChannelHandler handler = new OFChannelHandler(controller);
+
+ ChannelPipeline pipeline = Channels.pipeline();
+ if (sslEngine != null) {
+ log.info("OpenFlow SSL enabled.");
+ pipeline.addLast("ssl",
+ new org.jboss.netty.handler.ssl.SslHandler(sslEngine));
+ } else {
+ log.info("OpenFlow SSL disabled");
+ }
+ pipeline.addLast("ofmessagedecoder", new OFMessageDecoder());
+ pipeline.addLast("ofmessageencoder", new OFMessageEncoder());
+ pipeline.addLast("idle", idleHandler);
+ pipeline.addLast("timeout", readTimeoutHandler);
+ // XXX S ONOS: was 15 increased it to fix Issue #296
+ pipeline.addLast("handshaketimeout",
+ new HandshakeTimeoutHandler(handler, timer, 60));
+ if (pipelineExecutor != null) {
+ pipeline.addLast("pipelineExecutor",
+ new ExecutionHandler(pipelineExecutor));
+ }
+ pipeline.addLast("handler", handler);
+ return pipeline;
+ }
+
+ @Override
+ public void releaseExternalResources() {
+ timer.stop();
+ }
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/RoleManager.java b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/RoleManager.java
new file mode 100644
index 00000000..bd4875cf
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/RoleManager.java
@@ -0,0 +1,406 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow.controller.impl;
+
+import com.google.common.cache.Cache;
+import com.google.common.cache.CacheBuilder;
+import org.onosproject.openflow.controller.RoleState;
+import org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver;
+import org.onosproject.openflow.controller.driver.RoleHandler;
+import org.onosproject.openflow.controller.driver.RoleRecvStatus;
+import org.onosproject.openflow.controller.driver.RoleReplyInfo;
+import org.onosproject.openflow.controller.driver.SwitchStateException;
+import org.projectfloodlight.openflow.protocol.OFControllerRole;
+import org.projectfloodlight.openflow.protocol.OFErrorMsg;
+import org.projectfloodlight.openflow.protocol.OFErrorType;
+import org.projectfloodlight.openflow.protocol.OFExperimenter;
+import org.projectfloodlight.openflow.protocol.OFFactories;
+import org.projectfloodlight.openflow.protocol.OFNiciraControllerRole;
+import org.projectfloodlight.openflow.protocol.OFNiciraControllerRoleReply;
+import org.projectfloodlight.openflow.protocol.OFRoleReply;
+import org.projectfloodlight.openflow.protocol.OFRoleRequest;
+import org.projectfloodlight.openflow.protocol.OFVersion;
+import org.projectfloodlight.openflow.protocol.errormsg.OFBadRequestErrorMsg;
+import org.projectfloodlight.openflow.protocol.errormsg.OFRoleRequestFailedErrorMsg;
+import org.projectfloodlight.openflow.types.U64;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import java.io.IOException;
+import java.util.concurrent.TimeUnit;
+
+
+/**
+ * A utility class to handle role requests and replies for this channel.
+ * After a role request is submitted the role changer keeps track of the
+ * pending request, collects the reply (if any) and times out the request
+ * if necessary.
+ */
+class RoleManager implements RoleHandler {
+ protected static final long NICIRA_EXPERIMENTER = 0x2320;
+
+ private static Logger log = LoggerFactory.getLogger(RoleManager.class);
+
+ // The time until cached XID is evicted. Arbitrary for now.
+ private final int pendingXidTimeoutSeconds = 60;
+
+ // The cache for pending expected RoleReplies keyed on expected XID
+ private Cache<Integer, RoleState> pendingReplies =
+ CacheBuilder.newBuilder()
+ .expireAfterWrite(pendingXidTimeoutSeconds, TimeUnit.SECONDS)
+ .build();
+
+ // the expectation set by the caller for the returned role
+ private RoleRecvStatus expectation;
+ private final OpenFlowSwitchDriver sw;
+
+
+ public RoleManager(OpenFlowSwitchDriver sw) {
+ this.expectation = RoleRecvStatus.MATCHED_CURRENT_ROLE;
+ this.sw = sw;
+ }
+
+ /**
+ * Send NX role request message to the switch requesting the specified
+ * role.
+ *
+ * @param role role to request
+ */
+ private int sendNxRoleRequest(RoleState role) throws IOException {
+ // Convert the role enum to the appropriate role to send
+ OFNiciraControllerRole roleToSend = OFNiciraControllerRole.ROLE_OTHER;
+ switch (role) {
+ case MASTER:
+ roleToSend = OFNiciraControllerRole.ROLE_MASTER;
+ break;
+ case SLAVE:
+ case EQUAL:
+ default:
+ // ensuring that the only two roles sent to 1.0 switches with
+ // Nicira role support, are MASTER and SLAVE
+ roleToSend = OFNiciraControllerRole.ROLE_OTHER;
+ log.debug("Sending Nx Role.SLAVE to switch {}.", sw);
+ }
+ int xid = sw.getNextTransactionId();
+ OFExperimenter roleRequest = OFFactories.getFactory(OFVersion.OF_10)
+ .buildNiciraControllerRoleRequest()
+ .setXid(xid)
+ .setRole(roleToSend)
+ .build();
+ sw.sendRoleRequest(roleRequest);
+ return xid;
+ }
+
+ private int sendOF13RoleRequest(RoleState role) throws IOException {
+ // Convert the role enum to the appropriate role to send
+ OFControllerRole roleToSend = OFControllerRole.ROLE_NOCHANGE;
+ switch (role) {
+ case EQUAL:
+ roleToSend = OFControllerRole.ROLE_EQUAL;
+ break;
+ case MASTER:
+ roleToSend = OFControllerRole.ROLE_MASTER;
+ break;
+ case SLAVE:
+ roleToSend = OFControllerRole.ROLE_SLAVE;
+ break;
+ default:
+ log.warn("Sending default role.noChange to switch {}."
+ + " Should only be used for queries.", sw);
+ }
+
+ int xid = sw.getNextTransactionId();
+ OFRoleRequest rrm = OFFactories.getFactory(OFVersion.OF_13)
+ .buildRoleRequest()
+ .setRole(roleToSend)
+ .setXid(xid)
+ //FIXME fix below when we actually use generation ids
+ .setGenerationId(U64.ZERO)
+ .build();
+
+ sw.sendRoleRequest(rrm);
+ return xid;
+ }
+
+ @Override
+ public synchronized boolean sendRoleRequest(RoleState role, RoleRecvStatus exp)
+ throws IOException {
+ this.expectation = exp;
+
+ if (sw.factory().getVersion() == OFVersion.OF_10) {
+ Boolean supportsNxRole = sw.supportNxRole();
+ if (!supportsNxRole) {
+ log.debug("Switch driver indicates no support for Nicira "
+ + "role request messages. Not sending ...");
+ handleUnsentRoleMessage(role,
+ expectation);
+ return false;
+ }
+ // OF1.0 switch with support for NX_ROLE_REQUEST vendor extn.
+ // make Role.EQUAL become Role.SLAVE
+ RoleState roleToSend = (role == RoleState.EQUAL) ? RoleState.SLAVE : role;
+ pendingReplies.put(sendNxRoleRequest(roleToSend), role);
+ } else {
+ // OF1.3 switch, use OFPT_ROLE_REQUEST message
+ pendingReplies.put(sendOF13RoleRequest(role), role);
+ }
+ return true;
+ }
+
+ private void handleUnsentRoleMessage(RoleState role,
+ RoleRecvStatus exp) throws IOException {
+ // typically this is triggered for a switch where role messages
+ // are not supported - we confirm that the role being set is
+ // master
+ if (exp != RoleRecvStatus.MATCHED_SET_ROLE) {
+
+ log.error("Expected MASTER role from registry for switch "
+ + "which has no support for role-messages."
+ + "Received {}. It is possible that this switch "
+ + "is connected to other controllers, in which "
+ + "case it should support role messages - not "
+ + "moving forward.", role);
+
+ }
+
+ }
+
+
+ @Override
+ public synchronized RoleRecvStatus deliverRoleReply(RoleReplyInfo rri)
+ throws SwitchStateException {
+ int xid = (int) rri.getXid();
+ RoleState receivedRole = rri.getRole();
+ RoleState expectedRole = pendingReplies.getIfPresent(xid);
+
+ if (expectedRole == null) {
+ RoleState currentRole = (sw != null) ? sw.getRole() : null;
+ if (currentRole != null) {
+ if (currentRole == rri.getRole()) {
+ // Don't disconnect if the role reply we received is
+ // for the same role we are already in.
+ // FIXME: but we do from the caller anyways.
+ log.debug("Received unexpected RoleReply from "
+ + "Switch: {}. "
+ + "Role in reply is same as current role of this "
+ + "controller for this sw. Ignoring ...",
+ sw.getStringId());
+ return RoleRecvStatus.OTHER_EXPECTATION;
+ } else {
+ String msg = String.format("Switch: [%s], "
+ + "received unexpected RoleReply[%s]. "
+ + "No roles are pending, and this controller's "
+ + "current role:[%s] does not match reply. "
+ + "Disconnecting switch ... ",
+ sw.getStringId(),
+ rri, currentRole);
+ throw new SwitchStateException(msg);
+ }
+ }
+ log.debug("Received unexpected RoleReply {} from "
+ + "Switch: {}. "
+ + "This controller has no current role for this sw. "
+ + "Ignoring ...",
+ rri,
+ sw == null ? "(null)" : sw.getStringId());
+ return RoleRecvStatus.OTHER_EXPECTATION;
+ }
+
+ // XXX Should check generation id meaningfully and other cases of expectations
+ //if (pendingXid != xid) {
+ // log.info("Received older role reply from " +
+ // "switch {} ({}). Ignoring. " +
+ // "Waiting for {}, xid={}",
+ // new Object[] {sw.getStringId(), rri,
+ // pendingRole, pendingXid });
+ // return RoleRecvStatus.OLD_REPLY;
+ //}
+ sw.returnRoleReply(expectedRole, receivedRole);
+
+ if (expectedRole == receivedRole) {
+ log.debug("Received role reply message from {} that matched "
+ + "expected role-reply {} with expectations {}",
+ sw.getStringId(), receivedRole, expectation);
+
+ // Done with this RoleReply; Invalidate
+ pendingReplies.invalidate(xid);
+ if (expectation == RoleRecvStatus.MATCHED_CURRENT_ROLE ||
+ expectation == RoleRecvStatus.MATCHED_SET_ROLE) {
+ return expectation;
+ } else {
+ return RoleRecvStatus.OTHER_EXPECTATION;
+ }
+ }
+
+ pendingReplies.invalidate(xid);
+ // if xids match but role's don't, perhaps its a query (OF1.3)
+ if (expectation == RoleRecvStatus.REPLY_QUERY) {
+ return expectation;
+ }
+
+ return RoleRecvStatus.OTHER_EXPECTATION;
+ }
+
+ /**
+ * Called if we receive an error message. If the xid matches the
+ * pending request we handle it otherwise we ignore it.
+ *
+ * Note: since we only keep the last pending request we might get
+ * error messages for earlier role requests that we won't be able
+ * to handle
+ */
+ @Override
+ public synchronized RoleRecvStatus deliverError(OFErrorMsg error)
+ throws SwitchStateException {
+ RoleState errorRole = pendingReplies.getIfPresent(error.getXid());
+ if (errorRole == null) {
+ if (error.getErrType() == OFErrorType.ROLE_REQUEST_FAILED) {
+ log.debug("Received an error msg from sw {} for a role request,"
+ + " but not for pending request in role-changer; "
+ + " ignoring error {} ...",
+ sw.getStringId(), error);
+ } else {
+ log.debug("Received an error msg from sw {}, but no pending "
+ + "requests in role-changer; not handling ...",
+ sw.getStringId());
+ }
+ return RoleRecvStatus.OTHER_EXPECTATION;
+ }
+ // it is an error related to a currently pending role request message
+ if (error.getErrType() == OFErrorType.BAD_REQUEST) {
+ log.error("Received a error msg {} from sw {} for "
+ + "pending role request {}. Switch driver indicates "
+ + "role-messaging is supported. Possible issues in "
+ + "switch driver configuration?",
+ ((OFBadRequestErrorMsg) error).toString(),
+ sw.getStringId(),
+ errorRole);
+ return RoleRecvStatus.UNSUPPORTED;
+ }
+
+ if (error.getErrType() == OFErrorType.ROLE_REQUEST_FAILED) {
+ OFRoleRequestFailedErrorMsg rrerr =
+ (OFRoleRequestFailedErrorMsg) error;
+ switch (rrerr.getCode()) {
+ case BAD_ROLE:
+ // switch says that current-role-req has bad role?
+ // for now we disconnect
+ // fall-thru
+ case STALE:
+ // switch says that current-role-req has stale gen-id?
+ // for now we disconnect
+ // fall-thru
+ case UNSUP:
+ // switch says that current-role-req has role that
+ // cannot be supported? for now we disconnect
+ String msgx = String.format("Switch: [%s], "
+ + "received Error to for pending role request [%s]. "
+ + "Error:[%s]. Disconnecting switch ... ",
+ sw.getStringId(),
+ errorRole, rrerr);
+ throw new SwitchStateException(msgx);
+ default:
+ break;
+ }
+ }
+
+ // This error message was for a role request message but we dont know
+ // how to handle errors for nicira role request messages
+ return RoleRecvStatus.OTHER_EXPECTATION;
+ }
+
+ /**
+ * Extract the role from an OFVendor message.
+ *
+ * Extract the role from an OFVendor message if the message is a
+ * Nicira role reply. Otherwise return null.
+ *
+ * @param experimenterMsg message
+ * @return The role in the message if the message is a Nicira role
+ * reply, null otherwise.
+ * @throws SwitchStateException If the message is a Nicira role reply
+ * but the numeric role value is unknown.
+ */
+ @Override
+ public RoleState extractNiciraRoleReply(OFExperimenter experimenterMsg)
+ throws SwitchStateException {
+ int vendor = (int) experimenterMsg.getExperimenter();
+ if (vendor != 0x2320) {
+ return null;
+ }
+ OFNiciraControllerRoleReply nrr =
+ (OFNiciraControllerRoleReply) experimenterMsg;
+
+ RoleState role = null;
+ OFNiciraControllerRole ncr = nrr.getRole();
+ switch (ncr) {
+ case ROLE_MASTER:
+ role = RoleState.MASTER;
+ break;
+ case ROLE_OTHER:
+ role = RoleState.EQUAL;
+ break;
+ case ROLE_SLAVE:
+ role = RoleState.SLAVE;
+ break;
+ default: //handled below
+ }
+
+ if (role == null) {
+ String msg = String.format("Switch: [%s], "
+ + "received NX_ROLE_REPLY with invalid role "
+ + "value %s",
+ sw.getStringId(),
+ nrr.getRole());
+ throw new SwitchStateException(msg);
+ }
+ return role;
+ }
+
+ /**
+ * Extract the role information from an OF1.3 Role Reply Message.
+ *
+ * @param rrmsg the role message
+ * @return RoleReplyInfo object
+ * @throws SwitchStateException if the role information could not be extracted.
+ */
+ @Override
+ public RoleReplyInfo extractOFRoleReply(OFRoleReply rrmsg)
+ throws SwitchStateException {
+ OFControllerRole cr = rrmsg.getRole();
+ RoleState role = null;
+ switch (cr) {
+ case ROLE_EQUAL:
+ role = RoleState.EQUAL;
+ break;
+ case ROLE_MASTER:
+ role = RoleState.MASTER;
+ break;
+ case ROLE_SLAVE:
+ role = RoleState.SLAVE;
+ break;
+ case ROLE_NOCHANGE: // switch should send current role
+ default:
+ String msg = String.format("Unknown controller role %s "
+ + "received from switch %s", cr, sw);
+ throw new SwitchStateException(msg);
+ }
+
+ return new RoleReplyInfo(role, rrmsg.getGenerationId(), rrmsg.getXid());
+ }
+
+}
+
diff --git a/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/package-info.java b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/package-info.java
new file mode 100644
index 00000000..a5d9f274
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2014 Open Networking Laboratory
+ *
+ * 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.
+ */
+
+/**
+ * Implementation of the OpenFlow controller IO subsystem.
+ */
+package org.onosproject.openflow.controller.impl;
diff --git a/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/ChannelAdapter.java b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/ChannelAdapter.java
new file mode 100644
index 00000000..75260a1d
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/ChannelAdapter.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow;
+
+import java.net.SocketAddress;
+
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelConfig;
+import org.jboss.netty.channel.ChannelFactory;
+import org.jboss.netty.channel.ChannelFuture;
+import org.jboss.netty.channel.ChannelPipeline;
+
+/**
+ * Adapter for testing against a netty channel.
+ */
+public class ChannelAdapter implements Channel {
+ @Override
+ public Integer getId() {
+ return null;
+ }
+
+ @Override
+ public ChannelFactory getFactory() {
+ return null;
+ }
+
+ @Override
+ public Channel getParent() {
+ return null;
+ }
+
+ @Override
+ public ChannelConfig getConfig() {
+ return null;
+ }
+
+ @Override
+ public ChannelPipeline getPipeline() {
+ return null;
+ }
+
+ @Override
+ public boolean isOpen() {
+ return false;
+ }
+
+ @Override
+ public boolean isBound() {
+ return false;
+ }
+
+ @Override
+ public boolean isConnected() {
+ return false;
+ }
+
+ @Override
+ public SocketAddress getLocalAddress() {
+ return null;
+ }
+
+ @Override
+ public SocketAddress getRemoteAddress() {
+ return null;
+ }
+
+ @Override
+ public ChannelFuture write(Object o) {
+ return null;
+ }
+
+ @Override
+ public ChannelFuture write(Object o, SocketAddress socketAddress) {
+ return null;
+ }
+
+ @Override
+ public ChannelFuture bind(SocketAddress socketAddress) {
+ return null;
+ }
+
+ @Override
+ public ChannelFuture connect(SocketAddress socketAddress) {
+ return null;
+ }
+
+ @Override
+ public ChannelFuture disconnect() {
+ return null;
+ }
+
+ @Override
+ public ChannelFuture unbind() {
+ return null;
+ }
+
+ @Override
+ public ChannelFuture close() {
+ return null;
+ }
+
+ @Override
+ public ChannelFuture getCloseFuture() {
+ return null;
+ }
+
+ @Override
+ public int getInterestOps() {
+ return 0;
+ }
+
+ @Override
+ public boolean isReadable() {
+ return false;
+ }
+
+ @Override
+ public boolean isWritable() {
+ return false;
+ }
+
+ @Override
+ public ChannelFuture setInterestOps(int i) {
+ return null;
+ }
+
+ @Override
+ public ChannelFuture setReadable(boolean b) {
+ return null;
+ }
+
+ @Override
+ public Object getAttachment() {
+ return null;
+ }
+
+ @Override
+ public void setAttachment(Object o) {
+
+ }
+
+ @Override
+ public int compareTo(Channel o) {
+ return 0;
+ }
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/ChannelHandlerContextAdapter.java b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/ChannelHandlerContextAdapter.java
new file mode 100644
index 00000000..5b6c2a36
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/ChannelHandlerContextAdapter.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow;
+
+import org.jboss.netty.channel.Channel;
+import org.jboss.netty.channel.ChannelEvent;
+import org.jboss.netty.channel.ChannelHandler;
+import org.jboss.netty.channel.ChannelHandlerContext;
+import org.jboss.netty.channel.ChannelPipeline;
+
+/**
+ * Adapter for testing against a netty channel handler context.
+ */
+public class ChannelHandlerContextAdapter implements ChannelHandlerContext {
+ @Override
+ public Channel getChannel() {
+ return null;
+ }
+
+ @Override
+ public ChannelPipeline getPipeline() {
+ return null;
+ }
+
+ @Override
+ public String getName() {
+ return null;
+ }
+
+ @Override
+ public ChannelHandler getHandler() {
+ return null;
+ }
+
+ @Override
+ public boolean canHandleUpstream() {
+ return false;
+ }
+
+ @Override
+ public boolean canHandleDownstream() {
+ return false;
+ }
+
+ @Override
+ public void sendUpstream(ChannelEvent channelEvent) {
+
+ }
+
+ @Override
+ public void sendDownstream(ChannelEvent channelEvent) {
+
+ }
+
+ @Override
+ public Object getAttachment() {
+ return null;
+ }
+
+ @Override
+ public void setAttachment(Object o) {
+
+ }
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/DriverAdapter.java b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/DriverAdapter.java
new file mode 100644
index 00000000..57becf94
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/DriverAdapter.java
@@ -0,0 +1,104 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow;
+
+import java.util.Map;
+import java.util.Set;
+
+import org.onosproject.net.driver.Behaviour;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.net.driver.DriverData;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver;
+
+/**
+ * Created by ray on 11/4/15.
+ */
+public class DriverAdapter implements Driver {
+ @Override
+ public String name() {
+ return null;
+ }
+
+ @Override
+ public Driver parent() {
+ return null;
+ }
+
+ @Override
+ public String manufacturer() {
+ return null;
+ }
+
+ @Override
+ public String hwVersion() {
+ return null;
+ }
+
+ @Override
+ public String swVersion() {
+ return null;
+ }
+
+ @Override
+ public Set<Class<? extends Behaviour>> behaviours() {
+ return null;
+ }
+
+ @Override
+ public Class<? extends Behaviour> implementation(Class<? extends Behaviour> behaviour) {
+ return null;
+ }
+
+ @Override
+ public boolean hasBehaviour(Class<? extends Behaviour> behaviourClass) {
+ return true;
+ }
+
+ @Override
+ public <T extends Behaviour> T createBehaviour(DriverData data, Class<T> behaviourClass) {
+ return null;
+ }
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public <T extends Behaviour> T createBehaviour(DriverHandler handler, Class<T> behaviourClass) {
+ if (behaviourClass == OpenFlowSwitchDriver.class) {
+ return (T) new OpenflowSwitchDriverAdapter();
+ }
+ return null;
+ }
+
+ @Override
+ public Map<String, String> properties() {
+ return null;
+ }
+
+ @Override
+ public Driver merge(Driver other) {
+ return null;
+ }
+
+ @Override
+ public Set<String> keys() {
+ return null;
+ }
+
+ @Override
+ public String value(String key) {
+ return null;
+ }
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/DriverServiceAdapter.java b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/DriverServiceAdapter.java
new file mode 100644
index 00000000..25596ada
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/DriverServiceAdapter.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow;
+
+import java.util.Set;
+
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.driver.Behaviour;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.net.driver.DriverService;
+
+/**
+ * Created by ray on 11/4/15.
+ */
+public class DriverServiceAdapter implements DriverService {
+ @Override
+ public Set<Driver> getDrivers() {
+ return null;
+ }
+
+ @Override
+ public Set<Driver> getDrivers(Class<? extends Behaviour> withBehaviour) {
+ return null;
+ }
+
+ @Override
+ public Driver getDriver(String mfr, String hw, String sw) {
+ return null;
+ }
+
+ @Override
+ public Driver getDriver(DeviceId deviceId) {
+ return null;
+ }
+
+ @Override
+ public DriverHandler createHandler(DeviceId deviceId, String... credentials) {
+ return null;
+ }
+
+ @Override
+ public Driver getDriver(String driverName) {
+ return null;
+ }
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/ExecutorServiceAdapter.java b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/ExecutorServiceAdapter.java
new file mode 100644
index 00000000..54c9c94b
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/ExecutorServiceAdapter.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow;
+
+import java.util.Collection;
+import java.util.List;
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Future;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+/**
+ * Test harness adapter for the ExecutorService.
+ */
+public class ExecutorServiceAdapter implements ExecutorService {
+ @Override
+ public void shutdown() {
+
+ }
+
+ @Override
+ public List<Runnable> shutdownNow() {
+ return null;
+ }
+
+ @Override
+ public boolean isShutdown() {
+ return false;
+ }
+
+ @Override
+ public boolean isTerminated() {
+ return false;
+ }
+
+ @Override
+ public boolean awaitTermination(long timeout, TimeUnit unit)
+ throws InterruptedException {
+ return false;
+ }
+
+ @Override
+ public <T> Future<T> submit(Callable<T> task) {
+ return null;
+ }
+
+ @Override
+ public <T> Future<T> submit(Runnable task, T result) {
+ return null;
+ }
+
+ @Override
+ public Future<?> submit(Runnable task) {
+ return null;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
+ throws InterruptedException {
+ return null;
+ }
+
+ @Override
+ public <T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException {
+ return null;
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks) throws InterruptedException, ExecutionException {
+ return null;
+ }
+
+ @Override
+ public <T> T invokeAny(Collection<? extends Callable<T>> tasks, long timeout, TimeUnit unit)
+ throws InterruptedException, ExecutionException, TimeoutException {
+ return null;
+ }
+
+ @Override
+ public void execute(Runnable command) {
+
+ }
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/MockOfFeaturesReply.java b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/MockOfFeaturesReply.java
new file mode 100644
index 00000000..e280d56e
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/MockOfFeaturesReply.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow;
+
+import java.util.List;
+import java.util.Set;
+
+import org.projectfloodlight.openflow.protocol.OFActionType;
+import org.projectfloodlight.openflow.protocol.OFCapabilities;
+import org.projectfloodlight.openflow.protocol.OFFeaturesReply;
+import org.projectfloodlight.openflow.protocol.OFPortDesc;
+import org.projectfloodlight.openflow.protocol.OFType;
+import org.projectfloodlight.openflow.types.DatapathId;
+import org.projectfloodlight.openflow.types.OFAuxId;
+
+/**
+ * Mock of the Open FLow features reply message.
+ */
+public class MockOfFeaturesReply extends OfMessageAdapter implements OFFeaturesReply {
+ public MockOfFeaturesReply() {
+ super(OFType.FEATURES_REPLY);
+ }
+
+ @Override
+ public DatapathId getDatapathId() {
+ return null;
+ }
+
+ @Override
+ public long getNBuffers() {
+ return 0;
+ }
+
+ @Override
+ public short getNTables() {
+ return 0;
+ }
+
+ @Override
+ public Set<OFCapabilities> getCapabilities() {
+ return null;
+ }
+
+ @Override
+ public long getReserved() {
+ return 0;
+ }
+
+ @Override
+ public List<OFPortDesc> getPorts() {
+ return null;
+ }
+
+ @Override
+ public Set<OFActionType> getActions() {
+ return null;
+ }
+
+ @Override
+ public OFAuxId getAuxiliaryId() {
+ return null;
+ }
+
+ @Override
+ public OFFeaturesReply.Builder createBuilder() {
+ return null;
+ }
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/MockOfPacketIn.java b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/MockOfPacketIn.java
new file mode 100644
index 00000000..8e2069b0
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/MockOfPacketIn.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow;
+
+import org.projectfloodlight.openflow.protocol.OFPacketIn;
+import org.projectfloodlight.openflow.protocol.OFPacketInReason;
+import org.projectfloodlight.openflow.protocol.OFType;
+import org.projectfloodlight.openflow.protocol.match.Match;
+import org.projectfloodlight.openflow.types.OFBufferId;
+import org.projectfloodlight.openflow.types.OFPort;
+import org.projectfloodlight.openflow.types.TableId;
+import org.projectfloodlight.openflow.types.U64;
+
+/**
+ * Mock of the Open Flow packet in message.
+ */
+public class MockOfPacketIn extends OfMessageAdapter implements OFPacketIn {
+ public MockOfPacketIn() {
+ super(OFType.PACKET_IN);
+ }
+
+ @Override
+ public OFBufferId getBufferId() {
+ return null;
+ }
+
+ @Override
+ public int getTotalLen() {
+ return 0;
+ }
+
+ @Override
+ public OFPacketInReason getReason() {
+ return null;
+ }
+
+ @Override
+ public TableId getTableId() {
+ return null;
+ }
+
+ @Override
+ public Match getMatch() {
+ return null;
+ }
+
+ @Override
+ public byte[] getData() {
+ return new byte[0];
+ }
+
+ @Override
+ public OFPort getInPort() {
+ return null;
+ }
+
+ @Override
+ public OFPort getInPhyPort() {
+ return null;
+ }
+
+ @Override
+ public U64 getCookie() {
+ return null;
+ }
+
+ @Override
+ public OFPacketIn.Builder createBuilder() {
+ return null;
+ }
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/MockOfPortStatus.java b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/MockOfPortStatus.java
new file mode 100644
index 00000000..2e26542f
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/MockOfPortStatus.java
@@ -0,0 +1,45 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow;
+
+import org.projectfloodlight.openflow.protocol.OFPortDesc;
+import org.projectfloodlight.openflow.protocol.OFPortReason;
+import org.projectfloodlight.openflow.protocol.OFPortStatus;
+import org.projectfloodlight.openflow.protocol.OFType;
+
+/**
+ * Mocked open flow port status message.
+ */
+public class MockOfPortStatus extends OfMessageAdapter implements OFPortStatus {
+ public MockOfPortStatus() {
+ super(OFType.PORT_STATUS);
+ }
+
+ @Override
+ public OFPortReason getReason() {
+ return null;
+ }
+
+ @Override
+ public OFPortDesc getDesc() {
+ return null;
+ }
+
+ @Override
+ public OFPortStatus.Builder createBuilder() {
+ return null;
+ }
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OFDescStatsReplyAdapter.java b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OFDescStatsReplyAdapter.java
new file mode 100644
index 00000000..1e866413
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OFDescStatsReplyAdapter.java
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow;
+
+import java.util.Set;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
+import org.projectfloodlight.openflow.protocol.OFStatsReplyFlags;
+import org.projectfloodlight.openflow.protocol.OFStatsType;
+import org.projectfloodlight.openflow.protocol.OFType;
+import org.projectfloodlight.openflow.protocol.OFVersion;
+
+import com.google.common.hash.PrimitiveSink;
+
+/**
+ * Created by ray on 11/4/15.
+ */
+public class OFDescStatsReplyAdapter implements OFDescStatsReply {
+ @Override
+ public OFVersion getVersion() {
+ return null;
+ }
+
+ @Override
+ public OFType getType() {
+ return null;
+ }
+
+ @Override
+ public long getXid() {
+ return 0;
+ }
+
+ @Override
+ public OFStatsType getStatsType() {
+ return null;
+ }
+
+ @Override
+ public Set<OFStatsReplyFlags> getFlags() {
+ return null;
+ }
+
+ @Override
+ public String getMfrDesc() {
+ return null;
+ }
+
+ @Override
+ public String getHwDesc() {
+ return null;
+ }
+
+ @Override
+ public String getSwDesc() {
+ return null;
+ }
+
+ @Override
+ public String getSerialNum() {
+ return null;
+ }
+
+ @Override
+ public String getDpDesc() {
+ return null;
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer channelBuffer) {
+
+ }
+
+ @Override
+ public Builder createBuilder() {
+ return null;
+ }
+
+ @Override
+ public void putTo(PrimitiveSink sink) {
+
+ }
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OfMessageAdapter.java b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OfMessageAdapter.java
new file mode 100644
index 00000000..b7446eb9
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OfMessageAdapter.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.projectfloodlight.openflow.protocol.OFMessage;
+import org.projectfloodlight.openflow.protocol.OFType;
+import org.projectfloodlight.openflow.protocol.OFVersion;
+
+import com.google.common.hash.PrimitiveSink;
+
+/**
+ * Adapter for testing against an OpenFlow message.
+ */
+public class OfMessageAdapter implements OFMessage {
+ OFType type;
+
+ private OfMessageAdapter() {}
+
+ public OfMessageAdapter(OFType type) {
+ this.type = type;
+ }
+
+ @Override
+ public OFType getType() {
+ return type;
+ }
+
+ @Override
+ public OFVersion getVersion() {
+ return null;
+ }
+
+ @Override
+ public long getXid() {
+ return 0;
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer channelBuffer) { }
+
+ @Override
+ public Builder createBuilder() {
+ return null;
+ }
+
+ @Override
+ public void putTo(PrimitiveSink sink) { }
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OpenFlowSwitchListenerAdapter.java b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OpenFlowSwitchListenerAdapter.java
new file mode 100644
index 00000000..b018f42a
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OpenFlowSwitchListenerAdapter.java
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+import org.onosproject.openflow.controller.Dpid;
+import org.onosproject.openflow.controller.OpenFlowSwitchListener;
+import org.onosproject.openflow.controller.RoleState;
+import org.projectfloodlight.openflow.protocol.OFPortStatus;
+
+/**
+ * Test harness for a switch listener.
+ */
+public class OpenFlowSwitchListenerAdapter implements OpenFlowSwitchListener {
+ final List<Dpid> removedDpids = new ArrayList<>();
+ final List<Dpid> addedDpids = new ArrayList<>();
+ final List<Dpid> changedDpids = new ArrayList<>();
+ final Map<Dpid, OFPortStatus> portChangedDpids = new HashMap<>();
+
+ @Override
+ public void switchAdded(Dpid dpid) {
+ addedDpids.add(dpid);
+ }
+
+ @Override
+ public void switchRemoved(Dpid dpid) {
+ removedDpids.add(dpid);
+ }
+
+ @Override
+ public void switchChanged(Dpid dpid) {
+ changedDpids.add(dpid);
+ }
+
+ @Override
+ public void portChanged(Dpid dpid, OFPortStatus status) {
+ portChangedDpids.put(dpid, status);
+ }
+
+ @Override
+ public void receivedRoleReply(Dpid dpid, RoleState requested, RoleState response) {
+ // Stub
+ }
+
+ public List<Dpid> removedDpids() {
+ return removedDpids;
+ }
+
+ public List<Dpid> addedDpids() {
+ return addedDpids;
+ }
+
+ public List<Dpid> changedDpids() {
+ return changedDpids;
+ }
+
+ public Map<Dpid, OFPortStatus> portChangedDpids() {
+ return portChangedDpids;
+ }
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OpenflowSwitchDriverAdapter.java b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OpenflowSwitchDriverAdapter.java
new file mode 100644
index 00000000..9b899a67
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OpenflowSwitchDriverAdapter.java
@@ -0,0 +1,302 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow;
+
+import java.util.List;
+
+import org.jboss.netty.channel.Channel;
+import org.onosproject.net.Device;
+import org.onosproject.net.driver.DriverData;
+import org.onosproject.net.driver.DriverHandler;
+import org.onosproject.openflow.controller.Dpid;
+import org.onosproject.openflow.controller.RoleState;
+import org.onosproject.openflow.controller.driver.OpenFlowAgent;
+import org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver;
+import org.onosproject.openflow.controller.driver.RoleHandler;
+import org.onosproject.openflow.controller.driver.SwitchStateException;
+import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
+import org.projectfloodlight.openflow.protocol.OFErrorMsg;
+import org.projectfloodlight.openflow.protocol.OFFactories;
+import org.projectfloodlight.openflow.protocol.OFFactory;
+import org.projectfloodlight.openflow.protocol.OFFeaturesReply;
+import org.projectfloodlight.openflow.protocol.OFMessage;
+import org.projectfloodlight.openflow.protocol.OFPortDesc;
+import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply;
+import org.projectfloodlight.openflow.protocol.OFVersion;
+
+/**
+ * Testing adapter for the OpenFlow switch driver class.
+ */
+public class OpenflowSwitchDriverAdapter implements OpenFlowSwitchDriver {
+
+ RoleState role = RoleState.MASTER;
+
+ @Override
+ public void setAgent(OpenFlowAgent agent) {
+
+ }
+
+ @Override
+ public void setRoleHandler(RoleHandler roleHandler) {
+
+ }
+
+ @Override
+ public void reassertRole() {
+
+ }
+
+ @Override
+ public boolean handleRoleError(OFErrorMsg error) {
+ return false;
+ }
+
+ @Override
+ public void handleNiciraRole(OFMessage m) throws SwitchStateException {
+
+ }
+
+ @Override
+ public void handleRole(OFMessage m) throws SwitchStateException {
+
+ }
+
+ @Override
+ public boolean connectSwitch() {
+ return false;
+ }
+
+ @Override
+ public boolean activateMasterSwitch() {
+ return false;
+ }
+
+ @Override
+ public boolean activateEqualSwitch() {
+ return false;
+ }
+
+ @Override
+ public void transitionToEqualSwitch() {
+
+ }
+
+ @Override
+ public void transitionToMasterSwitch() {
+
+ }
+
+ @Override
+ public void removeConnectedSwitch() {
+
+ }
+
+ @Override
+ public void setPortDescReply(OFPortDescStatsReply portDescReply) {
+
+ }
+
+ @Override
+ public void setPortDescReplies(List<OFPortDescStatsReply> portDescReplies) {
+
+ }
+
+ @Override
+ public void setFeaturesReply(OFFeaturesReply featuresReply) {
+
+ }
+
+ @Override
+ public void setSwitchDescription(OFDescStatsReply desc) {
+
+ }
+
+ @Override
+ public int getNextTransactionId() {
+ return 0;
+ }
+
+ @Override
+ public void setOFVersion(OFVersion ofV) {
+
+ }
+
+ @Override
+ public void setTableFull(boolean full) {
+
+ }
+
+ @Override
+ public void setChannel(Channel channel) {
+
+ }
+
+ @Override
+ public void setConnected(boolean connected) {
+
+ }
+
+ @Override
+ public void init(Dpid dpid, OFDescStatsReply desc, OFVersion ofv) {
+
+ }
+
+ @Override
+ public Boolean supportNxRole() {
+ return true;
+ }
+
+ @Override
+ public void startDriverHandshake() {
+
+ }
+
+ @Override
+ public boolean isDriverHandshakeComplete() {
+ return false;
+ }
+
+ @Override
+ public void processDriverHandshakeMessage(OFMessage m) {
+
+ }
+
+ @Override
+ public void sendRoleRequest(OFMessage message) {
+
+ }
+
+ @Override
+ public void sendHandshakeMessage(OFMessage message) {
+
+ }
+
+ @Override
+ public DriverHandler handler() {
+ return null;
+ }
+
+ @Override
+ public void setHandler(DriverHandler handler) {
+
+ }
+
+ @Override
+ public DriverData data() {
+ return null;
+ }
+
+ @Override
+ public void setData(DriverData data) {
+
+ }
+
+ @Override
+ public void sendMsg(OFMessage msg) {
+
+ }
+
+ @Override
+ public void sendMsg(List<OFMessage> msgs) {
+
+ }
+
+ @Override
+ public void handleMessage(OFMessage fromSwitch) {
+
+ }
+
+ @Override
+ public void setRole(RoleState role) {
+ this.role = role;
+ }
+
+ @Override
+ public RoleState getRole() {
+ return role;
+ }
+
+ @Override
+ public List<OFPortDesc> getPorts() {
+ return null;
+ }
+
+ @Override
+ public OFFactory factory() {
+ // return what-ever triggers requestPending = true
+ return OFFactories.getFactory(OFVersion.OF_10);
+ }
+
+ @Override
+ public String getStringId() {
+ return "100";
+ }
+
+ @Override
+ public long getId() {
+ return 0;
+ }
+
+ @Override
+ public String manufacturerDescription() {
+ return null;
+ }
+
+ @Override
+ public String datapathDescription() {
+ return null;
+ }
+
+ @Override
+ public String hardwareDescription() {
+ return null;
+ }
+
+ @Override
+ public String softwareDescription() {
+ return null;
+ }
+
+ @Override
+ public String serialNumber() {
+ return null;
+ }
+
+ @Override
+ public boolean isConnected() {
+ return false;
+ }
+
+ @Override
+ public void disconnectSwitch() {
+
+ }
+
+ @Override
+ public void returnRoleReply(RoleState requested, RoleState response) {
+
+ }
+
+ @Override
+ public Device.Type deviceType() {
+ return Device.Type.SWITCH;
+ }
+
+ @Override
+ public String channelId() {
+ return null;
+ }
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/ControllerTest.java b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/ControllerTest.java
new file mode 100644
index 00000000..dddea328
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/ControllerTest.java
@@ -0,0 +1,219 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow.controller.impl;
+
+import java.io.File;
+import java.io.IOException;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.Map;
+import java.util.stream.IntStream;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestTools;
+import org.onlab.util.ItemNotFoundException;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.openflow.DriverAdapter;
+import org.onosproject.openflow.DriverServiceAdapter;
+import org.onosproject.openflow.OFDescStatsReplyAdapter;
+import org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver;
+import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+import com.google.common.io.Files;
+
+import static com.google.common.io.ByteStreams.toByteArray;
+import static com.google.common.io.Files.write;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasItem;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.lessThan;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+
+/**
+ * Unit tests for the OpenFlow controller class.
+ */
+public class ControllerTest {
+
+ Controller controller;
+ protected static final Logger log = LoggerFactory.getLogger(ControllerTest.class);
+
+ static final File TEST_DIR = Files.createTempDir();
+
+ /*
+ * Writes the necessary file for the tests in the temporary directory
+ */
+ static File stageTestResource(String name) throws IOException {
+ File file = new File(TEST_DIR, name);
+ byte[] bytes = toByteArray(ControllerTest.class.getResourceAsStream(name));
+ write(bytes, file);
+ return file;
+ }
+
+ class MockDriverService extends DriverServiceAdapter {
+ static final int NO_SUCH_DRIVER_ID = 1;
+ static final int ITEM_NOT_FOUND_DRIVER_ID = 2;
+ static final int DRIVER_EXISTS_ID = 3;
+
+ static final String BASE_DRIVER_NAME = "of:000000000000000";
+
+ static final String NO_SUCH_DRIVER = BASE_DRIVER_NAME
+ + NO_SUCH_DRIVER_ID;
+ static final String ITEM_NOT_FOUND_DRIVER = BASE_DRIVER_NAME
+ + ITEM_NOT_FOUND_DRIVER_ID;
+ static final String DRIVER_EXISTS = BASE_DRIVER_NAME
+ + DRIVER_EXISTS_ID;
+
+ @Override
+ public Driver getDriver(DeviceId deviceId) {
+ switch (deviceId.toString()) {
+ case NO_SUCH_DRIVER:
+ return null;
+ case ITEM_NOT_FOUND_DRIVER:
+ throw new ItemNotFoundException();
+ case DRIVER_EXISTS:
+ return new DriverAdapter();
+ default:
+ throw new AssertionError();
+ }
+ }
+ }
+
+ /**
+ * Creates and initializes a new controller.
+ */
+ @Before
+ public void setUp() {
+ controller = new Controller();
+ Dictionary<String, String> properties = new Hashtable<>();
+ properties.put("openflowPorts",
+ Integer.toString(TestTools.findAvailablePort(0)));
+ controller.setConfigParams(properties);
+ }
+
+ /**
+ * Tests fetching a driver that does not exist.
+ */
+ @Test
+ public void switchInstanceNotFoundTest() {
+ controller.start(null, new MockDriverService());
+ OpenFlowSwitchDriver driver =
+ controller.getOFSwitchInstance(MockDriverService.NO_SUCH_DRIVER_ID,
+ null,
+ null);
+ assertThat(driver, nullValue());
+ controller.stop();
+ }
+
+ /**
+ * Tests fetching a driver that throws an ItemNotFoundException.
+ */
+ @Test
+ public void switchItemNotFoundTest() {
+ controller.start(null, new MockDriverService());
+ OFDescStatsReply stats =
+ new OFDescStatsReplyAdapter();
+ OpenFlowSwitchDriver driver =
+ controller.getOFSwitchInstance(MockDriverService.ITEM_NOT_FOUND_DRIVER_ID,
+ stats,
+ null);
+ assertThat(driver, nullValue());
+ controller.stop();
+ }
+
+ /**
+ * Tests fetching a driver that throws an ItemNotFoundException.
+ */
+ @Test
+ public void driverExistsTest() {
+ controller.start(null, new MockDriverService());
+ OFDescStatsReply stats =
+ new OFDescStatsReplyAdapter();
+ OpenFlowSwitchDriver driver =
+ controller.getOFSwitchInstance(MockDriverService.DRIVER_EXISTS_ID,
+ stats,
+ null);
+ assertThat(driver, notNullValue());
+ controller.stop();
+ }
+
+ /**
+ * Tests configuring the controller.
+ */
+ @Test
+ public void testConfiguration() {
+ Dictionary<String, String> properties = new Hashtable<>();
+ properties.put("openflowPorts", "1,2,3,4,5");
+ properties.put("workerThreads", "5");
+
+ controller.setConfigParams(properties);
+ IntStream.rangeClosed(1, 5)
+ .forEach(i -> assertThat(controller.openFlowPorts, hasItem(i)));
+ assertThat(controller.workerThreads, is(5));
+ }
+
+ /**
+ * Tests the SSL/TLS methods in the controller.
+ */
+ @Test
+ public void testSsl() throws IOException {
+ File keystore = stageTestResource("ControllerTestKeystore.jks");
+ String keystoreName = keystore.getAbsolutePath();
+
+ System.setProperty("enableOFTLS", Boolean.toString(Boolean.TRUE));
+ System.setProperty("javax.net.ssl.keyStore", keystoreName);
+ System.setProperty("javax.net.ssl.trustStore", keystoreName);
+ System.setProperty("javax.net.ssl.keyStorePassword", "password");
+ System.setProperty("javax.net.ssl.trustStorePassword", "password");
+ Dictionary<String, String> properties = new Hashtable<>();
+ properties.put("openflowPorts",
+ Integer.toString(TestTools.findAvailablePort(0)));
+ properties.put("workerThreads", "0");
+
+ controller.setConfigParams(properties);
+ controller.start(null, new MockDriverService());
+
+ assertThat(controller.serverSslEngine, notNullValue());
+
+ controller.stop();
+ boolean removed = keystore.delete();
+ if (!removed) {
+ log.warn("Could not remove temporary file");
+ }
+ }
+
+ /**
+ * Tests controll utility health methods.
+ */
+ @Test
+ public void testHealth() {
+ Map<String, Long> memory = controller.getMemory();
+ assertThat(memory.size(), is(2));
+ assertThat(memory.get("total"), is(not(0)));
+ assertThat(memory.get("free"), is(not(0)));
+
+ long startTime = controller.getSystemStartTime();
+ assertThat(startTime, lessThan(System.currentTimeMillis()));
+
+ long upTime = controller.getSystemUptime();
+ assertThat(upTime, lessThan(30L * 1000));
+ }
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OFMessageDecoderTest.java b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OFMessageDecoderTest.java
new file mode 100644
index 00000000..ed1db238
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OFMessageDecoderTest.java
@@ -0,0 +1,84 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow.controller.impl;
+
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.jboss.netty.buffer.ChannelBuffers;
+import org.junit.Test;
+import org.onosproject.openflow.ChannelAdapter;
+import org.onosproject.openflow.ChannelHandlerContextAdapter;
+import org.projectfloodlight.openflow.protocol.OFHello;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.instanceOf;
+import static org.hamcrest.Matchers.notNullValue;
+import static org.hamcrest.Matchers.nullValue;
+
+/**
+ * Tests for the OpenFlow message decoder.
+ */
+public class OFMessageDecoderTest {
+
+ static class ConnectedChannel extends ChannelAdapter {
+ @Override
+ public boolean isConnected() {
+ return true;
+ }
+ }
+
+ private ChannelBuffer getHelloMessageBuffer() {
+ // OFHello, OF version 1, xid of 0, total of 8 bytes
+ byte[] messageData = {0x1, 0x0, 0x0, 0x8, 0x0, 0x0, 0x0, 0x0};
+ ChannelBuffer channelBuffer = ChannelBuffers.dynamicBuffer();
+ channelBuffer.writeBytes(messageData);
+ return channelBuffer;
+ }
+
+ /**
+ * Tests decoding a message on a closed channel.
+ *
+ * @throws Exception when an exception is thrown from the decoder
+ */
+ @Test
+ public void testDecodeNoChannel() throws Exception {
+ OFMessageDecoder decoder = new OFMessageDecoder();
+ ChannelBuffer channelBuffer = getHelloMessageBuffer();
+ Object message =
+ decoder.decode(new ChannelHandlerContextAdapter(),
+ new ChannelAdapter(),
+ channelBuffer);
+ assertThat(message, nullValue());
+ }
+
+ /**
+ * Tests decoding a message.
+ *
+ * @throws Exception when an exception is thrown from the decoder
+ */
+ @Test
+ public void testDecode() throws Exception {
+ OFMessageDecoder decoder = new OFMessageDecoder();
+ ChannelBuffer channelBuffer = getHelloMessageBuffer();
+ Object message =
+ decoder.decode(new ChannelHandlerContextAdapter(),
+ new ConnectedChannel(),
+ channelBuffer);
+ assertThat(message, notNullValue());
+ assertThat(message, instanceOf(OFHello.class));
+ }
+
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OFMessageEncoderTest.java b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OFMessageEncoderTest.java
new file mode 100644
index 00000000..d09e5661
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OFMessageEncoderTest.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow.controller.impl;
+
+import java.nio.charset.StandardCharsets;
+import java.util.List;
+
+import org.jboss.netty.buffer.ChannelBuffer;
+import org.junit.Test;
+import org.onosproject.openflow.OfMessageAdapter;
+import org.projectfloodlight.openflow.protocol.OFMessage;
+import org.projectfloodlight.openflow.protocol.OFType;
+
+import com.google.common.collect.ImmutableList;
+
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.notNullValue;
+
+/**
+ * Tests for the OpenFlow message encoder.
+ */
+public class OFMessageEncoderTest {
+
+ static class MockOfMessage extends OfMessageAdapter {
+ static int nextId = 1;
+ final int id;
+
+ MockOfMessage() {
+ super(OFType.ERROR);
+ id = nextId++;
+ }
+
+ @Override
+ public void writeTo(ChannelBuffer channelBuffer) {
+ String message = "message" + Integer.toString(id) + " ";
+ channelBuffer.writeBytes(message.getBytes(StandardCharsets.UTF_8));
+ }
+ }
+
+ /**
+ * Tests that encoding a non-list returns the object specified.
+ *
+ * @throws Exception on exception in the encoder
+ */
+ @Test
+ public void testNoList() throws Exception {
+ OFMessageEncoder encoder = new OFMessageEncoder();
+ MockOfMessage message = new MockOfMessage();
+ OFMessage returnedMessage =
+ (OFMessage) encoder.encode(null, null, message);
+ assertThat(message, is(returnedMessage));
+ }
+
+ /**
+ * Tests that encoding a list returns the proper encoded payload.
+ *
+ * @throws Exception on exception in the encoder
+ */
+ @Test
+ public void testList() throws Exception {
+ OFMessageEncoder encoder = new OFMessageEncoder();
+ MockOfMessage message1 = new MockOfMessage();
+ MockOfMessage message2 = new MockOfMessage();
+ MockOfMessage message3 = new MockOfMessage();
+ List<MockOfMessage> messages = ImmutableList.of(message1, message2, message3);
+ ChannelBuffer returnedChannel =
+ (ChannelBuffer) encoder.encode(null, null, messages);
+ assertThat(returnedChannel, notNullValue());
+ byte[] channelBytes = returnedChannel.array();
+ String expectedListMessage = "message1 message2 message3 ";
+ String listMessage =
+ (new String(channelBytes, StandardCharsets.UTF_8))
+ .substring(0, expectedListMessage.length());
+ assertThat(listMessage, is(expectedListMessage));
+ }
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImplPacketsTest.java b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImplPacketsTest.java
new file mode 100644
index 00000000..13086ca7
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImplPacketsTest.java
@@ -0,0 +1,167 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow.controller.impl;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.concurrent.Future;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.openflow.ExecutorServiceAdapter;
+import org.onosproject.openflow.MockOfFeaturesReply;
+import org.onosproject.openflow.MockOfPacketIn;
+import org.onosproject.openflow.MockOfPortStatus;
+import org.onosproject.openflow.OfMessageAdapter;
+import org.onosproject.openflow.OpenFlowSwitchListenerAdapter;
+import org.onosproject.openflow.OpenflowSwitchDriverAdapter;
+import org.onosproject.openflow.controller.Dpid;
+import org.onosproject.openflow.controller.OpenFlowPacketContext;
+import org.onosproject.openflow.controller.OpenFlowSwitch;
+import org.onosproject.openflow.controller.PacketListener;
+import org.projectfloodlight.openflow.protocol.OFMessage;
+import org.projectfloodlight.openflow.protocol.OFType;
+
+import static junit.framework.TestCase.fail;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+
+/**
+ * Tests for packet processing in the open flow controller impl class.
+ */
+public class OpenFlowControllerImplPacketsTest {
+ OpenFlowControllerImpl controller;
+ OpenFlowControllerImpl.OpenFlowSwitchAgent agent;
+ Dpid dpid1;
+ OpenFlowSwitch switch1;
+ OpenFlowSwitchListenerAdapter switchListener;
+ TestPacketListener packetListener;
+ TestExecutorService executorService;
+
+ /**
+ * Mock packet listener that accumulates packets.
+ */
+ class TestPacketListener implements PacketListener {
+ List<OpenFlowPacketContext> contexts = new ArrayList<>();
+
+ @Override
+ public void handlePacket(OpenFlowPacketContext pktCtx) {
+ contexts.add(pktCtx);
+ }
+
+ List<OpenFlowPacketContext> contexts() {
+ return contexts;
+ }
+ }
+
+
+ /**
+ * Mock executor service that tracks submits.
+ */
+ static class TestExecutorService extends ExecutorServiceAdapter {
+ private List<OFMessage> submittedMessages = new ArrayList<>();
+
+ List<OFMessage> submittedMessages() {
+ return submittedMessages;
+ }
+
+ @Override
+ public Future<?> submit(Runnable task) {
+ OpenFlowControllerImpl.OFMessageHandler handler =
+ (OpenFlowControllerImpl.OFMessageHandler) task;
+ submittedMessages.add(handler.msg);
+ return null;
+ }
+ }
+
+ /**
+ * Sets up switches to use as data, mocks and launches a controller instance.
+ */
+ @Before
+ public void setUp() {
+ try {
+ switch1 = new OpenflowSwitchDriverAdapter();
+ dpid1 = Dpid.dpid(new URI("of:0000000000000111"));
+ } catch (URISyntaxException ex) {
+ // Does not happen
+ fail();
+ }
+
+ controller = new OpenFlowControllerImpl();
+ agent = controller.agent;
+ switchListener = new OpenFlowSwitchListenerAdapter();
+ controller.addListener(switchListener);
+
+ packetListener = new TestPacketListener();
+ controller.addPacketListener(100, packetListener);
+
+ executorService = new TestExecutorService();
+ controller.executorMsgs = executorService;
+ }
+
+ /**
+ * Tests a port status operation.
+ */
+ @Test
+ public void testPortStatus() {
+ OFMessage portStatusPacket = new MockOfPortStatus();
+ controller.processPacket(dpid1, portStatusPacket);
+ assertThat(switchListener.portChangedDpids().size(), is(1));
+ assertThat(switchListener.portChangedDpids().containsKey(dpid1),
+ is(true));
+ assertThat(switchListener.portChangedDpids().get(dpid1),
+ equalTo(portStatusPacket));
+ }
+
+ /**
+ * Tests a features reply operation.
+ */
+ @Test
+ public void testFeaturesReply() {
+ OFMessage ofFeaturesReplyPacket = new MockOfFeaturesReply();
+ controller.processPacket(dpid1, ofFeaturesReplyPacket);
+ assertThat(switchListener.changedDpids(), hasSize(1));
+ assertThat(switchListener.changedDpids().get(0),
+ equalTo(dpid1));
+ }
+
+ /**
+ * Tests a packet in operation.
+ */
+ @Test
+ public void testPacketIn() {
+ agent.addConnectedSwitch(dpid1, switch1);
+ OFMessage packetInPacket = new MockOfPacketIn();
+ controller.processPacket(dpid1, packetInPacket);
+ assertThat(packetListener.contexts(), hasSize(1));
+ }
+
+ /**
+ * Tests an error operation.
+ */
+ @Test
+ public void testError() {
+ agent.addConnectedSwitch(dpid1, switch1);
+ OfMessageAdapter errorPacket = new OfMessageAdapter(OFType.ERROR);
+ controller.processPacket(dpid1, errorPacket);
+ assertThat(executorService.submittedMessages(), hasSize(1));
+ assertThat(executorService.submittedMessages().get(0), is(errorPacket));
+ }
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImplTest.java b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImplTest.java
new file mode 100644
index 00000000..e079c590
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImplTest.java
@@ -0,0 +1,283 @@
+/*
+ * Copyright 2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow.controller.impl;
+
+import java.net.URI;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Dictionary;
+import java.util.Hashtable;
+import java.util.List;
+import java.util.Spliterator;
+import java.util.Spliterators;
+import java.util.stream.Stream;
+import java.util.stream.StreamSupport;
+
+import org.easymock.EasyMock;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.junit.TestTools;
+import org.onosproject.cfg.ComponentConfigService;
+import org.onosproject.openflow.OpenflowSwitchDriverAdapter;
+import org.onosproject.openflow.controller.Dpid;
+import org.onosproject.openflow.controller.OpenFlowSwitch;
+import org.onosproject.openflow.controller.OpenFlowSwitchListener;
+import org.onosproject.openflow.controller.RoleState;
+import org.osgi.service.component.ComponentContext;
+import org.projectfloodlight.openflow.protocol.OFPortStatus;
+
+import com.google.common.collect.ImmutableSet;
+
+import static junit.framework.TestCase.fail;
+import static org.easymock.EasyMock.anyObject;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.expectLastCall;
+import static org.easymock.EasyMock.replay;
+import static org.hamcrest.MatcherAssert.assertThat;
+import static org.hamcrest.Matchers.hasItems;
+import static org.hamcrest.Matchers.hasSize;
+import static org.hamcrest.Matchers.is;
+import static org.hamcrest.Matchers.nullValue;
+
+/**
+ * Unit tests for the open flow controller implementation test.
+ */
+public class OpenFlowControllerImplTest {
+
+ OpenFlowSwitch switch1;
+ Dpid dpid1;
+ OpenFlowSwitch switch2;
+ Dpid dpid2;
+ OpenFlowSwitch switch3;
+ Dpid dpid3;
+
+ OpenFlowControllerImpl controller;
+ OpenFlowControllerImpl.OpenFlowSwitchAgent agent;
+ TestSwitchListener switchListener;
+
+ /**
+ * Test harness for a switch listener.
+ */
+ static class TestSwitchListener implements OpenFlowSwitchListener {
+ final List<Dpid> removedDpids = new ArrayList<>();
+ final List<Dpid> addedDpids = new ArrayList<>();
+ final List<Dpid> changedDpids = new ArrayList<>();
+
+ @Override
+ public void switchAdded(Dpid dpid) {
+ addedDpids.add(dpid);
+ }
+
+ @Override
+ public void switchRemoved(Dpid dpid) {
+ removedDpids.add(dpid);
+ }
+
+ @Override
+ public void switchChanged(Dpid dpid) {
+ changedDpids.add(dpid);
+ }
+
+ @Override
+ public void portChanged(Dpid dpid, OFPortStatus status) {
+ // Stub
+ }
+
+ @Override
+ public void receivedRoleReply(Dpid dpid, RoleState requested, RoleState response) {
+ // Stub
+ }
+ }
+
+
+ /**
+ * Sets up switches to use as data, mocks and launches a controller instance.
+ */
+ @Before
+ public void setUp() {
+ try {
+ switch1 = new OpenflowSwitchDriverAdapter();
+ dpid1 = Dpid.dpid(new URI("of:0000000000000111"));
+ switch2 = new OpenflowSwitchDriverAdapter();
+ dpid2 = Dpid.dpid(new URI("of:0000000000000222"));
+ switch3 = new OpenflowSwitchDriverAdapter();
+ dpid3 = Dpid.dpid(new URI("of:0000000000000333"));
+ } catch (URISyntaxException ex) {
+ // Does not happen
+ fail();
+ }
+
+ controller = new OpenFlowControllerImpl();
+ agent = controller.agent;
+
+ switchListener = new TestSwitchListener();
+ controller.addListener(switchListener);
+
+ ComponentConfigService mockConfigService =
+ EasyMock.createMock(ComponentConfigService.class);
+ expect(mockConfigService.getProperties(anyObject())).andReturn(ImmutableSet.of());
+ mockConfigService.registerProperties(controller.getClass());
+ expectLastCall();
+ mockConfigService.unregisterProperties(controller.getClass(), false);
+ expectLastCall();
+ expect(mockConfigService.getProperties(anyObject())).andReturn(ImmutableSet.of());
+ controller.cfgService = mockConfigService;
+ replay(mockConfigService);
+
+ ComponentContext mockContext = EasyMock.createMock(ComponentContext.class);
+ Dictionary<String, String> properties = new Hashtable<>();
+ properties.put("openflowPorts",
+ Integer.toString(TestTools.findAvailablePort(0)));
+ expect(mockContext.getProperties()).andReturn(properties);
+ replay(mockContext);
+ controller.activate(mockContext);
+ }
+
+ @After
+ public void tearDown() {
+ controller.removeListener(switchListener);
+ controller.deactivate();
+ }
+
+ /**
+ * Converts an Iterable of some type into a stream of that type.
+ *
+ * @param items Iterable of objects
+ * @param <T> type of the items in the iterable
+ * @return stream of objects of type T
+ */
+ private <T> Stream<T> makeIntoStream(Iterable<T> items) {
+ return StreamSupport.stream(
+ Spliterators.spliteratorUnknownSize(
+ items.iterator(), Spliterator.ORDERED), false);
+ }
+
+
+ /**
+ * Tests adding and removing connected switches.
+ */
+ @Test
+ public void testAddRemoveConnectedSwitch() {
+
+ // test adding connected switches
+ boolean addSwitch1 = agent.addConnectedSwitch(dpid1, switch1);
+ assertThat(addSwitch1, is(true));
+ boolean addSwitch2 = agent.addConnectedSwitch(dpid2, switch2);
+ assertThat(addSwitch2, is(true));
+ boolean addSwitch3 = agent.addConnectedSwitch(dpid3, switch3);
+ assertThat(addSwitch3, is(true));
+
+ // Make sure the listener add callbacks fired
+ assertThat(switchListener.addedDpids, hasSize(3));
+ assertThat(switchListener.addedDpids, hasItems(dpid1, dpid2, dpid3));
+
+ // Test adding a switch twice - it should fail
+ boolean addBadSwitch1 = agent.addConnectedSwitch(dpid1, switch1);
+ assertThat(addBadSwitch1, is(false));
+
+ assertThat(controller.connectedSwitches.size(), is(3));
+
+ // test querying the switch list
+ Stream<OpenFlowSwitch> fetchedSwitches =
+ makeIntoStream(controller.getSwitches());
+ long switchCount = fetchedSwitches.count();
+ assertThat(switchCount, is(3L));
+
+ // test querying the individual switch
+ OpenFlowSwitch queriedSwitch = controller.getSwitch(dpid1);
+ assertThat(queriedSwitch, is(switch1));
+
+ // Remove a switch
+ agent.removeConnectedSwitch(dpid3);
+ Stream<OpenFlowSwitch> fetchedSwitchesAfterRemove =
+ makeIntoStream(controller.getSwitches());
+ long switchCountAfterRemove = fetchedSwitchesAfterRemove.count();
+ assertThat(switchCountAfterRemove, is(2L));
+
+ // Make sure the listener delete callbacks fired
+ assertThat(switchListener.removedDpids, hasSize(1));
+ assertThat(switchListener.removedDpids, hasItems(dpid3));
+
+ // test querying the removed switch
+ OpenFlowSwitch queriedSwitchAfterRemove = controller.getSwitch(dpid3);
+ assertThat(queriedSwitchAfterRemove, nullValue());
+ }
+
+ /**
+ * Tests adding master switches.
+ */
+ @Test
+ public void testMasterSwitch() {
+ agent.addConnectedSwitch(dpid1, switch1);
+ agent.transitionToMasterSwitch(dpid1);
+
+ Stream<OpenFlowSwitch> fetchedMasterSwitches =
+ makeIntoStream(controller.getMasterSwitches());
+ assertThat(fetchedMasterSwitches.count(), is(1L));
+ Stream<OpenFlowSwitch> fetchedActivatedSwitches =
+ makeIntoStream(controller.getEqualSwitches());
+ assertThat(fetchedActivatedSwitches.count(), is(0L));
+ OpenFlowSwitch fetchedSwitch1 = controller.getMasterSwitch(dpid1);
+ assertThat(fetchedSwitch1, is(switch1));
+
+ agent.addConnectedSwitch(dpid2, switch2);
+ boolean addSwitch2 = agent.addActivatedMasterSwitch(dpid2, switch2);
+ assertThat(addSwitch2, is(true));
+ OpenFlowSwitch fetchedSwitch2 = controller.getMasterSwitch(dpid2);
+ assertThat(fetchedSwitch2, is(switch2));
+ }
+
+ /**
+ * Tests adding equal switches.
+ */
+ @Test
+ public void testEqualSwitch() {
+ agent.addConnectedSwitch(dpid1, switch1);
+ agent.transitionToEqualSwitch(dpid1);
+
+ Stream<OpenFlowSwitch> fetchedEqualSwitches =
+ makeIntoStream(controller.getEqualSwitches());
+ assertThat(fetchedEqualSwitches.count(), is(1L));
+ Stream<OpenFlowSwitch> fetchedActivatedSwitches =
+ makeIntoStream(controller.getMasterSwitches());
+ assertThat(fetchedActivatedSwitches.count(), is(0L));
+ OpenFlowSwitch fetchedSwitch1 = controller.getEqualSwitch(dpid1);
+ assertThat(fetchedSwitch1, is(switch1));
+
+ agent.addConnectedSwitch(dpid2, switch2);
+ boolean addSwitch2 = agent.addActivatedEqualSwitch(dpid2, switch2);
+ assertThat(addSwitch2, is(true));
+ OpenFlowSwitch fetchedSwitch2 = controller.getEqualSwitch(dpid2);
+ assertThat(fetchedSwitch2, is(switch2));
+ }
+
+ /**
+ * Tests changing switch role.
+ */
+ @Test
+ public void testRoleSetting() {
+ agent.addConnectedSwitch(dpid2, switch2);
+
+ // check that state can be changed for a connected switch
+ assertThat(switch2.getRole(), is(RoleState.MASTER));
+ controller.setRole(dpid2, RoleState.EQUAL);
+ assertThat(switch2.getRole(), is(RoleState.EQUAL));
+
+ // check that changing state on an unconnected switch does not crash
+ controller.setRole(dpid3, RoleState.SLAVE);
+ }
+}
diff --git a/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/RoleManagerTest.java b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/RoleManagerTest.java
new file mode 100644
index 00000000..4b594383
--- /dev/null
+++ b/framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/RoleManagerTest.java
@@ -0,0 +1,130 @@
+/*
+ * Copyright 2014-2015 Open Networking Laboratory
+ *
+ * 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 org.onosproject.openflow.controller.impl;
+
+import java.io.IOException;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+import org.onosproject.openflow.OpenflowSwitchDriverAdapter;
+import org.onosproject.openflow.controller.RoleState;
+import org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver;
+import org.onosproject.openflow.controller.driver.RoleRecvStatus;
+import org.onosproject.openflow.controller.driver.RoleReplyInfo;
+import org.onosproject.openflow.controller.driver.SwitchStateException;
+import org.projectfloodlight.openflow.protocol.OFDescStatsReply;
+import org.projectfloodlight.openflow.protocol.OFFeaturesReply;
+import org.projectfloodlight.openflow.types.U64;
+
+import static org.junit.Assert.assertEquals;
+import static org.onosproject.openflow.controller.RoleState.MASTER;
+import static org.onosproject.openflow.controller.RoleState.SLAVE;
+import static org.onosproject.openflow.controller.driver.RoleRecvStatus.MATCHED_CURRENT_ROLE;
+import static org.onosproject.openflow.controller.driver.RoleRecvStatus.OTHER_EXPECTATION;
+
+public class RoleManagerTest {
+
+ private static final U64 GID = U64.of(10L);
+ private static final long XID = 1L;
+
+ private OpenFlowSwitchDriver sw;
+ private RoleManager manager;
+
+ @Before
+ public void setUp() {
+ sw = new TestSwitchDriver();
+ manager = new RoleManager(sw);
+ }
+
+ @After
+ public void tearDown() {
+ manager = null;
+ sw = null;
+ }
+
+ @Test
+ public void deliverRoleReply() {
+ RoleRecvStatus status;
+
+ RoleReplyInfo asserted = new RoleReplyInfo(MASTER, GID, XID);
+ RoleReplyInfo unasserted = new RoleReplyInfo(SLAVE, GID, XID);
+
+ try {
+ //call without sendRoleReq() for requestPending = false
+ //first, sw.role == null
+ status = manager.deliverRoleReply(asserted);
+ assertEquals("expectation wrong", OTHER_EXPECTATION, status);
+
+ sw.setRole(MASTER);
+ assertEquals("expectation wrong", OTHER_EXPECTATION, status);
+ sw.setRole(SLAVE);
+
+ //match to pendingRole = MASTER, requestPending = true
+ manager.sendRoleRequest(MASTER, MATCHED_CURRENT_ROLE);
+ status = manager.deliverRoleReply(asserted);
+ assertEquals("expectation wrong", MATCHED_CURRENT_ROLE, status);
+
+ //requestPending never gets reset -- this might be a bug.
+ status = manager.deliverRoleReply(unasserted);
+ assertEquals("expectation wrong", OTHER_EXPECTATION, status);
+ assertEquals("pending role mismatch", MASTER, ((TestSwitchDriver) sw).failed);
+
+ } catch (IOException | SwitchStateException e) {
+ assertEquals("unexpected error thrown",
+ SwitchStateException.class, e.getClass());
+ }
+ }
+
+ private class TestSwitchDriver extends OpenflowSwitchDriverAdapter {
+
+ RoleState failed = null;
+ RoleState current = null;
+
+ @Override
+ public void setRole(RoleState role) {
+ current = role;
+ }
+
+ @Override
+ public RoleState getRole() {
+ return current;
+ }
+
+ @Override
+ public void setFeaturesReply(OFFeaturesReply featuresReply) {
+ }
+
+ @Override
+ public void setSwitchDescription(OFDescStatsReply desc) {
+ }
+
+ @Override
+ public int getNextTransactionId() {
+ return (int) XID;
+ }
+
+ @Override
+ public void returnRoleReply(RoleState requested, RoleState response) {
+ failed = requested;
+ }
+
+ @Override
+ public String channelId() {
+ return "1.2.3.4:1";
+ }
+ }
+}