Implement compile method for SingleDstTreeFlow class.
- This patch supports ADD operation only.
- This task is a part of ONOS-1694, ONOS-1841 and ONOS-1690.
Change-Id: I9133bf77711f3b02bce9099ddbaebe0fd89742c9
diff --git a/src/main/java/net/onrc/onos/api/flowmanager/SingleDstTreeFlow.java b/src/main/java/net/onrc/onos/api/flowmanager/SingleDstTreeFlow.java
index 60be78f..6fbed8a 100644
--- a/src/main/java/net/onrc/onos/api/flowmanager/SingleDstTreeFlow.java
+++ b/src/main/java/net/onrc/onos/api/flowmanager/SingleDstTreeFlow.java
@@ -1,59 +1,67 @@
package net.onrc.onos.api.flowmanager;
import static com.google.common.base.Preconditions.checkNotNull;
+import static com.google.common.base.Preconditions.checkState;
+import java.util.Arrays;
import java.util.Collection;
+import java.util.HashMap;
+import java.util.HashSet;
import java.util.List;
+import java.util.Map;
+import java.util.Map.Entry;
import java.util.Set;
import net.onrc.onos.api.flowmanager.FlowBatchOperation.Operator;
+import net.onrc.onos.core.matchaction.MatchAction;
import net.onrc.onos.core.matchaction.MatchActionIdGenerator;
+import net.onrc.onos.core.matchaction.MatchActionOperationEntry;
import net.onrc.onos.core.matchaction.MatchActionOperations;
import net.onrc.onos.core.matchaction.MatchActionOperationsIdGenerator;
import net.onrc.onos.core.matchaction.action.Action;
+import net.onrc.onos.core.matchaction.action.OutputAction;
import net.onrc.onos.core.matchaction.match.PacketMatch;
+import net.onrc.onos.core.util.Dpid;
+import net.onrc.onos.core.util.PortNumber;
import net.onrc.onos.core.util.SwitchPort;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
/**
- * A Flow object expressing the multipoints-to-point tree flow for the packet
- * layer.
+ * A {@link Flow} object expressing the multipoints-to-point tree flow for the
+ * packet layer.
* <p>
- * NOTE: This class might generate the MatchActionPlan which includes the MAC
- * address modifications or other the label-switching-like schemes.
+ * NOTE: This class might generate the {@link MatchAction} operations which
+ * includes the MAC address modifications or other the label-switching-like
+ * schemes.
*/
public class SingleDstTreeFlow extends Flow {
private final PacketMatch match;
private final Set<SwitchPort> ingressPorts;
private final Tree tree;
- private final List<Action> actions;
+ private final List<Action> egressActions;
/**
* Creates new instance using Tree object.
- * <p>
- * For now, the actions parameter must be the list of a single
- * ModifyDstMacAction object and a single OutputAction object. But in the
- * future, the parameter should accept any type of the list of IAction
- * objects.
*
* @param id ID for this object
* @param match the traffic filter for the tree
* @param ingressPorts the set of ingress ports of the tree
* @param tree the Tree object specifying tree topology for this object
- * @param actions the list of Action objects at the egress edge switch
+ * @param egressActions the list of {@link Action} objects to be executed at
+ * the egress edge switch
*/
public SingleDstTreeFlow(FlowId id, PacketMatch match,
- Collection<SwitchPort> ingressPorts, Tree tree, List<Action> actions) {
+ Collection<SwitchPort> ingressPorts, Tree tree, List<Action> egressActions) {
super(id);
this.match = checkNotNull(match);
this.ingressPorts = ImmutableSet.copyOf(checkNotNull(ingressPorts));
this.tree = checkNotNull(tree);
- this.actions = ImmutableList.copyOf(checkNotNull(actions));
+ this.egressActions = ImmutableList.copyOf(checkNotNull(egressActions));
// TODO: check if the tree is a MP2P tree.
- // TODO: check consistency among ingressPorts, tree, and actions.
+ // TODO: check consistency between ingressPorts and tree topology.
}
/**
@@ -80,7 +88,7 @@
* @return the list of actions at the egress edge switch
*/
public List<Action> getEgressActions() {
- return actions;
+ return egressActions;
}
@Override
@@ -92,7 +100,158 @@
public List<MatchActionOperations> compile(Operator op,
MatchActionIdGenerator maIdGenerator,
MatchActionOperationsIdGenerator maoIdGenerator) {
+ switch (op) {
+ case ADD:
+ return compileAddOperation(maIdGenerator, maoIdGenerator);
+ case REMOVE:
+ return compileRemoveOperation();
+ default:
+ throw new UnsupportedOperationException("Unknown operation.");
+ }
+ }
+
+ private MatchAction createMatchAction(SwitchPort port, List<Action> actions,
+ MatchActionIdGenerator maIdGenerator) {
+ checkNotNull(port);
+ checkNotNull(actions);
+
+ return new MatchAction(maIdGenerator.getNewId(), port, getMatch(), actions);
+ }
+
+ /**
+ * Generates MatchAactionOperations at inner ports and at the egress switch.
+ *
+ * @param egressSwitch the egress switch of the tree
+ * @param inPorts a map of a set of incoming ports on each switch in the
+ * tree
+ * @param outPorts a map of outgoing port on each switch in the tree
+ * @param maIdGenerator ID generator for MatchAction objects
+ * @param maoIdGenerator ID generator for MatchActionOperations objects
+ * @return the operations at inner ports and egress switch
+ */
+ private MatchActionOperations generateFirstAddOperations(
+ Dpid egressSwitch,
+ Map<Dpid, Set<PortNumber>> inPorts,
+ Map<Dpid, PortNumber> outPorts,
+ MatchActionIdGenerator maIdGenerator,
+ MatchActionOperationsIdGenerator maoIdGenerator) {
+ MatchActionOperations firstOps =
+ new MatchActionOperations(maoIdGenerator.getNewId());
+ for (Entry<Dpid, Set<PortNumber>> innerSw : inPorts.entrySet()) {
+ for (PortNumber innerPortNumber : innerSw.getValue()) {
+ SwitchPort innerPort = new SwitchPort(innerSw.getKey(), innerPortNumber);
+ MatchAction ma;
+ if (innerPort.getDpid().equals(egressSwitch)) {
+ ma = createMatchAction(innerPort, getEgressActions(), maIdGenerator);
+ } else {
+ PortNumber outputPortNumber = checkNotNull(
+ outPorts.get(innerPort.getDpid()),
+ String.format("The tree has no output port at %s",
+ innerPort.getDpid()));
+ ma = createMatchAction(innerPort,
+ Arrays.asList((Action) new OutputAction(outputPortNumber)),
+ maIdGenerator);
+ }
+ firstOps.addOperation(new MatchActionOperationEntry(
+ MatchActionOperations.Operator.ADD, ma));
+ }
+ }
+ return firstOps;
+ }
+
+ /**
+ * Generates MatchActionOperations for ingress switches in the tree.
+ *
+ * @param egressSwitch the egress switch of the tree
+ * @param outPorts a map of outgoing port on each switch in the tree
+ * @param maIdGenerator ID generator for MatchAction objects
+ * @param maoIdGenerator ID generator for MatchActionOperations objects
+ * @return operations at ingress switches in the tree
+ */
+ private MatchActionOperations generateSecondAddOperations(
+ Dpid egressSwitch,
+ Map<Dpid, PortNumber> outPorts,
+ MatchActionIdGenerator maIdGenerator,
+ MatchActionOperationsIdGenerator maoIdGenerator) {
+ MatchActionOperations secondOps =
+ new MatchActionOperations(maoIdGenerator.getNewId());
+ for (SwitchPort port : getIngressPorts()) {
+ PortNumber outputPort = outPorts.get(port.getDpid());
+ if (outputPort == null) {
+ if (port.getDpid().equals(egressSwitch)) {
+ MatchAction ma = createMatchAction(
+ port, getEgressActions(), maIdGenerator);
+ secondOps.addOperation(new MatchActionOperationEntry(
+ MatchActionOperations.Operator.ADD, ma));
+ } else {
+ throw new IllegalStateException(String.format(
+ "The switch %s specified as one of ingress ports "
+ + "does not have path to the egress switch.",
+ port.getDpid()));
+ }
+ } else {
+ MatchAction ma = createMatchAction(port,
+ Arrays.asList((Action) new OutputAction(outputPort)),
+ maIdGenerator);
+ secondOps.addOperation(new MatchActionOperationEntry(
+ MatchActionOperations.Operator.ADD, ma));
+ }
+ }
+ return secondOps;
+ }
+
+ private List<MatchActionOperations> compileAddOperation(
+ MatchActionIdGenerator maIdGenerator,
+ MatchActionOperationsIdGenerator maoIdGenerator) {
+ checkNotNull(tree);
+ checkState(tree.size() > 0, "Tree object has no link.");
+
+ // TODO: check consistency of the tree topology
+
+ // collect input ports and output ports checking consistency
+ Map<Dpid, PortNumber> outPorts = new HashMap<>();
+ Map<Dpid, Set<PortNumber>> inPorts = new HashMap<>();
+ for (FlowLink link : tree) {
+ SwitchPort srcPort = link.getSrcSwitchPort();
+ if (outPorts.containsKey(srcPort.getDpid())) {
+ throw new IllegalStateException(
+ String.format("Dpid:%s has multiple output ports.",
+ srcPort.getDpid()));
+ }
+ outPorts.put(srcPort.getDpid(), srcPort.getPortNumber());
+
+ SwitchPort dstPort = link.getDstSwitchPort();
+ Set<PortNumber> inPortNumbers = inPorts.get(dstPort.getDpid());
+ if (inPortNumbers == null) {
+ inPortNumbers = new HashSet<>();
+ }
+ inPortNumbers.add(dstPort.getPortNumber());
+ inPorts.put(dstPort.getDpid(), inPortNumbers);
+ }
+
+ // find the egress switch
+ Set<Dpid> egressSwitches = new HashSet<>(inPorts.keySet());
+ egressSwitches.removeAll(outPorts.keySet());
+ checkState(egressSwitches.size() == 1,
+ "The specified tree is not a single destination tree.");
+ Dpid egressSwitch = egressSwitches.iterator().next();
+
+ MatchActionOperations firstOps = generateFirstAddOperations(
+ egressSwitch, inPorts, outPorts, maIdGenerator, maoIdGenerator);
+ checkState(firstOps.size() > 0,
+ "No operations found for the first set of operations.");
+
+ MatchActionOperations secondOps = generateSecondAddOperations(
+ egressSwitch, outPorts, maIdGenerator, maoIdGenerator);
+ checkState(secondOps.size() > 0,
+ "No operations found for the second set of operations.");
+
+ return Arrays.asList(firstOps, secondOps);
+ }
+
+ private List<MatchActionOperations> compileRemoveOperation() {
// TODO Auto-generated method stub
- return null;
+ throw new UnsupportedOperationException(
+ "REMOVE operation is not implemented yet.");
}
}
diff --git a/src/test/java/net/onrc/onos/api/flowmanager/SingleDstTreeFlowTest.java b/src/test/java/net/onrc/onos/api/flowmanager/SingleDstTreeFlowTest.java
new file mode 100644
index 0000000..1a40013
--- /dev/null
+++ b/src/test/java/net/onrc/onos/api/flowmanager/SingleDstTreeFlowTest.java
@@ -0,0 +1,209 @@
+package net.onrc.onos.api.flowmanager;
+
+import static org.easymock.EasyMock.createMock;
+import static org.easymock.EasyMock.expect;
+import static org.easymock.EasyMock.replay;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+import net.floodlightcontroller.util.MACAddress;
+import net.onrc.onos.api.flowmanager.FlowBatchOperation.Operator;
+import net.onrc.onos.core.matchaction.MatchAction;
+import net.onrc.onos.core.matchaction.MatchActionIdGeneratorWithIdBlockAllocator;
+import net.onrc.onos.core.matchaction.MatchActionOperationEntry;
+import net.onrc.onos.core.matchaction.MatchActionOperations;
+import net.onrc.onos.core.matchaction.MatchActionOperationsIdGeneratorWithIdBlockAllocator;
+import net.onrc.onos.core.matchaction.action.Action;
+import net.onrc.onos.core.matchaction.action.ModifyDstMacAction;
+import net.onrc.onos.core.matchaction.action.OutputAction;
+import net.onrc.onos.core.matchaction.match.PacketMatch;
+import net.onrc.onos.core.matchaction.match.PacketMatchBuilder;
+import net.onrc.onos.core.util.IdBlock;
+import net.onrc.onos.core.util.IdBlockAllocator;
+import net.onrc.onos.core.util.PortNumber;
+import net.onrc.onos.core.util.SwitchPort;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.Test;
+
+import com.google.common.collect.Sets;
+
+/**
+ * Unit tests for {@link SingleDstTreeFlow} class.
+ */
+public class SingleDstTreeFlowTest {
+ private PacketMatch match;
+ private Set<SwitchPort> ingressPorts;
+ private Tree tree;
+ private List<Action> egressActions;
+ private IdBlockAllocator allocator;
+
+ @Before
+ public void setUp() throws Exception {
+ allocator = createMock(IdBlockAllocator.class);
+ expect(allocator.allocateUniqueIdBlock())
+ .andReturn(new IdBlock(0, 100))
+ .andReturn(new IdBlock(100, 100));
+ replay(allocator);
+
+ PacketMatchBuilder builder = new PacketMatchBuilder();
+ builder.setDstMac(MACAddress.valueOf(54321));
+ match = builder.build();
+
+ ingressPorts = Sets.newHashSet(
+ new SwitchPort(1L, (short) 101),
+ new SwitchPort(1L, (short) 102),
+ new SwitchPort(2L, (short) 103),
+ new SwitchPort(3L, (short) 104),
+ new SwitchPort(5L, (short) 105));
+
+ tree = new Tree();
+ tree.addLink(new FlowLink(
+ new SwitchPort(1L, (short) 10),
+ new SwitchPort(3L, (short) 12)));
+ tree.addLink(new FlowLink(
+ new SwitchPort(2L, (short) 11),
+ new SwitchPort(3L, (short) 13)));
+ tree.addLink(new FlowLink(
+ new SwitchPort(3L, (short) 14),
+ new SwitchPort(5L, (short) 15)));
+ tree.addLink(new FlowLink(
+ new SwitchPort(4L, (short) 16),
+ new SwitchPort(5L, (short) 17)));
+
+ egressActions = Arrays.asList(
+ new ModifyDstMacAction(MACAddress.valueOf(12345)),
+ new OutputAction(PortNumber.uint32(100)));
+ }
+
+ @After
+ public void tearDown() throws Exception {
+ }
+
+ /**
+ * Tests if constructor initialized fields properly.
+ */
+ @Test
+ public void testConstructor() {
+ SingleDstTreeFlow treeFlow = new SingleDstTreeFlow(
+ new FlowId(1L), match, ingressPorts, tree, egressActions);
+
+ assertNotNull(treeFlow);
+ assertEquals(new FlowId(1L), treeFlow.getId());
+ assertEquals(match, treeFlow.getMatch());
+ assertEquals(ingressPorts, treeFlow.getIngressPorts());
+ assertEquals(tree, treeFlow.getTree());
+ assertEquals(egressActions, treeFlow.getEgressActions());
+ }
+
+ /**
+ * Generates a list of {@link Action} contains {@link OutputAction} with
+ * specified output port.
+ *
+ * @param outputPort the output port number
+ * @return a list of {@link Action} contains one {@link OutputAction}
+ */
+ private List<Action> outputAction(int outputPort) {
+ return Arrays.asList(
+ (Action) new OutputAction(new PortNumber((short) outputPort)));
+ }
+
+ /**
+ * Tests if compile method with {@link Operator}.ADD generates two
+ * {@link MatchActionOperations} objects and they have
+ * {@link MatchActionOperationEntry} properly based on specified match,
+ * ingress ports, tree, and egress actinos.
+ */
+ @Test
+ public void testCompileWithAddOperation() {
+ SingleDstTreeFlow treeFlow = new SingleDstTreeFlow(
+ new FlowId(1L), match, ingressPorts, tree, egressActions);
+
+ List<MatchActionOperations> maOpsList =
+ treeFlow.compile(Operator.ADD,
+ new MatchActionIdGeneratorWithIdBlockAllocator(allocator),
+ new MatchActionOperationsIdGeneratorWithIdBlockAllocator(
+ allocator));
+
+ assertEquals(2, maOpsList.size());
+
+ MatchActionOperations firstOp = maOpsList.get(0);
+ MatchActionOperations secondOp = maOpsList.get(1);
+
+ assertEquals(4, firstOp.size());
+ Map<SwitchPort, MatchAction> maMap1 = new HashMap<>();
+ for (MatchActionOperationEntry entry : firstOp.getOperations()) {
+ assertEquals(MatchActionOperations.Operator.ADD, entry.getOperator());
+ MatchAction ma = entry.getTarget();
+ maMap1.put(ma.getSwitchPort(), ma);
+ }
+ assertEquals(4, maMap1.size());
+
+ assertEquals(5, secondOp.size());
+ Map<SwitchPort, MatchAction> maMap2 = new HashMap<>();
+ for (MatchActionOperationEntry entry : secondOp.getOperations()) {
+ assertEquals(MatchActionOperations.Operator.ADD, entry.getOperator());
+ MatchAction ma = entry.getTarget();
+ maMap2.put(ma.getSwitchPort(), ma);
+ }
+ assertEquals(5, maMap2.size());
+
+ assertEquals(Sets.newHashSet(
+ new SwitchPort(3L, (short) 12),
+ new SwitchPort(3L, (short) 13),
+ new SwitchPort(5L, (short) 15),
+ new SwitchPort(5L, (short) 17)
+ ), maMap1.keySet());
+
+ MatchAction ma11 = maMap1.get(new SwitchPort(3L, (short) 12));
+ assertEquals(match, ma11.getMatch());
+ assertEquals(outputAction(14), ma11.getActions());
+
+ MatchAction ma12 = maMap1.get(new SwitchPort(3L, (short) 13));
+ assertEquals(match, ma12.getMatch());
+ assertEquals(outputAction(14), ma12.getActions());
+
+ MatchAction ma13 = maMap1.get(new SwitchPort(5L, (short) 15));
+ assertEquals(match, ma13.getMatch());
+ assertEquals(egressActions, ma13.getActions());
+
+ MatchAction ma14 = maMap1.get(new SwitchPort(5L, (short) 17));
+ assertEquals(match, ma14.getMatch());
+ assertEquals(egressActions, ma14.getActions());
+
+ assertEquals(Sets.newHashSet(
+ new SwitchPort(1L, (short) 101),
+ new SwitchPort(1L, (short) 102),
+ new SwitchPort(2L, (short) 103),
+ new SwitchPort(3L, (short) 104),
+ new SwitchPort(5L, (short) 105)
+ ), maMap2.keySet());
+
+ MatchAction ma21 = maMap2.get(new SwitchPort(1L, (short) 101));
+ assertEquals(match, ma21.getMatch());
+ assertEquals(outputAction(10), ma21.getActions());
+
+ MatchAction ma22 = maMap2.get(new SwitchPort(1L, (short) 102));
+ assertEquals(match, ma22.getMatch());
+ assertEquals(outputAction(10), ma22.getActions());
+
+ MatchAction ma23 = maMap2.get(new SwitchPort(2L, (short) 103));
+ assertEquals(match, ma23.getMatch());
+ assertEquals(outputAction(11), ma23.getActions());
+
+ MatchAction ma24 = maMap2.get(new SwitchPort(3L, (short) 104));
+ assertEquals(match, ma24.getMatch());
+ assertEquals(outputAction(14), ma24.getActions());
+
+ MatchAction ma25 = maMap2.get(new SwitchPort(5L, (short) 105));
+ assertEquals(match, ma25.getMatch());
+ assertEquals(egressActions, ma25.getActions());
+ }
+}