diff options
Diffstat (limited to 'framework/src/onos/drivers')
20 files changed, 1902 insertions, 378 deletions
diff --git a/framework/src/onos/drivers/features.xml b/framework/src/onos/drivers/features.xml index e83f93fa..cb2ca0b4 100644 --- a/framework/src/onos/drivers/features.xml +++ b/framework/src/onos/drivers/features.xml @@ -15,7 +15,6 @@ ~ limitations under the License. --> <features xmlns="http://karaf.apache.org/xmlns/features/v1.2.0" name="${project.artifactId}-${project.version}"> - <repository>mvn:${project.groupId}/${project.artifactId}/${project.version}/xml/features</repository> <feature name="${project.artifactId}" version="${project.version}" description="${project.description}"> <feature>onos-api</feature> diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/extensions/NiciraExtensionInterpreter.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/extensions/NiciraExtensionTreatmentInterpreter.java index b8f1fd91..a7f70f98 100644 --- a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/extensions/NiciraExtensionInterpreter.java +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/extensions/NiciraExtensionTreatmentInterpreter.java @@ -17,11 +17,11 @@ package org.onosproject.driver.extensions; import org.onlab.packet.Ip4Address; -import org.onosproject.net.behaviour.ExtensionResolver; +import org.onosproject.net.behaviour.ExtensionTreatmentResolver; import org.onosproject.net.driver.AbstractHandlerBehaviour; -import org.onosproject.net.flow.instructions.ExtensionInstruction; -import org.onosproject.net.flow.instructions.ExtensionType; -import org.onosproject.openflow.controller.ExtensionInterpreter; +import org.onosproject.net.flow.instructions.ExtensionTreatment; +import org.onosproject.net.flow.instructions.ExtensionTreatmentType; +import org.onosproject.openflow.controller.ExtensionTreatmentInterpreter; import org.projectfloodlight.openflow.protocol.OFActionType; import org.projectfloodlight.openflow.protocol.OFFactory; import org.projectfloodlight.openflow.protocol.action.OFAction; @@ -33,36 +33,45 @@ import org.projectfloodlight.openflow.types.IPv4Address; /** * Interpreter for Nicira OpenFlow extensions. */ -public class NiciraExtensionInterpreter extends AbstractHandlerBehaviour - implements ExtensionInterpreter, ExtensionResolver { +public class NiciraExtensionTreatmentInterpreter extends AbstractHandlerBehaviour + implements ExtensionTreatmentInterpreter, ExtensionTreatmentResolver { @Override - public boolean supported(ExtensionType extensionType) { - if (extensionType.equals(ExtensionType.ExtensionTypes.NICIRA_SET_TUNNEL_DST.type())) { + public boolean supported(ExtensionTreatmentType extensionTreatmentType) { + if (extensionTreatmentType.equals( + ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST.type())) { return true; } - if (extensionType.equals(ExtensionType.ExtensionTypes.NICIRA_RESUBMIT.type())) { + if (extensionTreatmentType.equals( + ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_RESUBMIT.type())) { + return true; + } + if (extensionTreatmentType.equals( + ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_NSH_SPI.type())) { return true; } return false; } @Override - public OFAction mapInstruction(OFFactory factory, ExtensionInstruction extensionInstruction) { - ExtensionType type = extensionInstruction.type(); - if (type.equals(ExtensionType.ExtensionTypes.NICIRA_SET_TUNNEL_DST.type())) { - NiciraSetTunnelDst tunnelDst = (NiciraSetTunnelDst) extensionInstruction; + public OFAction mapInstruction(OFFactory factory, ExtensionTreatment extensionTreatment) { + ExtensionTreatmentType type = extensionTreatment.type(); + if (type.equals(ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST.type())) { + NiciraSetTunnelDst tunnelDst = (NiciraSetTunnelDst) extensionTreatment; return factory.actions().setField(factory.oxms().tunnelIpv4Dst( IPv4Address.of(tunnelDst.tunnelDst().toInt()))); } - if (type.equals(ExtensionType.ExtensionTypes.NICIRA_RESUBMIT.type())) { + if (type.equals(ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_RESUBMIT.type())) { // TODO this will be implemented later } + if (type.equals(ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_NSH_SPI.type())) { + // TODO this will be implemented later + } return null; } @Override - public ExtensionInstruction mapAction(OFAction action) { + public ExtensionTreatment mapAction(OFAction action) { if (action.getType().equals(OFActionType.SET_FIELD)) { OFActionSetField setFieldAction = (OFActionSetField) action; OFOxm<?> oxm = setFieldAction.getField(); @@ -79,13 +88,16 @@ public class NiciraExtensionInterpreter extends AbstractHandlerBehaviour } @Override - public ExtensionInstruction getExtensionInstruction(ExtensionType type) { - if (type.equals(ExtensionType.ExtensionTypes.NICIRA_SET_TUNNEL_DST.type())) { + public ExtensionTreatment getExtensionInstruction(ExtensionTreatmentType type) { + if (type.equals(ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST.type())) { return new NiciraSetTunnelDst(); } - if (type.equals(ExtensionType.ExtensionTypes.NICIRA_RESUBMIT.type())) { + if (type.equals(ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_RESUBMIT.type())) { return new NiciraResubmit(); } + if (type.equals(ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_NSH_SPI.type())) { + return new NiciraSetNshSpi(); + } throw new UnsupportedOperationException( "Driver does not support extension type " + type.toString()); } diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/extensions/NiciraResubmit.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/extensions/NiciraResubmit.java new file mode 100644 index 00000000..481b6f9c --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/extensions/NiciraResubmit.java @@ -0,0 +1,107 @@ +/* + * 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.driver.extensions; + +import com.google.common.base.MoreObjects; +import org.onlab.util.KryoNamespace; +import org.onosproject.net.PortNumber; +import org.onosproject.net.flow.instructions.AbstractExtensionTreatment; +import org.onosproject.net.flow.instructions.ExtensionTreatmentType; +import org.onosproject.store.serializers.PortNumberSerializer; + +import java.util.Objects; + +import static com.google.common.base.Preconditions.checkNotNull; + +/** + * Nicira resubmit extension instruction. + */ +public class NiciraResubmit extends AbstractExtensionTreatment { + + private PortNumber inPort; + + private final KryoNamespace appKryo = new KryoNamespace.Builder() + .register(new PortNumberSerializer(), PortNumber.class) + .register(byte[].class) + .build(); + + /** + * Creates a new resubmit instruction. + */ + NiciraResubmit() { + inPort = null; + } + + /** + * Creates a new resubmit instruction with a particular inPort. + * + * @param inPort in port number + */ + public NiciraResubmit(PortNumber inPort) { + checkNotNull(inPort); + this.inPort = inPort; + } + + /** + * Gets the inPort. + * + * @return inPort + */ + public PortNumber inPort() { + return inPort; + } + + @Override + public ExtensionTreatmentType type() { + return ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_RESUBMIT.type(); + } + + @Override + public void deserialize(byte[] data) { + inPort = appKryo.deserialize(data); + } + + @Override + public byte[] serialize() { + return appKryo.serialize(inPort); + } + + @Override + public int hashCode() { + return Objects.hash(inPort); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof NiciraResubmit) { + NiciraResubmit that = (NiciraResubmit) obj; + return Objects.equals(inPort, that.inPort); + + } + return false; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(getClass()) + .add("inPort", inPort) + .toString(); + } +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/extensions/NiciraSetNshSpi.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/extensions/NiciraSetNshSpi.java new file mode 100644 index 00000000..25358702 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/extensions/NiciraSetNshSpi.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.driver.extensions; + +import java.util.Objects; + +import org.onlab.util.KryoNamespace; +import org.onosproject.net.flow.instructions.AbstractExtensionTreatment; +import org.onosproject.net.flow.instructions.ExtensionTreatmentType; +import org.onosproject.store.serializers.Ip4AddressSerializer; + +import com.google.common.base.MoreObjects; + +/** + * Nicira set NSH SPI extension instruction. + */ +public class NiciraSetNshSpi extends AbstractExtensionTreatment { + + private int nshSpi; + + private final KryoNamespace appKryo = new KryoNamespace.Builder() + .register(new Ip4AddressSerializer(), Integer.class) + .register(byte[].class) + .build(); + + /** + * Creates a new set nsh spi instruction. + */ + NiciraSetNshSpi() { + nshSpi = 0; + } + + /** + * Creates a new set nsh spi instruction with given spi. + * + * @param nshSpi nsh service path index + */ + NiciraSetNshSpi(int nshSpi) { + this.nshSpi = nshSpi; + } + + /** + * Gets the nsh service path index. + * + * @return nsh service path index + */ + public int nshSpi() { + return nshSpi; + } + + @Override + public ExtensionTreatmentType type() { + return ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_NSH_SPI.type(); + } + + @Override + public void deserialize(byte[] data) { + nshSpi = appKryo.deserialize(data); + } + + @Override + public byte[] serialize() { + return appKryo.serialize(nshSpi); + } + + @Override + public int hashCode() { + return Objects.hash(nshSpi); + } + + @Override + public boolean equals(Object obj) { + if (this == obj) { + return true; + } + if (obj instanceof NiciraSetNshSpi) { + NiciraSetNshSpi that = (NiciraSetNshSpi) obj; + return Objects.equals(nshSpi, that.nshSpi); + + } + return false; + } + + @Override + public String toString() { + return MoreObjects.toStringHelper(getClass()) + .add("nshSpi", nshSpi) + .toString(); + } +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/extensions/NiciraSetTunnelDst.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/extensions/NiciraSetTunnelDst.java index a20b2479..ec23a9e0 100644 --- a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/extensions/NiciraSetTunnelDst.java +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/extensions/NiciraSetTunnelDst.java @@ -19,8 +19,8 @@ package org.onosproject.driver.extensions; import com.google.common.base.MoreObjects; import org.onlab.packet.Ip4Address; import org.onlab.util.KryoNamespace; -import org.onosproject.net.flow.instructions.AbstractExtensionInstruction; -import org.onosproject.net.flow.instructions.ExtensionType; +import org.onosproject.net.flow.instructions.AbstractExtensionTreatment; +import org.onosproject.net.flow.instructions.ExtensionTreatmentType; import org.onosproject.store.serializers.Ip4AddressSerializer; import java.util.Objects; @@ -30,7 +30,7 @@ import static com.google.common.base.Preconditions.checkNotNull; /** * Nicira set tunnel destination extension instruction. */ -public class NiciraSetTunnelDst extends AbstractExtensionInstruction { +public class NiciraSetTunnelDst extends AbstractExtensionTreatment { private Ip4Address tunnelDst; @@ -67,8 +67,8 @@ public class NiciraSetTunnelDst extends AbstractExtensionInstruction { } @Override - public ExtensionType type() { - return ExtensionType.ExtensionTypes.NICIRA_SET_TUNNEL_DST.type(); + public ExtensionTreatmentType type() { + return ExtensionTreatmentType.ExtensionTreatmentTypes.NICIRA_SET_TUNNEL_DST.type(); } @Override diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/OFOpticalSwitch13.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/OFOpticalSwitch13.java index a62b93c8..5c6ce360 100644 --- a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/OFOpticalSwitch13.java +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/OFOpticalSwitch13.java @@ -144,7 +144,16 @@ public class OFOpticalSwitch13 extends AbstractOpenFlowSwitch implements OpenFlo @Override public Device.Type deviceType() { - return Device.Type.ROADM; + String hwDesc = hardwareDescription(); + switch (hwDesc) { + case "Optical-ROADM": + return Device.Type.ROADM; + case "Optical-OTN": + return Device.Type.OTN; + default: + log.error("Unsupported hardwareDescription {}", hwDesc); + return Device.Type.OTHER; + } } /* diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/OfOpticalSwitchImplLinc13.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/OfOpticalSwitchImplLinc13.java index ff65e0c6..f91e2a7e 100644 --- a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/OfOpticalSwitchImplLinc13.java +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/handshaker/OfOpticalSwitchImplLinc13.java @@ -15,8 +15,8 @@ */ package org.onosproject.driver.handshaker; -import org.onosproject.net.Device; import com.google.common.collect.ImmutableSet; +import org.onosproject.net.Device; import org.onosproject.openflow.controller.OpenFlowOpticalSwitch; import org.onosproject.openflow.controller.PortDescPropertyType; import org.onosproject.openflow.controller.driver.AbstractOpenFlowSwitch; @@ -26,6 +26,8 @@ import org.onosproject.openflow.controller.driver.SwitchDriverSubHandshakeNotSta import org.projectfloodlight.openflow.protocol.OFCircuitPortStatus; import org.projectfloodlight.openflow.protocol.OFCircuitPortsReply; import org.projectfloodlight.openflow.protocol.OFCircuitPortsRequest; +import org.projectfloodlight.openflow.protocol.OFFlowMod; +import org.projectfloodlight.openflow.protocol.OFFlowStatsRequest; import org.projectfloodlight.openflow.protocol.OFMessage; import org.projectfloodlight.openflow.protocol.OFObject; import org.projectfloodlight.openflow.protocol.OFPortDesc; @@ -34,11 +36,19 @@ import org.projectfloodlight.openflow.protocol.OFPortDescStatsReply; import org.projectfloodlight.openflow.protocol.OFPortOptical; import org.projectfloodlight.openflow.protocol.OFStatsReply; import org.projectfloodlight.openflow.protocol.OFStatsType; +import org.projectfloodlight.openflow.protocol.action.OFAction; +import org.projectfloodlight.openflow.protocol.action.OFActionSetField; +import org.projectfloodlight.openflow.protocol.match.Match; +import org.projectfloodlight.openflow.protocol.match.MatchField; +import org.projectfloodlight.openflow.protocol.oxm.OFOxmExpOchSigId; +import org.projectfloodlight.openflow.types.CircuitSignalID; import org.projectfloodlight.openflow.types.OFPort; +import org.projectfloodlight.openflow.types.U8; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.LinkedList; import java.util.List; import java.util.Set; import java.util.concurrent.atomic.AtomicBoolean; @@ -52,6 +62,9 @@ import java.util.concurrent.atomic.AtomicBoolean; * LINC sends the tap ports (OCh for our purposes) in the regular port desc stats reply, * while it sends *all* ports (both tap and WDM ports, i.e., OCh and OMS) in the experimenter port desc stats reply. * + * As LINC implements custom OF optical extensions (in contrast to the final standard as specified in + * ONF TS-022 (March 15, 2015), we need to rewrite flow stat requests and flow mods in {@link #sendMsg(OFMessage)}. + * */ public class OfOpticalSwitchImplLinc13 extends AbstractOpenFlowSwitch implements OpenFlowOpticalSwitch { @@ -160,6 +173,86 @@ public class OfOpticalSwitchImplLinc13 return Collections.EMPTY_LIST; } + /** + * Rewrite match object to use LINC OF optical extensions. + * + * @param match original match + * @return rewritten match + */ + private Match rewriteMatch(Match match) { + Match.Builder mBuilder = factory().buildMatch(); + for (MatchField mf : match.getMatchFields()) { + if (mf == MatchField.EXP_OCH_SIG_ID) { + mBuilder.setExact(MatchField.OCH_SIGID, (CircuitSignalID) match.get(mf)); + continue; + } + if (mf == MatchField.EXP_OCH_SIGTYPE) { + mBuilder.setExact(MatchField.OCH_SIGTYPE, (U8) match.get(mf)); + continue; + } + mBuilder.setExact(mf, match.get(mf)); + } + + return mBuilder.build(); + } + + /** + * Rewrite actions to use LINC OF optical extensions. + * + * @param actions original actions + * @return rewritten actions + */ + private List<OFAction> rewriteActions(List<OFAction> actions) { + List<OFAction> newActions = new LinkedList<>(); + + for (OFAction action : actions) { + if (!(action instanceof OFActionSetField)) { + newActions.add(action); + continue; + } + + OFActionSetField sf = (OFActionSetField) action; + if (!(sf instanceof OFOxmExpOchSigId)) { + newActions.add(action); + } + + OFOxmExpOchSigId oxm = (OFOxmExpOchSigId) sf.getField(); + CircuitSignalID signalId = oxm.getValue(); + + newActions.add( + factory().actions().circuit(factory().oxms().ochSigid(signalId))); + } + + return newActions; + } + + @Override + public void sendMsg(OFMessage msg) { + // Ignore everything but flow mods and stat requests + if (!(msg instanceof OFFlowMod || msg instanceof OFFlowStatsRequest)) { + super.sendMsg(msg); + return; + } + + Match newMatch; + OFMessage newMsg = null; + + if (msg instanceof OFFlowStatsRequest) { + // Rewrite match only + OFFlowStatsRequest fsr = (OFFlowStatsRequest) msg; + newMatch = rewriteMatch(fsr.getMatch()); + newMsg = fsr.createBuilder().setMatch(newMatch).build(); + } else if (msg instanceof OFFlowMod) { + // Rewrite match and actions + OFFlowMod fm = (OFFlowMod) msg; + newMatch = rewriteMatch(fm.getMatch()); + List<OFAction> actions = rewriteActions(fm.getActions()); + + newMsg = fm.createBuilder().setMatch(newMatch).setActions(actions).build(); + } + + super.sendMsg(newMsg); + } @Override public Boolean supportNxRole() { diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/netconf/NetconfControllerConfig.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/netconf/NetconfControllerConfig.java new file mode 100644 index 00000000..84043b5b --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/netconf/NetconfControllerConfig.java @@ -0,0 +1,89 @@ +/* + * 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.driver.netconf; + +import com.google.common.base.Preconditions; +import org.onosproject.net.DeviceId; +import org.onosproject.net.behaviour.ControllerConfig; +import org.onosproject.net.behaviour.ControllerInfo; +import org.onosproject.net.driver.AbstractHandlerBehaviour; +import org.onosproject.net.driver.DriverHandler; +import org.onosproject.netconf.NetconfController; +import org.onosproject.netconf.NetconfDevice; +import org.slf4j.Logger; + +import java.io.ByteArrayInputStream; +import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.List; + +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Implementation of controller config which allows to get and set controllers + * through the Netconf protocol. + */ +public class NetconfControllerConfig extends AbstractHandlerBehaviour + implements ControllerConfig { + + private final Logger log = getLogger(NetconfControllerConfig.class); + + @Override + public List<ControllerInfo> getControllers() { + DriverHandler handler = handler(); + NetconfController controller = handler.get(NetconfController.class); + DeviceId ofDeviceId = handler.data().deviceId(); + Preconditions.checkNotNull(controller, "Netconf controller is null"); + List<ControllerInfo> controllers = new ArrayList<>(); + controllers.addAll(XmlConfigParser.parseStreamControllers(XmlConfigParser. + loadXml(new ByteArrayInputStream(controller. + getDevicesMap().get(ofDeviceId).getSession(). + getConfig("running").getBytes(StandardCharsets.UTF_8))))); + return controllers; + } + + @Override + public void setControllers(List<ControllerInfo> controllers) { + DriverHandler handler = handler(); + NetconfController controller = handler.get(NetconfController.class); + DeviceId deviceId = handler.data().deviceId(); + Preconditions.checkNotNull(controller, "Netconf controller is null"); + try { + NetconfDevice device = controller.getNetconfDevice(deviceId); + log.warn("provider map {}", controller.getDevicesMap()); + String config = XmlConfigParser.createControllersConfig( + XmlConfigParser.loadXml(getClass().getResourceAsStream("controllers.xml")), + XmlConfigParser.loadXml( + new ByteArrayInputStream(device.getSession() + .getConfig("running") + .getBytes( + StandardCharsets.UTF_8))), + "running", "merge", "create", controllers + ); + device.getSession().editConfig(config.substring(config.indexOf("-->") + 3)); + } catch (NullPointerException e) { + log.warn("No NETCONF device with requested parameters " + e); + throw new NullPointerException("No NETCONF device with requested parameters " + e); + } + + } + + //TODO maybe put method getNetconfClientService like in ovsdb if we need it + +} + + diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/netconf/XmlConfigParser.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/netconf/XmlConfigParser.java new file mode 100644 index 00000000..55826a25 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/netconf/XmlConfigParser.java @@ -0,0 +1,126 @@ +/* + * 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.driver.netconf; + +import org.apache.commons.configuration.ConfigurationException; +import org.apache.commons.configuration.HierarchicalConfiguration; +import org.apache.commons.configuration.XMLConfiguration; +import org.apache.commons.configuration.tree.ConfigurationNode; +import org.onlab.packet.IpAddress; +import org.onosproject.net.behaviour.ControllerInfo; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.io.InputStream; +import java.io.StringWriter; +import java.util.ArrayList; +import java.util.List; + +/** + * Parser for Netconf XML configurations and replys. + */ +final class XmlConfigParser { + public static final Logger log = LoggerFactory + .getLogger(XmlConfigParser.class); + + private XmlConfigParser() { + //not called, preventing any allocation + } + + + protected static HierarchicalConfiguration loadXml(InputStream xmlStream) { + XMLConfiguration cfg = new XMLConfiguration(); + try { + cfg.load(xmlStream); + return cfg; + } catch (ConfigurationException e) { + throw new IllegalArgumentException("Cannot load xml from Stream", e); + } + } + + protected static List<ControllerInfo> parseStreamControllers(HierarchicalConfiguration cfg) { + List<ControllerInfo> controllers = new ArrayList<>(); + List<HierarchicalConfiguration> fields = + cfg.configurationsAt("data.capable-switch." + + "logical-switches." + + "switch.controllers.controller"); + for (HierarchicalConfiguration sub : fields) { + controllers.add(new ControllerInfo( + IpAddress.valueOf(sub.getString("ip-address")), + Integer.parseInt(sub.getString("port")), + sub.getString("protocol"))); + } + return controllers; + } + + protected static String parseSwitchId(HierarchicalConfiguration cfg) { + HierarchicalConfiguration field = + cfg.configurationAt("data.capable-switch." + + "logical-switches." + + "switch"); + return field.getProperty("id").toString(); + } + + protected static String parseCapableSwitchId(HierarchicalConfiguration cfg) { + HierarchicalConfiguration field = + cfg.configurationAt("data.capable-switch"); + return field.getProperty("id").toString(); + } + + protected static String createControllersConfig(HierarchicalConfiguration cfg, + HierarchicalConfiguration actualCfg, + String target, String netconfOperation, + String controllerOperation, + List<ControllerInfo> controllers) { + //cfg.getKeys().forEachRemaining(key -> System.out.println(key)); + cfg.setProperty("edit-config.target", target); + cfg.setProperty("edit-config.default-operation", netconfOperation); + cfg.setProperty("edit-config.config.capable-switch.id", + parseCapableSwitchId(actualCfg)); + cfg.setProperty("edit-config.config.capable-switch." + + "logical-switches.switch.id", parseSwitchId(actualCfg)); + List<ConfigurationNode> newControllers = new ArrayList<>(); + for (ControllerInfo ci : controllers) { + XMLConfiguration controller = new XMLConfiguration(); + controller.setRoot(new HierarchicalConfiguration.Node("controller")); + String id = ci.type() + ":" + ci.ip() + ":" + ci.port(); + controller.setProperty("id", id); + controller.setProperty("ip-address", ci.ip()); + controller.setProperty("port", ci.port()); + controller.setProperty("protocol", ci.type()); + newControllers.add(controller.getRootNode()); + } + cfg.addNodes("edit-config.config.capable-switch.logical-switches." + + "switch.controllers", newControllers); + XMLConfiguration editcfg = (XMLConfiguration) cfg; + StringWriter stringWriter = new StringWriter(); + try { + editcfg.save(stringWriter); + } catch (ConfigurationException e) { + e.printStackTrace(); + } + String s = stringWriter.toString() + .replaceAll("<controller>", + "<controller nc:operation=\"" + controllerOperation + "\">"); + s = s.replace("<target>" + target + "</target>", + "<target><" + target + "/></target>"); + return s; + + } + + //TODO implement mor methods for parsing configuration when you need them +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/netconf/package-info.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/netconf/package-info.java new file mode 100644 index 00000000..200312b4 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/netconf/package-info.java @@ -0,0 +1,20 @@ +/* + * 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. + */ + +/** + * Implementations of the Netconf driver behaviours. + */ +package org.onosproject.driver.netconf;
\ No newline at end of file diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/ovsdb/OvsdbControllerConfig.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/ovsdb/OvsdbControllerConfig.java index a00d3dbc..f116ab84 100644 --- a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/ovsdb/OvsdbControllerConfig.java +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/ovsdb/OvsdbControllerConfig.java @@ -16,6 +16,7 @@ package org.onosproject.driver.ovsdb; +import com.google.common.collect.ImmutableSet; import org.onlab.packet.IpAddress; import org.onlab.packet.TpPort; import org.onosproject.net.AnnotationKeys; @@ -56,7 +57,7 @@ public class OvsdbControllerConfig extends AbstractHandlerBehaviour implements C DriverHandler handler = handler(); OvsdbClientService clientService = getOvsdbClientService(handler); if (!clientService.getControllers(handler().data().deviceId()) - .equals(controllers)) { + .equals(ImmutableSet.copyOf(controllers))) { clientService.setControllersWithDeviceId(handler(). data().deviceId(), controllers); } diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/ovsdb/OvsdbTunnelConfig.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/ovsdb/OvsdbTunnelConfig.java index a32553ad..ad90ca44 100644 --- a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/ovsdb/OvsdbTunnelConfig.java +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/ovsdb/OvsdbTunnelConfig.java @@ -68,10 +68,10 @@ public class OvsdbTunnelConfig extends AbstractHandlerBehaviour public boolean createTunnelInterface(BridgeName bridgeName, TunnelDescription tunnel) { Map<String, String> options = ((DefaultAnnotations) tunnel.annotations()).asMap(); if (tunnel.src() != null) { - options.put(OPTION_LOCAL_IP, tunnel.src().toString()); + options.put(OPTION_LOCAL_IP, ((IpTunnelEndPoint) tunnel.src()).ip().toString()); } if (tunnel.dst() != null) { - options.put(OPTION_REMOTE_IP, tunnel.dst().toString()); + options.put(OPTION_REMOTE_IP, ((IpTunnelEndPoint) tunnel.dst()).ip().toString()); } DriverHandler handler = handler(); diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2Pipeline.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2Pipeline.java index 0cb30d28..937c9ac8 100644 --- a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2Pipeline.java +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/CpqdOFDPA2Pipeline.java @@ -18,15 +18,19 @@ package org.onosproject.driver.pipeline; import static org.slf4j.LoggerFactory.getLogger; import java.util.ArrayList; +import java.util.Collection; import java.util.Collections; +import java.util.Deque; import java.util.List; import java.util.Set; import java.util.concurrent.ConcurrentHashMap; +import org.onlab.packet.Ethernet; import org.onlab.packet.VlanId; import org.onosproject.core.ApplicationId; import org.onosproject.net.Port; import org.onosproject.net.PortNumber; +import org.onosproject.net.behaviour.NextGroup; import org.onosproject.net.flow.DefaultFlowRule; import org.onosproject.net.flow.DefaultTrafficSelector; import org.onosproject.net.flow.DefaultTrafficTreatment; @@ -35,8 +39,18 @@ import org.onosproject.net.flow.FlowRuleOperations; import org.onosproject.net.flow.FlowRuleOperationsContext; import org.onosproject.net.flow.TrafficSelector; import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.criteria.EthTypeCriterion; +import org.onosproject.net.flow.criteria.IPCriterion; +import org.onosproject.net.flow.criteria.MplsBosCriterion; +import org.onosproject.net.flow.criteria.MplsCriterion; import org.onosproject.net.flow.criteria.PortCriterion; import org.onosproject.net.flow.criteria.VlanIdCriterion; +import org.onosproject.net.flow.instructions.Instruction; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.group.Group; +import org.onosproject.net.group.GroupKey; import org.slf4j.Logger; @@ -108,6 +122,81 @@ public class CpqdOFDPA2Pipeline extends OFDPA2Pipeline { return rules; } + @Override + protected Collection<FlowRule> processSpecific(ForwardingObjective fwd) { + TrafficSelector selector = fwd.selector(); + EthTypeCriterion ethType = + (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE); + if ((ethType == null) || + (ethType.ethType().toShort() != Ethernet.TYPE_IPV4) && + (ethType.ethType().toShort() != Ethernet.MPLS_UNICAST)) { + log.warn("processSpecific: Unsupported " + + "forwarding objective criteraia"); + fail(fwd, ObjectiveError.UNSUPPORTED); + return Collections.emptySet(); + } + + int forTableId = -1; + TrafficSelector.Builder filteredSelector = DefaultTrafficSelector.builder(); + if (ethType.ethType().toShort() == Ethernet.TYPE_IPV4) { + filteredSelector.matchEthType(Ethernet.TYPE_IPV4) + .matchIPDst(((IPCriterion) + selector.getCriterion(Criterion.Type.IPV4_DST)).ip()); + forTableId = UNICAST_ROUTING_TABLE; + log.debug("processing IPv4 specific forwarding objective {} hash{} in dev:{}", + fwd.id(), fwd.hashCode(), deviceId); + } else { + filteredSelector + .matchEthType(Ethernet.MPLS_UNICAST) + .matchMplsLabel(((MplsCriterion) + selector.getCriterion(Criterion.Type.MPLS_LABEL)).label()); + MplsBosCriterion bos = (MplsBosCriterion) selector + .getCriterion(Criterion.Type.MPLS_BOS); + if (bos != null) { + filteredSelector.matchMplsBos(bos.mplsBos()); + } + forTableId = MPLS_TABLE_1; + log.debug("processing MPLS specific forwarding objective {} hash:{} in dev {}", + fwd.id(), fwd.hashCode(), deviceId); + } + + TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder(); + if (fwd.treatment() != null) { + for (Instruction i : fwd.treatment().allInstructions()) { + tb.add(i); + } + } + + if (fwd.nextId() != null) { + NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId()); + List<Deque<GroupKey>> gkeys = appKryo.deserialize(next.data()); + // we only need the top level group's key to point the flow to it + Group group = groupService.getGroup(deviceId, gkeys.get(0).peekFirst()); + if (group == null) { + log.warn("The group left!"); + fail(fwd, ObjectiveError.GROUPMISSING); + return Collections.emptySet(); + } + tb.deferred().group(group.id()); + } + tb.transition(ACL_TABLE); + FlowRule.Builder ruleBuilder = DefaultFlowRule.builder() + .fromApp(fwd.appId()) + .withPriority(fwd.priority()) + .forDevice(deviceId) + .withSelector(filteredSelector.build()) + .withTreatment(tb.build()) + .forTable(forTableId); + + if (fwd.permanent()) { + ruleBuilder.makePermanent(); + } else { + ruleBuilder.makeTemporary(fwd.timeout()); + } + + return Collections.singletonList(ruleBuilder.build()); + } + @Override protected void initializePipeline() { diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java index cf3c7e89..863caebb 100644 --- a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OFDPA2Pipeline.java @@ -19,9 +19,11 @@ import static org.onlab.util.Tools.groupedThreads; import static org.slf4j.LoggerFactory.getLogger; import java.nio.ByteBuffer; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; +import java.util.Deque; import java.util.List; import java.util.Map; import java.util.Set; @@ -65,15 +67,20 @@ import org.onosproject.net.flow.TrafficSelector; import org.onosproject.net.flow.TrafficTreatment; import org.onosproject.net.flow.criteria.Criteria; import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.criteria.Criterion.Type; import org.onosproject.net.flow.criteria.EthCriterion; import org.onosproject.net.flow.criteria.EthTypeCriterion; import org.onosproject.net.flow.criteria.IPCriterion; +import org.onosproject.net.flow.criteria.MplsBosCriterion; +import org.onosproject.net.flow.criteria.MplsCriterion; import org.onosproject.net.flow.criteria.PortCriterion; import org.onosproject.net.flow.criteria.VlanIdCriterion; import org.onosproject.net.flow.instructions.Instruction; import org.onosproject.net.flow.instructions.Instructions.OutputInstruction; import org.onosproject.net.flow.instructions.L2ModificationInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction.L2SubType; import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction; +import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsLabelInstruction; import org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction; import org.onosproject.net.flowobjective.FilteringObjective; import org.onosproject.net.flowobjective.FlowObjectiveStore; @@ -128,50 +135,43 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline protected static final int LOWEST_PRIORITY = 0x0; /* - * Group keys are normally generated by using the next Objective id. In the - * case of a next objective resulting in a group chain, each group derives a - * group key from the next objective id in the following way: - * The upper 4 bits of the group-key are used to denote the position of the - * group in the group chain. For example, in the chain - * group0 --> group1 --> group2 --> port - * group0's group key would have the upper 4 bits as 0, group1's upper four - * bits would be 1, and so on - */ - private static final int GROUP0MASK = 0x0; - private static final int GROUP1MASK = 0x10000000; - - /* * OFDPA requires group-id's to have a certain form. * L2 Interface Groups have <4bits-0><12bits-vlanid><16bits-portid> * L3 Unicast Groups have <4bits-2><28bits-index> + * MPLS Interface Groups have <4bits-9><4bits:0><24bits-index> + * L3 ECMP Groups have <4bits-7><28bits-index> + * L2 Flood Groups have <4bits-4><12bits-vlanid><16bits-index> + * L3 VPN Groups have <4bits-9><4bits-2><24bits-index> */ private static final int L2INTERFACEMASK = 0x0; private static final int L3UNICASTMASK = 0x20000000; - //private static final int MPLSINTERFACEMASK = 0x90000000; + private static final int MPLSINTERFACEMASK = 0x90000000; private static final int L3ECMPMASK = 0x70000000; private static final int L2FLOODMASK = 0x40000000; + private static final int L3VPNMASK = 0x92000000; private final Logger log = getLogger(getClass()); private ServiceDirectory serviceDirectory; protected FlowRuleService flowRuleService; private CoreService coreService; - private GroupService groupService; - private FlowObjectiveStore flowObjectiveStore; + protected GroupService groupService; + protected FlowObjectiveStore flowObjectiveStore; protected DeviceId deviceId; protected ApplicationId driverId; protected PacketService packetService; protected DeviceService deviceService; private InternalPacketProcessor processor = new InternalPacketProcessor(); - private KryoNamespace appKryo = new KryoNamespace.Builder() + protected KryoNamespace appKryo = new KryoNamespace.Builder() .register(KryoNamespaces.API) .register(GroupKey.class) .register(DefaultGroupKey.class) - .register(OfdpaGroupChain.class) + .register(OfdpaNextGroup.class) .register(byte[].class) + .register(ArrayDeque.class) .build(); - private Cache<GroupKey, OfdpaGroupChain> pendingNextObjectives; - private ConcurrentHashMap<GroupKey, GroupChainElem> pendingGroups; + private Cache<GroupKey, OfdpaNextGroup> pendingNextObjectives; + private ConcurrentHashMap<GroupKey, Set<GroupChainElem>> pendingGroups; private ScheduledExecutorService groupChecker = Executors.newScheduledThreadPool(2, groupedThreads("onos/pipeliner", @@ -184,6 +184,8 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline Map<VlanId, Set<PortNumber>> vlan2Port = new ConcurrentHashMap<VlanId, Set<PortNumber>>(); + // index number for group creation + AtomicInteger l3vpnindex = new AtomicInteger(0); @Override @@ -193,15 +195,16 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline pendingNextObjectives = CacheBuilder.newBuilder() .expireAfterWrite(20, TimeUnit.SECONDS) - .removalListener((RemovalNotification<GroupKey, OfdpaGroupChain> notification) -> { - if (notification.getCause() == RemovalCause.EXPIRED) { - fail(notification.getValue().nextObjective(), - ObjectiveError.GROUPINSTALLATIONFAILED); - } + .removalListener(( + RemovalNotification<GroupKey, OfdpaNextGroup> notification) -> { + if (notification.getCause() == RemovalCause.EXPIRED) { + fail(notification.getValue().nextObjective(), + ObjectiveError.GROUPINSTALLATIONFAILED); + } }).build(); groupChecker.scheduleAtFixedRate(new GroupChecker(), 0, 500, TimeUnit.MILLISECONDS); - pendingGroups = new ConcurrentHashMap<GroupKey, GroupChainElem>(); + pendingGroups = new ConcurrentHashMap<GroupKey, Set<GroupChainElem>>(); coreService = serviceDirectory.get(CoreService.class); flowRuleService = serviceDirectory.get(FlowRuleService.class); @@ -285,22 +288,49 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline @Override public void next(NextObjective nextObjective) { - log.debug("Processing NextObjective id{} op{}", nextObjective.id(), - nextObjective.op()); - if (nextObjective.op() == Objective.Operation.REMOVE) { - if (nextObjective.next().isEmpty()) { - removeGroup(nextObjective); - } else { - removeBucketFromGroup(nextObjective); + NextGroup nextGroup = flowObjectiveStore.getNextGroup(nextObjective.id()); + switch (nextObjective.op()) { + case ADD: + if (nextGroup != null) { + log.warn("Cannot add next {} that already exists in device {}", + nextObjective.id(), deviceId); + return; } - } else if (nextObjective.op() == Objective.Operation.ADD) { - NextGroup nextGroup = flowObjectiveStore.getNextGroup(nextObjective.id()); + log.debug("Processing NextObjective id{} in dev{} - add group", + nextObjective.id(), deviceId); + addGroup(nextObjective); + break; + case ADD_TO_EXISTING: if (nextGroup != null) { + log.debug("Processing NextObjective id{} in dev{} - add bucket", + nextObjective.id(), deviceId); addBucketToGroup(nextObjective); } else { - addGroup(nextObjective); + // it is possible that group-chain has not been fully created yet + waitToAddBucketToGroup(nextObjective); } - } else { + break; + case REMOVE: + if (nextGroup == null) { + log.warn("Cannot remove next {} that does not exist in device {}", + nextObjective.id(), deviceId); + return; + } + log.debug("Processing NextObjective id{} in dev{} - remove group", + nextObjective.id(), deviceId); + removeGroup(nextObjective); + break; + case REMOVE_FROM_EXISTING: + if (nextGroup == null) { + log.warn("Cannot remove from next {} that does not exist in device {}", + nextObjective.id(), deviceId); + return; + } + log.debug("Processing NextObjective id{} in dev{} - remove bucket", + nextObjective.id(), deviceId); + removeBucketFromGroup(nextObjective); + break; + default: log.warn("Unsupported operation {}", nextObjective.op()); } } @@ -309,7 +339,6 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline // Flow handling ////////////////////////////////////// - /** * As per OFDPA 2.0 TTP, filtering of VLAN ids, MAC addresses (for routing) * and IP addresses configured on switch ports happen in different tables. @@ -520,7 +549,6 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline /** * Allows routed packets with correct destination MAC to be directed * to unicast-IP routing table or MPLS forwarding table. - * XXX need to add rule for multicast routing. * * @param portCriterion port on device for which this filter is programmed * @param ethCriterion dstMac of device for which is filter is programmed @@ -661,38 +689,78 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline /** * In the OF-DPA 2.0 pipeline, specific forwarding refers to the IP table - * (unicast or multicast) or the L2 table (mac + vlan). + * (unicast or multicast) or the L2 table (mac + vlan) or the MPLS table. * * @param fwd the forwarding objective of type 'specific' * @return a collection of flow rules. Typically there will be only one * for this type of forwarding objective. An empty set may be * returned if there is an issue in processing the objective. */ - private Collection<FlowRule> processSpecific(ForwardingObjective fwd) { - log.debug("Processing specific forwarding objective"); + protected Collection<FlowRule> processSpecific(ForwardingObjective fwd) { TrafficSelector selector = fwd.selector(); EthTypeCriterion ethType = (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE); - // XXX currently supporting only the L3 unicast table - if (ethType == null || ethType.ethType().toShort() != Ethernet.TYPE_IPV4) { + if ((ethType == null) || + (ethType.ethType().toShort() != Ethernet.TYPE_IPV4) && + (ethType.ethType().toShort() != Ethernet.MPLS_UNICAST)) { + log.warn("processSpecific: Unsupported " + + "forwarding objective criteraia"); fail(fwd, ObjectiveError.UNSUPPORTED); return Collections.emptySet(); } - TrafficSelector filteredSelector = - DefaultTrafficSelector.builder() - .matchEthType(Ethernet.TYPE_IPV4) - .matchIPDst( - ((IPCriterion) - selector.getCriterion(Criterion.Type.IPV4_DST)).ip()) - .build(); + int forTableId = -1; + TrafficSelector.Builder filteredSelector = DefaultTrafficSelector.builder(); + if (ethType.ethType().toShort() == Ethernet.TYPE_IPV4) { + filteredSelector.matchEthType(Ethernet.TYPE_IPV4) + .matchIPDst(((IPCriterion) + selector.getCriterion(Criterion.Type.IPV4_DST)).ip()); + forTableId = UNICAST_ROUTING_TABLE; + log.debug("processing IPv4 specific forwarding objective {} in dev:{}", + fwd.id(), deviceId); + } else { + filteredSelector + .matchEthType(Ethernet.MPLS_UNICAST) + .matchMplsLabel(((MplsCriterion) + selector.getCriterion(Criterion.Type.MPLS_LABEL)).label()); + MplsBosCriterion bos = (MplsBosCriterion) selector + .getCriterion(Criterion.Type.MPLS_BOS); + if (bos != null) { + filteredSelector.matchMplsBos(bos.mplsBos()); + } + forTableId = MPLS_TABLE_1; + log.debug("processing MPLS specific forwarding objective {} in dev {}", + fwd.id(), deviceId); + } TrafficTreatment.Builder tb = DefaultTrafficTreatment.builder(); + boolean popMpls = false; + if (fwd.treatment() != null) { + for (Instruction i : fwd.treatment().allInstructions()) { + tb.add(i); + if (i instanceof L2ModificationInstruction && + ((L2ModificationInstruction) i).subtype() == L2SubType.MPLS_POP) { + popMpls = true; + } + } + } if (fwd.nextId() != null) { + if (forTableId == MPLS_TABLE_1 && !popMpls) { + log.warn("SR CONTINUE case cannot be handled as MPLS ECMP " + + "is not implemented in OF-DPA yet. Aborting this flow " + + "in this device {}", deviceId); + // XXX We could convert to forwarding to a single-port, via a + // MPLS interface, or a MPLS SWAP (with-same) but that would + // have to be handled in the next-objective. Also the pop-mpls + // logic used here won't work in non-BoS case. + return Collections.emptySet(); + } + NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId()); - List<GroupKey> gkeys = appKryo.deserialize(next.data()); - Group group = groupService.getGroup(deviceId, gkeys.get(0)); + List<Deque<GroupKey>> gkeys = appKryo.deserialize(next.data()); + // we only need the top level group's key to point the flow to it + Group group = groupService.getGroup(deviceId, gkeys.get(0).peekFirst()); if (group == null) { log.warn("The group left!"); fail(fwd, ObjectiveError.GROUPMISSING); @@ -705,8 +773,9 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline .fromApp(fwd.appId()) .withPriority(fwd.priority()) .forDevice(deviceId) - .withSelector(filteredSelector) - .withTreatment(tb.build()); + .withSelector(filteredSelector.build()) + .withTreatment(tb.build()) + .forTable(forTableId); if (fwd.permanent()) { ruleBuilder.makePermanent(); @@ -714,7 +783,6 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline ruleBuilder.makeTemporary(fwd.timeout()); } - ruleBuilder.forTable(UNICAST_ROUTING_TABLE); return Collections.singletonList(ruleBuilder.build()); } @@ -724,7 +792,7 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline } } - private void fail(Objective obj, ObjectiveError error) { + protected void fail(Objective obj, ObjectiveError error) { if (obj.context().isPresent()) { obj.context().get().onError(obj, error); } @@ -765,20 +833,66 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline /** * As per the OFDPA 2.0 TTP, packets are sent out of ports by using - * a chain of groups, namely an L3 Unicast Group that points to an L2 Interface - * Group which in-turn points to an output port. The Next Objective passed + * a chain of groups. The simple Next Objective passed * in by the application has to be broken up into a group chain - * to satisfy this TTP. + * comprising of an L3 Unicast Group that points to an L2 Interface + * Group which in-turn points to an output port. In some cases, the simple + * next Objective can just be an L2 interface without the need for chaining. * * @param nextObj the nextObjective of type SIMPLE */ private void processSimpleNextObjective(NextObjective nextObj) { // break up simple next objective to GroupChain objects TrafficTreatment treatment = nextObj.next().iterator().next(); + + GroupInfo groupInfo = createL2L3Chain(treatment, nextObj.id(), + nextObj.appId(), false, + nextObj.meta()); + if (groupInfo == null) { + log.error("Could not process nextObj={} in dev:{}", nextObj.id(), deviceId); + return; + } + // create object for local and distributed storage + Deque<GroupKey> gkeyChain = new ArrayDeque<>(); + gkeyChain.addFirst(groupInfo.innerGrpDesc.appCookie()); + gkeyChain.addFirst(groupInfo.outerGrpDesc.appCookie()); + OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup( + Collections.singletonList(gkeyChain), + nextObj); + + // store l3groupkey with the ofdpaGroupChain for the nextObjective that depends on it + pendingNextObjectives.put(groupInfo.outerGrpDesc.appCookie(), ofdpaGrp); + + // now we are ready to send the l2 groupDescription (inner), as all the stores + // that will get async replies have been updated. By waiting to update + // the stores, we prevent nasty race conditions. + groupService.addGroup(groupInfo.innerGrpDesc); + } + + /** + * Creates one of two possible group-chains from the treatment + * passed in. Depending on the MPLS boolean, this method either creates + * an L3Unicast Group --> L2Interface Group, if mpls is false; + * or MPLSInterface Group --> L2Interface Group, if mpls is true; + * The returned 'inner' group description is always the L2 Interface group. + * + * @param treatment that needs to be broken up to create the group chain + * @param nextId of the next objective that needs this group chain + * @param appId of the application that sent this next objective + * @param mpls determines if L3Unicast or MPLSInterface group is created + * @param meta metadata passed in by the application as part of the nextObjective + * @return GroupInfo containing the GroupDescription of the + * L2Interface group(inner) and the GroupDescription of the (outer) + * L3Unicast/MPLSInterface group. May return null if there is an + * error in processing the chain + */ + private GroupInfo createL2L3Chain(TrafficTreatment treatment, int nextId, + ApplicationId appId, boolean mpls, + TrafficSelector meta) { // for the l2interface group, get vlan and port info - // for the l3unicast group, get the src/dst mac and vlan info - TrafficTreatment.Builder l3utt = DefaultTrafficTreatment.builder(); - TrafficTreatment.Builder l2itt = DefaultTrafficTreatment.builder(); + // for the outer group, get the src/dst mac, and vlan info + TrafficTreatment.Builder outerTtb = DefaultTrafficTreatment.builder(); + TrafficTreatment.Builder innerTtb = DefaultTrafficTreatment.builder(); VlanId vlanid = null; long portNum = 0; for (Instruction ins : treatment.allInstructions()) { @@ -786,76 +900,144 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline L2ModificationInstruction l2ins = (L2ModificationInstruction) ins; switch (l2ins.subtype()) { case ETH_DST: - l3utt.setEthDst(((ModEtherInstruction) l2ins).mac()); + outerTtb.setEthDst(((ModEtherInstruction) l2ins).mac()); break; case ETH_SRC: - l3utt.setEthSrc(((ModEtherInstruction) l2ins).mac()); + outerTtb.setEthSrc(((ModEtherInstruction) l2ins).mac()); break; case VLAN_ID: vlanid = ((ModVlanIdInstruction) l2ins).vlanId(); - l3utt.setVlanId(vlanid); + outerTtb.setVlanId(vlanid); + break; + case VLAN_POP: + innerTtb.popVlan(); break; case DEC_MPLS_TTL: case MPLS_LABEL: case MPLS_POP: case MPLS_PUSH: case VLAN_PCP: - case VLAN_POP: case VLAN_PUSH: default: break; } } else if (ins.type() == Instruction.Type.OUTPUT) { portNum = ((OutputInstruction) ins).port().toLong(); - l2itt.add(ins); + innerTtb.add(ins); } else { log.warn("Driver does not handle this type of TrafficTreatment" + " instruction in nextObjectives: {}", ins.type()); } } + if (vlanid == null) { + //use the vlanid associated with the port + vlanid = port2Vlan.get(PortNumber.portNumber(portNum)); + } + + if (vlanid == null) { + // use metadata + for (Criterion metaCriterion : meta.criteria()) { + if (metaCriterion.type() == Type.VLAN_VID) { + vlanid = ((VlanIdCriterion) metaCriterion).vlanId(); + } + } + } + + if (vlanid == null) { + log.error("Driver cannot process an L2/L3 group chain without " + + "egress vlan information for dev: {} port:{}", + deviceId, portNum); + return null; + } + // assemble information for ofdpa l2interface group - int l2gk = nextObj.id() | GROUP1MASK; - final GroupKey l2groupkey = new DefaultGroupKey(appKryo.serialize(l2gk)); Integer l2groupId = L2INTERFACEMASK | (vlanid.toShort() << 16) | (int) portNum; + // a globally unique groupkey that is different for ports in the same devices + // but different for the same portnumber on different devices. Also different + // for the various group-types created out of the same next objective. + int l2gk = 0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum); + final GroupKey l2groupkey = new DefaultGroupKey(appKryo.serialize(l2gk)); - // assemble information for ofdpa l3unicast group - int l3gk = nextObj.id() | GROUP0MASK; - final GroupKey l3groupkey = new DefaultGroupKey(appKryo.serialize(l3gk)); - Integer l3groupId = L3UNICASTMASK | (int) portNum; - l3utt.group(new DefaultGroupId(l2groupId)); - GroupChainElem gce = new GroupChainElem(l3groupkey, l3groupId, - GroupDescription.Type.INDIRECT, - Collections.singletonList(l3utt.build()), - nextObj.appId(), 1); - - // create object for local and distributed storage - List<GroupKey> gkeys = new ArrayList<GroupKey>(); - gkeys.add(l3groupkey); // group0 in chain - gkeys.add(l2groupkey); // group1 in chain - OfdpaGroupChain ofdpaGrp = new OfdpaGroupChain(gkeys, nextObj); + // assemble information for outer group + GroupDescription outerGrpDesc = null; + if (mpls) { + // outer group is MPLSInteface + Integer mplsgroupId = MPLSINTERFACEMASK | (int) portNum; + // using mplsinterfacemask in groupkey to differentiate from l2interface + int mplsgk = MPLSINTERFACEMASK | (0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum)); + final GroupKey mplsgroupkey = new DefaultGroupKey(appKryo.serialize(mplsgk)); + outerTtb.group(new DefaultGroupId(l2groupId)); + // create the mpls-interface group description to wait for the + // l2 interface group to be processed + GroupBucket mplsinterfaceGroupBucket = + DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build()); + outerGrpDesc = new DefaultGroupDescription( + deviceId, + GroupDescription.Type.INDIRECT, + new GroupBuckets(Collections.singletonList( + mplsinterfaceGroupBucket)), + mplsgroupkey, + mplsgroupId, + appId); + log.debug("Trying MPLS-Interface: device:{} gid:{} gkey:{} nextid:{}", + deviceId, Integer.toHexString(mplsgroupId), + mplsgroupkey, nextId); + } else { + // outer group is L3Unicast + Integer l3groupId = L3UNICASTMASK | (int) portNum; + int l3gk = L3UNICASTMASK | (0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum)); + final GroupKey l3groupkey = new DefaultGroupKey(appKryo.serialize(l3gk)); + outerTtb.group(new DefaultGroupId(l2groupId)); + // create the l3unicast group description to wait for the + // l2 interface group to be processed + GroupBucket l3unicastGroupBucket = + DefaultGroupBucket.createIndirectGroupBucket(outerTtb.build()); + outerGrpDesc = new DefaultGroupDescription( + deviceId, + GroupDescription.Type.INDIRECT, + new GroupBuckets(Collections.singletonList( + l3unicastGroupBucket)), + l3groupkey, + l3groupId, + appId); + log.debug("Trying L3Unicast: device:{} gid:{} gkey:{} nextid:{}", + deviceId, Integer.toHexString(l3groupId), + l3groupkey, nextId); + } - // store l2groupkey with the groupChainElem for the l3group that depends on it - pendingGroups.put(l2groupkey, gce); + // store l2groupkey with the groupChainElem for the outer-group that depends on it + GroupChainElem gce = new GroupChainElem(outerGrpDesc, 1); + Set<GroupChainElem> gceSet = Collections.newSetFromMap( + new ConcurrentHashMap<GroupChainElem, Boolean>()); + gceSet.add(gce); + Set<GroupChainElem> retval = pendingGroups.putIfAbsent(l2groupkey, gceSet); + if (retval != null) { + retval.add(gce); + } - // store l3groupkey with the ofdpaGroupChain for the nextObjective that depends on it - pendingNextObjectives.put(l3groupkey, ofdpaGrp); + // create group description for the inner l2interfacegroup + GroupBucket l2interfaceGroupBucket = + DefaultGroupBucket.createIndirectGroupBucket(innerTtb.build()); + GroupDescription l2groupDescription = + new DefaultGroupDescription( + deviceId, + GroupDescription.Type.INDIRECT, + new GroupBuckets(Collections.singletonList( + l2interfaceGroupBucket)), + l2groupkey, + l2groupId, + appId); + log.debug("Trying L2Interface: device:{} gid:{} gkey:{} nextId:{}", + deviceId, Integer.toHexString(l2groupId), + l2groupkey, nextId); + return new GroupInfo(l2groupDescription, outerGrpDesc); - // create group description for the ofdpa l2interfacegroup and send to groupservice - GroupBucket bucket = - DefaultGroupBucket.createIndirectGroupBucket(l2itt.build()); - GroupDescription groupDescription = new DefaultGroupDescription(deviceId, - GroupDescription.Type.INDIRECT, - new GroupBuckets(Collections.singletonList(bucket)), - l2groupkey, - l2groupId, - nextObj.appId()); - groupService.addGroup(groupDescription); } /** * As per the OFDPA 2.0 TTP, packets are sent out of ports by using - * a chain of groups. The Next Objective passed in by the application + * a chain of groups. The broadcast Next Objective passed in by the application * has to be broken up into a group chain comprising of an * L2 Flood group whose buckets point to L2 Interface groups. * @@ -866,9 +1048,9 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline Collection<TrafficTreatment> buckets = nextObj.next(); // each treatment is converted to an L2 interface group - int indicator = 0; VlanId vlanid = null; - List<GroupInfo> groupInfoCollection = new ArrayList<>(); + List<GroupDescription> l2interfaceGroupDescs = new ArrayList<>(); + List<Deque<GroupKey>> allGroupKeys = new ArrayList<>(); for (TrafficTreatment treatment : buckets) { TrafficTreatment.Builder newTreatment = DefaultTrafficTreatment.builder(); PortNumber portNum = null; @@ -907,83 +1089,284 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline } } - // assemble info for all l2 interface groups - indicator += GROUP1MASK; - int l2gk = nextObj.id() | indicator; + // assemble info for l2 interface group + int l2gk = 0x0ffffff & (deviceId.hashCode() << 8 | (int) portNum.toLong()); final GroupKey l2groupkey = new DefaultGroupKey(appKryo.serialize(l2gk)); Integer l2groupId = L2INTERFACEMASK | (vlanid.toShort() << 16) | (int) portNum.toLong(); - GroupBucket newbucket = + GroupBucket l2interfaceGroupBucket = DefaultGroupBucket.createIndirectGroupBucket(newTreatment.build()); + GroupDescription l2interfaceGroupDescription = + new DefaultGroupDescription( + deviceId, + GroupDescription.Type.INDIRECT, + new GroupBuckets(Collections.singletonList( + l2interfaceGroupBucket)), + l2groupkey, + l2groupId, + nextObj.appId()); + log.debug("Trying L2-Interface: device:{} gid:{} gkey:{} nextid:{}", + deviceId, Integer.toHexString(l2groupId), + l2groupkey, nextObj.id()); + + Deque<GroupKey> gkeyChain = new ArrayDeque<>(); + gkeyChain.addFirst(l2groupkey); // store the info needed to create this group - groupInfoCollection.add(new GroupInfo(l2groupId, l2groupkey, newbucket)); + l2interfaceGroupDescs.add(l2interfaceGroupDescription); + allGroupKeys.add(gkeyChain); } // assemble info for l2 flood group - int l2floodgk = nextObj.id() | GROUP0MASK; - final GroupKey l2floodgroupkey = new DefaultGroupKey(appKryo.serialize(l2floodgk)); Integer l2floodgroupId = L2FLOODMASK | (vlanid.toShort() << 16) | nextObj.id(); - // collection of treatment with groupids of l2 interface groups - List<TrafficTreatment> floodtt = new ArrayList<>(); - for (GroupInfo gi : groupInfoCollection) { + int l2floodgk = L2FLOODMASK | nextObj.id() << 12; + final GroupKey l2floodgroupkey = new DefaultGroupKey(appKryo.serialize(l2floodgk)); + // collection of group buckets pointing to all the l2 interface groups + List<GroupBucket> l2floodBuckets = new ArrayList<>(); + for (GroupDescription l2intGrpDesc : l2interfaceGroupDescs) { TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder(); - ttb.group(new DefaultGroupId(gi.groupId)); - floodtt.add(ttb.build()); + ttb.group(new DefaultGroupId(l2intGrpDesc.givenGroupId())); + GroupBucket abucket = DefaultGroupBucket.createAllGroupBucket(ttb.build()); + l2floodBuckets.add(abucket); } - GroupChainElem gce = new GroupChainElem(l2floodgroupkey, l2floodgroupId, - GroupDescription.Type.ALL, - floodtt, - nextObj.appId(), - groupInfoCollection.size()); + // create the l2flood group-description to wait for all the + // l2interface groups to be processed + GroupDescription l2floodGroupDescription = + new DefaultGroupDescription( + deviceId, + GroupDescription.Type.ALL, + new GroupBuckets(l2floodBuckets), + l2floodgroupkey, + l2floodgroupId, + nextObj.appId()); + GroupChainElem gce = new GroupChainElem(l2floodGroupDescription, + l2interfaceGroupDescs.size()); + log.debug("Trying L2-Flood: device:{} gid:{} gkey:{} nextid:{}", + deviceId, Integer.toHexString(l2floodgroupId), + l2floodgroupkey, nextObj.id()); // create objects for local and distributed storage - List<GroupKey> gkeys = new ArrayList<GroupKey>(); - gkeys.add(l2floodgroupkey); // group0 in chain - OfdpaGroupChain ofdpaGrp = new OfdpaGroupChain(gkeys, nextObj); + allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l2floodgroupkey)); + OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj); // store l2floodgroupkey with the ofdpaGroupChain for the nextObjective // that depends on it pendingNextObjectives.put(l2floodgroupkey, ofdpaGrp); - for (GroupInfo gi : groupInfoCollection) { + for (GroupDescription l2intGrpDesc : l2interfaceGroupDescs) { // store all l2groupkeys with the groupChainElem for the l2floodgroup // that depends on it - pendingGroups.put(gi.groupKey, gce); + Set<GroupChainElem> gceSet = Collections.newSetFromMap( + new ConcurrentHashMap<GroupChainElem, Boolean>()); + gceSet.add(gce); + Set<GroupChainElem> retval = pendingGroups.putIfAbsent( + l2intGrpDesc.appCookie(), gceSet); + if (retval != null) { + retval.add(gce); + } // create and send groups for all l2 interface groups - GroupDescription groupDescription = - new DefaultGroupDescription( - deviceId, - GroupDescription.Type.INDIRECT, - new GroupBuckets(Collections.singletonList(gi.groupBucket)), - gi.groupKey, - gi.groupId, - nextObj.appId()); - groupService.addGroup(groupDescription); + groupService.addGroup(l2intGrpDesc); } } + /** + * Utility class for moving group information around. + * + */ private class GroupInfo { - private Integer groupId; - private GroupKey groupKey; - private GroupBucket groupBucket; - - GroupInfo(Integer groupId, GroupKey groupKey, GroupBucket groupBucket) { - this.groupBucket = groupBucket; - this.groupId = groupId; - this.groupKey = groupKey; + private GroupDescription innerGrpDesc; + private GroupDescription outerGrpDesc; + + GroupInfo(GroupDescription innerGrpDesc, GroupDescription outerGrpDesc) { + this.innerGrpDesc = innerGrpDesc; + this.outerGrpDesc = outerGrpDesc; } } + /** + * As per the OFDPA 2.0 TTP, packets are sent out of ports by using + * a chain of groups. The hashed Next Objective passed in by the application + * has to be broken up into a group chain comprising of an + * L3 ECMP group as the top level group. Buckets of this group can point + * to a variety of groups in a group chain, depending on the whether + * MPLS labels are being pushed or not. + * <p> + * NOTE: We do not create MPLS ECMP groups as they are unimplemented in + * OF-DPA 2.0 (even though it is in the spec). Therefore we do not + * check the nextObjective meta. + * + * @param nextObj the nextObjective of type HASHED + */ private void processHashedNextObjective(NextObjective nextObj) { - // TODO Auto-generated method stub + // break up hashed next objective to multiple groups + Collection<TrafficTreatment> buckets = nextObj.next(); + + // storage for all group keys in the chain of groups created + List<Deque<GroupKey>> allGroupKeys = new ArrayList<>(); + List<GroupInfo> unsentGroups = new ArrayList<>(); + for (TrafficTreatment bucket : buckets) { + //figure out how many labels are pushed in each bucket + int labelsPushed = 0; + MplsLabel innermostLabel = null; + for (Instruction ins : bucket.allInstructions()) { + if (ins.type() == Instruction.Type.L2MODIFICATION) { + L2ModificationInstruction l2ins = (L2ModificationInstruction) ins; + if (l2ins.subtype() == L2SubType.MPLS_PUSH) { + labelsPushed++; + } + if (l2ins.subtype() == L2SubType.MPLS_LABEL) { + if (innermostLabel == null) { + innermostLabel = ((ModMplsLabelInstruction) l2ins).mplsLabel(); + } + } + } + } + + Deque<GroupKey> gkeyChain = new ArrayDeque<>(); + // XXX we only deal with 0 and 1 label push right now + if (labelsPushed == 0) { + GroupInfo nolabelGroupInfo = createL2L3Chain(bucket, nextObj.id(), + nextObj.appId(), false, + nextObj.meta()); + if (nolabelGroupInfo == null) { + log.error("Could not process nextObj={} in dev:{}", + nextObj.id(), deviceId); + return; + } + gkeyChain.addFirst(nolabelGroupInfo.innerGrpDesc.appCookie()); + gkeyChain.addFirst(nolabelGroupInfo.outerGrpDesc.appCookie()); + + // we can't send the inner group description yet, as we have to + // create the dependent ECMP group first. So we store.. + unsentGroups.add(nolabelGroupInfo); + + } else if (labelsPushed == 1) { + GroupInfo onelabelGroupInfo = createL2L3Chain(bucket, nextObj.id(), + nextObj.appId(), true, + nextObj.meta()); + if (onelabelGroupInfo == null) { + log.error("Could not process nextObj={} in dev:{}", + nextObj.id(), deviceId); + return; + } + // we need to add another group to this chain - the L3VPN group + TrafficTreatment.Builder l3vpnTtb = DefaultTrafficTreatment.builder(); + l3vpnTtb.pushMpls() + .setMpls(innermostLabel) + .setMplsBos(true) + .copyTtlOut() + .group(new DefaultGroupId( + onelabelGroupInfo.outerGrpDesc.givenGroupId())); + GroupBucket l3vpnGrpBkt = + DefaultGroupBucket.createIndirectGroupBucket(l3vpnTtb.build()); + int l3vpngroupId = L3VPNMASK | l3vpnindex.incrementAndGet(); + int l3vpngk = L3VPNMASK | nextObj.id() << 12 | l3vpnindex.get(); + GroupKey l3vpngroupkey = new DefaultGroupKey(appKryo.serialize(l3vpngk)); + GroupDescription l3vpnGroupDesc = + new DefaultGroupDescription( + deviceId, + GroupDescription.Type.INDIRECT, + new GroupBuckets(Collections.singletonList( + l3vpnGrpBkt)), + l3vpngroupkey, + l3vpngroupId, + nextObj.appId()); + GroupChainElem l3vpnGce = new GroupChainElem(l3vpnGroupDesc, 1); + Set<GroupChainElem> gceSet = Collections.newSetFromMap( + new ConcurrentHashMap<GroupChainElem, Boolean>()); + gceSet.add(l3vpnGce); + Set<GroupChainElem> retval = pendingGroups + .putIfAbsent(onelabelGroupInfo.outerGrpDesc.appCookie(), gceSet); + if (retval != null) { + retval.add(l3vpnGce); + } + + gkeyChain.addFirst(onelabelGroupInfo.innerGrpDesc.appCookie()); + gkeyChain.addFirst(onelabelGroupInfo.outerGrpDesc.appCookie()); + gkeyChain.addFirst(l3vpngroupkey); + + //now we can replace the outerGrpDesc with the one we just created + onelabelGroupInfo.outerGrpDesc = l3vpnGroupDesc; + + // we can't send the innermost group yet, as we have to create + // the dependent ECMP group first. So we store ... + unsentGroups.add(onelabelGroupInfo); + + log.debug("Trying L3VPN: device:{} gid:{} gkey:{} nextId:{}", + deviceId, Integer.toHexString(l3vpngroupId), + l3vpngroupkey, nextObj.id()); + + } else { + log.warn("Driver currently does not handle more than 1 MPLS " + + "labels. Not processing nextObjective {}", nextObj); + return; + } + + // all groups in this chain + allGroupKeys.add(gkeyChain); + } + + // now we can create the outermost L3 ECMP group + List<GroupBucket> l3ecmpGroupBuckets = new ArrayList<>(); + for (GroupInfo gi : unsentGroups) { + // create ECMP bucket to point to the outer group + TrafficTreatment.Builder ttb = DefaultTrafficTreatment.builder(); + ttb.group(new DefaultGroupId(gi.outerGrpDesc.givenGroupId())); + GroupBucket sbucket = DefaultGroupBucket + .createSelectGroupBucket(ttb.build()); + l3ecmpGroupBuckets.add(sbucket); + } + int l3ecmpGroupId = L3ECMPMASK | nextObj.id() << 12; + GroupKey l3ecmpGroupKey = new DefaultGroupKey(appKryo.serialize(l3ecmpGroupId)); + GroupDescription l3ecmpGroupDesc = + new DefaultGroupDescription( + deviceId, + GroupDescription.Type.SELECT, + new GroupBuckets(l3ecmpGroupBuckets), + l3ecmpGroupKey, + l3ecmpGroupId, + nextObj.appId()); + GroupChainElem l3ecmpGce = new GroupChainElem(l3ecmpGroupDesc, + l3ecmpGroupBuckets.size()); + + // create objects for local and distributed storage + allGroupKeys.forEach(gkeyChain -> gkeyChain.addFirst(l3ecmpGroupKey)); + OfdpaNextGroup ofdpaGrp = new OfdpaNextGroup(allGroupKeys, nextObj); + + // store l3ecmpGroupKey with the ofdpaGroupChain for the nextObjective + // that depends on it + pendingNextObjectives.put(l3ecmpGroupKey, ofdpaGrp); + + log.debug("Trying L3ECMP: device:{} gid:{} gkey:{} nextId:{}", + deviceId, Integer.toHexString(l3ecmpGroupId), + l3ecmpGroupKey, nextObj.id()); + // finally we are ready to send the innermost groups + for (GroupInfo gi : unsentGroups) { + log.debug("Sending innermost group {} in group chain on device {} ", + Integer.toHexString(gi.innerGrpDesc.givenGroupId()), deviceId); + Set<GroupChainElem> gceSet = Collections.newSetFromMap( + new ConcurrentHashMap<GroupChainElem, Boolean>()); + gceSet.add(l3ecmpGce); + Set<GroupChainElem> retval = pendingGroups + .putIfAbsent(gi.outerGrpDesc.appCookie(), gceSet); + if (retval != null) { + retval.add(l3ecmpGce); + } + + groupService.addGroup(gi.innerGrpDesc); + } + } private void addBucketToGroup(NextObjective nextObjective) { // TODO Auto-generated method stub } + private void waitToAddBucketToGroup(NextObjective nextObjective) { + // TODO Auto-generated method stub + } + private void removeBucketFromGroup(NextObjective nextObjective) { // TODO Auto-generated method stub } @@ -1009,45 +1392,11 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline private void processGroupChain(GroupChainElem gce) { int waitOnGroups = gce.decrementAndGetGroupsWaitedOn(); if (waitOnGroups != 0) { - log.debug("GCE: {} waiting on {} groups. Not processing yet", - gce, waitOnGroups); + log.debug("GCE: {} not ready to be processed", gce); return; } - List<GroupBucket> buckets = new ArrayList<>(); - switch (gce.groupType) { - case INDIRECT: - GroupBucket ibucket = DefaultGroupBucket - .createIndirectGroupBucket(gce.bucketActions.iterator().next()); - buckets.add(ibucket); - break; - case ALL: - for (TrafficTreatment tt : gce.bucketActions) { - GroupBucket abucket = DefaultGroupBucket - .createAllGroupBucket(tt); - buckets.add(abucket); - } - break; - case SELECT: - for (TrafficTreatment tt : gce.bucketActions) { - GroupBucket sbucket = DefaultGroupBucket - .createSelectGroupBucket(tt); - buckets.add(sbucket); - } - break; - case FAILOVER: - default: - log.error("Unknown or unimplemented GroupChainElem {}", gce); - } - - if (buckets.size() > 0) { - GroupDescription groupDesc = new DefaultGroupDescription( - deviceId, gce.groupType, - new GroupBuckets(buckets), - gce.gkey, - gce.givenGroupId, - gce.appId); - groupService.addGroup(groupDesc); - } + log.debug("GCE: {} ready to be processed", gce); + groupService.addGroup(gce.groupDescription); } private class GroupChecker implements Runnable { @@ -1063,19 +1412,23 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline keys.stream().forEach(key -> { //first check for group chain - GroupChainElem gce = pendingGroups.remove(key); - if (gce != null) { - log.info("Group service processed group key {}. Processing next " - + "group in group chain with group key {}", - appKryo.deserialize(key.key()), - appKryo.deserialize(gce.gkey.key())); - processGroupChain(gce); + Set<GroupChainElem> gceSet = pendingGroups.remove(key); + if (gceSet != null) { + for (GroupChainElem gce : gceSet) { + log.info("Group service processed group key {} in device {}. " + + "Processing next group in group chain with group id {}", + key, deviceId, + Integer.toHexString(gce.groupDescription.givenGroupId())); + processGroupChain(gce); + } } else { - OfdpaGroupChain obj = pendingNextObjectives.getIfPresent(key); - log.info("Group service processed group key {}. Done implementing " - + "next objective: {}", appKryo.deserialize(key.key()), - obj.nextObjective().id()); + OfdpaNextGroup obj = pendingNextObjectives.getIfPresent(key); if (obj != null) { + log.info("Group service processed group key {} in device:{}. " + + "Done implementing next objective: {} <<-->> gid:{}", + key, deviceId, obj.nextObjective().id(), + Integer.toHexString(groupService.getGroup(deviceId, key) + .givenGroupId())); pass(obj.nextObjective()); pendingNextObjectives.invalidate(key); flowObjectiveStore.putNextGroup(obj.nextObjective().id(), obj); @@ -1088,23 +1441,27 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline private class InnerGroupListener implements GroupListener { @Override public void event(GroupEvent event) { - log.debug("received group event of type {}", event.type()); + log.trace("received group event of type {}", event.type()); if (event.type() == GroupEvent.Type.GROUP_ADDED) { GroupKey key = event.subject().appCookie(); // first check for group chain - GroupChainElem gce = pendingGroups.remove(key); - if (gce != null) { - log.info("group ADDED with group key {} .. " - + "Processing next group in group chain with group key {}", - appKryo.deserialize(key.key()), - appKryo.deserialize(gce.gkey.key())); - processGroupChain(gce); + Set<GroupChainElem> gceSet = pendingGroups.remove(key); + if (gceSet != null) { + for (GroupChainElem gce : gceSet) { + log.info("group ADDED with group key {} .. " + + "Processing next group in group chain with group key {}", + key, + gce.groupDescription.appCookie()); + processGroupChain(gce); + } } else { - OfdpaGroupChain obj = pendingNextObjectives.getIfPresent(key); + OfdpaNextGroup obj = pendingNextObjectives.getIfPresent(key); if (obj != null) { - log.info("group ADDED with key {}.. Done implementing next " - + "objective: {}", - appKryo.deserialize(key.key()), obj.nextObjective().id()); + log.info("group ADDED with key {} in dev {}.. Done implementing next " + + "objective: {} <<-->> gid:{}", + key, deviceId, obj.nextObjective().id(), + Integer.toHexString(groupService.getGroup(deviceId, key) + .givenGroupId())); pass(obj.nextObjective()); pendingNextObjectives.invalidate(key); flowObjectiveStore.putNextGroup(obj.nextObjective().id(), obj); @@ -1115,30 +1472,35 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline } /** - * Represents a group-chain that implements a Next-Objective from - * the application. Includes information about the next objective Id, and the - * group keys for the groups in the group chain. The chain is expected to - * look like group0 --> group 1 --> outPort. Information about the groups - * themselves can be fetched from the Group Service using the group keys from - * objects instantiating this class. + * Represents an entire group-chain that implements a Next-Objective from + * the application. The objective is represented as a list of deques, where + * each deque can is a separate chain of groups. + * <p> + * For example, an ECMP group with 3 buckets, where each bucket points to + * a group chain of L3 Unicast and L2 interface groups will look like this: + * <ul> + * <li>List[0] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last) + * <li>List[1] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last) + * <li>List[2] is a Deque of GroupKeyECMP(first)-GroupKeyL3(middle)-GroupKeyL2(last) + * </ul> + * where the first element of each deque is the same, representing the + * top level ECMP group, while every other element represents a unique groupKey. + * <p> + * Also includes information about the next objective that + * resulted in this group-chain. * - * XXX Revisit this - since the forwarding objective only ever needs the - * groupkey of the top-level group in the group chain, why store a series - * of groupkeys. Also the group-chain list only works for 1-to-1 chaining, - * not for 1-to-many chaining. */ - private class OfdpaGroupChain implements NextGroup { + private class OfdpaNextGroup implements NextGroup { private final NextObjective nextObj; - private final List<GroupKey> gkeys; + private final List<Deque<GroupKey>> gkeys; - /** expected group chain: group0 --> group1 --> port. */ - public OfdpaGroupChain(List<GroupKey> gkeys, NextObjective nextObj) { + public OfdpaNextGroup(List<Deque<GroupKey>> gkeys, NextObjective nextObj) { this.gkeys = gkeys; this.nextObj = nextObj; } @SuppressWarnings("unused") - public List<GroupKey> groupKeys() { + public List<Deque<GroupKey>> groupKey() { return gkeys; } @@ -1161,22 +1523,11 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline * preceding groups in the group chain to be created. */ private class GroupChainElem { - private Collection<TrafficTreatment> bucketActions; - private Integer givenGroupId; - private GroupDescription.Type groupType; - private GroupKey gkey; - private ApplicationId appId; + private GroupDescription groupDescription; private AtomicInteger waitOnGroups; - GroupChainElem(GroupKey gkey, Integer givenGroupId, - GroupDescription.Type groupType, - Collection<TrafficTreatment> tr, ApplicationId appId, - int waitOnGroups) { - this.bucketActions = tr; - this.givenGroupId = givenGroupId; - this.groupType = groupType; - this.gkey = gkey; - this.appId = appId; + GroupChainElem(GroupDescription groupDescription, int waitOnGroups) { + this.groupDescription = groupDescription; this.waitOnGroups = new AtomicInteger(waitOnGroups); } @@ -1194,7 +1545,10 @@ public class OFDPA2Pipeline extends AbstractHandlerBehaviour implements Pipeline @Override public String toString() { - return Integer.toHexString(givenGroupId); + return (Integer.toHexString(groupDescription.givenGroupId()) + + " groupKey: " + groupDescription.appCookie() + + " waiting-on-groups: " + waitOnGroups.get() + + " device: " + deviceId); } } diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OltPipeline.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OltPipeline.java new file mode 100644 index 00000000..8a7b22b8 --- /dev/null +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/OltPipeline.java @@ -0,0 +1,238 @@ +/* + * 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.driver.pipeline; + +import org.onlab.osgi.ServiceDirectory; +import org.onlab.packet.EthType; +import org.onosproject.core.ApplicationId; +import org.onosproject.core.CoreService; +import org.onosproject.net.DefaultAnnotations; +import org.onosproject.net.Device; +import org.onosproject.net.DeviceId; +import org.onosproject.net.MastershipRole; +import org.onosproject.net.PortNumber; +import org.onosproject.net.behaviour.Pipeliner; +import org.onosproject.net.behaviour.PipelinerContext; +import org.onosproject.net.device.DefaultDeviceDescription; +import org.onosproject.net.device.DeviceDescription; +import org.onosproject.net.device.DeviceProvider; +import org.onosproject.net.device.DeviceProviderRegistry; +import org.onosproject.net.device.DeviceService; +import org.onosproject.net.driver.AbstractHandlerBehaviour; +import org.onosproject.net.flow.DefaultFlowRule; +import org.onosproject.net.flow.DefaultTrafficSelector; +import org.onosproject.net.flow.DefaultTrafficTreatment; +import org.onosproject.net.flow.FlowRule; +import org.onosproject.net.flow.FlowRuleOperations; +import org.onosproject.net.flow.FlowRuleOperationsContext; +import org.onosproject.net.flow.FlowRuleService; +import org.onosproject.net.flow.TrafficSelector; +import org.onosproject.net.flow.TrafficTreatment; +import org.onosproject.net.flow.instructions.Instructions; +import org.onosproject.net.flowobjective.FilteringObjective; +import org.onosproject.net.flowobjective.ForwardingObjective; +import org.onosproject.net.flowobjective.NextObjective; +import org.onosproject.net.flowobjective.ObjectiveError; +import org.onosproject.net.packet.PacketPriority; +import org.onosproject.net.provider.AbstractProvider; +import org.onosproject.net.provider.ProviderId; +import org.slf4j.Logger; + +import static com.google.common.base.Preconditions.checkNotNull; +import static org.slf4j.LoggerFactory.getLogger; + +/** + * Pipeliner for OLT device. + */ +public class OltPipeline extends AbstractHandlerBehaviour implements Pipeliner { + + private final Logger log = getLogger(getClass()); + + static final ProviderId PID = new ProviderId("olt", "org.onosproject.olt", true); + + static final String DEVICE = "isAccess"; + static final String OLT = "true"; + + private ServiceDirectory serviceDirectory; + private FlowRuleService flowRuleService; + private DeviceId deviceId; + private CoreService coreService; + + private ApplicationId appId; + + private DeviceProvider provider = new AnnotationProvider(); + + + @Override + public void init(DeviceId deviceId, PipelinerContext context) { + this.serviceDirectory = context.directory(); + this.deviceId = deviceId; + DeviceProviderRegistry registry = + serviceDirectory.get(DeviceProviderRegistry.class); + flowRuleService = serviceDirectory.get(FlowRuleService.class); + coreService = serviceDirectory.get(CoreService.class); + + /*try { + DeviceProviderService providerService = registry.register(provider); + providerService.deviceConnected(deviceId, + description(deviceId, DEVICE, OLT)); + } finally { + registry.unregister(provider); + }*/ + + appId = coreService.registerApplication( + "org.onosproject.driver.OLTPipeline"); + + TrafficSelector selector = DefaultTrafficSelector.builder() + .matchEthType(EthType.EtherType.EAPOL.ethType().toShort()) + .build(); + + TrafficTreatment treatment = DefaultTrafficTreatment.builder() + .punt() + .build(); + + FlowRule flowRule = new DefaultFlowRule(deviceId, selector, treatment, + PacketPriority.CONTROL.priorityValue(), + appId, 0, true, null); + + //flowRuleService.applyFlowRules(flowRule); + } + + @Override + public void filter(FilteringObjective filter) { + throw new UnsupportedOperationException("OLT does not filter."); + } + + @Override + public void forward(ForwardingObjective fwd) { + FlowRuleOperations.Builder flowBuilder = FlowRuleOperations.builder(); + + if (fwd.flag() != ForwardingObjective.Flag.VERSATILE) { + throw new UnsupportedOperationException( + "Only VERSATILE is supported."); + } + + boolean isPunt = fwd.treatment().immediate().stream().anyMatch(i -> { + if (i instanceof Instructions.OutputInstruction) { + Instructions.OutputInstruction out = (Instructions.OutputInstruction) i; + return out.port().equals(PortNumber.CONTROLLER); + } + return false; + }); + + if (isPunt) { + return; + } + + TrafficSelector selector = fwd.selector(); + TrafficTreatment treatment = fwd.treatment(); + + FlowRule.Builder ruleBuilder = DefaultFlowRule.builder() + .forDevice(deviceId) + .withSelector(selector) + .withTreatment(treatment) + .fromApp(fwd.appId()) + .withPriority(fwd.priority()); + + if (fwd.permanent()) { + ruleBuilder.makePermanent(); + } else { + ruleBuilder.makeTemporary(fwd.timeout()); + } + + switch (fwd.op()) { + case ADD: + flowBuilder.add(ruleBuilder.build()); + break; + case REMOVE: + flowBuilder.remove(ruleBuilder.build()); + break; + default: + log.warn("Unknown operation {}", fwd.op()); + } + + flowRuleService.apply(flowBuilder.build(new FlowRuleOperationsContext() { + @Override + public void onSuccess(FlowRuleOperations ops) { + if (fwd.context().isPresent()) { + fwd.context().get().onSuccess(fwd); + } + } + + @Override + public void onError(FlowRuleOperations ops) { + if (fwd.context().isPresent()) { + fwd.context().get().onError(fwd, ObjectiveError.FLOWINSTALLATIONFAILED); + } + } + })); + } + + @Override + public void next(NextObjective nextObjective) { + throw new UnsupportedOperationException("OLT does not next hop."); + } + + /** + * Build a device description. + * + * @param deviceId a deviceId + * @param key the key of the annotation + * @param value the value for the annotation + * @return a device description + */ + private DeviceDescription description(DeviceId deviceId, String key, String value) { + DeviceService deviceService = serviceDirectory.get(DeviceService.class); + Device device = deviceService.getDevice(deviceId); + + checkNotNull(device, "Device not found in device service."); + + DefaultAnnotations.Builder builder = DefaultAnnotations.builder(); + if (value != null) { + builder.set(key, value); + } else { + builder.remove(key); + } + return new DefaultDeviceDescription(device.id().uri(), device.type(), + device.manufacturer(), device.hwVersion(), + device.swVersion(), device.serialNumber(), + device.chassisId(), builder.build()); + } + + /** + * Simple ancillary provider used to annotate device. + */ + private static final class AnnotationProvider + extends AbstractProvider implements DeviceProvider { + private AnnotationProvider() { + super(PID); + } + + @Override + public void triggerProbe(DeviceId deviceId) { + } + + @Override + public void roleChanged(DeviceId deviceId, MastershipRole newRole) { + } + + @Override + public boolean isReachable(DeviceId deviceId) { + return false; + } + } + +} diff --git a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java index b5541065..8ac5eec8 100644 --- a/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java +++ b/framework/src/onos/drivers/src/main/java/org/onosproject/driver/pipeline/SpringOpenTTP.java @@ -47,6 +47,7 @@ import org.onosproject.net.flow.TrafficSelector; import org.onosproject.net.flow.TrafficTreatment; import org.onosproject.net.flow.criteria.Criteria; import org.onosproject.net.flow.criteria.Criterion; +import org.onosproject.net.flow.criteria.Criterion.Type; import org.onosproject.net.flow.criteria.EthCriterion; import org.onosproject.net.flow.criteria.EthTypeCriterion; import org.onosproject.net.flow.criteria.IPCriterion; @@ -73,6 +74,7 @@ import org.onosproject.net.group.GroupEvent; import org.onosproject.net.group.GroupKey; import org.onosproject.net.group.GroupListener; import org.onosproject.net.group.GroupService; +import org.onosproject.store.serializers.KryoNamespaces; import org.slf4j.Logger; import java.util.ArrayList; @@ -129,8 +131,13 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour groupedThreads("onos/pipeliner", "spring-open-%d")); protected KryoNamespace appKryo = new KryoNamespace.Builder() - .register(GroupKey.class).register(DefaultGroupKey.class) - .register(SegmentRoutingGroup.class).register(byte[].class).build(); + .register(KryoNamespaces.API) + .register(GroupKey.class) + .register(DefaultGroupKey.class) + .register(TrafficTreatment.class) + .register(SpringOpenGroup.class) + .register(byte[].class) + .build(); @Override public void init(DeviceId deviceId, PipelinerContext context) { @@ -202,17 +209,15 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour @Override public void onSuccess(FlowRuleOperations ops) { pass(fwd); - log.debug("Provisioned tables in {} with " - + "forwarding rules for segment " - + "router", deviceId); + log.debug("Provisioned tables in {} successfully with " + + "forwarding rules", deviceId); } @Override public void onError(FlowRuleOperations ops) { fail(fwd, ObjectiveError.FLOWINSTALLATIONFAILED); log.warn("Failed to provision tables in {} with " - + "forwarding rules for segment router", - deviceId); + + "forwarding rules", deviceId); } })); @@ -220,26 +225,50 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour @Override public void next(NextObjective nextObjective) { - - log.debug("Processing NextObjective id{} op{}", nextObjective.id(), - nextObjective.op()); - if (nextObjective.op() == Objective.Operation.REMOVE) { - if (nextObjective.next().isEmpty()) { - removeGroup(nextObjective); - } else { - removeBucketFromGroup(nextObjective); + NextGroup nextGroup = flowObjectiveStore.getNextGroup(nextObjective.id()); + switch (nextObjective.op()) { + case ADD: + if (nextGroup != null) { + log.warn("Cannot add next {} that already exists in device {}", + nextObjective.id(), deviceId); + return; } - } else if (nextObjective.op() == Objective.Operation.ADD) { - NextGroup nextGroup = flowObjectiveStore.getNextGroup(nextObjective.id()); + log.debug("Processing NextObjective id{} in dev{} - add group", + nextObjective.id(), deviceId); + addGroup(nextObjective); + break; + case ADD_TO_EXISTING: if (nextGroup != null) { + log.debug("Processing NextObjective id{} in dev{} - add bucket", + nextObjective.id(), deviceId); addBucketToGroup(nextObjective); } else { - addGroup(nextObjective); + log.warn("Cannot add to group that does not exist"); } - } else { + break; + case REMOVE: + if (nextGroup == null) { + log.warn("Cannot remove next {} that does not exist in device {}", + nextObjective.id(), deviceId); + return; + } + log.debug("Processing NextObjective id{} in dev{} - remove group", + nextObjective.id(), deviceId); + removeGroup(nextObjective); + break; + case REMOVE_FROM_EXISTING: + if (nextGroup == null) { + log.warn("Cannot remove from next {} that does not exist in device {}", + nextObjective.id(), deviceId); + return; + } + log.debug("Processing NextObjective id{} in dev{} - remove bucket", + nextObjective.id(), deviceId); + removeBucketFromGroup(nextObjective); + break; + default: log.warn("Unsupported operation {}", nextObjective.op()); } - } private void removeGroup(NextObjective nextObjective) { @@ -256,7 +285,6 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour List<GroupBucket> buckets; switch (nextObjective.type()) { case SIMPLE: - log.debug("processing SIMPLE next objective"); Collection<TrafficTreatment> treatments = nextObjective.next(); if (treatments.size() == 1) { TrafficTreatment treatment = treatments.iterator().next(); @@ -273,39 +301,57 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour key, null, nextObjective.appId()); - log.debug("Creating SIMPLE group for next objective id {}", - nextObjective.id()); - groupService.addGroup(groupDescription); + log.debug("Creating SIMPLE group for next objective id {} " + + "in dev:{}", nextObjective.id(), deviceId); pendingGroups.put(key, nextObjective); + groupService.addGroup(groupDescription); } break; case HASHED: - log.debug("processing HASHED next objective"); - buckets = nextObjective - .next() - .stream() - .map((treatment) -> DefaultGroupBucket - .createSelectGroupBucket(treatment)) - .collect(Collectors.toList()); - if (!buckets.isEmpty()) { - final GroupKey key = new DefaultGroupKey( - appKryo.serialize(nextObjective - .id())); - GroupDescription groupDescription = new DefaultGroupDescription( - deviceId, - GroupDescription.Type.SELECT, - new GroupBuckets(buckets), - key, - null, - nextObjective.appId()); - log.debug("Creating HASHED group for next objective id {}", - nextObjective.id()); - groupService.addGroup(groupDescription); - pendingGroups.put(key, nextObjective); + // we convert MPLS ECMP groups to flow-actions for a single + // bucket(output port). + boolean mplsEcmp = false; + if (nextObjective.meta() != null) { + for (Criterion c : nextObjective.meta().criteria()) { + if (c.type() == Type.MPLS_LABEL) { + mplsEcmp = true; + } + } + } + if (mplsEcmp) { + // covert to flow-actions in a dummy group by choosing the first bucket + log.debug("Converting HASHED group for next objective id {} " + + "to flow-actions in device:{}", nextObjective.id(), + deviceId); + TrafficTreatment treatment = nextObjective.next().iterator().next(); + flowObjectiveStore.putNextGroup(nextObjective.id(), + new SpringOpenGroup(null, treatment)); + } else { + // process as ECMP group + buckets = nextObjective + .next() + .stream() + .map((treatment) -> DefaultGroupBucket + .createSelectGroupBucket(treatment)) + .collect(Collectors.toList()); + if (!buckets.isEmpty()) { + final GroupKey key = new DefaultGroupKey( + appKryo.serialize(nextObjective.id())); + GroupDescription groupDescription = new DefaultGroupDescription( + deviceId, + GroupDescription.Type.SELECT, + new GroupBuckets(buckets), + key, + null, + nextObjective.appId()); + log.debug("Creating HASHED group for next objective id {}" + + " in dev:{}", nextObjective.id(), deviceId); + pendingGroups.put(key, nextObjective); + groupService.addGroup(groupDescription); + } } break; case BROADCAST: - log.debug("processing BROADCAST next objective"); buckets = nextObjective .next() .stream() @@ -323,10 +369,10 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour key, null, nextObjective.appId()); - log.debug("Creating BROADCAST group for next objective id {}", - nextObjective.id()); - groupService.addGroup(groupDescription); + log.debug("Creating BROADCAST group for next objective id {} " + + "in device {}", nextObjective.id(), deviceId); pendingGroups.put(key, nextObjective); + groupService.addGroup(groupDescription); } break; case FAILOVER: @@ -417,9 +463,8 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour } private Collection<FlowRule> processVersatile(ForwardingObjective fwd) { - log.debug("Processing versatile forwarding objective"); + log.debug("Processing versatile forwarding objective in dev:{}", deviceId); TrafficSelector selector = fwd.selector(); - TrafficTreatment treatment = null; EthTypeCriterion ethType = (EthTypeCriterion) selector.getCriterion(Criterion.Type.ETH_TYPE); if (ethType == null) { @@ -428,50 +473,60 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour return Collections.emptySet(); } + if (fwd.treatment() == null && fwd.nextId() == null) { + log.error("VERSATILE forwarding objective needs next objective ID " + + "or treatment."); + return Collections.emptySet(); + } + // emulation of ACL table (for versatile fwd objective) requires + // overriding any previous instructions TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment .builder(); treatmentBuilder.wipeDeferred(); if (fwd.nextId() != null) { NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId()); - if (next != null) { - GroupKey key = appKryo.deserialize(next.data()); - - Group group = groupService.getGroup(deviceId, key); - - if (group == null) { - log.warn("The group left!"); - fail(fwd, ObjectiveError.GROUPMISSING); - return Collections.emptySet(); + SpringOpenGroup soGroup = appKryo.deserialize(next.data()); + if (soGroup.dummy) { + // need to convert to flow-actions + for (Instruction ins : soGroup.treatment.allInstructions()) { + treatmentBuilder.add(ins); + } + } else { + GroupKey key = soGroup.key; + Group group = groupService.getGroup(deviceId, key); + if (group == null) { + log.warn("The group left!"); + fail(fwd, ObjectiveError.GROUPMISSING); + return Collections.emptySet(); + } + treatmentBuilder.deferred().group(group.id()); + log.debug("Adding OUTGROUP action"); } - treatmentBuilder.deferred().group(group.id()); - treatment = treatmentBuilder.build(); - log.debug("Adding OUTGROUP action"); } - } else if (fwd.treatment() != null) { + } + + if (fwd.treatment() != null) { if (fwd.treatment().allInstructions().size() == 1 && fwd.treatment().allInstructions().get(0).type() == Instruction.Type.OUTPUT) { OutputInstruction o = (OutputInstruction) fwd.treatment().allInstructions().get(0); if (o.port() == PortNumber.CONTROLLER) { treatmentBuilder.punt(); - treatment = treatmentBuilder.build(); } else { - treatment = fwd.treatment(); + treatmentBuilder.add(o); } } else { - treatment = fwd.treatment(); + for (Instruction ins : fwd.treatment().allInstructions()) { + treatmentBuilder.add(ins); + } } - } else { - log.warn("VERSATILE forwarding objective needs next objective ID " - + "or treatment."); - return Collections.emptySet(); } FlowRule.Builder ruleBuilder = DefaultFlowRule.builder() .fromApp(fwd.appId()).withPriority(fwd.priority()) .forDevice(deviceId).withSelector(fwd.selector()) - .withTreatment(treatment); + .withTreatment(treatmentBuilder.build()); if (fwd.permanent()) { ruleBuilder.makePermanent(); @@ -508,7 +563,8 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour } protected Collection<FlowRule> processSpecific(ForwardingObjective fwd) { - log.debug("Processing specific"); + log.debug("Processing specific fwd objective:{} in dev:{} with next:{}", + fwd.id(), deviceId, fwd.nextId()); boolean isEthTypeObj = isSupportedEthTypeObjective(fwd); boolean isEthDstObj = isSupportedEthDstObjective(fwd); @@ -518,7 +574,7 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour return processEthDstSpecificObjective(fwd); } else { log.warn("processSpecific: Unsupported " - + "forwarding objective criteraia"); + + "forwarding objective criteria"); fail(fwd, ObjectiveError.UNSUPPORTED); return Collections.emptySet(); } @@ -540,7 +596,8 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour .getCriterion(Criterion.Type.IPV4_DST)) .ip()); forTableId = ipv4UnicastTableId; - log.debug("processing IPv4 specific forwarding objective"); + log.debug("processing IPv4 specific forwarding objective:{} in dev:{}", + fwd.id(), deviceId); } else { filteredSelectorBuilder = filteredSelectorBuilder .matchEthType(Ethernet.MPLS_UNICAST) @@ -550,7 +607,8 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour //if (selector.getCriterion(Criterion.Type.MPLS_BOS) != null) { //} forTableId = mplsTableId; - log.debug("processing MPLS specific forwarding objective"); + log.debug("processing MPLS specific forwarding objective:{} in dev:{}", + fwd.id(), deviceId); } TrafficTreatment.Builder treatmentBuilder = DefaultTrafficTreatment @@ -561,24 +619,28 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour } } - //TODO: Analyze the forwarding objective here to make - //device specific decision such as no ECMP groups in Dell - //switches. if (fwd.nextId() != null) { NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId()); - if (next != null) { - GroupKey key = appKryo.deserialize(next.data()); - - Group group = groupService.getGroup(deviceId, key); - - if (group == null) { - log.warn("The group left!"); - fail(fwd, ObjectiveError.GROUPMISSING); - return Collections.emptySet(); + SpringOpenGroup soGroup = appKryo.deserialize(next.data()); + if (soGroup.dummy) { + log.debug("Adding flow-actions for fwd. obj. {} " + + "in dev: {}", fwd.id(), deviceId); + for (Instruction ins : soGroup.treatment.allInstructions()) { + treatmentBuilder.add(ins); + } + } else { + GroupKey key = soGroup.key; + Group group = groupService.getGroup(deviceId, key); + if (group == null) { + log.warn("The group left!"); + fail(fwd, ObjectiveError.GROUPMISSING); + return Collections.emptySet(); + } + treatmentBuilder.deferred().group(group.id()); + log.debug("Adding OUTGROUP action to group:{} for fwd. obj. {} " + + "in dev: {}", group.id(), fwd.id(), deviceId); } - treatmentBuilder.deferred().group(group.id()); - log.debug("Adding OUTGROUP action"); } else { log.warn("processSpecific: No associated next objective object"); fail(fwd, ObjectiveError.GROUPMISSING); @@ -621,6 +683,12 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour // Do not match MacAddress for subnet broadcast entry if (!ethCriterion.mac().equals(MacAddress.NONE)) { filteredSelectorBuilder.matchEthDst(ethCriterion.mac()); + log.debug("processing L2 forwarding objective:{} in dev:{}", + fwd.id(), deviceId); + } else { + log.debug("processing L2 Broadcast forwarding objective:{} " + + "in dev:{} for vlan:{}", + fwd.id(), deviceId, vlanIdCriterion.vlanId()); } filteredSelectorBuilder.matchVlanId(vlanIdCriterion.vlanId()); TrafficSelector filteredSelector = filteredSelectorBuilder.build(); @@ -635,14 +703,24 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour if (fwd.nextId() != null) { NextGroup next = flowObjectiveStore.getNextGroup(fwd.nextId()); if (next != null) { - GroupKey key = appKryo.deserialize(next.data()); - Group group = groupService.getGroup(deviceId, key); - if (group != null) { - treatmentBuilder.deferred().group(group.id()); + SpringOpenGroup soGrp = appKryo.deserialize(next.data()); + if (soGrp.dummy) { + log.debug("Adding flow-actions for fwd. obj. {} " + + "in dev: {}", fwd.id(), deviceId); + for (Instruction ins : soGrp.treatment.allInstructions()) { + treatmentBuilder.add(ins); + } } else { - log.warn("Group Missing"); - fail(fwd, ObjectiveError.GROUPMISSING); - return Collections.emptySet(); + GroupKey key = soGrp.key; + Group group = groupService.getGroup(deviceId, key); + if (group == null) { + log.warn("The group left!"); + fail(fwd, ObjectiveError.GROUPMISSING); + return Collections.emptySet(); + } + treatmentBuilder.deferred().group(group.id()); + log.debug("Adding OUTGROUP action to group:{} for fwd. obj. {} " + + "in dev: {}", group.id(), fwd.id(), deviceId); } } } @@ -869,14 +947,14 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour public void onSuccess(FlowRuleOperations ops) { pass(filt); log.debug("Provisioned tables in {} with fitering " - + "rules for segment router", deviceId); + + "rules", deviceId); } @Override public void onError(FlowRuleOperations ops) { fail(filt, ObjectiveError.FLOWINSTALLATIONFAILED); log.warn("Failed to provision tables in {} with " - + "fitering rules for segment router", deviceId); + + "fitering rules", deviceId); } })); } @@ -934,15 +1012,17 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour @Override public void event(GroupEvent event) { if (event.type() == GroupEvent.Type.GROUP_ADDED) { - log.debug("InnerGroupListener: Group ADDED " + log.trace("InnerGroupListener: Group ADDED " + "event received in device {}", deviceId); GroupKey key = event.subject().appCookie(); NextObjective obj = pendingGroups.getIfPresent(key); if (obj != null) { + log.debug("Group verified: dev:{} gid:{} <<->> nextId:{}", + deviceId, event.subject().id(), obj.id()); flowObjectiveStore .putNextGroup(obj.id(), - new SegmentRoutingGroup(key)); + new SpringOpenGroup(key, null)); pass(obj); pendingGroups.invalidate(key); } @@ -971,21 +1051,47 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour if (obj == null) { return; } + log.debug("Group verified: dev:{} gid:{} <<->> nextId:{}", + deviceId, + groupService.getGroup(deviceId, key).id(), + obj.id()); pass(obj); pendingGroups.invalidate(key); - flowObjectiveStore.putNextGroup(obj.id(), - new SegmentRoutingGroup( - key)); - }); + flowObjectiveStore.putNextGroup( + obj.id(), + new SpringOpenGroup(key, null)); + }); } } - private class SegmentRoutingGroup implements NextGroup { - + /** + * SpringOpenGroup can either serve as storage for a GroupKey which can be + * used to fetch the group from the Group Service, or it can be serve as storage + * for Traffic Treatments which can be used as flow actions. In the latter + * case, we refer to this as a dummy group. + * + */ + private class SpringOpenGroup implements NextGroup { + private final boolean dummy; private final GroupKey key; + private final TrafficTreatment treatment; - public SegmentRoutingGroup(GroupKey key) { - this.key = key; + /** + * Storage for a GroupKey or a TrafficTreatment. One of the params + * to this constructor must be null. + * @param key represents a GroupKey + * @param treatment represents flow actions in a dummy group + */ + public SpringOpenGroup(GroupKey key, TrafficTreatment treatment) { + if (key == null) { + this.key = new DefaultGroupKey(new byte[]{0}); + this.treatment = treatment; + this.dummy = true; + } else { + this.key = key; + this.treatment = DefaultTrafficTreatment.builder().build(); + this.dummy = false; + } } @SuppressWarnings("unused") @@ -995,7 +1101,7 @@ public class SpringOpenTTP extends AbstractHandlerBehaviour @Override public byte[] data() { - return appKryo.serialize(key); + return appKryo.serialize(this); } } diff --git a/framework/src/onos/drivers/src/main/resources/onos-drivers.xml b/framework/src/onos/drivers/src/main/resources/onos-drivers.xml index 0349e1c2..a172c18b 100644 --- a/framework/src/onos/drivers/src/main/resources/onos-drivers.xml +++ b/framework/src/onos/drivers/src/main/resources/onos-drivers.xml @@ -32,13 +32,14 @@ impl="org.onosproject.driver.handshaker.NiciraSwitchHandshaker"/> <behaviour api="org.onosproject.net.behaviour.ControllerConfig" impl="org.onosproject.driver.ovsdb.OvsdbControllerConfig"/> - <behaviour api="org.onosproject.openflow.controller.ExtensionInterpreter" - impl="org.onosproject.driver.extensions.NiciraExtensionInterpreter" /> - <behaviour api="org.onosproject.net.behaviour.ExtensionResolver" - impl="org.onosproject.driver.extensions.NiciraExtensionInterpreter" /> + <behaviour api="org.onosproject.openflow.controller.ExtensionTreatmentInterpreter" + impl="org.onosproject.driver.extensions.NiciraExtensionTreatmentInterpreter" /> + <behaviour api="org.onosproject.net.behaviour.ExtensionTreatmentResolver" + impl="org.onosproject.driver.extensions.NiciraExtensionTreatmentInterpreter" /> </driver> + <!--This driver is for simulated NETCONF devices through of-config tool on top og OVSDB--> <driver name="ovs-netconf" extends="default" - manufacturer="Nicira, Inc\." hwVersion="Open vSwitch" swVersion="2\..*"> + manufacturer="" hwVersion="" swVersion=""> <behaviour api="org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver" impl="org.onosproject.driver.handshaker.NiciraSwitchHandshaker"/> <behaviour api="org.onosproject.net.behaviour.ControllerConfig" @@ -137,7 +138,7 @@ impl="org.onosproject.driver.pipeline.OpenVSwitchPipeline"/> </driver> <driver name="eci" extends="default" - manufacturer="ECI Telecom" hwVersion="optical" swVersion="V_1_0"> + manufacturer="ECI Telecom" hwVersion="Optical.*" swVersion="V_1_0"> <behaviour api="org.onosproject.openflow.controller.driver.OpenFlowSwitchDriver" impl="org.onosproject.driver.handshaker.OFOpticalSwitch13"/> </driver> diff --git a/framework/src/onos/drivers/src/main/resources/org/onosproject/driver/netconf/controllers.xml b/framework/src/onos/drivers/src/main/resources/org/onosproject/driver/netconf/controllers.xml new file mode 100644 index 00000000..21e4dd14 --- /dev/null +++ b/framework/src/onos/drivers/src/main/resources/org/onosproject/driver/netconf/controllers.xml @@ -0,0 +1,36 @@ +<!-- + ~ 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. + --> + +<rpc xmlns="urn:ietf:params:xml:ns:netconf:base:1.0"> + <edit-config> + <target> + </target> + <default-operation> + </default-operation> + <config> + <capable-switch xmlns="urn:onf:config:yang"> + <id></id> + <logical-switches> + <switch> + <id></id> + <controllers> + </controllers> + </switch> + </logical-switches> + </capable-switch> + </config> + </edit-config> +</rpc>
\ No newline at end of file diff --git a/framework/src/onos/drivers/src/test/java/org/onosproject/driver/netconf/XmlConfigParserTest.java b/framework/src/onos/drivers/src/test/java/org/onosproject/driver/netconf/XmlConfigParserTest.java new file mode 100644 index 00000000..d8b695c3 --- /dev/null +++ b/framework/src/onos/drivers/src/test/java/org/onosproject/driver/netconf/XmlConfigParserTest.java @@ -0,0 +1,80 @@ +/* + * 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.driver.netconf; + +import org.junit.Test; +import org.onlab.packet.IpAddress; +import org.onosproject.net.behaviour.ControllerInfo; + +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertTrue; +import static org.onosproject.driver.netconf.XmlConfigParser.*; + +//import static org.junit.Assert.*; + +/** + * Test the XML document Parsing for netconf configuration. + */ +public class XmlConfigParserTest { + + + @Test + public void basics() throws IOException { + InputStream stream = getClass().getResourceAsStream("testConfig.xml"); + List<ControllerInfo> controllers = parseStreamControllers(loadXml(stream)); + assertTrue(controllers.get(0).equals(new ControllerInfo( + IpAddress.valueOf("10.128.12.1"), 6653, "tcp"))); + assertTrue(controllers.get(1).equals(new ControllerInfo( + IpAddress.valueOf("10.128.12.2"), 6654, "tcp"))); + + } + + @Test + public void switchId() { + InputStream stream = getClass().getResourceAsStream("testConfig.xml"); + String switchId = parseSwitchId(loadXml(stream)); + assertTrue(switchId.equals("ofc-bridge")); + } + + @Test + public void capableSwitchId() { + InputStream stream = getClass().getResourceAsStream("testConfig.xml"); + String capableSwitchId = parseCapableSwitchId(loadXml(stream)); + assertTrue(capableSwitchId.equals("openvswitch")); + } + + @Test + public void controllersConfig() { + InputStream streamOrig = getClass().getResourceAsStream("testConfig.xml"); + InputStream streamCFG = XmlConfigParser.class + .getResourceAsStream("controllers.xml"); + String config = createControllersConfig(loadXml(streamCFG), + loadXml(streamOrig), "running", "merge", + "create", new ArrayList<>(Arrays.asList( + new ControllerInfo(IpAddress.valueOf("192.168.1.1"), + 5000, "tcp")))); + assertTrue(config.contains("192.168.1.1")); + assertTrue(config.contains("tcp")); + assertTrue(config.contains("5000")); + + } +}
\ No newline at end of file diff --git a/framework/src/onos/drivers/src/test/resources/org/onosproject/driver/netconf/testConfig.xml b/framework/src/onos/drivers/src/test/resources/org/onosproject/driver/netconf/testConfig.xml new file mode 100644 index 00000000..88d1a3a3 --- /dev/null +++ b/framework/src/onos/drivers/src/test/resources/org/onosproject/driver/netconf/testConfig.xml @@ -0,0 +1,60 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!-- + ~ 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. + --> + +<rpc-reply xmlns="urn:ietf:params:xml:ns:netconf:base:1.0" message-id="7"> + <data> + <capable-switch xmlns="urn:onf:config:yang"> + <id>openvswitch</id> + <resources> + <port> + <name>ofc-bridge</name> + <requested-number>666</requested-number> + <configuration> + <admin-state>down</admin-state> + <no-receive>false</no-receive> + <no-forward>false</no-forward> + <no-packet-in>false</no-packet-in> + </configuration> + </port> + </resources> + <logical-switches> + <switch> + <id>ofc-bridge</id> + <datapath-id>00:01:02:03:04:05:06:07</datapath-id> + <lost-connection-behavior>failSecureMode</lost-connection-behavior> + <controllers> + <controller> + <id>(null)</id> + <ip-address>10.128.12.1</ip-address> + <port>6653</port> + <protocol>tcp</protocol> + </controller> + <controller> + <id>(null)</id> + <ip-address>10.128.12.2</ip-address> + <port>6654</port> + <protocol>tcp</protocol> + </controller> + </controllers> + <resources> + <port>ofc-bridge</port> + </resources> + </switch> + </logical-switches> + </capable-switch> + </data> +</rpc-reply>
\ No newline at end of file |