From e63291850fd0795c5700e25e67e5dee89ba54c5f Mon Sep 17 00:00:00 2001 From: Ashlee Young Date: Tue, 1 Dec 2015 05:49:27 -0800 Subject: onos commit hash c2999f30c69e50df905a9d175ef80b3f23a98514 Change-Id: I2bb8562c4942b6d6a6d60b663db2e17540477b81 Signed-off-by: Ashlee Young --- framework/src/onos/protocols/openflow/api/pom.xml | 93 ++ .../controller/DefaultOpenFlowPacketContext.java | 182 +++ .../org/onosproject/openflow/controller/Dpid.java | 132 ++ .../controller/ExtensionTreatmentInterpreter.java | 58 + .../openflow/controller/OpenFlowController.java | 130 ++ .../openflow/controller/OpenFlowEventListener.java | 33 + .../openflow/controller/OpenFlowOpticalSwitch.java | 23 + .../openflow/controller/OpenFlowPacketContext.java | 90 ++ .../openflow/controller/OpenFlowSwitch.java | 160 +++ .../controller/OpenFlowSwitchListener.java | 58 + .../openflow/controller/PacketListener.java | 29 + .../openflow/controller/PortDescPropertyType.java | 39 + .../onosproject/openflow/controller/RoleState.java | 40 + .../openflow/controller/ThirdPartyMessage.java | 74 ++ .../openflow/controller/WithTypedPorts.java | 45 + .../controller/driver/AbstractOpenFlowSwitch.java | 493 ++++++++ .../openflow/controller/driver/OpenFlowAgent.java | 102 ++ .../controller/driver/OpenFlowSwitchDriver.java | 221 ++++ .../driver/OpenFlowSwitchDriverFactory.java | 39 + .../openflow/controller/driver/RoleHandler.java | 114 ++ .../openflow/controller/driver/RoleRecvStatus.java | 52 + .../openflow/controller/driver/RoleReplyInfo.java | 48 + .../SwitchDriverSubHandshakeAlreadyStarted.java | 29 + .../driver/SwitchDriverSubHandshakeCompleted.java | 34 + .../driver/SwitchDriverSubHandshakeException.java | 41 + .../driver/SwitchDriverSubHandshakeNotStarted.java | 30 + .../SwitchDriverSubHandshakeStateException.java | 30 + .../controller/driver/SwitchStateException.java | 49 + .../openflow/controller/driver/package-info.java | 20 + .../openflow/controller/package-info.java | 20 + .../controller/OpenflowControllerAdapter.java | 89 ++ framework/src/onos/protocols/openflow/ctl/pom.xml | 65 + .../openflow/controller/impl/Controller.java | 328 +++++ .../controller/impl/HandshakeTimeoutException.java | 28 + .../controller/impl/HandshakeTimeoutHandler.java | 93 ++ .../openflow/controller/impl/OFChannelHandler.java | 1320 ++++++++++++++++++++ .../openflow/controller/impl/OFMessageDecoder.java | 55 + .../openflow/controller/impl/OFMessageEncoder.java | 59 + .../controller/impl/OpenFlowControllerImpl.java | 633 ++++++++++ .../controller/impl/OpenflowPipelineFactory.java | 93 ++ .../openflow/controller/impl/RoleManager.java | 406 ++++++ .../openflow/controller/impl/package-info.java | 20 + .../org/onosproject/openflow/ChannelAdapter.java | 159 +++ .../openflow/ChannelHandlerContextAdapter.java | 77 ++ .../org/onosproject/openflow/DriverAdapter.java | 104 ++ .../onosproject/openflow/DriverServiceAdapter.java | 59 + .../openflow/ExecutorServiceAdapter.java | 99 ++ .../onosproject/openflow/MockOfFeaturesReply.java | 81 ++ .../org/onosproject/openflow/MockOfPacketIn.java | 84 ++ .../org/onosproject/openflow/MockOfPortStatus.java | 45 + .../openflow/OFDescStatsReplyAdapter.java | 97 ++ .../org/onosproject/openflow/OfMessageAdapter.java | 62 + .../openflow/OpenFlowSwitchListenerAdapter.java | 77 ++ .../openflow/OpenflowSwitchDriverAdapter.java | 302 +++++ .../openflow/controller/impl/ControllerTest.java | 219 ++++ .../controller/impl/OFMessageDecoderTest.java | 84 ++ .../controller/impl/OFMessageEncoderTest.java | 90 ++ .../impl/OpenFlowControllerImplPacketsTest.java | 167 +++ .../impl/OpenFlowControllerImplTest.java | 283 +++++ .../openflow/controller/impl/RoleManagerTest.java | 130 ++ .../drivers/OFSwitchImplSpringOpenTTPDellOSR.java | 65 + framework/src/onos/protocols/openflow/pom.xml | 86 ++ 62 files changed, 8067 insertions(+) create mode 100644 framework/src/onos/protocols/openflow/api/pom.xml create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/DefaultOpenFlowPacketContext.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/Dpid.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/ExtensionTreatmentInterpreter.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowController.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowEventListener.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowOpticalSwitch.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowPacketContext.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowSwitch.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowSwitchListener.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/PacketListener.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/PortDescPropertyType.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/RoleState.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/ThirdPartyMessage.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/WithTypedPorts.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/AbstractOpenFlowSwitch.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/OpenFlowAgent.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/OpenFlowSwitchDriver.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/OpenFlowSwitchDriverFactory.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/RoleHandler.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/RoleRecvStatus.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/RoleReplyInfo.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeAlreadyStarted.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeCompleted.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeException.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeNotStarted.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeStateException.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchStateException.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/package-info.java create mode 100644 framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/package-info.java create mode 100644 framework/src/onos/protocols/openflow/api/src/test/java/org/onosproject/openflow/controller/OpenflowControllerAdapter.java create mode 100644 framework/src/onos/protocols/openflow/ctl/pom.xml create mode 100644 framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/Controller.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/HandshakeTimeoutException.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/HandshakeTimeoutHandler.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFChannelHandler.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFMessageDecoder.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OFMessageEncoder.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImpl.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/OpenflowPipelineFactory.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/RoleManager.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/main/java/org/onosproject/openflow/controller/impl/package-info.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/ChannelAdapter.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/ChannelHandlerContextAdapter.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/DriverAdapter.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/DriverServiceAdapter.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/ExecutorServiceAdapter.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/MockOfFeaturesReply.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/MockOfPacketIn.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/MockOfPortStatus.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OFDescStatsReplyAdapter.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OfMessageAdapter.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OpenFlowSwitchListenerAdapter.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/OpenflowSwitchDriverAdapter.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/ControllerTest.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OFMessageDecoderTest.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OFMessageEncoderTest.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImplPacketsTest.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/OpenFlowControllerImplTest.java create mode 100644 framework/src/onos/protocols/openflow/ctl/src/test/java/org/onosproject/openflow/controller/impl/RoleManagerTest.java create mode 100644 framework/src/onos/protocols/openflow/drivers/src/main/java/org/onosproject/openflow/drivers/OFSwitchImplSpringOpenTTPDellOSR.java create mode 100644 framework/src/onos/protocols/openflow/pom.xml (limited to 'framework/src/onos/protocols/openflow') diff --git a/framework/src/onos/protocols/openflow/api/pom.xml b/framework/src/onos/protocols/openflow/api/pom.xml new file mode 100644 index 00000000..e535ac64 --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/pom.xml @@ -0,0 +1,93 @@ + + + + 4.0.0 + + + org.onosproject + onos-of + 1.4.0-SNAPSHOT + ../pom.xml + + + onos-of-api + bundle + + ONOS OpenFlow controller subsystem API + + + + + org.onosproject + openflowj + ${openflowj.version} + + + io.netty + netty + + + org.onosproject + onos-api + + + + + + + + org.apache.maven.plugins + maven-shade-plugin + 2.3 + + + + io.netty:netty + com.google.guava:guava + org.slf4j:slfj-api + ch.qos.logback:logback-core + ch.qos.logback:logback-classic + com.google.code.findbugs:annotations + + + + + + package + + shade + + + + + + org.apache.felix + maven-bundle-plugin + + + + org.onosproject.openflow.*,org.projectfloodlight.openflow.* + + + + + + + + diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/DefaultOpenFlowPacketContext.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/DefaultOpenFlowPacketContext.java new file mode 100644 index 00000000..af92a1d0 --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/DefaultOpenFlowPacketContext.java @@ -0,0 +1,182 @@ +/* + * 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; + +import org.onlab.packet.DeserializationException; +import org.onlab.packet.Ethernet; +import org.projectfloodlight.openflow.protocol.OFPacketIn; +import org.projectfloodlight.openflow.protocol.OFPacketOut; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.action.OFActionOutput; +import org.projectfloodlight.openflow.protocol.match.MatchField; +import org.projectfloodlight.openflow.types.OFBufferId; +import org.projectfloodlight.openflow.types.OFPort; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.nio.BufferUnderflowException; +import java.util.Collections; +import java.util.concurrent.atomic.AtomicBoolean; + +import static org.onosproject.security.AppGuard.checkPermission; +import static org.onosproject.security.AppPermission.Type.*; + + +/** + * Default implementation of an OpenFlowPacketContext. + */ +public final class DefaultOpenFlowPacketContext implements OpenFlowPacketContext { + + private final AtomicBoolean free = new AtomicBoolean(true); + private final AtomicBoolean isBuilt = new AtomicBoolean(false); + private final OpenFlowSwitch sw; + private final OFPacketIn pktin; + private OFPacketOut pktout = null; + + private final boolean isBuffered; + + private DefaultOpenFlowPacketContext(OpenFlowSwitch s, OFPacketIn pkt) { + this.sw = s; + this.pktin = pkt; + this.isBuffered = pktin.getBufferId() != OFBufferId.NO_BUFFER; + } + + @Override + public void send() { + checkPermission(PACKET_WRITE); + + if (block() && isBuilt.get()) { + sw.sendMsg(pktout); + } + } + + @Override + public void build(OFPort outPort) { + if (isBuilt.getAndSet(true)) { + return; + } + OFPacketOut.Builder builder = sw.factory().buildPacketOut(); + OFAction act = buildOutput(outPort.getPortNumber()); + pktout = builder.setXid(pktin.getXid()) + .setInPort(pktinInPort()) + .setBufferId(OFBufferId.NO_BUFFER) + .setData(pktin.getData()) +// .setBufferId(pktin.getBufferId()) + .setActions(Collections.singletonList(act)) + .build(); + } + + @Override + public void build(Ethernet ethFrame, OFPort outPort) { + if (isBuilt.getAndSet(true)) { + return; + } + OFPacketOut.Builder builder = sw.factory().buildPacketOut(); + OFAction act = buildOutput(outPort.getPortNumber()); + pktout = builder.setXid(pktin.getXid()) + .setBufferId(OFBufferId.NO_BUFFER) + .setInPort(pktinInPort()) + .setActions(Collections.singletonList(act)) + .setData(ethFrame.serialize()) + .build(); + } + + @Override + public Ethernet parsed() { + checkPermission(PACKET_READ); + + try { + return Ethernet.deserializer().deserialize(pktin.getData(), 0, pktin.getData().length); + } catch (BufferUnderflowException | NullPointerException | + DeserializationException e) { + Logger log = LoggerFactory.getLogger(getClass()); + log.error("packet deserialization problem : {}", e.getMessage()); + return null; + } + } + + @Override + public Dpid dpid() { + checkPermission(PACKET_READ); + + return new Dpid(sw.getId()); + } + + /** + * Creates an OpenFlow packet context based on a packet-in. + * + * @param s OpenFlow switch + * @param pkt OpenFlow packet-in + * @return the OpenFlow packet context + */ + public static OpenFlowPacketContext packetContextFromPacketIn(OpenFlowSwitch s, + OFPacketIn pkt) { + return new DefaultOpenFlowPacketContext(s, pkt); + } + + @Override + public Integer inPort() { + checkPermission(PACKET_READ); + + return pktinInPort().getPortNumber(); + } + + private OFPort pktinInPort() { + if (pktin.getVersion() == OFVersion.OF_10) { + return pktin.getInPort(); + } + return pktin.getMatch().get(MatchField.IN_PORT); + } + + @Override + public byte[] unparsed() { + checkPermission(PACKET_READ); + + return pktin.getData().clone(); + + } + + private OFActionOutput buildOutput(Integer port) { + OFActionOutput act = sw.factory().actions() + .buildOutput() + .setPort(OFPort.of(port)) + .build(); + return act; + } + + @Override + public boolean block() { + checkPermission(PACKET_WRITE); + + return free.getAndSet(false); + } + + @Override + public boolean isHandled() { + checkPermission(PACKET_READ); + + return !free.get(); + } + + @Override + public boolean isBuffered() { + checkPermission(PACKET_READ); + + return isBuffered; + } + +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/Dpid.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/Dpid.java new file mode 100644 index 00000000..6e0f65be --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/Dpid.java @@ -0,0 +1,132 @@ +/* + * 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; + +import org.projectfloodlight.openflow.util.HexString; + +import java.net.URI; +import java.net.URISyntaxException; + +import static com.google.common.base.Preconditions.checkArgument; +import static org.onlab.util.Tools.fromHex; +import static org.onlab.util.Tools.toHex; + +/** + * The class representing a network switch DPID. + * This class is immutable. + */ +public final class Dpid { + + private static final String SCHEME = "of"; + private static final long UNKNOWN = 0; + private final long value; + + /** + * Default constructor. + */ + public Dpid() { + this.value = Dpid.UNKNOWN; + } + + /** + * Constructor from a long value. + * + * @param value the value to use. + */ + public Dpid(long value) { + this.value = value; + } + + /** + * Constructor from a string. + * + * @param value the value to use. + */ + public Dpid(String value) { + this.value = HexString.toLong(value); + } + + /** + * Get the value of the DPID. + * + * @return the value of the DPID. + */ + public long value() { + return value; + } + + /** + * Convert the DPID value to a ':' separated hexadecimal string. + * + * @return the DPID value as a ':' separated hexadecimal string. + */ + @Override + public String toString() { + return HexString.toHexString(this.value); + } + + @Override + public boolean equals(Object other) { + if (!(other instanceof Dpid)) { + return false; + } + + Dpid otherDpid = (Dpid) other; + + return value == otherDpid.value; + } + + @Override + public int hashCode() { + return Long.hashCode(value); + } + + /** + * Returns DPID created from the given device URI. + * + * @param uri device URI + * @return dpid + */ + public static Dpid dpid(URI uri) { + checkArgument(uri.getScheme().equals(SCHEME), "Unsupported URI scheme"); + return new Dpid(fromHex(uri.getSchemeSpecificPart())); + } + + /** + * Produces device URI from the given DPID. + * + * @param dpid device dpid + * @return device URI + */ + public static URI uri(Dpid dpid) { + return uri(dpid.value); + } + + /** + * Produces device URI from the given DPID long. + * + * @param value device dpid as long + * @return device URI + */ + public static URI uri(long value) { + try { + return new URI(SCHEME, toHex(value), null); + } catch (URISyntaxException e) { + return null; + } + } + +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/ExtensionTreatmentInterpreter.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/ExtensionTreatmentInterpreter.java new file mode 100644 index 00000000..dc57977f --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/ExtensionTreatmentInterpreter.java @@ -0,0 +1,58 @@ +/* + * 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; + +import com.google.common.annotations.Beta; +import org.onosproject.net.driver.HandlerBehaviour; +import org.onosproject.net.flow.instructions.ExtensionTreatment; +import org.onosproject.net.flow.instructions.ExtensionTreatmentType; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.action.OFAction; + +/** + * Interprets extension instructions and converts them to/from OpenFlow objects. + */ +@Beta +public interface ExtensionTreatmentInterpreter extends HandlerBehaviour { + + /** + * Returns true if the given extension instruction is supported by this + * driver. + * + * @param extensionTreatmentType extension instruction type + * @return true if the instruction is supported, otherwise false + */ + boolean supported(ExtensionTreatmentType extensionTreatmentType); + + /** + * Maps an extension instruction to an OpenFlow action. + * + * @param factory OpenFlow factory + * @param extensionTreatment extension instruction + * @return OpenFlow action + */ + OFAction mapInstruction(OFFactory factory, ExtensionTreatment extensionTreatment); + + /** + * Maps an OpenFlow action to an extension instruction. + * + * @param action OpenFlow action + * @return extension instruction + */ + ExtensionTreatment mapAction(OFAction action); + +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowController.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowController.java new file mode 100644 index 00000000..2c68fa0b --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowController.java @@ -0,0 +1,130 @@ +/* + * 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; + +import org.projectfloodlight.openflow.protocol.OFMessage; + +/** + * Abstraction of an OpenFlow controller. Serves as a one stop + * shop for obtaining OpenFlow devices and (un)register listeners + * on OpenFlow events + */ +public interface OpenFlowController { + + /** + * Returns all switches known to this OF controller. + * @return Iterable of dpid elements + */ + Iterable getSwitches(); + + /** + * Returns all master switches known to this OF controller. + * @return Iterable of dpid elements + */ + Iterable getMasterSwitches(); + + /** + * Returns all equal switches known to this OF controller. + * @return Iterable of dpid elements + */ + Iterable getEqualSwitches(); + + + /** + * Returns the actual switch for the given Dpid. + * @param dpid the switch to fetch + * @return the interface to this switch + */ + OpenFlowSwitch getSwitch(Dpid dpid); + + /** + * Returns the actual master switch for the given Dpid, if one exists. + * @param dpid the switch to fetch + * @return the interface to this switch + */ + OpenFlowSwitch getMasterSwitch(Dpid dpid); + + /** + * Returns the actual equal switch for the given Dpid, if one exists. + * @param dpid the switch to fetch + * @return the interface to this switch + */ + OpenFlowSwitch getEqualSwitch(Dpid dpid); + + /** + * Register a listener for meta events that occur to OF + * devices. + * @param listener the listener to notify + */ + void addListener(OpenFlowSwitchListener listener); + + /** + * Unregister a listener. + * + * @param listener the listener to unregister + */ + void removeListener(OpenFlowSwitchListener listener); + + /** + * Register a listener for packet events. + * @param priority the importance of this listener, lower values are more important + * @param listener the listener to notify + */ + void addPacketListener(int priority, PacketListener listener); + + /** + * Unregister a listener. + * + * @param listener the listener to unregister + */ + void removePacketListener(PacketListener listener); + + /** + * Register a listener for OF msg events. + * + * @param listener the listener to notify + */ + void addEventListener(OpenFlowEventListener listener); + + /** + * Unregister a listener. + * + * @param listener the listener to unregister + */ + void removeEventListener(OpenFlowEventListener listener); + + /** + * Send a message to a particular switch. + * @param dpid the switch to send to. + * @param msg the message to send + */ + void write(Dpid dpid, OFMessage msg); + + /** + * Process a message and notify the appropriate listeners. + * + * @param dpid the dpid the message arrived on + * @param msg the message to process. + */ + void processPacket(Dpid dpid, OFMessage msg); + + /** + * Sets the role for a given switch. + * @param role the desired role + * @param dpid the switch to set the role for. + */ + void setRole(Dpid dpid, RoleState role); +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowEventListener.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowEventListener.java new file mode 100644 index 00000000..5deccf5e --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowEventListener.java @@ -0,0 +1,33 @@ +/* + * 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; + +import org.projectfloodlight.openflow.protocol.OFMessage; + + +/** + * Notifies providers about openflow msg events. + */ +public interface OpenFlowEventListener { + + /** + * Handles the message event. + * + * @param dpid switch data path identifier + * @param msg the message + */ + void handleMessage(Dpid dpid, OFMessage msg); +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowOpticalSwitch.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowOpticalSwitch.java new file mode 100644 index 00000000..af678d63 --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowOpticalSwitch.java @@ -0,0 +1,23 @@ +/* + * 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; + +/** + * A marker interface for optical switches, which require the ability to pass + * port information to a Device provider. + */ +public interface OpenFlowOpticalSwitch extends OpenFlowSwitch, WithTypedPorts { +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowPacketContext.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowPacketContext.java new file mode 100644 index 00000000..740d89d0 --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowPacketContext.java @@ -0,0 +1,90 @@ +/* + * 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; + +import org.onlab.packet.Ethernet; +import org.projectfloodlight.openflow.types.OFPort; + +/** + * A representation of a packet context which allows any provider + * to view a packet in event, but may block the response to the + * event if blocked has been called. This packet context can be used + * to react to the packet in event with a packet out. + */ +public interface OpenFlowPacketContext { + + /** + * Blocks further responses (ie. send() calls) on this + * packet in event. + * @return true if blocks + */ + boolean block(); + + /** + * Checks whether the packet has been handled. + * @return true if handled, false otherwise. + */ + boolean isHandled(); + + /** + * Provided build has been called send the packet + * out the switch it came in on. + */ + void send(); + + /** + * Build the packet out in response to this packet in event. + * @param outPort the out port to send to packet out of. + */ + void build(OFPort outPort); + + /** + * Build the packet out in response to this packet in event. + * @param ethFrame the actual packet to send out. + * @param outPort the out port to send to packet out of. + */ + void build(Ethernet ethFrame, OFPort outPort); + + /** + * Provided a handle onto the parsed payload. + * @return the parsed form of the payload. + */ + Ethernet parsed(); + + /** + * Provide an unparsed copy of the data. + * @return the unparsed form of the payload. + */ + byte[] unparsed(); + + /** + * Provide the dpid of the switch where the packet in arrived. + * @return the dpid of the switch. + */ + Dpid dpid(); + + /** + * Provide the port on which the packet arrived. + * @return the port + */ + Integer inPort(); + + /** + * Indicates that this packet is buffered at the switch. + * @return buffer indication + */ + boolean isBuffered(); +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowSwitch.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowSwitch.java new file mode 100644 index 00000000..b6ec5744 --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowSwitch.java @@ -0,0 +1,160 @@ +/* + * 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; + +import org.onosproject.net.Device; +import org.projectfloodlight.openflow.protocol.OFFactory; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortDesc; + +import java.util.List; + +/** + * Represents to provider facing side of a switch. + */ +public interface OpenFlowSwitch { + + /** + * Writes the message to the driver. + *

+ * Note: Messages may be silently dropped/lost due to IOExceptions or + * role. If this is a concern, then a caller should use barriers. + *

+ * + * @param msg the message to write + */ + void sendMsg(OFMessage msg); + + /** + * Writes the OFMessage list to the driver. + *

+ * Note: Messages may be silently dropped/lost due to IOExceptions or + * role. If this is a concern, then a caller should use barriers. + *

+ * + * @param msgs the messages to be written + */ + void sendMsg(List msgs); + + /** + * Handle a message from the switch. + * @param fromSwitch the message to handle + */ + void handleMessage(OFMessage fromSwitch); + + /** + * Sets the role for this switch. + * @param role the role to set. + */ + void setRole(RoleState role); + + /** + * Fetch the role for this switch. + * @return the role. + */ + RoleState getRole(); + + /** + * Fetches the ports of this switch. + * @return unmodifiable list of the ports. + */ + List getPorts(); + + /** + * Provides the factory for this OF version. + * @return OF version specific factory. + */ + OFFactory factory(); + + /** + * Gets a string version of the ID for this switch. + * + * @return string version of the ID + */ + String getStringId(); + + /** + * Gets the datapathId of the switch. + * + * @return the switch dpid in long format + */ + long getId(); + + /** + * fetch the manufacturer description. + * @return the description + */ + String manufacturerDescription(); + + /** + * fetch the datapath description. + * @return the description + */ + String datapathDescription(); + + /** + * fetch the hardware description. + * @return the description + */ + String hardwareDescription(); + + /** + * fetch the software description. + * @return the description + */ + String softwareDescription(); + + /** + * fetch the serial number. + * @return the serial + */ + String serialNumber(); + + /** + * Checks if the switch is still connected. + * + * @return whether the switch is still connected + */ + boolean isConnected(); + + /** + * Disconnects the switch by closing the TCP connection. Results in a call + * to the channel handler's channelDisconnected method for cleanup + */ + void disconnectSwitch(); + + /** + * Notifies the controller that the device has responded to a set-role request. + * + * @param requested the role requested by the controller + * @param response the role set at the device + */ + void returnRoleReply(RoleState requested, RoleState response); + + /** + * Returns the switch device type. + * + * @return device type + */ + Device.Type deviceType(); + + /** + * Identifies the channel used to communicate with the switch. + * + * @return string representation of the connection to the device + */ + String channelId(); +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowSwitchListener.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowSwitchListener.java new file mode 100644 index 00000000..2da41335 --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowSwitchListener.java @@ -0,0 +1,58 @@ +/* + * 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; + +import org.projectfloodlight.openflow.protocol.OFPortStatus; + +/** + * Allows for providers interested in Switch events to be notified. + */ +public interface OpenFlowSwitchListener { + + /** + * Notify that the switch was added. + * @param dpid the switch where the event occurred + */ + void switchAdded(Dpid dpid); + + /** + * Notify that the switch was removed. + * @param dpid the switch where the event occurred. + */ + void switchRemoved(Dpid dpid); + + /** + * Notify that the switch has changed in some way. + * @param dpid the switch that changed + */ + void switchChanged(Dpid dpid); + + /** + * Notify that a port has changed. + * @param dpid the switch on which the change happened. + * @param status the new state of the port. + */ + void portChanged(Dpid dpid, OFPortStatus status); + + /** + * Notify that a role imposed on a switch failed to take hold. + * + * @param dpid the switch that failed role assertion + * @param requested the role controller requested + * @param response role reply from the switch + */ + void receivedRoleReply(Dpid dpid, RoleState requested, RoleState response); +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/PacketListener.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/PacketListener.java new file mode 100644 index 00000000..817a6cdf --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/PacketListener.java @@ -0,0 +1,29 @@ +/* + * 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; + +/** + * Notifies providers about Packet in events. + */ +public interface PacketListener { + + /** + * Handles the packet. + * + * @param pktCtx the packet context + */ + void handlePacket(OpenFlowPacketContext pktCtx); +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/PortDescPropertyType.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/PortDescPropertyType.java new file mode 100644 index 00000000..3a0f1a0d --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/PortDescPropertyType.java @@ -0,0 +1,39 @@ +/* + * 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; + +/** + * Port description property types (OFPPDPT enums) in OF 1.3 <. + */ +public enum PortDescPropertyType { + ETHERNET(0), /* Ethernet port */ + OPTICAL(1), /* Optical port */ + OPTICAL_TRANSPORT(2), /* OF1.3 Optical transport extension */ + PIPELINE_INPUT(2), /* Ingress pipeline */ + PIPELINE_OUTPUT(3), /* Egress pipeline */ + RECIRCULATE(4), /* Recirculation */ + EXPERIMENTER(0xffff); /* Experimenter-implemented */ + + private final int value; + + PortDescPropertyType(int v) { + value = v; + } + + public int valueOf() { + return value; + } +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/RoleState.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/RoleState.java new file mode 100644 index 00000000..b8304f39 --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/RoleState.java @@ -0,0 +1,40 @@ +/* + * 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; + +import org.projectfloodlight.openflow.protocol.OFControllerRole; + +/** + * The role of the controller as it pertains to a particular switch. + * Note that this definition of the role enum is different from the + * OF1.3 definition. It is maintained here to be backward compatible to + * earlier versions of the controller code. This enum is translated + * to the OF1.3 enum, before role messages are sent to the switch. + * See sendRoleRequestMessage method in OFSwitchImpl + */ +public enum RoleState { + EQUAL(OFControllerRole.ROLE_EQUAL), + MASTER(OFControllerRole.ROLE_MASTER), + SLAVE(OFControllerRole.ROLE_SLAVE); + + private RoleState(OFControllerRole nxRole) { + nxRole.ordinal(); + } + +} + + + diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/ThirdPartyMessage.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/ThirdPartyMessage.java new file mode 100644 index 00000000..59ef33cf --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/ThirdPartyMessage.java @@ -0,0 +1,74 @@ +/* + * 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; + +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; +/** + * Used to support for the third party privacy flow rule. + * it implements OFMessage interface to use exist adapter API. + */ +public class ThirdPartyMessage implements OFMessage { + + private final byte[] payLoad; //privacy flow rule + + public ThirdPartyMessage(byte[] payLoad) { + this.payLoad = payLoad; + } + + public byte[] payLoad() { + return payLoad; + } + + @Override + public void putTo(PrimitiveSink sink) { + // Do nothing here for now. + } + + @Override + public OFVersion getVersion() { + // Do nothing here for now. + return null; + } + + @Override + public OFType getType() { + // Do nothing here for now. + return null; + } + + @Override + public long getXid() { + // Do nothing here for now. + return 0; + } + + @Override + public void writeTo(ChannelBuffer channelBuffer) { + // Do nothing here for now. + } + + @Override + public Builder createBuilder() { + // Do nothing here for now. + return null; + } + +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/WithTypedPorts.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/WithTypedPorts.java new file mode 100644 index 00000000..8b82b4a7 --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/WithTypedPorts.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.controller; + +import java.util.List; +import java.util.Set; + +import org.projectfloodlight.openflow.protocol.OFObject; + +/** + * An interface implemented by OpenFlow devices that enables providers to + * retrieve ports based on port property. + */ +public interface WithTypedPorts { + + /** + * Return a list of interfaces (ports) of the type associated with this + * OpenFlow switch. + * + * @param type The port description property type of requested ports + * @return A potentially empty list of ports. + */ + List getPortsOf(PortDescPropertyType type); + + /** + * Returns the port property types supported by the driver implementing this + * interface. + * + * @return A set of port property types + */ + Set getPortTypes(); +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/AbstractOpenFlowSwitch.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/AbstractOpenFlowSwitch.java new file mode 100644 index 00000000..c7174192 --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/AbstractOpenFlowSwitch.java @@ -0,0 +1,493 @@ +/* + * 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.driver; + +import com.google.common.collect.Lists; +import org.jboss.netty.channel.Channel; +import org.onlab.packet.IpAddress; +import org.onosproject.net.Device; +import org.onosproject.net.driver.AbstractHandlerBehaviour; +import org.onosproject.openflow.controller.Dpid; +import org.onosproject.openflow.controller.RoleState; +import org.projectfloodlight.openflow.protocol.OFDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFErrorMsg; +import org.projectfloodlight.openflow.protocol.OFExperimenter; +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.OFNiciraControllerRoleRequest; +import org.projectfloodlight.openflow.protocol.OFPortDesc; +import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFPortStatus; +import org.projectfloodlight.openflow.protocol.OFRoleReply; +import org.projectfloodlight.openflow.protocol.OFRoleRequest; +import org.projectfloodlight.openflow.protocol.OFVersion; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.IOException; +import java.net.InetSocketAddress; +import java.net.SocketAddress; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.concurrent.atomic.AtomicReference; +import java.util.stream.Collectors; + +/** + * An abstract representation of an OpenFlow switch. Can be extended by others + * to serve as a base for their vendor specific representation of a switch. + */ +public abstract class AbstractOpenFlowSwitch extends AbstractHandlerBehaviour + implements OpenFlowSwitchDriver { + + protected final Logger log = LoggerFactory.getLogger(getClass()); + + private Channel channel; + protected String channelId; + + private boolean connected; + protected boolean startDriverHandshakeCalled = false; + private Dpid dpid; + private OpenFlowAgent agent; + private final AtomicInteger xidCounter = new AtomicInteger(0); + + private OFVersion ofVersion; + + protected List ports = new ArrayList<>(); + + protected boolean tableFull; + + private RoleHandler roleMan; + + // TODO this is accessed from multiple threads, but volatile may have performance implications + protected volatile RoleState role; + + protected OFFeaturesReply features; + protected OFDescStatsReply desc; + + private final AtomicReference> messagesPendingMastership + = new AtomicReference<>(); + + @Override + public void init(Dpid dpid, OFDescStatsReply desc, OFVersion ofv) { + this.dpid = dpid; + this.desc = desc; + this.ofVersion = ofv; + } + + //************************ + // Channel related + //************************ + + @Override + public final void disconnectSwitch() { + this.channel.close(); + } + + @Override + public void sendMsg(OFMessage msg) { + this.sendMsg(Collections.singletonList(msg)); + } + + @Override + public final void sendMsg(List msgs) { + /* + It is possible that in this block, we transition to SLAVE/EQUAL. + If this is the case, the supplied messages will race with the + RoleRequest message, and they could be rejected by the switch. + In the interest of performance, we will not protect this block with + a synchronization primitive, because the message would have just been + dropped anyway. + */ + if (role == RoleState.MASTER) { + // fast path send when we are master + + sendMsgsOnChannel(msgs); + return; + } + // check to see if mastership transition is in progress + synchronized (messagesPendingMastership) { + /* + messagesPendingMastership is used as synchronization variable for + all mastership related changes. In this block, mastership (including + role update) will have either occurred or not. + */ + if (role == RoleState.MASTER) { + // transition to MASTER complete, send messages + sendMsgsOnChannel(msgs); + return; + } + + List messages = messagesPendingMastership.get(); + if (messages != null) { + // we are transitioning to MASTER, so add messages to queue + messages.addAll(msgs); + log.debug("Enqueue message for switch {}. queue size after is {}", + dpid, messages.size()); + } else { + // not transitioning to MASTER + log.warn("Dropping message for switch {} (role: {}, connected: {}): {}", + dpid, role, channel.isConnected(), msgs); + } + } + } + + private void sendMsgsOnChannel(List msgs) { + if (channel.isConnected()) { + channel.write(msgs); + } else { + log.warn("Dropping messages for switch {} because channel is not connected: {}", + dpid, msgs); + } + } + + @Override + public final void sendRoleRequest(OFMessage msg) { + if (msg instanceof OFRoleRequest || + msg instanceof OFNiciraControllerRoleRequest) { + sendMsgsOnChannel(Collections.singletonList(msg)); + return; + } + throw new IllegalArgumentException("Someone is trying to send " + + "a non role request message"); + } + + @Override + public final void sendHandshakeMessage(OFMessage message) { + if (!this.isDriverHandshakeComplete()) { + sendMsgsOnChannel(Collections.singletonList(message)); + } + } + + @Override + public final boolean isConnected() { + return this.connected; + } + + @Override + public final void setConnected(boolean connected) { + this.connected = connected; + } + + @Override + public final void setChannel(Channel channel) { + this.channel = channel; + final SocketAddress address = channel.getRemoteAddress(); + if (address instanceof InetSocketAddress) { + final InetSocketAddress inetAddress = (InetSocketAddress) address; + final IpAddress ipAddress = IpAddress.valueOf(inetAddress.getAddress()); + if (ipAddress.isIp4()) { + channelId = ipAddress.toString() + ':' + inetAddress.getPort(); + } else { + channelId = '[' + ipAddress.toString() + "]:" + inetAddress.getPort(); + } + } + } + + @Override + public String channelId() { + return channelId; + } + + //************************ + // Switch features related + //************************ + + @Override + public final long getId() { + return this.dpid.value(); + } + + @Override + public final String getStringId() { + return this.dpid.toString(); + } + + @Override + public final void setOFVersion(OFVersion ofV) { + this.ofVersion = ofV; + } + + @Override + public void setTableFull(boolean full) { + this.tableFull = full; + } + + @Override + public void setFeaturesReply(OFFeaturesReply featuresReply) { + this.features = featuresReply; + } + + @Override + public abstract Boolean supportNxRole(); + + //************************ + // Message handling + //************************ + /** + * Handle the message coming from the dataplane. + * + * @param m the actual message + */ + @Override + public final void handleMessage(OFMessage m) { + if (this.role == RoleState.MASTER || m instanceof OFPortStatus) { + this.agent.processMessage(dpid, m); + } + } + + @Override + public RoleState getRole() { + return role; + } + + @Override + public final boolean connectSwitch() { + return this.agent.addConnectedSwitch(dpid, this); + } + + @Override + public final boolean activateMasterSwitch() { + return this.agent.addActivatedMasterSwitch(dpid, this); + } + + @Override + public final boolean activateEqualSwitch() { + return this.agent.addActivatedEqualSwitch(dpid, this); + } + + @Override + public final void transitionToEqualSwitch() { + this.agent.transitionToEqualSwitch(dpid); + } + + @Override + public final void transitionToMasterSwitch() { + this.agent.transitionToMasterSwitch(dpid); + synchronized (messagesPendingMastership) { + List messages = messagesPendingMastership.get(); + if (messages != null) { + this.sendMsg(messages); + log.debug("Sending {} pending messages to switch {}", + messages.size(), dpid); + messagesPendingMastership.set(null); + } + // perform role transition after clearing messages queue + this.role = RoleState.MASTER; + } + } + + @Override + public final void removeConnectedSwitch() { + this.agent.removeConnectedSwitch(dpid); + } + + @Override + public OFFactory factory() { + return OFFactories.getFactory(ofVersion); + } + + @Override + public void setPortDescReply(OFPortDescStatsReply portDescReply) { + this.ports.add(portDescReply); + } + + @Override + public void setPortDescReplies(List portDescReplies) { + this.ports.addAll(portDescReplies); + } + + @Override + public void returnRoleReply(RoleState requested, RoleState response) { + this.agent.returnRoleReply(dpid, requested, response); + } + + @Override + public abstract void startDriverHandshake(); + + @Override + public abstract boolean isDriverHandshakeComplete(); + + @Override + public abstract void processDriverHandshakeMessage(OFMessage m); + + + // Role Handling + + @Override + public void setRole(RoleState role) { + try { + if (role == RoleState.SLAVE || role == RoleState.EQUAL) { + // perform role transition to SLAVE/EQUAL before sending role request + this.role = role; + } + if (this.roleMan.sendRoleRequest(role, RoleRecvStatus.MATCHED_SET_ROLE)) { + log.debug("Sending role {} to switch {}", role, getStringId()); + if (role == RoleState.MASTER) { + synchronized (messagesPendingMastership) { + if (messagesPendingMastership.get() == null) { + log.debug("Initializing new message queue for switch {}", dpid); + /* + The presence of messagesPendingMastership indicates that + a switch is currently transitioning to MASTER, but + is still awaiting role reply from switch. + */ + messagesPendingMastership.set(Lists.newArrayList()); + } + } + } + } else if (role == RoleState.MASTER) { + // role request not support; transition switch to MASTER + this.role = role; + } + } catch (IOException e) { + log.error("Unable to write to switch {}.", this.dpid); + } + } + + @Override + public void reassertRole() { + // TODO should messages be sent directly or queue during reassertion? + if (this.getRole() == RoleState.MASTER) { + log.warn("Received permission error from switch {} while " + + "being master. Reasserting master role.", + this.getStringId()); + this.setRole(RoleState.MASTER); + } + } + + @Override + public void handleRole(OFMessage m) throws SwitchStateException { + RoleReplyInfo rri = roleMan.extractOFRoleReply((OFRoleReply) m); + RoleRecvStatus rrs = roleMan.deliverRoleReply(rri); + if (rrs == RoleRecvStatus.MATCHED_SET_ROLE) { + if (rri.getRole() == RoleState.MASTER) { + this.transitionToMasterSwitch(); + } else if (rri.getRole() == RoleState.EQUAL || + rri.getRole() == RoleState.SLAVE) { + this.transitionToEqualSwitch(); + } + } else { + log.warn("Failed to set role for {}", this.getStringId()); + } + } + + @Override + public void handleNiciraRole(OFMessage m) throws SwitchStateException { + RoleState r = this.roleMan.extractNiciraRoleReply((OFExperimenter) m); + if (r == null) { + // The message wasn't really a Nicira role reply. We just + // dispatch it to the OFMessage listeners in this case. + this.handleMessage(m); + return; + } + + RoleRecvStatus rrs = this.roleMan.deliverRoleReply( + new RoleReplyInfo(r, null, m.getXid())); + if (rrs == RoleRecvStatus.MATCHED_SET_ROLE) { + if (r == RoleState.MASTER) { + this.transitionToMasterSwitch(); + } else if (r == RoleState.EQUAL || + r == RoleState.SLAVE) { + this.transitionToEqualSwitch(); + } + } else { + this.disconnectSwitch(); + } + } + + @Override + public boolean handleRoleError(OFErrorMsg error) { + try { + return RoleRecvStatus.OTHER_EXPECTATION != this.roleMan.deliverError(error); + } catch (SwitchStateException e) { + this.disconnectSwitch(); + } + return true; + } + + @Override + public final void setAgent(OpenFlowAgent ag) { + if (this.agent == null) { + this.agent = ag; + } + } + + @Override + public final void setRoleHandler(RoleHandler roleHandler) { + if (this.roleMan == null) { + this.roleMan = roleHandler; + } + } + + @Override + public void setSwitchDescription(OFDescStatsReply d) { + this.desc = d; + } + + @Override + public int getNextTransactionId() { + return this.xidCounter.getAndIncrement(); + } + + @Override + public List getPorts() { + return this.ports.stream() + .flatMap(portReply -> portReply.getEntries().stream()) + .collect(Collectors.toList()); + } + + @Override + public String manufacturerDescription() { + return this.desc.getMfrDesc(); + } + + @Override + public String datapathDescription() { + return this.desc.getDpDesc(); + } + + @Override + public String hardwareDescription() { + return this.desc.getHwDesc(); + } + + @Override + public String softwareDescription() { + return this.desc.getSwDesc(); + } + + @Override + public String serialNumber() { + return this.desc.getSerialNum(); + } + + @Override + public Device.Type deviceType() { + return Device.Type.SWITCH; + } + + @Override + public String toString() { + return this.getClass().getName() + " [" + ((channel != null) + ? channel.getRemoteAddress() : "?") + + " DPID[" + ((getStringId() != null) ? getStringId() : "?") + "]]"; + } +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/OpenFlowAgent.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/OpenFlowAgent.java new file mode 100644 index 00000000..ad6dede1 --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/OpenFlowAgent.java @@ -0,0 +1,102 @@ +/* + * 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.driver; + +import org.onosproject.openflow.controller.Dpid; +import org.onosproject.openflow.controller.OpenFlowSwitch; +import org.onosproject.openflow.controller.RoleState; +import org.projectfloodlight.openflow.protocol.OFMessage; + +/** + * Responsible for keeping track of the current set of switches + * connected to the system. As well as whether they are in Master + * role or not. + * + */ +public interface OpenFlowAgent { + + /** + * Add a switch that has just connected to the system. + * @param dpid the dpid to add + * @param sw the actual switch object. + * @return true if added, false otherwise. + */ + boolean addConnectedSwitch(Dpid dpid, OpenFlowSwitch sw); + + /** + * Checks if the activation for this switch is valid. + * @param dpid the dpid to check + * @return true if valid, false otherwise + */ + boolean validActivation(Dpid dpid); + + /** + * Called when a switch is activated, with this controller's role as MASTER. + * @param dpid the dpid to add. + * @param sw the actual switch + * @return true if added, false otherwise. + */ + boolean addActivatedMasterSwitch(Dpid dpid, OpenFlowSwitch sw); + + /** + * Called when a switch is activated, with this controller's role as EQUAL. + * @param dpid the dpid to add. + * @param sw the actual switch + * @return true if added, false otherwise. + */ + boolean addActivatedEqualSwitch(Dpid dpid, OpenFlowSwitch sw); + + /** + * Called when this controller's role for a switch transitions from equal + * to master. For 1.0 switches, we internally refer to the role 'slave' as + * 'equal' - so this transition is equivalent to 'addActivatedMasterSwitch'. + * @param dpid the dpid to transistion. + */ + void transitionToMasterSwitch(Dpid dpid); + + /** + * Called when this controller's role for a switch transitions to equal. + * For 1.0 switches, we internally refer to the role 'slave' as + * 'equal'. + * @param dpid the dpid to transistion. + */ + void transitionToEqualSwitch(Dpid dpid); + + /** + * Clear all state in controller switch maps for a switch that has + * disconnected from the local controller. Also release control for + * that switch from the global repository. Notify switch listeners. + * @param dpid the dpid to remove. + */ + void removeConnectedSwitch(Dpid dpid); + + /** + * Process a message coming from a switch. + * + * @param dpid the dpid the message came on. + * @param m the message to process + */ + void processMessage(Dpid dpid, OFMessage m); + + /** + * Notifies the controller that role assertion has failed. + * + * @param dpid the switch that failed role assertion + * @param requested the role controller requested + * @param response role reply from the switch + */ + void returnRoleReply(Dpid dpid, RoleState requested, RoleState response); +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/OpenFlowSwitchDriver.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/OpenFlowSwitchDriver.java new file mode 100644 index 00000000..b259388c --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/OpenFlowSwitchDriver.java @@ -0,0 +1,221 @@ +/* + * 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.driver; + +import org.jboss.netty.channel.Channel; +import org.onosproject.net.driver.HandlerBehaviour; +import org.onosproject.openflow.controller.Dpid; +import org.onosproject.openflow.controller.OpenFlowSwitch; +import org.projectfloodlight.openflow.protocol.OFDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFErrorMsg; +import org.projectfloodlight.openflow.protocol.OFFeaturesReply; +import org.projectfloodlight.openflow.protocol.OFMessage; +import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFVersion; + +import java.util.List; + +/** + * Represents the driver side of an OpenFlow switch. + * This interface should never be exposed to consumers. + * + */ +public interface OpenFlowSwitchDriver extends OpenFlowSwitch, HandlerBehaviour { + + /** + * Sets the OpenFlow agent to be used. This method + * can only be called once. + * @param agent the agent to set. + */ + void setAgent(OpenFlowAgent agent); + + /** + * Sets the Role handler object. + * This method can only be called once. + * @param roleHandler the roleHandler class + */ + void setRoleHandler(RoleHandler roleHandler); + + /** + * Reasserts this controllers role to the switch. + * Useful in cases where the switch no longer agrees + * that this controller has the role it claims. + */ + void reassertRole(); + + /** + * Handle the situation where the role request triggers an error. + * @param error the error to handle. + * @return true if handled, false if not. + */ + boolean handleRoleError(OFErrorMsg error); + + /** + * If this driver know of Nicira style role messages, these should + * be handled here. + * @param m the role message to handle. + * @throws SwitchStateException if the message received was + * not a nicira role or was malformed. + */ + void handleNiciraRole(OFMessage m) throws SwitchStateException; + + /** + * Handle OF 1.x (where x > 0) role messages. + * @param m the role message to handle + * @throws SwitchStateException if the message received was + * not a nicira role or was malformed. + */ + void handleRole(OFMessage m) throws SwitchStateException; + + /** + * Announce to the OpenFlow agent that this switch has connected. + * @return true if successful, false if duplicate switch. + */ + boolean connectSwitch(); + + /** + * Activate this MASTER switch-controller relationship in the OF agent. + * @return true is successful, false is switch has not + * connected or is unknown to the system. + */ + boolean activateMasterSwitch(); + + /** + * Activate this EQUAL switch-controller relationship in the OF agent. + * @return true is successful, false is switch has not + * connected or is unknown to the system. + */ + boolean activateEqualSwitch(); + + /** + * Transition this switch-controller relationship to an EQUAL state. + */ + void transitionToEqualSwitch(); + + /** + * Transition this switch-controller relationship to an Master state. + */ + void transitionToMasterSwitch(); + + /** + * Remove this switch from the openflow agent. + */ + void removeConnectedSwitch(); + + /** + * Sets the ports on this switch. + * @param portDescReply the port set and descriptions + */ + void setPortDescReply(OFPortDescStatsReply portDescReply); + + /** + * Sets the ports on this switch. + * @param portDescReplies list of port set and descriptions + */ + void setPortDescReplies(List portDescReplies); + + /** + * Sets the features reply for this switch. + * @param featuresReply the features to set. + */ + void setFeaturesReply(OFFeaturesReply featuresReply); + + /** + * Sets the switch description. + * @param desc the descriptions + */ + void setSwitchDescription(OFDescStatsReply desc); + + /** + * Gets the next transaction id to use. + * @return the xid + */ + int getNextTransactionId(); + + + /** + * Sets the OF version for this switch. + * @param ofV the version to set. + */ + void setOFVersion(OFVersion ofV); + + /** + * Sets this switch has having a full flowtable. + * @param full true if full, false otherswise. + */ + void setTableFull(boolean full); + + /** + * Sets the associated Netty channel for this switch. + * @param channel the Netty channel + */ + void setChannel(Channel channel); + + /** + * Sets whether the switch is connected. + * + * @param connected whether the switch is connected + */ + void setConnected(boolean connected); + + /** + * Initialises the behaviour. + * @param dpid a dpid + * @param desc a switch description + * @param ofv OpenFlow version + */ + void init(Dpid dpid, OFDescStatsReply desc, OFVersion ofv); + + /** + * Does this switch support Nicira Role messages. + * @return true if supports, false otherwise. + */ + Boolean supportNxRole(); + + + /** + * Starts the driver specific handshake process. + */ + void startDriverHandshake(); + + /** + * Checks whether the driver specific handshake is complete. + * @return true is finished, false if not. + */ + boolean isDriverHandshakeComplete(); + + /** + * Process a message during the driver specific handshake. + * @param m the message to process. + */ + void processDriverHandshakeMessage(OFMessage m); + + /** + * Sends only role request messages. + * + * @param message a role request message. + */ + void sendRoleRequest(OFMessage message); + + /** + * Allows the handshaker behaviour to send messages during the + * handshake phase only. + * + * @param message an OpenFlow message + */ + void sendHandshakeMessage(OFMessage message); + +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/OpenFlowSwitchDriverFactory.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/OpenFlowSwitchDriverFactory.java new file mode 100644 index 00000000..a0d8f18f --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/OpenFlowSwitchDriverFactory.java @@ -0,0 +1,39 @@ +/* + * 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.driver; + +import org.onosproject.openflow.controller.Dpid; +import org.projectfloodlight.openflow.protocol.OFDescStatsReply; +import org.projectfloodlight.openflow.protocol.OFVersion; + +/** + * Switch factory which returns concrete switch objects for the + * physical openflow switch in use. + * + */ +public interface OpenFlowSwitchDriverFactory { + + + /** + * Constructs the real openflow switch representation. + * @param dpid the dpid for this switch. + * @param desc its description. + * @param ofv the OF version in use + * @return the openflow switch representation. + */ + OpenFlowSwitchDriver getOFSwitchImpl(Dpid dpid, + OFDescStatsReply desc, OFVersion ofv); +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/RoleHandler.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/RoleHandler.java new file mode 100644 index 00000000..b4068886 --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/RoleHandler.java @@ -0,0 +1,114 @@ +/* + * 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.driver; + +import java.io.IOException; + +import org.onosproject.openflow.controller.RoleState; +import org.projectfloodlight.openflow.protocol.OFErrorMsg; +import org.projectfloodlight.openflow.protocol.OFExperimenter; +import org.projectfloodlight.openflow.protocol.OFRoleReply; + +/** + * Role handling. + * + */ +public interface RoleHandler { + + /** + * 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 The vendor message to parse. + * @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. + */ + RoleState extractNiciraRoleReply(OFExperimenter experimenterMsg) + throws SwitchStateException; + + /** + * Send a role request with the given role to the switch and update + * the pending request and timestamp. + * Sends an OFPT_ROLE_REQUEST to an OF1.3 switch, OR + * Sends an NX_ROLE_REQUEST to an OF1.0 switch if configured to support it + * in the IOFSwitch driver. If not supported, this method sends nothing + * and returns 'false'. The caller should take appropriate action. + * + * One other optimization we do here is that for OF1.0 switches with + * Nicira role message support, we force the Role.EQUAL to become + * Role.SLAVE, as there is no defined behavior for the Nicira role OTHER. + * We cannot expect it to behave like SLAVE. We don't have this problem with + * OF1.3 switches, because Role.EQUAL is well defined and we can simulate + * SLAVE behavior by using ASYNC messages. + * + * @param role role to request + * @param exp expectation + * @throws IOException when I/O exception of some sort has occurred + * @return false if and only if the switch does not support role-request + * messages, according to the switch driver; true otherwise. + */ + boolean sendRoleRequest(RoleState role, RoleRecvStatus exp) + throws IOException; + + /** + * Extract the role information from an OF1.3 Role Reply Message. + * @param rrmsg role reply message + * @return RoleReplyInfo object + * @throws SwitchStateException If unknown role encountered + */ + RoleReplyInfo extractOFRoleReply(OFRoleReply rrmsg) + throws SwitchStateException; + + /** + * Deliver a received role reply. + * + * Check if a request is pending and if the received reply matches the + * the expected pending reply (we check both role and xid) we set + * the role for the switch/channel. + * + * If a request is pending but doesn't match the reply we ignore it, and + * return + * + * If no request is pending we disconnect with a SwitchStateException + * + * @param rri information about role-reply in format that + * controller can understand. + * @return result comparing expected and received reply + * @throws SwitchStateException if no request is pending + */ + RoleRecvStatus deliverRoleReply(RoleReplyInfo rri) + throws SwitchStateException; + + + /** + * 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 + * @param error error message + * @return result comparing expected and received reply + * @throws SwitchStateException if switch did not support requested role + */ + RoleRecvStatus deliverError(OFErrorMsg error) + throws SwitchStateException; + +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/RoleRecvStatus.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/RoleRecvStatus.java new file mode 100644 index 00000000..88c4cc70 --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/RoleRecvStatus.java @@ -0,0 +1,52 @@ +/* + * 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.driver; + +/** + * When we remove a pending role request we use this enum to indicate how we + * arrived at the decision. When we send a role request to the switch, we + * also use this enum to indicate what we expect back from the switch, so the + * role changer can match the reply to our expectation. + */ +public enum RoleRecvStatus { + /** The switch returned an error indicating that roles are not. + * supported*/ + UNSUPPORTED, + /** The request timed out. */ + NO_REPLY, + /** The reply was old, there is a newer request pending. */ + OLD_REPLY, + /** + * The reply's role matched the role that this controller set in the + * request message - invoked either initially at startup or to reassert + * current role. + */ + MATCHED_CURRENT_ROLE, + /** + * The reply's role matched the role that this controller set in the + * request message - this is the result of a callback from the + * global registry, followed by a role request sent to the switch. + */ + MATCHED_SET_ROLE, + /** + * The reply's role was a response to the query made by this controller. + */ + REPLY_QUERY, + /** We received a role reply message from the switch + * but the expectation was unclear, or there was no expectation. + */ + OTHER_EXPECTATION, +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/RoleReplyInfo.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/RoleReplyInfo.java new file mode 100644 index 00000000..dc9b6bad --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/RoleReplyInfo.java @@ -0,0 +1,48 @@ +/* + * 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.driver; + +import org.onosproject.openflow.controller.RoleState; +import org.projectfloodlight.openflow.types.U64; + +/** + * Helper class returns role reply information in the format understood + * by the controller. + */ +public class RoleReplyInfo { + private final RoleState role; + private final U64 genId; + private final long xid; + + public RoleReplyInfo(RoleState role, U64 genId, long xid) { + this.role = role; + this.genId = genId; + this.xid = xid; + } + public RoleState getRole() { + return role; + } + public U64 getGenId() { + return genId; + } + public long getXid() { + return xid; + } + @Override + public String toString() { + return "[Role:" + role + " GenId:" + genId + " Xid:" + xid + "]"; + } +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeAlreadyStarted.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeAlreadyStarted.java new file mode 100644 index 00000000..96b4bd73 --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeAlreadyStarted.java @@ -0,0 +1,29 @@ +/* + * 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.driver; + +/** + * Thrown when IOFSwitch.startDriverHandshake() is called more than once. + * + */ +public class SwitchDriverSubHandshakeAlreadyStarted extends + SwitchDriverSubHandshakeException { + private static final long serialVersionUID = -5491845708752443501L; + + public SwitchDriverSubHandshakeAlreadyStarted() { + super(); + } +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeCompleted.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeCompleted.java new file mode 100644 index 00000000..b0f59fe5 --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeCompleted.java @@ -0,0 +1,34 @@ +/* + * 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.driver; + +import org.projectfloodlight.openflow.protocol.OFMessage; + + +/** + * Indicates that a message was passed to a switch driver's subhandshake + * handling code but the driver has already completed the sub-handshake. + * + */ +public class SwitchDriverSubHandshakeCompleted + extends SwitchDriverSubHandshakeException { + private static final long serialVersionUID = -8817822245846375995L; + + public SwitchDriverSubHandshakeCompleted(OFMessage m) { + super("Sub-Handshake is already complete but received message " + + m.getType()); + } +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeException.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeException.java new file mode 100644 index 00000000..1bc750ab --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeException.java @@ -0,0 +1,41 @@ +/* + * 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.driver; + +/** + * Base class for exception thrown by switch driver sub-handshake processing. + * + */ +public class SwitchDriverSubHandshakeException extends RuntimeException { + private static final long serialVersionUID = -6257836781419604438L; + + protected SwitchDriverSubHandshakeException() { + super(); + } + + protected SwitchDriverSubHandshakeException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + + protected SwitchDriverSubHandshakeException(String arg0) { + super(arg0); + } + + protected SwitchDriverSubHandshakeException(Throwable arg0) { + super(arg0); + } + +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeNotStarted.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeNotStarted.java new file mode 100644 index 00000000..a073683c --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeNotStarted.java @@ -0,0 +1,30 @@ +/* + * 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.driver; + +/** + * Thrown when a switch driver's sub-handshake has not been started but an + * operation requiring the sub-handshake has been attempted. + * + */ +public class SwitchDriverSubHandshakeNotStarted extends + SwitchDriverSubHandshakeException { + private static final long serialVersionUID = -5491845708752443501L; + + public SwitchDriverSubHandshakeNotStarted() { + super(); + } +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeStateException.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeStateException.java new file mode 100644 index 00000000..3f4be813 --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchDriverSubHandshakeStateException.java @@ -0,0 +1,30 @@ +/* + * 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.driver; + +/** + * Thrown when a switch driver's sub-handshake state-machine receives an + * unexpected OFMessage and/or is in an invald state. + * + */ +public class SwitchDriverSubHandshakeStateException extends + SwitchDriverSubHandshakeException { + private static final long serialVersionUID = -8249926069195147051L; + + public SwitchDriverSubHandshakeStateException(String msg) { + super(msg); + } +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchStateException.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchStateException.java new file mode 100644 index 00000000..30c4e917 --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/SwitchStateException.java @@ -0,0 +1,49 @@ +/* + * 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.driver; + +/** + * This exception indicates an error or unexpected message during + * message handling. E.g., if an OFMessage is received that is illegal or + * unexpected given the current handshake state. + * + * We don't allow wrapping other exception in a switch state exception. We + * only log the SwitchStateExceptions message so the causing exceptions + * stack trace is generally not available. + * + */ +public class SwitchStateException extends Exception { + + private static final long serialVersionUID = 9153954512470002631L; + + public SwitchStateException() { + super(); + } + + public SwitchStateException(String arg0, Throwable arg1) { + super(arg0, arg1); + } + + public SwitchStateException(String arg0) { + super(arg0); + } + + public SwitchStateException(Throwable arg0) { + super(arg0); + } + +} diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/package-info.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/package-info.java new file mode 100644 index 00000000..c03a5840 --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/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. + */ + +/** + * OpenFlow controller switch driver API. + */ +package org.onosproject.openflow.controller.driver; diff --git a/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/package-info.java b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/package-info.java new file mode 100644 index 00000000..54778426 --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/main/java/org/onosproject/openflow/controller/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. + */ + +/** + * OpenFlow controller API. + */ +package org.onosproject.openflow.controller; diff --git a/framework/src/onos/protocols/openflow/api/src/test/java/org/onosproject/openflow/controller/OpenflowControllerAdapter.java b/framework/src/onos/protocols/openflow/api/src/test/java/org/onosproject/openflow/controller/OpenflowControllerAdapter.java new file mode 100644 index 00000000..f4fe490f --- /dev/null +++ b/framework/src/onos/protocols/openflow/api/src/test/java/org/onosproject/openflow/controller/OpenflowControllerAdapter.java @@ -0,0 +1,89 @@ +/* + * 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; + +import org.projectfloodlight.openflow.protocol.OFMessage; + +/** + * Test adapter for the OpenFlow controller interface. + */ +public class OpenflowControllerAdapter implements OpenFlowController { + @Override + public Iterable getSwitches() { + return null; + } + + @Override + public Iterable getMasterSwitches() { + return null; + } + + @Override + public Iterable getEqualSwitches() { + return null; + } + + @Override + public OpenFlowSwitch getSwitch(Dpid dpid) { + return null; + } + + @Override + public OpenFlowSwitch getMasterSwitch(Dpid dpid) { + return null; + } + + @Override + public OpenFlowSwitch getEqualSwitch(Dpid dpid) { + return null; + } + + @Override + public void addListener(OpenFlowSwitchListener listener) { + } + + @Override + public void removeListener(OpenFlowSwitchListener listener) { + } + + @Override + public void addPacketListener(int priority, PacketListener listener) { + } + + @Override + public void removePacketListener(PacketListener listener) { + } + + @Override + public void write(Dpid dpid, OFMessage msg) { + } + + @Override + public void processPacket(Dpid dpid, OFMessage msg) { + } + + @Override + public void setRole(Dpid dpid, RoleState role) { + } + + @Override + public void addEventListener(OpenFlowEventListener listener) { + } + + @Override + public void removeEventListener(OpenFlowEventListener listener) { + } +} diff --git a/framework/src/onos/protocols/openflow/ctl/pom.xml b/framework/src/onos/protocols/openflow/ctl/pom.xml new file mode 100644 index 00000000..56d48550 --- /dev/null +++ b/framework/src/onos/protocols/openflow/ctl/pom.xml @@ -0,0 +1,65 @@ + + + 4.0.0 + + + org.onosproject + onos-of + 1.4.0-SNAPSHOT + ../pom.xml + + + onos-of-ctl + bundle + + ONOS OpenFlow controller subsystem API + + + + org.onosproject + onos-of-api + + + io.netty + netty + + + org.apache.felix + org.apache.felix.scr.annotations + + + org.osgi + org.osgi.compendium + + + + + + + org.apache.felix + maven-scr-plugin + + + org.onosproject + onos-maven-plugin + + + + + 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 controllerNodeIPsCache; + + private ChannelGroup cg; + + // Configuration options + protected List 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 getMemory() { + Map 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 portDescReplies; + //private OFPortDescStatsReply portDescReply; + // a concurrent ArrayList to temporarily store port status messages + // before we are ready to deal with them + private final CopyOnWriteArrayList 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(); + this.portDescReplies = new ArrayList(); + 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 temp = new ArrayList(); + 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 msglist = (List) 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 msglist = new ArrayList(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 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 msglist = (List) 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 connectedSwitches = + new ConcurrentHashMap<>(); + protected ConcurrentHashMap activeMasterSwitches = + new ConcurrentHashMap<>(); + protected ConcurrentHashMap activeEqualSwitches = + new ConcurrentHashMap<>(); + + protected OpenFlowSwitchAgent agent = new OpenFlowSwitchAgent(); + protected Set ofSwitchListener = new CopyOnWriteArraySet<>(); + + protected Multimap ofPacketListener = + ArrayListMultimap.create(); + + protected Set ofEventListener = new CopyOnWriteArraySet<>(); + + protected Multimap fullFlowStats = + ArrayListMultimap.create(); + + protected Multimap fullTableStats = + ArrayListMultimap.create(); + + protected Multimap fullGroupStats = + ArrayListMultimap.create(); + + protected Multimap fullGroupDescStats = + ArrayListMultimap.create(); + + protected Multimap 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 getSwitches() { + return connectedSwitches.values(); + } + + @Override + public Iterable getMasterSwitches() { + return activeMasterSwitches.values(); + } + + @Override + public Iterable 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 flowStats; + Collection tableStats; + Collection groupStats; + Collection groupDescStats; + Collection 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 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 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 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 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 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 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 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> behaviours() { + return null; + } + + @Override + public Class implementation(Class behaviour) { + return null; + } + + @Override + public boolean hasBehaviour(Class behaviourClass) { + return true; + } + + @Override + public T createBehaviour(DriverData data, Class behaviourClass) { + return null; + } + + @SuppressWarnings("unchecked") + @Override + public T createBehaviour(DriverHandler handler, Class behaviourClass) { + if (behaviourClass == OpenFlowSwitchDriver.class) { + return (T) new OpenflowSwitchDriverAdapter(); + } + return null; + } + + @Override + public Map properties() { + return null; + } + + @Override + public Driver merge(Driver other) { + return null; + } + + @Override + public Set 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 getDrivers() { + return null; + } + + @Override + public Set getDrivers(Class 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 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 Future submit(Callable task) { + return null; + } + + @Override + public Future submit(Runnable task, T result) { + return null; + } + + @Override + public Future submit(Runnable task) { + return null; + } + + @Override + public List> invokeAll(Collection> tasks) + throws InterruptedException { + return null; + } + + @Override + public List> invokeAll(Collection> tasks, long timeout, TimeUnit unit) + throws InterruptedException { + return null; + } + + @Override + public T invokeAny(Collection> tasks) throws InterruptedException, ExecutionException { + return null; + } + + @Override + public T invokeAny(Collection> 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 getCapabilities() { + return null; + } + + @Override + public long getReserved() { + return 0; + } + + @Override + public List getPorts() { + return null; + } + + @Override + public Set 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 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 removedDpids = new ArrayList<>(); + final List addedDpids = new ArrayList<>(); + final List changedDpids = new ArrayList<>(); + final Map 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 removedDpids() { + return removedDpids; + } + + public List addedDpids() { + return addedDpids; + } + + public List changedDpids() { + return changedDpids; + } + + public Map 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 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 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 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 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 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 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 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 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 contexts = new ArrayList<>(); + + @Override + public void handlePacket(OpenFlowPacketContext pktCtx) { + contexts.add(pktCtx); + } + + List contexts() { + return contexts; + } + } + + + /** + * Mock executor service that tracks submits. + */ + static class TestExecutorService extends ExecutorServiceAdapter { + private List submittedMessages = new ArrayList<>(); + + List 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 removedDpids = new ArrayList<>(); + final List addedDpids = new ArrayList<>(); + final List 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 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 type of the items in the iterable + * @return stream of objects of type T + */ + private Stream makeIntoStream(Iterable 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 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 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 fetchedMasterSwitches = + makeIntoStream(controller.getMasterSwitches()); + assertThat(fetchedMasterSwitches.count(), is(1L)); + Stream 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 fetchedEqualSwitches = + makeIntoStream(controller.getEqualSwitches()); + assertThat(fetchedEqualSwitches.count(), is(1L)); + Stream 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"; + } + } +} diff --git a/framework/src/onos/protocols/openflow/drivers/src/main/java/org/onosproject/openflow/drivers/OFSwitchImplSpringOpenTTPDellOSR.java b/framework/src/onos/protocols/openflow/drivers/src/main/java/org/onosproject/openflow/drivers/OFSwitchImplSpringOpenTTPDellOSR.java new file mode 100644 index 00000000..783a37e6 --- /dev/null +++ b/framework/src/onos/protocols/openflow/drivers/src/main/java/org/onosproject/openflow/drivers/OFSwitchImplSpringOpenTTPDellOSR.java @@ -0,0 +1,65 @@ +/* + * 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.drivers; + +import org.onosproject.openflow.controller.Dpid; +import org.projectfloodlight.openflow.protocol.OFDescStatsReply; +import org.projectfloodlight.openflow.types.TableId; + +/** + * OFDescriptionStatistics Vendor (Manufacturer Desc.): Dell Make (Hardware + * Desc.) : OpenFlow 1.3 Reference Userspace Switch Model (Datapath Desc.) : + * None Software : Serial : None. + */ +//TODO: Knock-off this class as we don't need any switch/app specific +//drivers in the south bound layers. +public class OFSwitchImplSpringOpenTTPDellOSR extends OFSwitchImplSpringOpenTTP { + + /* Table IDs to be used for Dell Open Segment Routers*/ + private static final int DELL_TABLE_VLAN = 17; + private static final int DELL_TABLE_TMAC = 18; + private static final int DELL_TABLE_IPV4_UNICAST = 30; + private static final int DELL_TABLE_MPLS = 25; + private static final int DELL_TABLE_ACL = 40; + + public OFSwitchImplSpringOpenTTPDellOSR(Dpid dpid, OFDescStatsReply desc) { + super(dpid, desc); + vlanTableId = DELL_TABLE_VLAN; + tmacTableId = DELL_TABLE_TMAC; + ipv4UnicastTableId = DELL_TABLE_IPV4_UNICAST; + mplsTableId = DELL_TABLE_MPLS; + aclTableId = DELL_TABLE_ACL; + } + + @Override + public TableType getTableType(TableId tid) { + switch (tid.getValue()) { + case DELL_TABLE_IPV4_UNICAST: + return TableType.IP; + case DELL_TABLE_MPLS: + return TableType.MPLS; + case DELL_TABLE_ACL: + return TableType.ACL; + case DELL_TABLE_VLAN: + return TableType.VLAN; + case DELL_TABLE_TMAC: + return TableType.ETHER; + default: + log.error("Table type for Table id {} is not supported in the driver", tid); + return TableType.NONE; + } + } +} \ No newline at end of file diff --git a/framework/src/onos/protocols/openflow/pom.xml b/framework/src/onos/protocols/openflow/pom.xml new file mode 100644 index 00000000..5a136a19 --- /dev/null +++ b/framework/src/onos/protocols/openflow/pom.xml @@ -0,0 +1,86 @@ + + + + 4.0.0 + + + org.onosproject + onos-protocols + 1.4.0-SNAPSHOT + ../pom.xml + + + onos-of + pom + + ONOS OpenFlow Protocol subsystem + + + api + ctl + + + + + org.onosproject + onlab-misc + + + org.onosproject + onlab-junit + + + junit + junit + 4.11 + test + + + org.hamcrest + hamcrest-core + 1.3 + test + + + org.hamcrest + hamcrest-library + 1.3 + test + + + org.easymock + easymock + test + + + org.osgi + org.osgi.core + + + + + + + org.apache.felix + maven-bundle-plugin + + + + + -- cgit 1.2.3-korg