[CORD-2635][CORD-2632] Handle Clear Deferred actions. Support for DHCP packets trace. Output optimization to handle also gateways

Change-Id: Ief37895b8b80ecb10274deac506c3d0c8c127e6d
(cherry picked from commit 603a50f706beda9c720c801b66c1a14a200945af)
diff --git a/src/main/java/org/onosproject/t3/cli/TroubleshootTraceCommand.java b/src/main/java/org/onosproject/t3/cli/TroubleshootTraceCommand.java
index bdd67d1..7387c5f 100644
--- a/src/main/java/org/onosproject/t3/cli/TroubleshootTraceCommand.java
+++ b/src/main/java/org/onosproject/t3/cli/TroubleshootTraceCommand.java
@@ -88,9 +88,18 @@
     @Option(name = "-ml", aliases = "--mplsLabel", description = "Mpls label of incoming packet")
     String mplsLabel = null;
 
-    @Option(name = "-mb", aliases = "--mplsBos", description = "MPLS BOS", valueToShowInHelp = "True")
+    @Option(name = "-mb", aliases = "--mplsBos", description = "MPLS BOS")
     String mplsBos = null;
 
+    @Option(name = "-ipp", aliases = "--ipProto", description = "IP Proto")
+    String ipProto = null;
+
+    @Option(name = "-udps", aliases = "--udpSrc", description = "UDP Source")
+    String udpSrc = null;
+
+    @Option(name = "-udpd", aliases = "--udpDst", description = "UDP Destination")
+    String udpDst = null;
+
     @Override
     protected void execute() {
         TroubleshootService service = get(TroubleshootService.class);
@@ -146,6 +155,19 @@
             selectorBuilder.matchMplsBos(Boolean.valueOf(mplsBos));
         }
 
+        if (ipProto != null) {
+            selectorBuilder.matchIPProtocol(Byte.valueOf(ipProto));
+        }
+
+        if (udpSrc != null) {
+            selectorBuilder.matchUdpSrc(TpPort.tpPort(Integer.parseInt(udpSrc)));
+        }
+
+        if (udpDst != null) {
+            selectorBuilder.matchUdpDst(TpPort.tpPort(Integer.parseInt(udpDst)));
+        }
+
+
         TrafficSelector packet = selectorBuilder.build();
 
         //Printing the created packet
diff --git a/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java b/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java
index 8f03ddc..8aeb874 100644
--- a/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java
+++ b/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java
@@ -70,6 +70,7 @@
 import java.util.stream.Collectors;
 
 import static org.onlab.packet.EthType.EtherType;
+import static org.onosproject.net.flow.TrafficSelector.*;
 import static org.onosproject.net.flow.instructions.Instructions.GroupInstruction;
 import static org.slf4j.LoggerFactory.getLogger;
 
@@ -168,23 +169,13 @@
                 computePath(completePath, trace, outputPath.getOutput());
                 break;
             } else if (cp.port().equals(PortNumber.CONTROLLER)) {
+
                 //Getting the master when the packet gets sent as packet in
                 NodeId master = mastershipService.getMasterFor(cp.deviceId());
                 trace.addResultMessage("Packet goes to the controller " + master.id());
                 computePath(completePath, trace, outputPath.getOutput());
 
-            } else if (outputPath.getFinalPacket().getCriterion(Criterion.Type.ETH_TYPE) != null &&
-                    ((EthTypeCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.ETH_TYPE)).ethType()
-                            .equals(EtherType.ARP.ethType()) && deviceService.getPort(cp).isEnabled() &&
-                    linkService.getEgressLinks(cp).isEmpty()) {
-                if (hostsList.isEmpty()) {
-                    trace.addResultMessage("Packet is ARP and reached " + cp + " with no hosts connected ");
-                } else {
-                    trace.addResultMessage("Packet is ARP and reached " + cp + " with hosts " + hostsList);
-                }
-                computePath(completePath, trace, outputPath.getOutput());
-
-            } else {
+            } else if (linkService.getEgressLinks(cp).size() > 0) {
 
                 //TODO this can be optimized if we use a Tree structure for paths.
                 //if we already have outputs let's check if the one we are considering starts from one of the devices
@@ -214,17 +205,11 @@
                 //let's compute the links for the given output
                 Set<Link> links = linkService.getEgressLinks(cp);
                 log.debug("Egress Links {}", links);
-                //No links means that the packet gets dropped.
-                if (links.size() == 0) {
-                    log.warn("No links out of {}", cp);
-                    computePath(completePath, trace, cp);
-                    trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
-                }
                 //For each link we trace the corresponding device
                 for (Link link : links) {
                     ConnectPoint dst = link.dst();
                     //change in-port to the dst link in port
-                    TrafficSelector.Builder updatedPacket = DefaultTrafficSelector.builder();
+                    Builder updatedPacket = DefaultTrafficSelector.builder();
                     outputPath.getFinalPacket().criteria().forEach(updatedPacket::add);
                     updatedPacket.add(Criteria.matchInPort(dst.port()));
                     log.debug("DST Connect Point {}", dst);
@@ -234,6 +219,23 @@
                     getTrace(completePath, dst, trace);
                 }
 
+            } else if (deviceService.getPort(cp).isEnabled()) {
+                if (hostsList.isEmpty()) {
+                    trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
+                            .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
+                            cp + " with no hosts connected ");
+                } else {
+                    trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
+                            .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
+                            cp + " with hosts " + hostsList);
+                }
+                computePath(completePath, trace, outputPath.getOutput());
+
+            } else {
+                //No links means that the packet gets dropped.
+                log.warn("No links out of {}", cp);
+                computePath(completePath, trace, cp);
+                trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
             }
         }
         return trace;
@@ -327,6 +329,8 @@
         List<FlowEntry> flows = new ArrayList<>();
         List<FlowEntry> outputFlows = new ArrayList<>();
 
+        List<Instruction> deferredInstructions = new ArrayList<>();
+
         FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
         if (nextTableIdEntry == null) {
             trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
@@ -396,14 +400,24 @@
                     outputFlows.add(flowEntry);
                     output = true;
                 }
-                //update the packet according to the actions of this flow rule.
-                packet = updatePacket(packet, flowEntry.treatment().allInstructions()).build();
+                //update the packet according to the immediate actions of this flow rule.
+                packet = updatePacket(packet, flowEntry.treatment().immediate()).build();
+
+                //save the deferred rules for later
+                deferredInstructions.addAll(flowEntry.treatment().deferred());
+
+                //If the flow requires to clear deferred actions we do so for all the ones we encountered.
+                if (flowEntry.treatment().clearedDeferred()) {
+                    deferredInstructions.clear();
+                }
+
             }
         }
 
         //Creating a modifiable builder for the output packet
-        TrafficSelector.Builder builder = DefaultTrafficSelector.builder();
+        Builder builder = DefaultTrafficSelector.builder();
         packet.criteria().forEach(builder::add);
+
         //Adding all the flows to the trace
         trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
 
@@ -449,10 +463,17 @@
         Collection<FlowEntry> nonOutputFlows = flows;
         nonOutputFlows.removeAll(outputFlowEntries);
 
+        //Handling groups pointed at by immediate instructions
         for (FlowEntry entry : flows) {
-            getGroupsFromInstructions(trace, groups, entry.treatment().allInstructions(),
+            getGroupsFromInstructions(trace, groups, entry.treatment().immediate(),
                     entry.deviceId(), builder, outputPorts, in);
         }
+
+        //If we have deferred instructions at this point we handle them.
+        if (deferredInstructions.size() > 0) {
+            builder = handleDeferredActions(trace, packet, in, deferredInstructions, outputPorts, groups);
+
+        }
         packet = builder.build();
         log.debug("Groups hit by packet {}", packet);
         groups.forEach(group -> {
@@ -463,10 +484,11 @@
         outputPorts.forEach(port -> {
             log.debug("Port {}", port);
         });
-        log.debug("Output Packet {}", packet);
+        log.info("Output Packet {}", packet);
         return trace;
     }
 
+
     /**
      * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
      *
@@ -507,6 +529,36 @@
                 .stream().filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
     }
 
+    private Builder handleDeferredActions(StaticPacketTrace trace, TrafficSelector packet,
+                                          ConnectPoint in, List<Instruction> deferredInstructions,
+                                          List<PortNumber> outputPorts, List<Group> groups) {
+
+        //Update the packet with the deferred instructions
+        Builder builder = updatePacket(packet, deferredInstructions);
+
+        //Gather any output instructions from the deferred instruction
+        List<Instruction> outputFlowInstruction = deferredInstructions.stream().filter(instruction -> {
+            return instruction.type().equals(Instruction.Type.OUTPUT);
+        }).collect(Collectors.toList());
+
+        //We are considering deferred instructions from flows, there can only be one output.
+        if (outputFlowInstruction.size() > 1) {
+            trace.addResultMessage("More than one flow rule with OUTPUT instruction");
+            log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
+        }
+        //If there is one output let's go through that
+        if (outputFlowInstruction.size() == 1) {
+            buildOutputFromDevice(trace, in, builder, outputPorts, (OutputInstruction) outputFlowInstruction.get(0),
+                    ImmutableList.of());
+        }
+        //If there is no output let's see if there any deferred instruction point to groups.
+        if (outputFlowInstruction.size() == 0) {
+            getGroupsFromInstructions(trace, groups, deferredInstructions,
+                    in.deviceId(), builder, outputPorts, in);
+        }
+        return builder;
+    }
+
     /**
      * Gets group information from instructions.
      *
@@ -519,7 +571,7 @@
      */
     private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
                                            List<Instruction> instructions, DeviceId deviceId,
-                                           TrafficSelector.Builder builder, List<PortNumber> outputPorts,
+                                           Builder builder, List<PortNumber> outputPorts,
                                            ConnectPoint in) {
         List<Instruction> groupInstructionlist = new ArrayList<>();
         for (Instruction instruction : instructions) {
@@ -569,9 +621,9 @@
      * @param builder           the packet builder
      * @param outputPorts       the list of output ports for this device
      * @param outputInstruction the output instruction
-     * @param groupsForDevice
+     * @param groupsForDevice   the groups we output from
      */
-    private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, TrafficSelector.Builder builder,
+    private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, Builder builder,
                                        List<PortNumber> outputPorts, OutputInstruction outputInstruction,
                                        List<Group> groupsForDevice) {
         ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
@@ -592,8 +644,8 @@
      * @param instructions the set of instructions
      * @return the packet with the applied instructions
      */
-    private TrafficSelector.Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
-        TrafficSelector.Builder newSelector = DefaultTrafficSelector.builder();
+    private Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
+        Builder newSelector = DefaultTrafficSelector.builder();
         packet.criteria().forEach(newSelector::add);
         //FIXME optimize
         for (Instruction instruction : instructions) {
@@ -609,7 +661,7 @@
      * @param instruction the instruction to be translated
      * @return the new selector with the applied instruction
      */
-    private TrafficSelector.Builder translateInstruction(TrafficSelector.Builder newSelector, Instruction instruction) {
+    private Builder translateInstruction(Builder newSelector, Instruction instruction) {
         log.debug("Translating instruction {}", instruction);
         log.debug("New Selector {}", newSelector.build());
         //TODO add as required
@@ -640,7 +692,7 @@
                         //When popping MPLS we remove label and BOS
                         TrafficSelector temporaryPacket = newSelector.build();
                         if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
-                            TrafficSelector.Builder noMplsSelector = DefaultTrafficSelector.builder();
+                            Builder noMplsSelector = DefaultTrafficSelector.builder();
                             temporaryPacket.criteria().stream().filter(c -> {
                                 return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
                                         !c.type().equals(Criterion.Type.MPLS_BOS);
diff --git a/src/test/java/org/onosproject/t3/impl/T3TestObjects.java b/src/test/java/org/onosproject/t3/impl/T3TestObjects.java
index 5f6a7d9..9009c8e 100644
--- a/src/test/java/org/onosproject/t3/impl/T3TestObjects.java
+++ b/src/test/java/org/onosproject/t3/impl/T3TestObjects.java
@@ -440,6 +440,44 @@
 
     static final Group DUAL_LINK_GROUP = new DefaultGroup(GROUP_ID, DUAL_LINK_1, Group.Type.SELECT, BUCKETS_DUAL);
 
+    //Clear Deferred
+    static final DeviceId DEFERRED_1 = DeviceId.deviceId("Deferred");
+
+    static final ConnectPoint DEFERRED_CP_1_IN = ConnectPoint.deviceConnectPoint(DEFERRED_1 + "/" + 1);
+    static final ConnectPoint DEFERRED_CP_2_OUT = ConnectPoint.deviceConnectPoint(DEFERRED_1 + "/" + 2);
+
+    //match on port 1 and apply deferred actions
+    private static final TrafficTreatment DEFERRED_1_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+            .transition(10)
+            .deferred()
+            .pushMpls()
+            .setMpls(MplsLabel.mplsLabel(100))
+            .build();
+    private static final FlowRule DEFERRED_FLOW = DefaultFlowEntry.builder().forDevice(DEFERRED_1)
+            .forTable(0)
+            .withPriority(100)
+            .withSelector(SINGLE_FLOW_SELECTOR)
+            .withTreatment(DEFERRED_1_FLOW_TREATMENT)
+            .fromApp(new DefaultApplicationId(0, "TestApp"))
+            .makePermanent()
+            .build();
+    static final FlowEntry DEFERRED_FLOW_ENTRY = new DefaultFlowEntry(DEFERRED_FLOW);
+
+    //match on port 1, clear deferred actions and output
+    private static final TrafficTreatment DEFERRED_CLEAR_1_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+            .wipeDeferred()
+            .setOutput(PortNumber.portNumber(2))
+            .build();
+    private static final FlowRule DEFERRED_CLEAR_FLOW = DefaultFlowEntry.builder().forDevice(DEFERRED_1)
+            .forTable(10)
+            .withPriority(100)
+            .withSelector(SINGLE_FLOW_SELECTOR)
+            .withTreatment(DEFERRED_CLEAR_1_FLOW_TREATMENT)
+            .fromApp(new DefaultApplicationId(0, "TestApp"))
+            .makePermanent()
+            .build();
+    static final FlowEntry DEFERRED_CLEAR_FLOW_ENTRY = new DefaultFlowEntry(DEFERRED_CLEAR_FLOW);
+
     //helper elements
 
     static final String MASTER_1 = "Master1";
diff --git a/src/test/java/org/onosproject/t3/impl/TroubleshootManagerTest.java b/src/test/java/org/onosproject/t3/impl/TroubleshootManagerTest.java
index d7d3f33..7c584c9 100644
--- a/src/test/java/org/onosproject/t3/impl/TroubleshootManagerTest.java
+++ b/src/test/java/org/onosproject/t3/impl/TroubleshootManagerTest.java
@@ -272,6 +272,21 @@
 
     }
 
+    /**
+     * Test proper clear deferred behaviour.
+     */
+    @Test
+    public void clearDeferred() throws Exception {
+
+        StaticPacketTrace traceSuccess = testSuccess(PACKET_OK, DEFERRED_CP_1_IN,
+                DEFERRED_1, DEFERRED_CP_2_OUT, 1, 1);
+
+        assertNull("MPLS should have been not applied due to clear deferred", traceSuccess
+                .getGroupOuputs(DEFERRED_1).get(0).getFinalPacket().getCriterion(Criterion.Type.MPLS_LABEL));
+
+    }
+
+
     private StaticPacketTrace testSuccess(TrafficSelector packet, ConnectPoint in, DeviceId deviceId, ConnectPoint out,
                                           int paths, int outputs) {
         StaticPacketTrace traceSuccess = mngr.trace(packet, in);
@@ -329,6 +344,8 @@
                 return ImmutableList.of(DUAL_LINK_1_GROUP_FLOW_ENTRY, DUAL_LINK_2_GROUP_FLOW_ENTRY);
             } else if (deviceId.equals(DUAL_LINK_3)) {
                 return ImmutableList.of(DUAL_LINK_3_FLOW_ENTRY, DUAL_LINK_3_FLOW_ENTRY_2);
+            } else if (deviceId.equals(DEFERRED_1)) {
+                return ImmutableList.of(DEFERRED_FLOW_ENTRY, DEFERRED_CLEAR_FLOW_ENTRY);
             }
             return ImmutableList.of();
         }