[CORD-2582] Adding ARP support to T3

Change-Id: Ie027c47d58cbb473caf065dba75b46f316ac75e9
(cherry picked from commit 236b70250169db9f3231c5e6923fbcb191174d53)
diff --git a/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java b/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java
index 1481c30..c206176 100644
--- a/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java
+++ b/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java
@@ -24,6 +24,8 @@
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
 import org.onlab.packet.VlanId;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.mastership.MastershipService;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
@@ -59,6 +61,7 @@
 
 import java.net.UnknownHostException;
 import java.util.ArrayList;
+import java.util.Collection;
 import java.util.Collections;
 import java.util.Comparator;
 import java.util.HashSet;
@@ -99,6 +102,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected DeviceService deviceService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected MastershipService mastershipService;
+
     @Override
     public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
         log.info("Tracing packet {} coming in through {}", packet, in);
@@ -143,20 +149,39 @@
 
         //If the trace has ouputs we analyze them all
         for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
-            log.debug("Output path {}", outputPath.getOutput());
+
+            ConnectPoint cp = outputPath.getOutput();
+            log.debug("Output path {}", cp);
+
             //Hosts for the the given output
-            Set<Host> hostsList = hostService.getConnectedHosts(outputPath.getOutput());
+            Set<Host> hostsList = hostService.getConnectedHosts(cp);
             //Hosts queried from the original ip or mac
             Set<Host> hosts = getHosts(trace);
 
             //If the two host collections contain the same item it means we reached the proper output
             if (!Collections.disjoint(hostsList, hosts)) {
                 log.debug("Stopping here because host is expected destination");
-                trace.addResultMessage("Reached required destination Host");
+                trace.addResultMessage("Reached required destination Host " + cp);
                 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 {
-                ConnectPoint cp = outputPath.getOutput();
                 //let's add the ouput for the input
                 completePath.add(cp);
                 log.debug("------------------------------------------------------------");
@@ -169,7 +194,6 @@
                     log.warn("No links out of {}", cp);
                     computePath(completePath, trace, cp);
                     trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
-                    return trace;
                 }
                 //For each link we trace the corresponding device
                 for (Link link : links) {
@@ -351,7 +375,7 @@
         TrafficSelector.Builder builder = DefaultTrafficSelector.builder();
         packet.criteria().forEach(builder::add);
         //Adding all the flows to the trace
-        trace.addFlowsForDevice(in.deviceId(), flows);
+        trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
 
         log.debug("Flows traversed by {}", packet);
         flows.forEach(entry -> {
@@ -364,33 +388,47 @@
         });
         List<PortNumber> outputPorts = new ArrayList<>();
 
-        //Decide Output for packet when flow rule contains an OUTPUT instruction
-        Set<Instruction> outputFlowEntries = outputFlows.stream().flatMap(flow -> {
-            return flow.treatment().allInstructions().stream();
-        })
-                .filter(instruction -> {
-                    return instruction.type().equals(Instruction.Type.OUTPUT);
-                }).collect(Collectors.toSet());
-        log.debug("Output instructions {}", outputFlowEntries);
+        //TODO optimization
 
-        if (outputFlowEntries.size() != 0) {
-            outputThroughFlows(trace, packet, in, builder, outputPorts, outputFlowEntries);
+        //outputFlows contains also last rule of device, so we need filtering for OUTPUT instructions.
+        List<FlowEntry> outputFlowEntries = outputFlows.stream().filter(flow -> flow.treatment()
+                .allInstructions().stream().filter(instruction -> instruction.type()
+                        .equals(Instruction.Type.OUTPUT)).count() > 0).collect(Collectors.toList());
 
-        } else {
-            log.debug("Handling Groups");
-            //Analyze Groups
-            List<Group> groups = new ArrayList<>();
-
-            for (FlowEntry entry : flows) {
-                getGroupsFromInstructions(trace, groups, entry.treatment().allInstructions(),
-                        entry.deviceId(), builder, outputPorts, in);
-            }
-            packet = builder.build();
-            log.debug("Groups hit by packet {}", packet);
-            groups.forEach(group -> {
-                log.debug("Group {}", group);
-            });
+        if (outputFlowEntries.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 (outputFlowEntries.size() == 1) {
+
+            OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.get(0).treatment()
+                    .allInstructions().stream()
+                    .filter(instruction -> {
+                        return instruction.type().equals(Instruction.Type.OUTPUT);
+                    }).findFirst().get();
+
+            //FIXME using GroupsInDevice for output even if flows.
+            buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
+
+        }
+        log.debug("Handling Groups");
+        //Analyze Groups
+        List<Group> groups = new ArrayList<>();
+
+        Collection<FlowEntry> nonOutputFlows = flows;
+        nonOutputFlows.removeAll(outputFlowEntries);
+
+        for (FlowEntry entry : flows) {
+            getGroupsFromInstructions(trace, groups, entry.treatment().allInstructions(),
+                    entry.deviceId(), builder, outputPorts, in);
+        }
+        packet = builder.build();
+        log.debug("Groups hit by packet {}", packet);
+        groups.forEach(group -> {
+            log.debug("Group {}", group);
+        });
+
         log.debug("Output ports for packet {}", packet);
         outputPorts.forEach(port -> {
             log.debug("Port {}", port);
@@ -400,28 +438,6 @@
     }
 
     /**
-     * Method that saves the output if that si done via an OUTPUT treatment of a flow rule.
-     *
-     * @param trace             the trace
-     * @param packet            the packet coming in to this device
-     * @param in                the input connect point for this device
-     * @param builder           the updated packet0
-     * @param outputPorts       the list of output ports for this device
-     * @param outputFlowEntries the list of flow entries with OUTPUT treatment
-     */
-    private void outputThroughFlows(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in,
-                                    TrafficSelector.Builder builder, List<PortNumber> outputPorts,
-                                    Set<Instruction> outputFlowEntries) {
-        if (outputFlowEntries.size() > 1) {
-            log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
-        } else {
-            OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.iterator().next();
-            //FIXME using GroupsInDevice for output even if flows.
-            buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
-        }
-    }
-
-    /**
      * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
      *
      * @param packet the incoming packet
@@ -528,8 +544,7 @@
     private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, TrafficSelector.Builder builder,
                                        List<PortNumber> outputPorts, OutputInstruction outputInstruction,
                                        List<Group> groupsForDevice) {
-        ConnectPoint output = ConnectPoint.deviceConnectPoint(in.deviceId() + "/" +
-                outputInstruction.port());
+        ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
         if (output.equals(in)) {
             trace.addResultMessage("Connect point out " + output + " is same as initial input " +
                     trace.getInitialConnectPoint());
diff --git a/src/test/java/org/onosproject/t3/impl/T3TestObjects.java b/src/test/java/org/onosproject/t3/impl/T3TestObjects.java
index 15cee5f..744b19b 100644
--- a/src/test/java/org/onosproject/t3/impl/T3TestObjects.java
+++ b/src/test/java/org/onosproject/t3/impl/T3TestObjects.java
@@ -51,7 +51,7 @@
  */
 final class T3TestObjects {
 
-    private T3TestObjects(){
+    private T3TestObjects() {
         //banning construction
     }
 
@@ -106,6 +106,28 @@
 
     static final ConnectPoint SAME_OUTPUT_FLOW_CP = ConnectPoint.deviceConnectPoint(SAME_OUTPUT_FLOW_DEVICE + "/" + 1);
 
+    //ARP
+    static final DeviceId ARP_FLOW_DEVICE = DeviceId.deviceId("ArpDevice");
+
+    private static final TrafficSelector ARP_FLOW_SELECTOR = DefaultTrafficSelector.builder()
+            .matchInPort(PortNumber.portNumber(1))
+            .matchEthType(EthType.EtherType.ARP.ethType().toShort())
+            .build();
+
+    private static final TrafficTreatment ARP_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+            .setOutput(PortNumber.CONTROLLER).build();
+    private static final FlowRule ARP_FLOW = DefaultFlowEntry.builder().forDevice(ARP_FLOW_DEVICE)
+            .forTable(0)
+            .withPriority(100)
+            .withSelector(ARP_FLOW_SELECTOR)
+            .withTreatment(ARP_FLOW_TREATMENT)
+            .fromApp(new DefaultApplicationId(0, "TestApp"))
+            .makePermanent()
+            .build();
+    static final FlowEntry ARP_FLOW_ENTRY = new DefaultFlowEntry(ARP_FLOW);
+
+    static final ConnectPoint ARP_FLOW_CP = ConnectPoint.deviceConnectPoint(ARP_FLOW_DEVICE + "/" + 1);
+
 
     //Dual Flow Test
     static final DeviceId DUAL_FLOW_DEVICE = DeviceId.deviceId("DualFlowDevice");
@@ -323,10 +345,10 @@
     static final FlowEntry HARDWARE_ETH_FLOW_ENTRY = new DefaultFlowEntry(HARDWARE_ETH_FLOW);
 
 
-
-
     //helper elements
 
+    static final String MASTER_1 = "Master1";
+
     static final Host H1 = new DefaultHost(ProviderId.NONE, HostId.hostId(HOST_ONE), MacAddress.valueOf(100),
             VlanId.NONE, new HostLocation(SINGLE_FLOW_DEVICE, PortNumber.portNumber(2), 0),
             ImmutableSet.of(IpAddress.valueOf("127.0.0.2")));
@@ -348,6 +370,12 @@
             .matchIPDst(IpPrefix.valueOf("127.0.0.3/32"))
             .build();
 
+    static final TrafficSelector PACKET_ARP = DefaultTrafficSelector.builder()
+            .matchInPort(PortNumber.portNumber(1))
+            .matchIPDst(IpPrefix.valueOf("255.255.255.255/32"))
+            .matchEthType(EthType.EtherType.ARP.ethType().toShort())
+            .build();
+
     static final TrafficSelector PACKET_FAIL = DefaultTrafficSelector.builder()
             .matchInPort(PortNumber.portNumber(1))
             .matchIPSrc(IpPrefix.valueOf("127.0.0.1/32"))
diff --git a/src/test/java/org/onosproject/t3/impl/TroubleshootManagerTest.java b/src/test/java/org/onosproject/t3/impl/TroubleshootManagerTest.java
index a8f6524..7ca5136 100644
--- a/src/test/java/org/onosproject/t3/impl/TroubleshootManagerTest.java
+++ b/src/test/java/org/onosproject/t3/impl/TroubleshootManagerTest.java
@@ -24,6 +24,8 @@
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.MacAddress;
 import org.onlab.packet.VlanId;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.mastership.MastershipServiceAdapter;
 import org.onosproject.net.ConnectPoint;
 import org.onosproject.net.DefaultAnnotations;
 import org.onosproject.net.DefaultDevice;
@@ -32,6 +34,7 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
 import org.onosproject.net.Link;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.device.DeviceServiceAdapter;
 import org.onosproject.net.driver.DefaultDriver;
 import org.onosproject.net.driver.Driver;
@@ -79,6 +82,7 @@
         mngr.driverService = new TestDriverService();
         mngr.groupService = new TestGroupService();
         mngr.deviceService = new TestDeviceService();
+        mngr.mastershipService = new TestMastershipService();
 
         assertNotNull("Manager should not be null", mngr);
 
@@ -120,6 +124,22 @@
         log.info("trace {}", traceFail.resultMessage());
     }
 
+    /**
+     * Tests ARP to controller.
+     */
+    @Test
+    public void arpToController() {
+        StaticPacketTrace traceSuccess = mngr.trace(PACKET_ARP, ARP_FLOW_CP);
+        assertNotNull("Trace should not be null", traceSuccess);
+        assertTrue("Trace should be successful",
+                traceSuccess.resultMessage().contains("Packet goes to the controller"));
+        assertTrue("Master should be Master1",
+                traceSuccess.resultMessage().contains(MASTER_1));
+        ConnectPoint connectPoint = traceSuccess.getGroupOuputs(ARP_FLOW_DEVICE).get(0).getOutput();
+        assertEquals("Packet Should go to CONTROLLER", PortNumber.CONTROLLER, connectPoint.port());
+        log.info("trace {}", traceSuccess.resultMessage());
+    }
+
 
     /**
      * Tests failure on device with no flows.
@@ -285,6 +305,8 @@
                 return ImmutableList.of(HARDWARE_ETH_FLOW_ENTRY, HARDWARE_FLOW_ENTRY);
             } else if (deviceId.equals(SAME_OUTPUT_FLOW_DEVICE)) {
                 return ImmutableList.of(SAME_OUTPUT_FLOW_ENTRY);
+            } else if (deviceId.equals(ARP_FLOW_DEVICE)) {
+                return ImmutableList.of(ARP_FLOW_ENTRY);
             }
             return ImmutableList.of();
         }
@@ -400,4 +422,11 @@
             return true;
         }
     }
+
+    private class TestMastershipService extends MastershipServiceAdapter {
+        @Override
+        public NodeId getMasterFor(DeviceId deviceId) {
+            return NodeId.nodeId(MASTER_1);
+        }
+    }
 }
\ No newline at end of file