[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