[CORD-2432][CORD-2431] Initial t3 implementation

Change-Id: I1ed421f82c234bb006ed2cefefea53d773f1efc9
diff --git a/apps/t3/BUCK b/apps/t3/BUCK
index bca18be..a21999d 100644
--- a/apps/t3/BUCK
+++ b/apps/t3/BUCK
@@ -6,16 +6,23 @@
     '//core/api:onos-api',
     '//lib:org.apache.karaf.shell.console',
     '//cli:onos-cli',
+    '//drivers/default:onos-drivers-default',
+]
+
+TEST_DEPS = [
+    '//lib:TEST_ADAPTERS',
+    '//utils/misc:onlab-misc',
 ]
 
 osgi_jar_with_tests (
     deps = COMPILE_DEPS,
+    test_deps = TEST_DEPS,
 )
 
 onos_app (
     title = 'Trellis Troubleshooting Toolkit',
-    category = 'Utility',
-    url = 'http://onosproject.org',
+    category = 'Utilities',
+    url = 'https://wiki.opencord.org/pages/viewpage.action?pageId=4456974',
     description = 'Provides static analysis of flows and groups ' +
     'to determine the possible paths a packet may take.',
 )
diff --git a/apps/t3/pom.xml b/apps/t3/pom.xml
index ef1349e..e82a900 100644
--- a/apps/t3/pom.xml
+++ b/apps/t3/pom.xml
@@ -54,6 +54,12 @@
         </dependency>
         <dependency>
             <groupId>org.onosproject</groupId>
+            <artifactId>onos-api</artifactId>
+            <scope>test</scope>
+            <classifier>tests</classifier>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
             <artifactId>onos-core-primitives</artifactId>
             <version>${project.version}</version>
         </dependency>
@@ -70,6 +76,16 @@
             <groupId>org.apache.felix</groupId>
             <artifactId>org.apache.felix.scr.annotations</artifactId>
         </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-drivers-default</artifactId>
+            <version>${project.version}</version>
+        </dependency>
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onlab-misc</artifactId>
+            <version>${project.version}</version>
+        </dependency>
     </dependencies>
 
 </project>
diff --git a/apps/t3/src/main/java/org/onosproject/t3/api/GroupsInDevice.java b/apps/t3/src/main/java/org/onosproject/t3/api/GroupsInDevice.java
new file mode 100644
index 0000000..3cfb41a
--- /dev/null
+++ b/apps/t3/src/main/java/org/onosproject/t3/api/GroupsInDevice.java
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.t3.api;
+
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.group.Group;
+
+import java.util.List;
+
+/**
+ * Class to represent the groups in a device for a given output and packet.
+ */
+//FIXME consider removing.
+public class GroupsInDevice {
+
+    private ConnectPoint output;
+    private List<Group> groups;
+    private TrafficSelector selector;
+
+    /**
+     * Saves the given groups for the output connect point and the selector.
+     * @param output the output connect point
+     * @param groups the groups
+     * @param selector the selector representing the final packet
+     */
+    public GroupsInDevice(ConnectPoint output, List<Group> groups, TrafficSelector selector) {
+
+        this.output = output;
+        this.groups = groups;
+        this.selector = selector;
+    }
+
+    /**
+     * Returns the output connect point.
+     * @return the connect point
+     */
+    public ConnectPoint getOutput() {
+        return output;
+    }
+
+    /**
+     * Returns the groups.
+     * @return groups.
+     */
+    public List<Group> getGroups() {
+        return groups;
+    }
+
+    /**
+     * Returns the final packet after traversing the network.
+     * @return the selector with packet info
+     */
+    public TrafficSelector getFinalPacket() {
+        return selector;
+    }
+
+    @Override
+    public String toString() {
+        return "GroupsInDevice{" +
+                "output=" + output +
+                ", groups=" + groups +
+                ", selector=" + selector +
+                '}';
+    }
+
+}
diff --git a/apps/t3/src/main/java/org/onosproject/t3/api/StaticPacketTrace.java b/apps/t3/src/main/java/org/onosproject/t3/api/StaticPacketTrace.java
index e6ee8e8..cfc6371 100644
--- a/apps/t3/src/main/java/org/onosproject/t3/api/StaticPacketTrace.java
+++ b/apps/t3/src/main/java/org/onosproject/t3/api/StaticPacketTrace.java
@@ -16,10 +16,153 @@
 
 package org.onosproject.t3.api;
 
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.TrafficSelector;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
 /**
  * Encapsulates the result of tracing a packet (traffic selector) through
  * the current topology.
  */
 public class StaticPacketTrace {
 
+    private final TrafficSelector inPacket;
+    private final ConnectPoint in;
+    List<List<ConnectPoint>> completePaths;
+    private Map<DeviceId, List<GroupsInDevice>> outputsForDevice;
+    private Map<DeviceId, List<FlowEntry>> flowsForDevice;
+    private StringBuilder resultMessage;
+
+    /**
+     * Builds the trace with a given packet and a connect point.
+     *
+     * @param packet the packet to trace
+     * @param in     the initial connect point
+     */
+    public StaticPacketTrace(TrafficSelector packet, ConnectPoint in) {
+        this.inPacket = packet;
+        this.in = in;
+        completePaths = new ArrayList<>();
+        outputsForDevice = new HashMap<>();
+        flowsForDevice = new HashMap<>();
+        resultMessage = new StringBuilder();
+    }
+
+    /**
+     * Return the initial packet.
+     *
+     * @return the initial packet in the form of a selector.
+     */
+    public TrafficSelector getInitialPacket() {
+        return inPacket;
+    }
+
+    /**
+     * Returns the first connect point the packet came in through.
+     *
+     * @return the connect point
+     */
+    public ConnectPoint getInitialConnectPoint() {
+        return in;
+    }
+
+    /**
+     * Add a result message for the Trace.
+     *
+     * @param resultMessage the message
+     */
+    public void addResultMessage(String resultMessage) {
+        if (this.resultMessage.length() != 0) {
+            this.resultMessage.append("\n");
+        }
+        this.resultMessage.append(resultMessage);
+    }
+
+    /**
+     * Return the result message.
+     *
+     * @return the message
+     */
+    public String resultMessage() {
+        return resultMessage.toString();
+    }
+
+    /**
+     * Adds the groups for a given device.
+     *
+     * @param deviceId   the device
+     * @param outputPath the groups in device objects
+     */
+    public void addGroupOutputPath(DeviceId deviceId, GroupsInDevice outputPath) {
+        if (!outputsForDevice.containsKey(deviceId)) {
+            outputsForDevice.put(deviceId, new ArrayList<>());
+        }
+        outputsForDevice.get(deviceId).add(outputPath);
+    }
+
+    /**
+     * Returns all the possible group-based outputs for a given device.
+     *
+     * @param deviceId the device
+     * @return the list of Groups for this device.
+     */
+    public List<GroupsInDevice> getGroupOuputs(DeviceId deviceId) {
+        return outputsForDevice.get(deviceId);
+    }
+
+    /**
+     * Adds a complete possible path.
+     *
+     * @param completePath the path
+     */
+    public void addCompletePath(List<ConnectPoint> completePath) {
+        completePaths.add(completePath);
+    }
+
+    /**
+     * Return all the possible path the packet can take through the network.
+     *
+     * @return a list of paths
+     */
+    public List<List<ConnectPoint>> getCompletePaths() {
+        return completePaths;
+    }
+
+    /**
+     * Add the flows traversed by the packet in a given device.
+     *
+     * @param deviceId the device considered
+     * @param flows    the flows
+     */
+    public void addFlowsForDevice(DeviceId deviceId, List<FlowEntry> flows) {
+        flowsForDevice.put(deviceId, flows);
+    }
+
+    /**
+     * Returns the flows matched by this trace's packet for a given device.
+     *
+     * @param deviceId the device
+     * @return the flows matched
+     */
+    public List<FlowEntry> getFlowsForDevice(DeviceId deviceId) {
+        return flowsForDevice.get(deviceId);
+    }
+
+    @Override
+    public String toString() {
+        return "StaticPacketTrace{" +
+                "inPacket=" + inPacket +
+                ", in=" + in +
+                ", completePaths=" + completePaths +
+                ", outputsForDevice=" + outputsForDevice +
+                ", flowsForDevice=" + flowsForDevice +
+                ", resultMessage=" + resultMessage +
+                '}';
+    }
 }
diff --git a/apps/t3/src/main/java/org/onosproject/t3/cli/TroubleshootTraceCommand.java b/apps/t3/src/main/java/org/onosproject/t3/cli/TroubleshootTraceCommand.java
new file mode 100644
index 0000000..a42872a
--- /dev/null
+++ b/apps/t3/src/main/java/org/onosproject/t3/cli/TroubleshootTraceCommand.java
@@ -0,0 +1,235 @@
+/*
+ * Copyright 2015-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.t3.cli;
+
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.onlab.packet.EthType;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.t3.api.StaticPacketTrace;
+import org.onosproject.t3.api.TroubleshootService;
+
+import java.util.List;
+
+/**
+ * Starts a Static Packet Trace for a given input and prints the result.
+ */
+@Command(scope = "onos", name = "troubleshoot",
+        description = "troubleshoots flows and groups between source and destination")
+public class TroubleshootTraceCommand extends AbstractShellCommand {
+
+
+    private static final String FLOW_SHORT_FORMAT = "    %s, bytes=%s, packets=%s, "
+            + "table=%s, priority=%s, selector=%s, treatment=%s";
+
+    private static final String GROUP_FORMAT =
+            "   id=0x%s, state=%s, type=%s, bytes=%s, packets=%s, appId=%s, referenceCount=%s";
+    private static final String GROUP_BUCKET_FORMAT =
+            "       id=0x%s, bucket=%s, bytes=%s, packets=%s, actions=%s";
+
+    @Option(name = "-v", aliases = "--verbose", description = "Outputs complete path")
+    private boolean verbosity1 = false;
+
+    @Option(name = "-vv", aliases = "--veryverbose", description = "Outputs flows and groups for every device")
+    private boolean verbosity2 = false;
+
+    @Option(name = "-s", aliases = "--srcIp", description = "Source IP")
+    String srcIp = null;
+
+    @Option(name = "-sp", aliases = "--srcPort", description = "Source Port", required = true)
+    String srcPort = null;
+
+    @Option(name = "-sm", aliases = "--srcMac", description = "Source MAC")
+    String srcMac = null;
+
+    @Option(name = "-et", aliases = "--ethType", description = "ETH Type", valueToShowInHelp = "ipv4")
+    String ethType = "ipv4";
+
+    @Option(name = "-stp", aliases = "--srcTcpPort", description = "Source TCP Port")
+    String srcTcpPort = null;
+
+    @Option(name = "-d", aliases = "--dstIp", description = "Destination IP", valueToShowInHelp = "255.255.255.255")
+    String dstIp = "255.255.255.255";
+
+    @Option(name = "-dm", aliases = "--dstMac", description = "Destination MAC")
+    String dstMac = null;
+
+    @Option(name = "-dtp", aliases = "dstTcpPort", description = "destination TCP Port")
+    String dstTcpPort = null;
+
+    @Option(name = "-vid", aliases = "--vlanId", description = "Vlan of incoming packet", valueToShowInHelp = "None")
+    String vlan = "None";
+
+    @Option(name = "-mb", aliases = "--mplsBos", description = "MPLS BOS", valueToShowInHelp = "True")
+    String mplsBos = "true";
+
+    @Override
+    protected void execute() {
+        TroubleshootService service = get(TroubleshootService.class);
+        ConnectPoint cp = ConnectPoint.deviceConnectPoint(srcPort);
+
+        //Input Port must be specified
+        TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder()
+                .matchInPort(cp.port());
+
+        if (srcIp != null) {
+            selectorBuilder.matchIPSrc(IpAddress.valueOf(srcIp).toIpPrefix());
+        }
+
+        if (srcMac != null) {
+            selectorBuilder.matchEthSrc(MacAddress.valueOf(srcMac));
+        }
+
+        //if EthType option is not specified using IPv4
+        selectorBuilder.matchEthType(EthType.EtherType.valueOf(ethType.toUpperCase()).ethType().toShort());
+
+        if (srcTcpPort != null) {
+            selectorBuilder.matchTcpSrc(TpPort.tpPort(Integer.parseInt(srcTcpPort)));
+        }
+
+        //if destination Ip option is not specified using broadcast 255.255.255.255
+        selectorBuilder.matchIPDst(IpAddress.valueOf(dstIp).toIpPrefix());
+
+        if (dstMac != null) {
+            selectorBuilder.matchEthDst(MacAddress.valueOf(dstMac));
+        }
+        if (dstTcpPort != null) {
+            selectorBuilder.matchTcpDst(TpPort.tpPort(Integer.parseInt(dstTcpPort)));
+        }
+
+        //if vlan option is not specified using NONE
+        selectorBuilder.matchVlanId(VlanId.vlanId(vlan));
+
+        //if mplsBos option is not specified using True
+        selectorBuilder.matchMplsBos(Boolean.valueOf(mplsBos));
+
+        TrafficSelector packet = selectorBuilder.build();
+
+        //Printing the created packet
+        print("Tracing packet: %s", packet.criteria());
+
+        //Build the trace
+        StaticPacketTrace trace = service.trace(packet, cp);
+
+        //Print based on verbosity
+        if (verbosity1) {
+            printTrace(trace, false);
+        } else if (verbosity2) {
+            printTrace(trace, true);
+        } else {
+            print("Paths");
+            List<List<ConnectPoint>> paths = trace.getCompletePaths();
+            paths.forEach(path -> print("%s", path));
+        }
+        print("Result: \n%s", trace.resultMessage());
+    }
+
+    //prints the trace
+    private void printTrace(StaticPacketTrace trace, boolean verbose) {
+        List<List<ConnectPoint>> paths = trace.getCompletePaths();
+        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("");
+                }
+                previous = connectPoint;
+            }
+        });
+    }
+
+    //Prints the flows for a given trace and a specified level of verbosity
+    private void printFlows(StaticPacketTrace trace, boolean verbose, ConnectPoint connectPoint) {
+        print("Flows");
+        trace.getFlowsForDevice(connectPoint.deviceId()).forEach(f -> {
+            if (verbose) {
+                print(FLOW_SHORT_FORMAT, f.state(), f.bytes(), f.packets(),
+                        f.table(), f.priority(), f.selector().criteria(),
+                        printTreatment(f.treatment()));
+            } else {
+                print("   flowId=%s, selector=%s ", f.id(), f.selector().criteria());
+            }
+        });
+    }
+
+    //Prints the groups for a given trace and a specified level of verbosity
+    private void printGroups(StaticPacketTrace trace, boolean verbose, ConnectPoint connectPoint) {
+        print("Groups");
+        trace.getGroupOuputs(connectPoint.deviceId()).forEach(output -> {
+            if (output.getOutput().equals(connectPoint)) {
+                output.getGroups().forEach(group -> {
+                    if (verbose) {
+                        print(GROUP_FORMAT, Integer.toHexString(group.id().id()), group.state(), group.type(),
+                                group.bytes(), group.packets(), group.appId().name(), group.referenceCount());
+                        int i = 0;
+                        for (GroupBucket bucket : group.buckets().buckets()) {
+                            print(GROUP_BUCKET_FORMAT, Integer.toHexString(group.id().id()), ++i,
+                                    bucket.bytes(), bucket.packets(),
+                                    bucket.treatment().allInstructions());
+                        }
+                    } else {
+                        print("   groupId=%s", group.id());
+                    }
+                });
+                print("Outgoing Packet %s", output.getFinalPacket());
+            }
+        });
+    }
+
+    private String printTreatment(TrafficTreatment treatment) {
+        final String delimiter = ", ";
+        StringBuilder builder = new StringBuilder("[");
+        if (!treatment.immediate().isEmpty()) {
+            builder.append("immediate=" + treatment.immediate() + delimiter);
+        }
+        if (!treatment.deferred().isEmpty()) {
+            builder.append("deferred=" + treatment.deferred() + delimiter);
+        }
+        if (treatment.clearedDeferred()) {
+            builder.append("clearDeferred" + delimiter);
+        }
+        if (treatment.tableTransition() != null) {
+            builder.append("transition=" + treatment.tableTransition() + delimiter);
+        }
+        if (treatment.metered() != null) {
+            builder.append("meter=" + treatment.metered() + delimiter);
+        }
+        if (treatment.writeMetadata() != null) {
+            builder.append("metadata=" + treatment.writeMetadata() + delimiter);
+        }
+        // Chop off last delimiter
+        builder.replace(builder.length() - delimiter.length(), builder.length(), "");
+        builder.append("]");
+        return builder.toString();
+    }
+}
diff --git a/apps/t3/src/main/java/org/onosproject/t3/impl/Subnet.java b/apps/t3/src/main/java/org/onosproject/t3/impl/Subnet.java
new file mode 100644
index 0000000..ac5a741
--- /dev/null
+++ b/apps/t3/src/main/java/org/onosproject/t3/impl/Subnet.java
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.t3.impl;
+
+import java.math.BigInteger;
+import java.net.InetAddress;
+import java.net.UnknownHostException;
+
+/**
+ * Utility class to test if an Ip is in a given subnet.
+ */
+public class Subnet {
+    private final int bytesSubnetCount;
+    private final BigInteger bigMask;
+    private final BigInteger bigSubnetMasked;
+
+    /**
+     * Constructor for use via format "192.168.0.0/24" or "2001:db8:85a3:880:0:0:0:0/57".
+     * @param subnetAddress the address
+     * @param bits the mask
+     */
+    public Subnet(InetAddress subnetAddress, int bits) {
+        bytesSubnetCount = subnetAddress.getAddress().length; // 4 or 16
+        bigMask = BigInteger.valueOf(-1).shiftLeft(bytesSubnetCount * 8 - bits); // mask = -1 << 32 - bits
+        bigSubnetMasked = new BigInteger(subnetAddress.getAddress()).and(bigMask);
+    }
+
+    /**
+     * Constructor for use via format "192.168.0.0/255.255.255.0" or single address.
+     * @param subnetAddress the address
+     * @param mask the mask
+     */
+    public Subnet(InetAddress subnetAddress, InetAddress mask) {
+        bytesSubnetCount = subnetAddress.getAddress().length;
+        // no mask given case is handled here.
+        bigMask = null == mask ? BigInteger.valueOf(-1) : new BigInteger(mask.getAddress());
+        bigSubnetMasked = new BigInteger(subnetAddress.getAddress()).and(bigMask);
+    }
+
+    /**
+     * Subnet factory method.
+     *
+     * @param subnetMask format: "192.168.0.0/24" or "192.168.0.0/255.255.255.0"
+     *                   or single address or "2001:db8:85a3:880:0:0:0:0/57"
+     * @return a new instance
+     * @throws UnknownHostException thrown if unsupported subnet mask.
+     */
+    public static Subnet createInstance(String subnetMask)
+            throws UnknownHostException {
+        final String[] stringArr = subnetMask.split("/");
+        if (2 > stringArr.length) {
+            return new Subnet(InetAddress.getByName(stringArr[0]), (InetAddress) null);
+        } else if (stringArr[1].contains(".") || stringArr[1].contains(":")) {
+            return new Subnet(InetAddress.getByName(stringArr[0]), InetAddress.getByName(stringArr[1]));
+        } else {
+            return new Subnet(InetAddress.getByName(stringArr[0]), Integer.parseInt(stringArr[1]));
+        }
+    }
+
+    /**
+     * Tests if the address is in the given subnet.
+     * @param address the address to test.
+     * @return true if inside the subnet
+     */
+    public boolean isInSubnet(InetAddress address) {
+        byte[] bytesAddress = address.getAddress();
+        if (this.bytesSubnetCount != bytesAddress.length) {
+            return false;
+        }
+        BigInteger bigAddress = new BigInteger(bytesAddress);
+        return bigAddress.and(this.bigMask).equals(this.bigSubnetMasked);
+    }
+
+    @Override
+    public final boolean equals(Object obj) {
+        if (!(obj instanceof Subnet)) {
+            return false;
+        }
+        final Subnet other = (Subnet) obj;
+        return bigSubnetMasked.equals(other.bigSubnetMasked) &&
+                bigMask.equals(other.bigMask) &&
+                bytesSubnetCount == other.bytesSubnetCount;
+    }
+
+    @Override
+    public final int hashCode() {
+        return bytesSubnetCount;
+    }
+
+    @Override
+    public String toString() {
+        final StringBuilder buf = new StringBuilder();
+        bigInteger2IpString(buf, bigSubnetMasked, bytesSubnetCount);
+        buf.append('/');
+        bigInteger2IpString(buf, bigMask, bytesSubnetCount);
+        return buf.toString();
+    }
+
+    private void bigInteger2IpString(StringBuilder buf, BigInteger bigInteger, int displayBytes) {
+        boolean isIPv4 = 4 == displayBytes;
+        byte[] bytes = bigInteger.toByteArray();
+        int diffLen = displayBytes - bytes.length;
+        byte fillByte = 0 > (int) bytes[0] ? (byte) 0xFF : (byte) 0x00;
+
+        int integer;
+        for (int i = 0; i < displayBytes; i++) {
+            if (0 < i && !isIPv4 && i % 2 == 0) {
+                buf.append(':');
+            } else if (0 < i && isIPv4) {
+                buf.append('.');
+            }
+            integer = 0xFF & (i < diffLen ? fillByte : bytes[i - diffLen]);
+            if (!isIPv4 && 0x10 > integer) {
+                buf.append('0');
+            }
+            buf.append(isIPv4 ? integer : Integer.toHexString(integer));
+        }
+    }
+}
diff --git a/apps/t3/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java b/apps/t3/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java
index 03f1e75..1a5c946 100644
--- a/apps/t3/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java
+++ b/apps/t3/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java
@@ -16,22 +16,62 @@
 
 package org.onosproject.t3.impl;
 
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.Lists;
 import org.apache.felix.scr.annotations.Component;
 import org.apache.felix.scr.annotations.Reference;
 import org.apache.felix.scr.annotations.ReferenceCardinality;
 import org.apache.felix.scr.annotations.Service;
+import org.onlab.packet.VlanId;
 import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.Link;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
 import org.onosproject.net.flow.FlowRuleService;
+import org.onosproject.net.flow.IndexTableId;
+import org.onosproject.net.flow.TableId;
 import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.criteria.Criteria;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.EthCriterion;
+import org.onosproject.net.flow.criteria.EthTypeCriterion;
+import org.onosproject.net.flow.criteria.IPCriterion;
+import org.onosproject.net.flow.instructions.Instruction;
+import org.onosproject.net.flow.instructions.Instructions;
+import org.onosproject.net.flow.instructions.Instructions.OutputInstruction;
+import org.onosproject.net.flow.instructions.L2ModificationInstruction;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
 import org.onosproject.net.group.GroupService;
+import org.onosproject.net.host.HostService;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.t3.api.GroupsInDevice;
 import org.onosproject.t3.api.StaticPacketTrace;
 import org.onosproject.t3.api.TroubleshootService;
 import org.slf4j.Logger;
 
+import java.net.UnknownHostException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.onlab.packet.EthType.EtherType;
+import static org.onosproject.net.flow.instructions.Instructions.GroupInstruction;
 import static org.slf4j.LoggerFactory.getLogger;
 
 /**
- * Designates ...
+ * Manager to troubleshoot packets inside the network.
+ * Given a representation of a packet follows it's path in the network according to the existing flows and groups in
+ * the devices.
  */
 @Service
 @Component(immediate = true)
@@ -45,10 +85,554 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected GroupService groupService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected LinkService linkService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected HostService hostService;
+
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected DriverService driverService;
 
     @Override
     public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
-        return null;
+        log.info("Tracing packet {} coming in through {}", packet, in);
+        StaticPacketTrace trace = new StaticPacketTrace(packet, in);
+        //FIXME this can be done recursively
+        trace = traceInDevice(trace, packet, in);
+        //Building output connect Points
+        List<ConnectPoint> path = new ArrayList<>();
+        trace = getTrace(path, in, trace);
+        return trace;
+    }
+
+    /**
+     * Computes a trace for a give packet that start in the network at the given connect point.
+     *
+     * @param completePath the path traversed by the packet
+     * @param in           the input connect point
+     * @param trace        the trace to build
+     * @return the build trace for that packet.
+     */
+    private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace) {
+
+        //if the trace already contains the input connect point there is a loop
+        if (pathContainsDevice(completePath, in.deviceId())) {
+            trace.addResultMessage("Loop encountered in device " + in.deviceId());
+            return trace;
+        }
+
+        //let's add the input connect point
+        completePath.add(in);
+
+        //If the trace has no outputs for the given input we stop here
+        if (trace.getGroupOuputs(in.deviceId()) == null) {
+            computePath(completePath, trace, null);
+            trace.addResultMessage("No output out of device " + in.deviceId() + ". Packet is dropped");
+            return trace;
+        }
+        //If the trace has ouputs we analyze them all
+        for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
+            log.debug("Output path {}", outputPath.getOutput());
+            //Hosts for the the given output
+            Set<Host> hostsList = hostService.getConnectedHosts(outputPath.getOutput());
+            //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");
+                computePath(completePath, trace, outputPath.getOutput());
+                break;
+            } else {
+                ConnectPoint cp = outputPath.getOutput();
+                //let's add the ouput for the input
+                completePath.add(cp);
+                log.debug("------------------------------------------------------------");
+                log.debug("Connect Point out {}", cp);
+                //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");
+                    return trace;
+                }
+                //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();
+                    outputPath.getFinalPacket().criteria().forEach(updatedPacket::add);
+                    updatedPacket.add(Criteria.matchInPort(dst.port()));
+                    log.debug("DST Connect Point {}", dst);
+                    //build the elements for that device
+                    traceInDevice(trace, updatedPacket.build(), dst);
+                    //continue the trace along the path
+                    getTrace(completePath, dst, trace);
+                }
+
+            }
+        }
+        return trace;
+    }
+
+    /**
+     * Checks if the path contains the device.
+     *
+     * @param completePath the path
+     * @param deviceId     the device to check
+     * @return true if the path contains the device
+     */
+    //TODO might prove costly, improvement: a class with both CPs and DeviceIds point.
+    private boolean pathContainsDevice(List<ConnectPoint> completePath, DeviceId deviceId) {
+        for (ConnectPoint cp : completePath) {
+            if (cp.deviceId().equals(deviceId)) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    /**
+     * Gets the hosts for the given initial packet.
+     *
+     * @param trace the trace we are building
+     * @return set of the hosts we are trying to reach
+     */
+    private Set<Host> getHosts(StaticPacketTrace trace) {
+        IPCriterion ipv4Criterion = ((IPCriterion) trace.getInitialPacket()
+                .getCriterion(Criterion.Type.IPV4_DST));
+        IPCriterion ipv6Criterion = ((IPCriterion) trace.getInitialPacket()
+                .getCriterion(Criterion.Type.IPV6_DST));
+        Set<Host> hosts = new HashSet<>();
+        if (ipv4Criterion != null) {
+            hosts.addAll(hostService.getHostsByIp(ipv4Criterion.ip().address()));
+        }
+        if (ipv6Criterion != null) {
+            hosts.addAll(hostService.getHostsByIp(ipv6Criterion.ip().address()));
+        }
+        EthCriterion ethCriterion = ((EthCriterion) trace.getInitialPacket()
+                .getCriterion(Criterion.Type.ETH_DST));
+        if (ethCriterion != null) {
+            hosts.addAll(hostService.getHostsByMac(ethCriterion.mac()));
+        }
+        return hosts;
+    }
+
+    /**
+     * Computes the list of traversed connect points.
+     *
+     * @param completePath the list of devices
+     * @param trace        the trace we are building
+     * @param output       the final output connect point
+     */
+    private void computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
+        List<ConnectPoint> traverseList = new ArrayList<>();
+        if (!completePath.contains(trace.getInitialConnectPoint())) {
+            traverseList.add(trace.getInitialConnectPoint());
+        }
+        traverseList.addAll(completePath);
+        if (output != null && !completePath.contains(output)) {
+            traverseList.add(output);
+        }
+        trace.addCompletePath(traverseList);
+        completePath.clear();
+    }
+
+    /**
+     * Traces the packet inside a device starting from an input connect point.
+     *
+     * @param trace  the trace we are building
+     * @param packet the packet we are tracing
+     * @param in     the input connect point.
+     * @return updated trace
+     */
+    private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in) {
+        log.debug("Packet {} coming in from {}", packet, in);
+        List<FlowEntry> flows = new ArrayList<>();
+        List<FlowEntry> outputFlows = new ArrayList<>();
+
+        FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
+        if (nextTableIdEntry == null) {
+            trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
+            return trace;
+        }
+        TableId tableId = nextTableIdEntry.table();
+        FlowEntry flowEntry;
+        boolean output = false;
+        while (!output) {
+            log.debug("Searching a Flow Entry on table {} for packet {}", tableId, packet);
+            //get the rule that matches the incoming packet
+            flowEntry = matchHighestPriority(packet, in, tableId);
+            log.debug("Found Flow Entry {}", flowEntry);
+
+            boolean isOfdpaHardware = TroubleshootUtils.hardwareOfdpaMap
+                    .getOrDefault(driverService.getDriver(in.deviceId()).name(), false);
+
+            //if the flow entry on a table is null and we are on hardware we treat as table miss, with few exceptions
+            if (flowEntry == null && isOfdpaHardware) {
+                log.debug("Ofdpa Hw setup, no flow rule means table miss");
+
+                //Handling Hardware Specifics
+                if (((IndexTableId) tableId).id() == 27) {
+                    //Apparently a miss but Table 27 on OFDPA is a fixed table
+                    packet = handleOfdpa27FixedTable(trace, packet);
+                }
+
+                //Finding next table to go In case of miss
+                nextTableIdEntry = findNextTableIdEntry(in.deviceId(), ((IndexTableId) tableId).id());
+                log.debug("Next table id entry {}", nextTableIdEntry);
+
+                //FIXME find better solution that enable granularity greater than 0 or all rules
+                //(another possibility is max tableId)
+                if (nextTableIdEntry == null && flows.size() == 0) {
+                    trace.addResultMessage("No flow rules for device" + in.deviceId() + ". Aborting");
+                    return trace;
+
+                } else if (nextTableIdEntry == null) {
+                    //Means that no more flow rules are present
+                    output = true;
+
+                } else if (((IndexTableId) tableId).id() == 20) {
+                    //if the table is 20 OFDPA skips to table 50
+                    log.debug("A miss on Table 20 on OFDPA means that we skip directly to table 50");
+                    tableId = IndexTableId.of(50);
+
+                } else {
+                    tableId = nextTableIdEntry.table();
+                }
+
+
+            } else if (flowEntry == null) {
+                trace.addResultMessage("Packet has no match on table " + tableId + " in device " +
+                        in.deviceId() + ". Dropping");
+                return trace;
+            } else {
+                //IF the table has a transition
+                if (flowEntry.treatment().tableTransition() != null) {
+                    //update the next table we transitions to
+                    tableId = IndexTableId.of(flowEntry.treatment().tableTransition().tableId());
+                    log.debug("Flow Entry has transition to table Id {}", tableId);
+                    flows.add(flowEntry);
+                } else {
+                    //table has no transition so it means that it's an output rule if on the last table
+                    log.debug("Flow Entry has no transition to table, treating as last rule {}", flowEntry);
+                    flows.add(flowEntry);
+                    outputFlows.add(flowEntry);
+                    output = true;
+                }
+                //update the packet according to the actions of this flow rule.
+                packet = updatePacket(packet, flowEntry.treatment().allInstructions()).build();
+            }
+        }
+
+        //Creating a modifiable builder for the output packet
+        TrafficSelector.Builder builder = DefaultTrafficSelector.builder();
+        packet.criteria().forEach(builder::add);
+        //Adding all the flows to the trace
+        trace.addFlowsForDevice(in.deviceId(), flows);
+
+        log.debug("Flows traversed by {}", packet);
+        flows.forEach(entry -> {
+            log.debug("Flow {}", entry);
+        });
+
+        log.debug("Output Flows for {}", packet);
+        outputFlows.forEach(entry -> {
+            log.debug("Output Flow {}", entry);
+        });
+        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);
+
+        if (outputFlowEntries.size() != 0) {
+            outputThroughFlows(trace, packet, in, builder, outputPorts, outputFlowEntries);
+
+        } 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);
+            }
+            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);
+        });
+        log.debug("Output Packet {}", packet);
+        return trace;
+    }
+
+    /**
+     * 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 OUTPUT instruction for {}", packet);
+        } else {
+            OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.iterator().next();
+            //FIXME using GroupsInDevice for output even if flows.
+            trace.addGroupOutputPath(in.deviceId(),
+                    new GroupsInDevice(ConnectPoint.deviceConnectPoint(in.deviceId()
+                            + "/" + outputInstruction.port()),
+                            ImmutableList.of(), builder.build()));
+            outputPorts.add(outputInstruction.port());
+        }
+    }
+
+    /**
+     * Handles table 27 in Ofpda which is a fixed table not visible to any controller that handles Mpls Labels.
+     *
+     * @param packet the incoming packet
+     * @return the updated packet
+     */
+    private TrafficSelector handleOfdpa27FixedTable(StaticPacketTrace trace, TrafficSelector packet) {
+        log.debug("Handling table 27 on OFDPA, removing mpls ETH Type and change mpls label");
+        Criterion mplsCriterion = packet.getCriterion(Criterion.Type.ETH_TYPE);
+        ImmutableList.Builder<Instruction> builder = ImmutableList.builder();
+
+        //If the pakcet comes in with the expected elements we update it as per OFDPA spec.
+        if (mplsCriterion != null && ((EthTypeCriterion) mplsCriterion).ethType()
+                .equals(EtherType.MPLS_UNICAST.ethType())) {
+            Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
+                    .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
+            //FIXME what do we use as L3_Unicast mpls Label ?
+            builder.add(ethInstruction);
+        }
+        packet = updatePacket(packet, builder.build()).build();
+        return packet;
+    }
+
+    /**
+     * Finds the flow entry with the minimun next table Id.
+     *
+     * @param deviceId  the device to search
+     * @param currentId the current id. the search will use this as minimum
+     * @return the flow entry with the minimum table Id after the given one.
+     */
+    private FlowEntry findNextTableIdEntry(DeviceId deviceId, int currentId) {
+
+        final Comparator<FlowEntry> comparator = Comparator.comparing((FlowEntry f) -> ((IndexTableId) f.table()).id());
+
+        return Lists.newArrayList(flowRuleService.getFlowEntries(deviceId).iterator())
+                .stream().filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
+    }
+
+    /**
+     * Gets group information from instructions.
+     *
+     * @param trace           the trace we are building
+     * @param groupsForDevice the set of groups for this device
+     * @param instructions    the set of instructions we are searching for groups.
+     * @param deviceId        the device we are considering
+     * @param builder         the builder of the input packet
+     * @param outputPorts     the output ports for that packet
+     */
+    private void getGroupsFromInstructions(StaticPacketTrace trace, List<Group> groupsForDevice,
+                                           List<Instruction> instructions, DeviceId deviceId,
+                                           TrafficSelector.Builder builder, List<PortNumber> outputPorts) {
+        List<Instruction> groupInstructionlist = new ArrayList<>();
+        for (Instruction instruction : instructions) {
+            log.debug("Considering Instruction {}", instruction);
+            //if the instruction is not group we need to update the packet or add the output
+            //to the possible outputs for this packet
+            if (!instruction.type().equals(Instruction.Type.GROUP)) {
+                //if the instruction is not group we need to update the packet or add the output
+                //to the possible outputs for this packet
+                if (instruction.type().equals(Instruction.Type.OUTPUT)) {
+                    outputPorts.add(((OutputInstruction) instruction).port());
+                    trace.addGroupOutputPath(deviceId,
+                            new GroupsInDevice(ConnectPoint.deviceConnectPoint(deviceId + "/" +
+                                    ((OutputInstruction) instruction).port()),
+                                    groupsForDevice, builder.build()));
+                } else {
+                    builder = translateInstruction(builder, instruction);
+                }
+            } else {
+                //if the instuction is pointing to a group we need to get the group
+                groupInstructionlist.add(instruction);
+            }
+        }
+        //handle all the internal instructions pointing to a group.
+        for (Instruction instr : groupInstructionlist) {
+            GroupInstruction groupInstruction = (GroupInstruction) instr;
+            Group group = Lists.newArrayList(groupService.getGroups(deviceId)).stream().filter(groupInternal -> {
+                return groupInternal.id().equals(groupInstruction.groupId());
+            }).findAny().orElse(null);
+            if (group == null) {
+                trace.addResultMessage("Null group for Instruction " + instr);
+                break;
+            }
+            //add the group to the traversed groups
+            groupsForDevice.add(group);
+            //Cycle in each of the group's buckets and add them to the groups for this Device.
+            for (GroupBucket bucket : group.buckets().buckets()) {
+                getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
+                        deviceId, builder, outputPorts);
+            }
+        }
+    }
+
+    /**
+     * Applies all give instructions to the input packet.
+     *
+     * @param packet       the input packet
+     * @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();
+        packet.criteria().forEach(newSelector::add);
+        instructions.forEach(instruction -> {
+            translateInstruction(newSelector, instruction);
+        });
+        return newSelector;
+    }
+
+    /**
+     * Applies an instruction to the packet in the form of a selector.
+     *
+     * @param newSelector the packet selector
+     * @param instruction the instruction to be translated
+     * @return the new selector with the applied instruction
+     */
+    private TrafficSelector.Builder translateInstruction(TrafficSelector.Builder newSelector, Instruction instruction) {
+        log.debug("Translating instruction {}", instruction);
+        //TODO add as required
+        Criterion criterion = null;
+        switch (instruction.type()) {
+            case L2MODIFICATION:
+                L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
+                switch (l2Instruction.subtype()) {
+                    case VLAN_ID:
+                        L2ModificationInstruction.ModVlanIdInstruction vlanIdInstruction =
+                                (L2ModificationInstruction.ModVlanIdInstruction) instruction;
+                        VlanId id = vlanIdInstruction.vlanId();
+                        criterion = Criteria.matchVlanId(id);
+                        break;
+                    case VLAN_POP:
+                        criterion = Criteria.matchVlanId(VlanId.NONE);
+                        break;
+                    case MPLS_PUSH:
+                        L2ModificationInstruction.ModMplsHeaderInstruction mplsEthInstruction =
+                                (L2ModificationInstruction.ModMplsHeaderInstruction) instruction;
+                        criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
+                        break;
+                    case MPLS_POP:
+                        L2ModificationInstruction.ModMplsHeaderInstruction mplsPopInstruction =
+                                (L2ModificationInstruction.ModMplsHeaderInstruction) instruction;
+                        criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
+                        break;
+                    case MPLS_LABEL:
+                        L2ModificationInstruction.ModMplsLabelInstruction mplsLabelInstruction =
+                                (L2ModificationInstruction.ModMplsLabelInstruction) instruction;
+                        criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
+                        break;
+                    case ETH_DST:
+                        L2ModificationInstruction.ModEtherInstruction modEtherDstInstruction =
+                                (L2ModificationInstruction.ModEtherInstruction) instruction;
+                        criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
+                        break;
+                    case ETH_SRC:
+                        L2ModificationInstruction.ModEtherInstruction modEtherSrcInstruction =
+                                (L2ModificationInstruction.ModEtherInstruction) instruction;
+                        criterion = Criteria.matchEthSrc(modEtherSrcInstruction.mac());
+                        break;
+                    default:
+                        log.debug("Unsupported L2 Instruction");
+                        break;
+                }
+                break;
+            default:
+                log.debug("Unsupported Instruction");
+                break;
+        }
+        if (criterion != null) {
+            log.debug("Adding criterion {}", criterion);
+            newSelector.add(criterion);
+        }
+        return newSelector;
+    }
+
+    /**
+     * Finds the rule in the device that mathces the input packet and has the highest priority.
+     *
+     * @param packet  the input packet
+     * @param in      the connect point the packet comes in from
+     * @param tableId the table to search
+     * @return the flow entry
+     */
+    private FlowEntry matchHighestPriority(TrafficSelector packet, ConnectPoint in, TableId tableId) {
+        //Computing the possible match rules.
+        final Comparator<FlowEntry> comparator = Comparator.comparing(FlowRule::priority);
+        return Lists.newArrayList(flowRuleService.getFlowEntries(in.deviceId()).iterator())
+                .stream()
+                .filter(flowEntry -> {
+                    return flowEntry.table().equals(tableId);
+                })
+                .filter(flowEntry -> {
+                    return match(packet, flowEntry);
+                }).max(comparator).orElse(null);
+    }
+
+    /**
+     * Matches the packet with the given flow entry.
+     *
+     * @param packet    the packet to match
+     * @param flowEntry the flow entry to match the packet against
+     * @return true if the packet matches the flow.
+     */
+    private boolean match(TrafficSelector packet, FlowEntry flowEntry) {
+        //TODO handle MAC matching
+        return flowEntry.selector().criteria().stream().allMatch(criterion -> {
+            Criterion.Type type = criterion.type();
+            //If the critrion has IP we need to do LPM to establish matching.
+            if (type.equals(Criterion.Type.IPV4_SRC) || type.equals(Criterion.Type.IPV4_DST) ||
+                    type.equals(Criterion.Type.IPV6_SRC) || type.equals(Criterion.Type.IPV6_DST)) {
+                IPCriterion ipCriterion = (IPCriterion) criterion;
+                IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(ipCriterion.type());
+                //if the packet does not have an IPv4 or IPv6 criterion we return true
+                if (matchCriterion == null) {
+                    return true;
+                }
+                try {
+                    Subnet subnet = Subnet.createInstance(ipCriterion.ip().toString());
+                    return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
+                } catch (UnknownHostException e) {
+                    return false;
+                }
+                //we check that the packet contains the criterion provided by the flow rule.
+            } else {
+                return packet.criteria().contains(criterion);
+            }
+        });
     }
 }
diff --git a/apps/t3/src/main/java/org/onosproject/t3/impl/TroubleshootUtils.java b/apps/t3/src/main/java/org/onosproject/t3/impl/TroubleshootUtils.java
new file mode 100644
index 0000000..f89a413
--- /dev/null
+++ b/apps/t3/src/main/java/org/onosproject/t3/impl/TroubleshootUtils.java
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package org.onosproject.t3.impl;
+
+import com.google.common.collect.ImmutableMap;
+
+import java.util.Map;
+
+/**
+ * Utility class for the troubleshooting tool.
+ */
+final class TroubleshootUtils {
+
+    private TroubleshootUtils() {
+        //Banning construction
+    }
+
+    /**
+     * Map defining if a specific driver is for a HW switch.
+     */
+    //Done with builder() instead of of() for clarity
+    static Map<String, Boolean> hardwareOfdpaMap = ImmutableMap.<String, Boolean>builder()
+            .put("ofdpa", true)
+            .put("ofdpa3", true)
+            .put("qmx-ofdpa3", true)
+            .put("as7712-32x-premium", true)
+            .put("as5912-54x-premium", true)
+            .put("as5916-54x-premium", true)
+            .put("accton-ofdpa3", true)
+            .put("znyx-ofdpa", true)
+            .build();
+}
diff --git a/apps/t3/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/t3/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..7da87bc
--- /dev/null
+++ b/apps/t3/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,26 @@
+<!--
+  ~ Copyright 2017-present Open Networking Foundation
+  ~
+  ~ Licensed under the Apache License, Version 2.0 (the "License");
+  ~ you may not use this file except in compliance with the License.
+  ~ You may obtain a copy of the License at
+  ~
+  ~     http://www.apache.org/licenses/LICENSE-2.0
+  ~
+  ~ Unless required by applicable law or agreed to in writing, software
+  ~ distributed under the License is distributed on an "AS IS" BASIS,
+  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  ~ See the License for the specific language governing permissions and
+  ~ limitations under the License.
+  -->
+<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
+
+    <command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
+        <command>
+            <action class="org.onosproject.t3.cli.TroubleshootTraceCommand"/>
+        </command>
+    </command-bundle>
+
+</blueprint>
+
+
diff --git a/apps/t3/src/test/org/onosproject/t3/impl/T3TestObjects.java b/apps/t3/src/test/org/onosproject/t3/impl/T3TestObjects.java
new file mode 100644
index 0000000..8a3f6eb
--- /dev/null
+++ b/apps/t3/src/test/org/onosproject/t3/impl/T3TestObjects.java
@@ -0,0 +1,321 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.t3.impl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.onlab.packet.EthType;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultHost;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.HostLocation;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultFlowEntry;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.DefaultTrafficTreatment;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRule;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.group.DefaultGroup;
+import org.onosproject.net.group.DefaultGroupBucket;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.net.group.GroupBuckets;
+import org.onosproject.net.provider.ProviderId;
+
+/**
+ * Helper class for objects related to the Troubleshoot Manager Test.
+ */
+final class T3TestObjects {
+
+    private static final String HOST_ONE_MAC = "00:00:00:00:00:01";
+    private static final String HOST_TWO_MAC = "00:00:00:00:00:02";
+    private static final String HOST_ONE_VLAN = "None";
+    private static final String HOST_TWO_VLAN = "None";
+    private static final String HOST_ONE = HOST_ONE_MAC + "/" + HOST_ONE_VLAN;
+    private static final String HOST_TWO = HOST_TWO_MAC + "/" + HOST_TWO_VLAN;
+
+    //Single Flow Test
+    static final DeviceId SINGLE_FLOW_DEVICE = DeviceId.deviceId("SingleFlowDevice");
+    private static final TrafficSelector SINGLE_FLOW_SELECTOR = DefaultTrafficSelector.builder()
+            .matchInPort(PortNumber.portNumber(1))
+            .matchIPSrc(IpPrefix.valueOf("127.0.0.1/32"))
+            .matchIPDst(IpPrefix.valueOf("127.0.0.2/32"))
+            .build();
+
+    private static final TrafficTreatment OUTPUT_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+            .setOutput(PortNumber.portNumber(2)).build();
+    private static final FlowRule SINGLE_FLOW = DefaultFlowEntry.builder().forDevice(SINGLE_FLOW_DEVICE)
+            .forTable(0)
+            .withPriority(100)
+            .withSelector(SINGLE_FLOW_SELECTOR)
+            .withTreatment(OUTPUT_FLOW_TREATMENT)
+            .fromApp(new DefaultApplicationId(0, "TestApp"))
+            .makePermanent()
+            .build();
+    static final FlowEntry SINGLE_FLOW_ENTRY = new DefaultFlowEntry(SINGLE_FLOW);
+
+    static final ConnectPoint SINGLE_FLOW_IN_CP = ConnectPoint.deviceConnectPoint(SINGLE_FLOW_DEVICE + "/" + 1);
+
+    static final ConnectPoint SINGLE_FLOW_OUT_CP = ConnectPoint.deviceConnectPoint(SINGLE_FLOW_DEVICE + "/" + 2);
+
+    //Dual Flow Test
+    static final DeviceId DUAL_FLOW_DEVICE = DeviceId.deviceId("DualFlowDevice");
+    private static final TrafficTreatment TRANSITION_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+            .setVlanId(VlanId.vlanId((short) 100))
+            .transition(10)
+            .build();
+    private static final TrafficSelector VLAN_FLOW_SELECTOR = DefaultTrafficSelector.builder()
+            .matchVlanId(VlanId.vlanId((short) 100))
+            .build();
+    private static final FlowRule FIRST_FLOW = DefaultFlowEntry.builder().forDevice(DUAL_FLOW_DEVICE)
+            .forTable(0)
+            .withPriority(100)
+            .withSelector(SINGLE_FLOW_SELECTOR)
+            .withTreatment(TRANSITION_FLOW_TREATMENT)
+            .fromApp(new DefaultApplicationId(0, "TestApp"))
+            .makePermanent()
+            .build();
+    static final FlowEntry FIRST_FLOW_ENTRY = new DefaultFlowEntry(FIRST_FLOW);
+    private static final FlowRule SECOND_FLOW = DefaultFlowEntry.builder().forDevice(DUAL_FLOW_DEVICE)
+            .forTable(10)
+            .withPriority(100)
+            .withSelector(VLAN_FLOW_SELECTOR)
+            .withTreatment(OUTPUT_FLOW_TREATMENT)
+            .fromApp(new DefaultApplicationId(0, "TestApp"))
+            .makePermanent()
+            .build();
+    static final FlowEntry SECOND_FLOW_ENTRY = new DefaultFlowEntry(SECOND_FLOW);
+
+    static final ConnectPoint DUAL_FLOW_IN_CP = ConnectPoint.deviceConnectPoint(DUAL_FLOW_DEVICE + "/" + 1);
+
+    static final ConnectPoint DUAL_FLOW_OUT_CP = ConnectPoint.deviceConnectPoint(DUAL_FLOW_DEVICE + "/" + 2);
+
+    //Flow and Group Test
+    static final DeviceId GROUP_FLOW_DEVICE = DeviceId.deviceId("GroupFlowDevice");
+
+    private static final GroupId GROUP_ID = GroupId.valueOf(1);
+
+    private static final TrafficTreatment GROUP_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+            .group(GROUP_ID)
+            .build();
+    private static final FlowRule GROUP_FLOW = DefaultFlowEntry.builder().forDevice(GROUP_FLOW_DEVICE)
+            .forTable(0)
+            .withPriority(100)
+            .withSelector(SINGLE_FLOW_SELECTOR)
+            .withTreatment(GROUP_FLOW_TREATMENT)
+            .fromApp(new DefaultApplicationId(0, "TestApp"))
+            .makePermanent()
+            .build();
+    static final FlowEntry GROUP_FLOW_ENTRY = new DefaultFlowEntry(GROUP_FLOW);
+
+    private static final GroupBucket BUCKET = DefaultGroupBucket.createSelectGroupBucket(OUTPUT_FLOW_TREATMENT);
+
+    private static final GroupBuckets BUCKETS = new GroupBuckets(ImmutableList.of(BUCKET));
+
+    static final Group GROUP = new DefaultGroup(GROUP_ID, GROUP_FLOW_DEVICE, Group.Type.SELECT, BUCKETS);
+
+    static final ConnectPoint GROUP_FLOW_IN_CP = ConnectPoint.deviceConnectPoint(GROUP_FLOW_DEVICE + "/" + 1);
+
+    static final ConnectPoint GROUP_FLOW_OUT_CP = ConnectPoint.deviceConnectPoint(GROUP_FLOW_DEVICE + "/" + 2);
+
+    //topology
+
+    static final DeviceId TOPO_FLOW_DEVICE = DeviceId.deviceId("SingleFlowDevice1");
+
+    static final DeviceId TOPO_FLOW_2_DEVICE = DeviceId.deviceId("SingleFlowDevice2");
+
+    static final DeviceId TOPO_FLOW_3_DEVICE = DeviceId.deviceId("SingleFlowDevice3");
+
+    private static final TrafficSelector TOPO_FLOW_SELECTOR = DefaultTrafficSelector.builder()
+            .matchInPort(PortNumber.portNumber(1))
+            .matchIPSrc(IpPrefix.valueOf("127.0.0.1/32"))
+            .matchIPDst(IpPrefix.valueOf("127.0.0.3/32"))
+            .build();
+
+    private static final FlowRule TOPO_SINGLE_FLOW = DefaultFlowEntry.builder().forDevice(TOPO_FLOW_DEVICE)
+            .forTable(0)
+            .withPriority(100)
+            .withSelector(TOPO_FLOW_SELECTOR)
+            .withTreatment(OUTPUT_FLOW_TREATMENT)
+            .fromApp(new DefaultApplicationId(0, "TestApp"))
+            .makePermanent()
+            .build();
+
+    static final FlowEntry TOPO_SINGLE_FLOW_ENTRY = new DefaultFlowEntry(TOPO_SINGLE_FLOW);
+
+    static final ConnectPoint TOPO_FLOW_1_IN_CP = ConnectPoint.deviceConnectPoint(TOPO_FLOW_DEVICE + "/" + 1);
+
+    static final ConnectPoint TOPO_FLOW_1_OUT_CP = ConnectPoint.deviceConnectPoint(TOPO_FLOW_DEVICE + "/" + 2);
+
+    static final ConnectPoint TOPO_FLOW_2_IN_CP = ConnectPoint.deviceConnectPoint(TOPO_FLOW_2_DEVICE + "/" + 1);
+
+    static final ConnectPoint TOPO_FLOW_2_OUT_CP = ConnectPoint.deviceConnectPoint(TOPO_FLOW_2_DEVICE + "/" + 2);
+
+    static final ConnectPoint TOPO_FLOW_3_IN_CP = ConnectPoint.deviceConnectPoint(TOPO_FLOW_3_DEVICE + "/" + 1);
+
+    static final ConnectPoint TOPO_FLOW_3_OUT_CP = ConnectPoint.deviceConnectPoint(TOPO_FLOW_3_DEVICE + "/" + 2);
+
+
+    //Topology with Groups
+
+    static final DeviceId TOPO_GROUP_FLOW_DEVICE = DeviceId.deviceId("TopoGroupFlowDevice");
+
+    private static final TrafficSelector TOPO_SECOND_INPUT_FLOW_SELECTOR = DefaultTrafficSelector.builder()
+            .matchInPort(PortNumber.portNumber(3))
+            .matchIPSrc(IpPrefix.valueOf("127.0.0.1/32"))
+            .matchIPDst(IpPrefix.valueOf("127.0.0.3/32"))
+            .build();
+
+    private static final FlowRule TOPO_SECOND_INPUT_FLOW = DefaultFlowEntry.builder().forDevice(TOPO_FLOW_3_DEVICE)
+            .forTable(0)
+            .withPriority(100)
+            .withSelector(TOPO_SECOND_INPUT_FLOW_SELECTOR)
+            .withTreatment(OUTPUT_FLOW_TREATMENT)
+            .fromApp(new DefaultApplicationId(0, "TestApp"))
+            .makePermanent()
+            .build();
+
+    private static final TrafficTreatment OUTPUT_2_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+            .setOutput(PortNumber.portNumber(3)).build();
+
+
+    private static final GroupId TOPO_GROUP_ID = GroupId.valueOf(1);
+
+    private static final TrafficTreatment TOPO_GROUP_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+            .group(TOPO_GROUP_ID)
+            .build();
+    private static final FlowRule TOPO_GROUP_FLOW = DefaultFlowEntry.builder().forDevice(TOPO_GROUP_FLOW_DEVICE)
+            .forTable(0)
+            .withPriority(100)
+            .withSelector(TOPO_FLOW_SELECTOR)
+            .withTreatment(TOPO_GROUP_FLOW_TREATMENT)
+            .fromApp(new DefaultApplicationId(0, "TestApp"))
+            .makePermanent()
+            .build();
+    static final FlowEntry TOPO_GROUP_FLOW_ENTRY = new DefaultFlowEntry(TOPO_GROUP_FLOW);
+
+    private static final GroupBucket BUCKET_2 = DefaultGroupBucket.createSelectGroupBucket(OUTPUT_2_FLOW_TREATMENT);
+
+    private static final GroupBuckets BUCKETS_MULTIPLE = new GroupBuckets(ImmutableList.of(BUCKET, BUCKET_2));
+
+    static final Group TOPO_GROUP = new DefaultGroup(TOPO_GROUP_ID, TOPO_GROUP_FLOW_DEVICE, Group.Type.SELECT, BUCKETS_MULTIPLE);
+
+    static final FlowEntry TOPO_SECOND_INPUT_FLOW_ENTRY = new DefaultFlowEntry(TOPO_SECOND_INPUT_FLOW);
+
+    static final DeviceId TOPO_FLOW_4_DEVICE = DeviceId.deviceId("SingleFlowDevice4");
+
+    static final ConnectPoint TOPO_FLOW_IN_CP = ConnectPoint.deviceConnectPoint(TOPO_GROUP_FLOW_DEVICE + "/" + 1);
+
+    static final ConnectPoint TOPO_FLOW_OUT_CP_1 = ConnectPoint.deviceConnectPoint(TOPO_GROUP_FLOW_DEVICE + "/" + 2);
+
+    protected static final ConnectPoint TOPO_FLOW_OUT_CP_2 = ConnectPoint.deviceConnectPoint(TOPO_GROUP_FLOW_DEVICE + "/" + 3);
+
+    static final ConnectPoint TOPO_FLOW_4_IN_CP = ConnectPoint.deviceConnectPoint(TOPO_FLOW_4_DEVICE + "/" + 1);
+
+    static final ConnectPoint TOPO_FLOW_3_IN_2_CP = ConnectPoint.deviceConnectPoint(TOPO_FLOW_3_DEVICE + "/" + 3);
+
+    static final ConnectPoint TOPO_FLOW_4_OUT_CP = ConnectPoint.deviceConnectPoint(TOPO_FLOW_4_DEVICE + "/" + 2);
+
+
+    //hardware
+
+    static final DeviceId HARDWARE_DEVICE = DeviceId.deviceId("HardwareDevice");
+
+    static final ConnectPoint HARDWARE_DEVICE_IN_CP = ConnectPoint.deviceConnectPoint(HARDWARE_DEVICE + "/" + 1);
+
+    static final ConnectPoint HARDWARE_DEVICE_OUT_CP = ConnectPoint.deviceConnectPoint(HARDWARE_DEVICE + "/" + 2);
+
+    private static final TrafficSelector HARDWARE_FLOW_SELECTOR = DefaultTrafficSelector.builder()
+            .matchInPort(PortNumber.portNumber(1))
+            .matchIPSrc(IpPrefix.valueOf("127.0.0.1/32"))
+            .matchIPDst(IpPrefix.valueOf("127.0.0.2/32"))
+            .build();
+
+    private static final TrafficTreatment HW_TRANSITION_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+            .pushMpls()
+            .transition(27)
+            .build();
+
+    private static final FlowRule HARDWARE_FLOW = DefaultFlowEntry.builder().forDevice(TOPO_FLOW_3_DEVICE)
+            .forTable(0)
+            .withPriority(100)
+            .withSelector(HARDWARE_FLOW_SELECTOR)
+            .withTreatment(HW_TRANSITION_FLOW_TREATMENT)
+            .fromApp(new DefaultApplicationId(0, "TestApp"))
+            .makePermanent()
+            .build();
+
+    static final FlowEntry HARDWARE_FLOW_ENTRY = new DefaultFlowEntry(HARDWARE_FLOW);
+
+    private static final TrafficSelector HARDWARE_ETH_FLOW_SELECTOR = DefaultTrafficSelector.builder()
+            .matchInPort(PortNumber.portNumber(1))
+            .matchIPSrc(IpPrefix.valueOf("127.0.0.1/32"))
+            .matchIPDst(IpPrefix.valueOf("127.0.0.2/32"))
+            .matchEthType(EthType.EtherType.IPV4.ethType().toShort())
+            .build();
+
+    private static final FlowRule HARDWARE_ETH_FLOW = DefaultFlowEntry.builder().forDevice(TOPO_FLOW_3_DEVICE)
+            .forTable(30)
+            .withPriority(100)
+            .withSelector(HARDWARE_ETH_FLOW_SELECTOR)
+            .withTreatment(OUTPUT_FLOW_TREATMENT)
+            .fromApp(new DefaultApplicationId(0, "TestApp"))
+            .makePermanent()
+            .build();
+
+    static final FlowEntry HARDWARE_ETH_FLOW_ENTRY = new DefaultFlowEntry(HARDWARE_ETH_FLOW);
+
+
+
+
+    //helper elements
+
+    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")));
+
+    static final Host H2 = new DefaultHost(ProviderId.NONE, HostId.hostId(HOST_TWO), MacAddress.valueOf(100),
+            VlanId.NONE, new HostLocation(TOPO_FLOW_3_DEVICE, PortNumber.portNumber(2), 0),
+            ImmutableSet.of(IpAddress.valueOf("127.0.0.3")));
+
+    static final TrafficSelector PACKET_OK = DefaultTrafficSelector.builder()
+            .matchInPort(PortNumber.portNumber(1))
+            .matchIPSrc(IpPrefix.valueOf("127.0.0.1/32"))
+            .matchIPDst(IpPrefix.valueOf("127.0.0.2/32"))
+            .build();
+
+    static final TrafficSelector PACKET_OK_TOPO = DefaultTrafficSelector.builder()
+            .matchInPort(PortNumber.portNumber(1))
+            .matchIPSrc(IpPrefix.valueOf("127.0.0.1/32"))
+            .matchIPDst(IpPrefix.valueOf("127.0.0.3/32"))
+            .build();
+
+    static final TrafficSelector PACKET_FAIL = DefaultTrafficSelector.builder()
+            .matchInPort(PortNumber.portNumber(1))
+            .matchIPSrc(IpPrefix.valueOf("127.0.0.1/32"))
+            .matchIPDst(IpPrefix.valueOf("127.0.0.99/32"))
+            .build();
+}
diff --git a/apps/t3/src/test/org/onosproject/t3/impl/TroubleshootManagerTest.java b/apps/t3/src/test/org/onosproject/t3/impl/TroubleshootManagerTest.java
new file mode 100644
index 0000000..0417faf
--- /dev/null
+++ b/apps/t3/src/test/org/onosproject/t3/impl/TroubleshootManagerTest.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.t3.impl;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import org.junit.Before;
+import org.junit.Test;
+import org.onlab.packet.EthType;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.Link;
+import org.onosproject.net.device.DeviceServiceAdapter;
+import org.onosproject.net.driver.DefaultDriver;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.net.driver.DriverServiceAdapter;
+import org.onosproject.net.flow.FlowEntry;
+import org.onosproject.net.flow.FlowRuleServiceAdapter;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.EthTypeCriterion;
+import org.onosproject.net.flow.criteria.VlanIdCriterion;
+import org.onosproject.net.group.Group;
+import org.onosproject.net.group.GroupServiceAdapter;
+import org.onosproject.net.host.HostServiceAdapter;
+import org.onosproject.net.link.LinkServiceAdapter;
+import org.onosproject.net.provider.ProviderId;
+import org.onosproject.t3.api.StaticPacketTrace;
+import org.slf4j.Logger;
+
+import java.util.HashMap;
+import java.util.Set;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+import static org.onosproject.t3.impl.T3TestObjects.*;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Test Class for Troubleshoot Manager.
+ */
+public class TroubleshootManagerTest {
+
+    private static final Logger log = getLogger(TroubleshootManager.class);
+
+    private TroubleshootManager mngr;
+
+    @Before
+    public void setUp() throws Exception {
+        mngr = new TroubleshootManager();
+        mngr.flowRuleService = new TestFlowRuleService();
+        mngr.hostService = new TestHostService();
+        mngr.linkService = new TestLinkService();
+        mngr.driverService = new TestDriverService();
+        mngr.groupService = new TestGroupService();
+
+        assertNotNull("Manager should not be null", mngr);
+
+        assertNotNull("Flow rule Service should not be null", mngr.flowRuleService);
+        assertNotNull("Host Service should not be null", mngr.hostService);
+        assertNotNull("Group Service should not be null", mngr.groupService);
+        assertNotNull("Driver Service should not be null", mngr.driverService);
+        assertNotNull("Link Service should not be null", mngr.linkService);
+    }
+
+    /**
+     * Tests failure on device with no flows
+     */
+    @Test
+    public void noFlows() {
+        StaticPacketTrace traceFail = mngr.trace(PACKET_OK, ConnectPoint.deviceConnectPoint("test/1"));
+        assertNotNull("Trace should not be null", traceFail);
+        assertNull("Trace should have 0 output", traceFail.getGroupOuputs(SINGLE_FLOW_DEVICE));
+        log.info("trace {}", traceFail.resultMessage());
+    }
+
+    /**
+     * Test a single flow rule that has output port in it.
+     */
+    @Test
+    public void testSingleFlowRule() {
+
+        testSuccess(PACKET_OK, SINGLE_FLOW_IN_CP, SINGLE_FLOW_DEVICE, SINGLE_FLOW_OUT_CP, 1);
+
+        testFaliure(PACKET_FAIL, SINGLE_FLOW_IN_CP, SINGLE_FLOW_DEVICE);
+    }
+
+    /**
+     * Tests two flow rule the last one of which has output port in it.
+     */
+    @Test
+    public void testDualFlowRule() {
+
+        //Test Success
+
+        StaticPacketTrace traceSuccess = testSuccess(PACKET_OK, DUAL_FLOW_IN_CP, DUAL_FLOW_DEVICE, DUAL_FLOW_OUT_CP, 1);
+
+        //Testing Vlan
+        Criterion criterion = traceSuccess.getGroupOuputs(DUAL_FLOW_DEVICE).get(0).
+                getFinalPacket().getCriterion(Criterion.Type.VLAN_VID);
+        assertNotNull("Packet Should have Vlan", criterion);
+
+        VlanIdCriterion vlanIdCriterion = (VlanIdCriterion) criterion;
+
+        assertEquals("Vlan should be 100", VlanId.vlanId((short) 100), vlanIdCriterion.vlanId());
+
+        //Test Faliure
+        testFaliure(PACKET_FAIL, DUAL_FLOW_IN_CP, DUAL_FLOW_DEVICE);
+
+    }
+
+    /**
+     * Test a single flow rule that points to a group with output port in it.
+     */
+    @Test
+    public void flowAndGroup() throws Exception {
+
+        StaticPacketTrace traceSuccess = testSuccess(PACKET_OK, GROUP_FLOW_IN_CP, GROUP_FLOW_DEVICE, GROUP_FLOW_OUT_CP, 1);
+
+        assertTrue("Wrong Output Group", traceSuccess.getGroupOuputs(GROUP_FLOW_DEVICE)
+                .get(0).getGroups().contains(GROUP));
+
+    }
+
+    /**
+     * Test path through a 3 device topology.
+     */
+    @Test
+    public void singlePathTopology() throws Exception {
+
+        StaticPacketTrace traceSuccess = testSuccess(PACKET_OK_TOPO, TOPO_FLOW_1_IN_CP,
+                TOPO_FLOW_3_DEVICE, TOPO_FLOW_3_OUT_CP, 1);
+
+        assertTrue("Incorrect path",
+                traceSuccess.getCompletePaths().get(0).contains(TOPO_FLOW_2_IN_CP));
+        assertTrue("Incorrect path",
+                traceSuccess.getCompletePaths().get(0).contains(TOPO_FLOW_2_OUT_CP));
+        assertTrue("Incorrect path",
+                traceSuccess.getCompletePaths().get(0).contains(TOPO_FLOW_3_IN_CP));
+
+    }
+
+    /**
+     * Test path through a 4 device topology with first device that has groups with multiple output buckets.
+     */
+    @Test
+    public void testGroupTopo() throws Exception {
+
+        StaticPacketTrace traceSuccess = testSuccess(PACKET_OK_TOPO, TOPO_FLOW_IN_CP,
+                TOPO_FLOW_3_DEVICE, TOPO_FLOW_3_OUT_CP, 2);
+
+        assertTrue("Incorrect groups",
+                traceSuccess.getGroupOuputs(TOPO_GROUP_FLOW_DEVICE).get(0).getGroups().contains(TOPO_GROUP));
+        assertTrue("Incorrect bucket",
+                traceSuccess.getGroupOuputs(TOPO_GROUP_FLOW_DEVICE).get(1).getGroups().contains(TOPO_GROUP));
+    }
+
+    /**
+     * Test HW support in a single device with 2 flow rules to check hit of static HW rules.
+     */
+    @Test
+    public void hardwareTest() throws Exception {
+
+        StaticPacketTrace traceSuccess = testSuccess(PACKET_OK, HARDWARE_DEVICE_IN_CP,
+                HARDWARE_DEVICE, HARDWARE_DEVICE_OUT_CP, 1);
+
+        assertEquals("wrong ETH type", EthType.EtherType.IPV4.ethType(),
+                ((EthTypeCriterion) traceSuccess.getGroupOuputs(HARDWARE_DEVICE).get(0).getFinalPacket()
+                        .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
+
+    }
+
+    private StaticPacketTrace testSuccess(TrafficSelector packet, ConnectPoint in, DeviceId deviceId, ConnectPoint out,
+                                          int paths) {
+        StaticPacketTrace traceSuccess = mngr.trace(packet, in);
+
+        log.info("trace {}", traceSuccess);
+
+        log.info("trace {}", traceSuccess.resultMessage());
+
+        assertNotNull("trace should not be null", traceSuccess);
+        assertEquals("Trace should have " + paths + " output", paths, traceSuccess.getGroupOuputs(deviceId).size());
+        assertEquals("Trace should only have " + paths + "output", paths, traceSuccess.getCompletePaths().size());
+        assertTrue("Trace should be successful",
+                traceSuccess.resultMessage().contains("Reached required destination Host"));
+        assertEquals("Incorrect Output CP", out,
+                traceSuccess.getGroupOuputs(deviceId).get(0).getOutput());
+
+        return traceSuccess;
+    }
+
+    private void testFaliure(TrafficSelector packet, ConnectPoint in, DeviceId deviceId) {
+        StaticPacketTrace traceFail = mngr.trace(packet, in);
+
+        log.info("trace {}", traceFail.resultMessage());
+
+        assertNotNull("Trace should not be null", traceFail);
+        assertNull("Trace should have 0 output", traceFail.getGroupOuputs(deviceId));
+    }
+
+    private class TestFlowRuleService extends FlowRuleServiceAdapter {
+        @Override
+        public Iterable<FlowEntry> getFlowEntries(DeviceId deviceId) {
+            if (deviceId.equals(SINGLE_FLOW_DEVICE)) {
+                return ImmutableList.of(SINGLE_FLOW_ENTRY);
+            } else if (deviceId.equals(DUAL_FLOW_DEVICE)) {
+                return ImmutableList.of(FIRST_FLOW_ENTRY, SECOND_FLOW_ENTRY);
+            } else if (deviceId.equals(GROUP_FLOW_DEVICE)) {
+                return ImmutableList.of(GROUP_FLOW_ENTRY);
+            } else if (deviceId.equals(TOPO_FLOW_DEVICE) ||
+                    deviceId.equals(TOPO_FLOW_2_DEVICE) ||
+                    deviceId.equals(TOPO_FLOW_3_DEVICE) ||
+                    deviceId.equals(TOPO_FLOW_4_DEVICE)) {
+                return ImmutableList.of(TOPO_SINGLE_FLOW_ENTRY, TOPO_SECOND_INPUT_FLOW_ENTRY);
+            } else if (deviceId.equals(TOPO_GROUP_FLOW_DEVICE)) {
+                return ImmutableList.of(TOPO_GROUP_FLOW_ENTRY);
+            } else if (deviceId.equals(HARDWARE_DEVICE)){
+                return ImmutableList.of(HARDWARE_ETH_FLOW_ENTRY, HARDWARE_FLOW_ENTRY);
+            }
+            return ImmutableList.of();
+        }
+    }
+
+    private class TestDriverService extends DriverServiceAdapter {
+        @Override
+        public Driver getDriver(DeviceId deviceId) {
+            if(deviceId.equals(HARDWARE_DEVICE)){
+                return new DefaultDriver("ofdpa", ImmutableList.of(),
+                        "test", "test", "test", new HashMap<>(), new HashMap<>());
+            }
+            return new DefaultDriver("NotHWDriver", ImmutableList.of(),
+                    "test", "test", "test", new HashMap<>(), new HashMap<>());
+        }
+    }
+
+    private class TestGroupService extends GroupServiceAdapter {
+        @Override
+        public Iterable<Group> getGroups(DeviceId deviceId) {
+            if (deviceId.equals(GROUP_FLOW_DEVICE)) {
+                return ImmutableList.of(GROUP);
+            } else if (deviceId.equals(TOPO_GROUP_FLOW_DEVICE)) {
+                return ImmutableList.of(TOPO_GROUP);
+            }
+            return ImmutableList.of();
+        }
+    }
+
+    private class TestHostService extends HostServiceAdapter {
+        @Override
+        public Set<Host> getConnectedHosts(ConnectPoint connectPoint) {
+            if (connectPoint.equals(TOPO_FLOW_3_OUT_CP)) {
+                return ImmutableSet.of(H2);
+            }
+            return ImmutableSet.of(H1);
+        }
+
+        @Override
+        public Set<Host> getHostsByMac(MacAddress mac) {
+            if (mac.equals(H1.mac())) {
+                return ImmutableSet.of(H1);
+            } else if (mac.equals(H2.mac())) {
+                return ImmutableSet.of(H2);
+            }
+            return ImmutableSet.of();
+        }
+
+        @Override
+        public Set<Host> getHostsByIp(IpAddress ip) {
+            if ((H1.ipAddresses().contains(ip))) {
+                return ImmutableSet.of(H1);
+            } else if ((H2.ipAddresses().contains(ip))) {
+                return ImmutableSet.of(H2);
+            }
+            return ImmutableSet.of();
+        }
+    }
+
+    private class TestLinkService extends LinkServiceAdapter {
+        @Override
+        public Set<Link> getEgressLinks(ConnectPoint connectPoint) {
+            if (connectPoint.equals(TOPO_FLOW_1_OUT_CP)
+                    || connectPoint.equals(TOPO_FLOW_OUT_CP_1)) {
+                return ImmutableSet.of(DefaultLink.builder()
+                        .providerId(ProviderId.NONE)
+                        .type(Link.Type.DIRECT)
+                        .src(connectPoint)
+                        .dst(TOPO_FLOW_2_IN_CP)
+                        .build());
+            } else if (connectPoint.equals(TOPO_FLOW_2_OUT_CP)) {
+                return ImmutableSet.of(DefaultLink.builder()
+                        .providerId(ProviderId.NONE)
+                        .type(Link.Type.DIRECT)
+                        .src(TOPO_FLOW_2_OUT_CP)
+                        .dst(TOPO_FLOW_3_IN_CP)
+                        .build());
+            } else if (connectPoint.equals(TOPO_FLOW_OUT_CP_2)) {
+                return ImmutableSet.of(DefaultLink.builder()
+                        .providerId(ProviderId.NONE)
+                        .type(Link.Type.DIRECT)
+                        .src(TOPO_FLOW_OUT_CP_2)
+                        .dst(TOPO_FLOW_4_IN_CP)
+                        .build());
+            } else if (connectPoint.equals(TOPO_FLOW_4_OUT_CP)) {
+                return ImmutableSet.of(DefaultLink.builder()
+                        .providerId(ProviderId.NONE)
+                        .type(Link.Type.DIRECT)
+                        .src(TOPO_FLOW_4_OUT_CP)
+                        .dst(TOPO_FLOW_3_IN_2_CP)
+                        .build());
+            }
+            return ImmutableSet.of();
+        }
+    }
+}
\ No newline at end of file