T3 Handling lldp and Bddp

Change-Id: Iecbe22bbbaa6d4719092be97385f70828d628b8c
(cherry picked from commit e30f33e3eb8b0250894c856bb0a0e260fcd349cd)
diff --git a/src/main/java/org/onosproject/t3/cli/TroubleshootTraceCommand.java b/src/main/java/org/onosproject/t3/cli/TroubleshootTraceCommand.java
index 587c748..ae68d93 100644
--- a/src/main/java/org/onosproject/t3/cli/TroubleshootTraceCommand.java
+++ b/src/main/java/org/onosproject/t3/cli/TroubleshootTraceCommand.java
@@ -16,6 +16,7 @@
 
 package org.onosproject.t3.cli;
 
+import com.google.common.base.Preconditions;
 import org.apache.karaf.shell.commands.Command;
 import org.apache.karaf.shell.commands.Option;
 import org.onlab.packet.IpAddress;
@@ -25,6 +26,8 @@
 import org.onlab.packet.VlanId;
 import org.onosproject.cli.AbstractShellCommand;
 import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.TrafficSelector;
 import org.onosproject.net.flow.TrafficTreatment;
@@ -53,6 +56,8 @@
     private static final String GROUP_BUCKET_FORMAT =
             "       id=0x%s, bucket=%s, bytes=%s, packets=%s, actions=%s";
 
+    private static final String CONTROLLER = "CONTROLLER";
+
     @Option(name = "-v", aliases = "--verbose", description = "Outputs complete path")
     private boolean verbosity1 = false;
 
@@ -80,7 +85,7 @@
     @Option(name = "-dm", aliases = "--dstMac", description = "Destination MAC")
     String dstMac = null;
 
-    @Option(name = "-dtp", aliases = "dstTcpPort", description = "destination TCP Port")
+    @Option(name = "-dtp", aliases = "--dstTcpPort", description = "destination TCP Port")
     String dstTcpPort = null;
 
     @Option(name = "-vid", aliases = "--vlanId", description = "Vlan of incoming packet", valueToShowInHelp = "None")
@@ -104,7 +109,17 @@
     @Override
     protected void execute() {
         TroubleshootService service = get(TroubleshootService.class);
-        ConnectPoint cp = ConnectPoint.deviceConnectPoint(srcPort);
+        String[] cpInfo = srcPort.split("/");
+        Preconditions.checkArgument(cpInfo.length == 2, "wrong format of source port");
+        ConnectPoint cp;
+        //Uses input port as a convenience to carry the Controller port, proper flood behaviour is handled in the
+        // troubleshoot manager.
+        if (cpInfo[1].equalsIgnoreCase(CONTROLLER)) {
+            cp = new ConnectPoint(DeviceId.deviceId(cpInfo[0]), PortNumber.CONTROLLER);
+        } else {
+            cp = ConnectPoint.deviceConnectPoint(srcPort);
+        }
+
         EtherType type = EtherType.valueOf(ethType.toUpperCase());
 
         //Input Port must be specified
@@ -176,7 +191,6 @@
 
         //Build the trace
         StaticPacketTrace trace = service.trace(packet, cp);
-
         //Print based on verbosity
         if (verbosity1) {
             printTrace(trace, false);
@@ -196,17 +210,27 @@
         paths.forEach(path -> {
             print("Path %s", path);
             ConnectPoint previous = null;
-            for (ConnectPoint connectPoint : path) {
-                if (previous == null || !previous.deviceId().equals(connectPoint.deviceId())) {
-                    print("Device %s", connectPoint.deviceId());
-                    print("Input from %s", connectPoint);
-                    printFlows(trace, verbose, connectPoint);
-                } else {
-                    printGroups(trace, verbose, connectPoint);
-                    print("Output through %s", connectPoint);
-                    print("");
+            if (path.size() == 1) {
+                ConnectPoint connectPoint = path.get(0);
+                print("Device %s", connectPoint.deviceId());
+                print("Input from %s", connectPoint);
+                printFlows(trace, verbose, connectPoint);
+                printGroups(trace, verbose, connectPoint);
+                print("Output through %s", connectPoint);
+                print("");
+            } else {
+                for (ConnectPoint connectPoint : path) {
+                    if (previous == null || !previous.deviceId().equals(connectPoint.deviceId())) {
+                        print("Device %s", connectPoint.deviceId());
+                        print("Input from %s", connectPoint);
+                        printFlows(trace, verbose, connectPoint);
+                    } else {
+                        printGroups(trace, verbose, connectPoint);
+                        print("Output through %s", connectPoint);
+                        print("");
+                    }
+                    previous = connectPoint;
                 }
-                previous = connectPoint;
             }
             print("---------------------------------------------------------------\n");
         });
diff --git a/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java b/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java
index bbec3ea..a1b1d68 100644
--- a/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java
+++ b/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java
@@ -30,6 +30,7 @@
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.Host;
 import org.onosproject.net.Link;
+import org.onosproject.net.Port;
 import org.onosproject.net.PortNumber;
 import org.onosproject.net.device.DeviceService;
 import org.onosproject.net.driver.DriverService;
@@ -90,6 +91,8 @@
 
     private static final Logger log = getLogger(TroubleshootManager.class);
 
+    static final String PACKET_TO_CONTROLLER = "Packet goes to the controller";
+
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected FlowRuleService flowRuleService;
 
@@ -177,7 +180,7 @@
 
                 //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());
+                trace.addResultMessage(PACKET_TO_CONTROLLER + " " + master.id());
                 computePath(completePath, trace, outputPath.getOutput());
                 handleVlanToController(outputPath, trace);
 
@@ -226,16 +229,22 @@
                 }
 
             } 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);
+                EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
+                        .getCriterion(Criterion.Type.ETH_TYPE);
+                //We treat as correct output only if it's not LLDP or BDDP
+                if (!(ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
+                        || !ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
+                    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());
                 }
-                computePath(completePath, trace, outputPath.getOutput());
 
             } else {
                 //No links means that the packet gets dropped.
@@ -358,6 +367,17 @@
             return trace;
         }
 
+        //handle when the input is the controller
+        //NOTE, we are using the input port as a convenience to carry the CONTROLLER port number even if
+        // a packet in from the controller will not actually traverse the pipeline and have no such notion
+        // as the input port.
+        if (in.port().equals(PortNumber.CONTROLLER)) {
+            StaticPacketTrace outputTrace = inputFromController(trace, in);
+            if (outputTrace != null) {
+                return trace;
+            }
+        }
+
         List<FlowEntry> flows = new ArrayList<>();
         List<FlowEntry> outputFlows = new ArrayList<>();
 
@@ -532,6 +552,37 @@
         return trace;
     }
 
+    /**
+     * Handles the specific case where the Input is the controller.
+     * Note that the in port is used as a convenience to store the port of the controller even if the packet in
+     * from a controller should not have a physical input port. The in port from the Controller is used to make sure
+     * the flood to all active physical ports of the device.
+     *
+     * @param trace the trace
+     * @param in    the controller port
+     * @return the augmented trace.
+     */
+    private StaticPacketTrace inputFromController(StaticPacketTrace trace, ConnectPoint in) {
+        EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
+                .getCriterion(Criterion.Type.ETH_TYPE);
+        //If the packet is LLDP or BDDP we flood it on all active ports of the switch.
+        if (ethTypeCriterion != null && (ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
+                || ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
+            //get the active ports
+            List<Port> enabledPorts = deviceService.getPorts(in.deviceId()).stream()
+                    .filter(Port::isEnabled)
+                    .collect(Collectors.toList());
+            //build an output from each one
+            enabledPorts.forEach(port -> {
+                GroupsInDevice output = new GroupsInDevice(new ConnectPoint(port.element().id(), port.number()),
+                        ImmutableList.of(), trace.getInitialPacket());
+                trace.addGroupOutputPath(in.deviceId(), output);
+            });
+            return trace;
+        }
+        return null;
+    }
+
     private boolean needsSecondTable10Flow(FlowEntry flowEntry, boolean isOfdpaHardware) {
         return isOfdpaHardware && flowEntry.table().equals(IndexTableId.of(10))
                 && flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID) != null
@@ -713,6 +764,8 @@
                                        List<PortNumber> outputPorts, OutputInstruction outputInstruction,
                                        List<Group> groupsForDevice) {
         ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
+
+        //if the output is the input same we drop, except if the output is the controller
         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 abbe77e..4e1eb68 100644
--- a/src/test/java/org/onosproject/t3/impl/T3TestObjects.java
+++ b/src/test/java/org/onosproject/t3/impl/T3TestObjects.java
@@ -539,6 +539,29 @@
             .build();
     static final FlowEntry DEFERRED_CLEAR_FLOW_ENTRY = new DefaultFlowEntry(DEFERRED_CLEAR_FLOW);
 
+    //LLDP
+
+    static final DeviceId LLDP_FLOW_DEVICE = DeviceId.deviceId("LldpDevice");
+
+    private static final TrafficSelector LLDP_FLOW_SELECTOR = DefaultTrafficSelector.builder()
+            .matchInPort(PortNumber.portNumber(1))
+            .matchEthType(EthType.EtherType.LLDP.ethType().toShort())
+            .build();
+
+    private static final TrafficTreatment LLDP_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+            .setOutput(PortNumber.CONTROLLER).build();
+    private static final FlowRule LLDP_FLOW = DefaultFlowEntry.builder().forDevice(LLDP_FLOW_DEVICE)
+            .forTable(0)
+            .withPriority(100)
+            .withSelector(LLDP_FLOW_SELECTOR)
+            .withTreatment(LLDP_FLOW_TREATMENT)
+            .fromApp(new DefaultApplicationId(0, "TestApp"))
+            .makePermanent()
+            .build();
+    static final FlowEntry LLDP_FLOW_ENTRY = new DefaultFlowEntry(LLDP_FLOW);
+
+    static final ConnectPoint LLDP_FLOW_CP = ConnectPoint.deviceConnectPoint(LLDP_FLOW_DEVICE + "/" + 1);
+
     //helper elements
 
     static final String MASTER_1 = "Master1";
@@ -571,6 +594,11 @@
             .matchEthType(EthType.EtherType.ARP.ethType().toShort())
             .build();
 
+    static final TrafficSelector PACKET_LLDP = DefaultTrafficSelector.builder()
+            .matchInPort(PortNumber.portNumber(1))
+            .matchEthType(EthType.EtherType.LLDP.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 9767730..7b6c570 100644
--- a/src/test/java/org/onosproject/t3/impl/TroubleshootManagerTest.java
+++ b/src/test/java/org/onosproject/t3/impl/TroubleshootManagerTest.java
@@ -62,6 +62,7 @@
 import static org.junit.Assert.assertTrue;
 import static org.onosproject.net.Device.Type.SWITCH;
 import static org.onosproject.t3.impl.T3TestObjects.*;
+import static org.onosproject.t3.impl.TroubleshootManager.PACKET_TO_CONTROLLER;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
@@ -132,7 +133,7 @@
         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"));
+                traceSuccess.resultMessage().contains(PACKET_TO_CONTROLLER));
         assertTrue("Master should be Master1",
                 traceSuccess.resultMessage().contains(MASTER_1));
         ConnectPoint connectPoint = traceSuccess.getGroupOuputs(ARP_FLOW_DEVICE).get(0).getOutput();
@@ -303,6 +304,22 @@
     }
 
 
+    /**
+     * Test LLDP output to controller.
+     */
+    @Test
+    public void lldpToController() {
+        StaticPacketTrace traceSuccess = mngr.trace(PACKET_LLDP, LLDP_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(LLDP_FLOW_DEVICE).get(0).getOutput();
+        assertEquals("Packet Should go to CONTROLLER", PortNumber.CONTROLLER, connectPoint.port());
+        log.info("trace {}", traceSuccess.resultMessage());
+    }
+
     private StaticPacketTrace testSuccess(TrafficSelector packet, ConnectPoint in, DeviceId deviceId, ConnectPoint out,
                                           int paths, int outputs) {
         StaticPacketTrace traceSuccess = mngr.trace(packet, in);
@@ -365,6 +382,8 @@
             } else if (deviceId.equals(HARDWARE_DEVICE_10)) {
                 return ImmutableList.of(HARDWARE_10_FLOW_ENTRY, HARDWARE_10_SECOND_FLOW_ENTRY,
                         HARDWARE_10_OUTPUT_FLOW_ENTRY);
+            } else if (deviceId.equals(LLDP_FLOW_DEVICE)) {
+                return ImmutableList.of(LLDP_FLOW_ENTRY);
             }
             return ImmutableList.of();
         }