Lambdas are reinterpreted before being sent to Linc-OE switches. This includes
adding ability to intercept messages at the switch driver for modification before
being sent down.
Reference: ONOS-1980
Change-Id: I405b89a0fc3844555c9efa0cd9fc887a90d00280
diff --git a/drivers/src/main/java/org/onosproject/driver/handshaker/OFOpticalSwitchImplLINC13.java b/drivers/src/main/java/org/onosproject/driver/handshaker/OFOpticalSwitchImplLINC13.java
index e1461e3..4c64940 100644
--- a/drivers/src/main/java/org/onosproject/driver/handshaker/OFOpticalSwitchImplLINC13.java
+++ b/drivers/src/main/java/org/onosproject/driver/handshaker/OFOpticalSwitchImplLINC13.java
@@ -24,30 +24,61 @@
import org.projectfloodlight.openflow.protocol.OFCircuitPortStatus;
import org.projectfloodlight.openflow.protocol.OFCircuitPortsReply;
import org.projectfloodlight.openflow.protocol.OFCircuitPortsRequest;
+import org.projectfloodlight.openflow.protocol.OFFactories;
+import org.projectfloodlight.openflow.protocol.OFFactory;
+import org.projectfloodlight.openflow.protocol.OFFlowMod;
+import org.projectfloodlight.openflow.protocol.OFFlowModCommand;
+import org.projectfloodlight.openflow.protocol.OFInstructionType;
import org.projectfloodlight.openflow.protocol.OFMessage;
import org.projectfloodlight.openflow.protocol.OFObject;
import org.projectfloodlight.openflow.protocol.OFPortDesc;
import org.projectfloodlight.openflow.protocol.OFStatsReply;
import org.projectfloodlight.openflow.protocol.OFStatsType;
+import org.projectfloodlight.openflow.protocol.OFType;
+import org.projectfloodlight.openflow.protocol.OFVersion;
+import org.projectfloodlight.openflow.protocol.action.OFAction;
+import org.projectfloodlight.openflow.protocol.action.OFActionCircuit;
+import org.projectfloodlight.openflow.protocol.instruction.OFInstruction;
+import org.projectfloodlight.openflow.protocol.instruction.OFInstructionApplyActions;
+import org.projectfloodlight.openflow.protocol.match.Match;
+import org.projectfloodlight.openflow.protocol.match.MatchField;
+import org.projectfloodlight.openflow.protocol.OFActionType;
+import org.projectfloodlight.openflow.types.CircuitSignalID;
+import org.projectfloodlight.openflow.types.OFPort;
+import org.projectfloodlight.openflow.types.U8;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import java.io.IOException;
import java.util.List;
+import java.util.Map;
+import java.util.ArrayList;
import java.util.Set;
+import java.util.BitSet;
+import java.util.stream.Collectors;
import java.util.concurrent.atomic.AtomicBoolean;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.ConcurrentHashMap;
+
+import static org.projectfloodlight.openflow.protocol.OFFlowMod.Builder;
/**
* LINC-OE Optical Emulator switch class.
*/
public class OFOpticalSwitchImplLINC13
extends AbstractOpenFlowSwitch implements OpenFlowOpticalSwitch {
+ // default number of lambdas, assuming 50GHz channels.
+ private static final int NUM_CHLS = 80;
+ private final OFFactory factory = OFFactories.getFactory(OFVersion.OF_13);
private final AtomicBoolean driverHandshakeComplete = new AtomicBoolean(false);
private long barrierXidToWaitFor = -1;
private OFCircuitPortsReply wPorts;
+ // book-keeping maps for allocated Linc-OE lambdas
+ protected final ConcurrentMap<OFPort, BitSet> portChannelMap = new ConcurrentHashMap<>();
+ protected final ConcurrentMap<Match, Integer> matchMap = new ConcurrentHashMap<>();
@Override
public void startDriverHandshake() {
@@ -170,4 +201,136 @@
return ImmutableSet.of(PortDescPropertyType.OPTICAL_TRANSPORT);
}
+ @Override
+ public OFMessage prepareMessage(OFMessage msg) {
+ if (OFVersion.OF_13 != msg.getVersion() || msg.getType() != OFType.FLOW_MOD) {
+ return msg;
+ }
+ OFFlowMod fm = (OFFlowMod) msg;
+ Match match = fm.getMatch();
+ // Don't touch FlowMods that aren't Optical-related.
+ if (match.get(MatchField.OCH_SIGTYPE) == null) {
+ return msg;
+ }
+
+ OFMessage newFM;
+ Builder builder = null;
+ List<OFAction> actions = new ArrayList<>();
+ if (fm.getCommand() == OFFlowModCommand.ADD) {
+ builder = factory.buildFlowAdd();
+ int lambda = allocateLambda(match.get(MatchField.IN_PORT), match);
+ CircuitSignalID sigid = new CircuitSignalID((byte) 1, (byte) 2, (short) lambda, (short) 1);
+ List<OFInstruction> instructions = fm.getInstructions();
+
+ newFM = buildFlowMod(builder, fm, buildMatch(match, sigid), buildActions(instructions, sigid));
+ } else if (fm.getCommand() == OFFlowModCommand.DELETE) {
+ builder = factory.buildFlowDelete();
+ int lambda = freeLambda(match.get(MatchField.IN_PORT), match);
+ CircuitSignalID sigid = new CircuitSignalID((byte) 1, (byte) 2, (short) lambda, (short) 1);
+
+ newFM = buildFlowMod(builder, fm, buildMatch(match, sigid), actions);
+ } else {
+ newFM = msg;
+ }
+ log.debug("new FM = {}", newFM);
+ return newFM;
+ }
+
+ // fetch the next available channel as the flat lambda value, or the lambda
+ // associated with a port/match combination
+ private int allocateLambda(OFPort port, Match match) {
+ Integer lambda = null;
+ synchronized (this) {
+ BitSet channels = portChannelMap.getOrDefault(port, new BitSet(NUM_CHLS + 1));
+ lambda = matchMap.get(match);
+ if (lambda == null) {
+ // TODO : double check behavior when bitset is full
+ // Linc lambdas start at 1.
+ lambda = channels.nextClearBit(1);
+ channels.set(lambda);
+ portChannelMap.put(port, channels);
+ matchMap.put(match, lambda);
+ }
+ }
+ return lambda;
+ }
+
+ // free lambda that was mapped to Port/Match combination and return its
+ // value to caller.
+ private int freeLambda(OFPort port, Match match) {
+ synchronized (this) {
+ Integer lambda = matchMap.get(match);
+ if (lambda != null) {
+ portChannelMap.get(port).clear(lambda);
+ return lambda;
+ }
+ // 1 is a sane-ish default for Linc.
+ return 1;
+ }
+ }
+
+ // build matches - *tons of assumptions are made here based on Linc-OE's behavior.*
+ // gridType = 1 (DWDM)
+ // channelSpacing = 2 (50GHz)
+ // spectralWidth = 1 (fixed grid default value)
+ private Match buildMatch(Match original, CircuitSignalID sigid) {
+ Match.Builder mBuilder = factory.buildMatch();
+
+ original.getMatchFields().forEach(mf -> {
+ String name = mf.getName();
+ if (MatchField.OCH_SIGID.getName().equals(name)) {
+ mBuilder.setExact(MatchField.OCH_SIGID, sigid);
+ } else if (MatchField.OCH_SIGTYPE.getName().equals(name)) {
+ mBuilder.setExact(MatchField.OCH_SIGTYPE, U8.of((short) 1));
+ } else if (MatchField.IN_PORT.getName().equals(name)) {
+ mBuilder.setExact(MatchField.IN_PORT, original.get(MatchField.IN_PORT));
+ }
+ });
+
+ return mBuilder.build();
+ }
+
+ private List<OFAction> buildActions(List<OFInstruction> iList, CircuitSignalID sigid) {
+ List<OFAction> actions = new ArrayList<>();
+ Map<OFInstructionType, OFInstruction> instructions = iList.stream()
+ .collect(Collectors.toMap(OFInstruction::getType, inst -> inst));
+
+ OFInstruction inst = instructions.get(OFInstructionType.APPLY_ACTIONS);
+ if (inst != null) {
+ OFInstructionApplyActions iaa = (OFInstructionApplyActions) inst;
+ if (iaa.getActions() == null) {
+ return actions;
+ }
+ iaa.getActions().forEach(action -> {
+ if (OFActionType.EXPERIMENTER == action.getType()) {
+ OFActionCircuit.Builder cBuilder = factory.actions().buildCircuit()
+ .setField(factory.oxms()
+ .buildOchSigid()
+ .setValue(sigid)
+ .build());
+ actions.add(cBuilder.build());
+ } else {
+ actions.add(action);
+ }
+ });
+ }
+ return actions;
+ }
+
+ private OFMessage buildFlowMod(Builder builder, OFFlowMod fm, Match m, List<OFAction> act) {
+ return builder
+ .setXid(fm.getXid())
+ .setCookie(fm.getCookie())
+ .setCookieMask(fm.getCookieMask())
+ .setTableId(fm.getTableId())
+ .setIdleTimeout(fm.getIdleTimeout())
+ .setHardTimeout(fm.getHardTimeout())
+ .setBufferId(fm.getBufferId())
+ .setOutPort(fm.getOutPort())
+ .setOutGroup(fm.getOutGroup())
+ .setFlags(fm.getFlags())
+ .setMatch(m)
+ .setActions(act)
+ .build();
+ }
}
diff --git a/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowSwitch.java b/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowSwitch.java
index 49ca5a8..0d2dc3a 100644
--- a/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowSwitch.java
+++ b/openflow/api/src/main/java/org/onosproject/openflow/controller/OpenFlowSwitch.java
@@ -148,4 +148,16 @@
* @return string representation of the connection to the device
*/
String channelId();
+
+ /**
+ * Prepares a message to be sent, if necessary. Default is to do nothing,
+ * since most Devices do not need to pre-process a message that's about to
+ * be sent.
+ *
+ * @param msg The message to prepare for sending
+ * @return the prepared OFMessage
+ */
+ default OFMessage prepareMessage(OFMessage msg) {
+ return msg;
+ }
}
diff --git a/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/AbstractOpenFlowSwitch.java b/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/AbstractOpenFlowSwitch.java
index 50c0351..94e82a2 100644
--- a/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/AbstractOpenFlowSwitch.java
+++ b/openflow/api/src/main/java/org/onosproject/openflow/controller/driver/AbstractOpenFlowSwitch.java
@@ -97,7 +97,7 @@
@Override
public final void sendMsg(OFMessage m) {
if (role == RoleState.MASTER && channel.isWritable()) {
- channel.write(Collections.singletonList(m));
+ channel.write(Collections.singletonList(prepareMessage(m)));
}
}
@@ -119,6 +119,7 @@
"a non role request message");
}
+ @Override
public final void sendHandshakeMessage(OFMessage message) {
if (!this.isDriverHandshakeComplete()) {
channel.write(Collections.singletonList(message));
@@ -155,7 +156,6 @@
return channelId;
}
-
//************************
// Switch features related
//************************