REST API support added for t3-troubleshoot-simple
Change-Id: I1f31b48fb866e0a61e66b61ad2e6a30b5eddb04c
diff --git a/apps/t3/app/BUCK b/apps/t3/app/BUCK
new file mode 100644
index 0000000..55092ea
--- /dev/null
+++ b/apps/t3/app/BUCK
@@ -0,0 +1,26 @@
+COMPILE_DEPS = [
+ '//lib:CORE_DEPS',
+ '//lib:KRYO',
+ '//core/store/serializers:onos-core-serializers',
+ '//core/store/primitives:onos-core-primitives',
+ '//lib:JACKSON',
+ '//lib:jackson-databind',
+ '//core/api:onos-api',
+ '//lib:org.apache.karaf.shell.console',
+ '//cli:onos-cli',
+ '//drivers/default:onos-drivers-default',
+ '//apps/segmentrouting/app:onos-apps-segmentrouting-app',
+ '//apps/route-service/api:onos-apps-route-service-api',
+ '//apps/mcast/api:onos-apps-mcast-api',
+]
+
+TEST_DEPS = [
+ '//lib:TEST_ADAPTERS',
+ '//utils/misc:onlab-misc',
+ '//apps/route-service/api:onos-apps-route-service-api-tests',
+]
+
+osgi_jar_with_tests (
+ deps = COMPILE_DEPS,
+ test_deps = TEST_DEPS,
+)
diff --git a/apps/t3/app/pom.xml b/apps/t3/app/pom.xml
new file mode 100644
index 0000000..592a6c1
--- /dev/null
+++ b/apps/t3/app/pom.xml
@@ -0,0 +1,117 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!--
+ ~ Copyright 2017 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.
+ -->
+<project xmlns="http://maven.apache.org/POM/4.0.0"
+ xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
+ <modelVersion>4.0.0</modelVersion>
+
+ <parent>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-apps</artifactId>
+ <version>1.14.0-SNAPSHOT</version>
+ </parent>
+
+ <artifactId>onos-apps-t3-app</artifactId>
+ <packaging>bundle</packaging>
+
+ <description>Trellis Troubleshooting Toolkit</description>
+
+ <properties>
+ <onos.app.title>Trellis Troubleshooting Toolkit</onos.app.title>
+ <onos.app.category>Utilities</onos.app.category>
+ <onos.app.url>http://onosproject.org</onos.app.url>
+ <onos.app.readme>Static analysis of flows and groups to determine paths of packets.</onos.app.readme>
+ </properties>
+
+ <dependencies>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-apps-mcast-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-apps-route-service-api</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-apps-route-service-api</artifactId>
+ <version>${project.version}</version>
+ <classifier>tests</classifier>
+ <scope>test</scope>
+ </dependency>
+
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-apps-segmentrouting-app</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+
+ <dependency>
+ <groupId>org.osgi</groupId>
+ <artifactId>org.osgi.compendium</artifactId>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-core-serializers</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-api</artifactId>
+ </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>
+ <dependency>
+ <groupId>org.onosproject</groupId>
+ <artifactId>onos-cli</artifactId>
+ <version>${project.version}</version>
+ </dependency>
+ <dependency>
+ <groupId>org.apache.karaf.shell</groupId>
+ <artifactId>org.apache.karaf.shell.console</artifactId>
+ </dependency>
+ <dependency>
+ <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>
+ </dependency>
+ </dependencies>
+
+</project>
+
diff --git a/apps/t3/app/src/main/java/org/onosproject/t3/api/GroupsInDevice.java b/apps/t3/app/src/main/java/org/onosproject/t3/api/GroupsInDevice.java
new file mode 100644
index 0000000..74203af
--- /dev/null
+++ b/apps/t3/app/src/main/java/org/onosproject/t3/api/GroupsInDevice.java
@@ -0,0 +1,118 @@
+/*
+ * 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;
+import java.util.Objects;
+
+/**
+ * Class to represent the groups in a device for a given output and packet.
+ */
+//FIXME consider name change.
+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;
+ }
+
+ /**
+ * Updates the final packet.
+ *
+ * @param updatedPacket the updated final packet
+ */
+ public void setFinalPacket(TrafficSelector updatedPacket) {
+ selector = updatedPacket;
+ }
+
+ @Override
+ public boolean equals(Object o) {
+ if (this == o) {
+ return true;
+ }
+
+ if (o == null || getClass() != o.getClass()) {
+ return false;
+ }
+
+ GroupsInDevice that = (GroupsInDevice) o;
+
+ return Objects.equals(output, that.output) &&
+ Objects.equals(groups, that.groups) &&
+ Objects.equals(selector, that.selector);
+ }
+
+ @Override
+ public int hashCode() {
+ return Objects.hash(output, groups, selector);
+ }
+
+ @Override
+ public String toString() {
+ return "GroupsInDevice{" +
+
+ "output=" + output +
+ ", groups=" + groups +
+ ", selector=" + selector +
+ '}';
+ }
+
+}
diff --git a/apps/t3/app/src/main/java/org/onosproject/t3/api/StaticPacketTrace.java b/apps/t3/app/src/main/java/org/onosproject/t3/api/StaticPacketTrace.java
new file mode 100644
index 0000000..22db007
--- /dev/null
+++ b/apps/t3/app/src/main/java/org/onosproject/t3/api/StaticPacketTrace.java
@@ -0,0 +1,229 @@
+/*
+ * 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 com.google.common.collect.ImmutableList;
+import org.apache.commons.lang3.tuple.Pair;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+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;
+import java.util.Optional;
+
+/**
+ * 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;
+ private Pair<Host, Host> hosts;
+ private List<Boolean> success = new ArrayList<>();
+
+ /**
+ * 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();
+ hosts = null;
+ }
+
+ /**
+ * Builds the trace with a given packet and a connect point.
+ *
+ * @param packet the packet to trace
+ * @param in the initial connect point
+ * @param hosts pair of source and destination hosts
+ */
+ public StaticPacketTrace(TrafficSelector packet, ConnectPoint in, Pair<Host, Host> hosts) {
+ this.inPacket = packet;
+ this.in = in;
+ completePaths = new ArrayList<>();
+ outputsForDevice = new HashMap<>();
+ flowsForDevice = new HashMap<>();
+ resultMessage = new StringBuilder();
+ this.hosts = hosts;
+ }
+
+ /**
+ * 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) == null ? null : ImmutableList.copyOf(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.getOrDefault(deviceId, ImmutableList.of());
+ }
+
+ /**
+ * Return, if present, the two hosts at the endpoints of this trace.
+ *
+ * @return pair of source and destination hosts
+ */
+ public Optional<Pair<Host, Host>> getEndpointHosts() {
+ return Optional.ofNullable(hosts);
+ }
+
+ /**
+ * Sets the two hosts at the endpoints of this trace.
+ *
+ * @param endpointHosts pair of source and destination hosts
+ */
+ public void addEndpointHosts(Pair<Host, Host> endpointHosts) {
+ hosts = endpointHosts;
+ }
+
+ /**
+ * Return if all the possible paths of this trace are successful.
+ *
+ * @return true if all paths are successful
+ */
+ public boolean isSuccess() {
+ return !success.contains(false);
+ }
+
+ /**
+ * Sets if a path from this trace is successful.
+ *
+ * @param success true if a path of trace is successful.
+ */
+ public void setSuccess(boolean success) {
+ this.success.add(success);
+ }
+
+
+ @Override
+ public String toString() {
+ return "StaticPacketTrace{" +
+ "inPacket=" + inPacket +
+ ", in=" + in +
+ ", completePaths=" + completePaths +
+ ", outputsForDevice=" + outputsForDevice +
+ ", flowsForDevice=" + flowsForDevice +
+ ", resultMessage=" + resultMessage +
+ '}';
+ }
+}
diff --git a/apps/t3/app/src/main/java/org/onosproject/t3/api/TroubleshootService.java b/apps/t3/app/src/main/java/org/onosproject/t3/api/TroubleshootService.java
new file mode 100644
index 0000000..5b19cc2
--- /dev/null
+++ b/apps/t3/app/src/main/java/org/onosproject/t3/api/TroubleshootService.java
@@ -0,0 +1,78 @@
+/*
+ * 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.onlab.packet.EthType;
+import org.onlab.packet.VlanId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.HostId;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.t3.impl.Generator;
+
+import java.util.List;
+import java.util.Set;
+
+/**
+ * API for troubleshooting services, providing static analysis of installed
+ * flows and groups.
+ */
+public interface TroubleshootService {
+
+ /**
+ * Requests a static trace be performed between all hosts in the network, given a type of traffic.
+ *
+ * @param type the etherType of the traffic we want to trace.
+ * @return a trace result
+ */
+ List<StaticPacketTrace> pingAll(EthType.EtherType type);
+
+ /**
+ * Requests a static trace be performed between all hosts in the network, given a type of traffic.
+ *
+ * @param type the etherType of the traffic we want to trace.
+ * @return a trace result
+ */
+ Generator<Set<StaticPacketTrace>> pingAllGenerator(EthType.EtherType type);
+
+ /**
+ * Requests a static trace be performed for all mcast Routes in the network.
+ *
+ * @param vlanId the vlanId configured for multicast.
+ * @return a set of trace result yielded one by one.
+ */
+ Generator<Set<StaticPacketTrace>> traceMcast(VlanId vlanId);
+
+ /**
+ * Requests a static trace be performed between the two hosts in the network, given a type of traffic.
+ *
+ * @param sourceHost source host
+ * @param destinationHost destination host
+ * @param type the etherType of the traffic we want to trace.
+ * @return a trace result
+ */
+ Set<StaticPacketTrace> trace(HostId sourceHost, HostId destinationHost, EthType.EtherType type);
+
+ /**
+ * Requests a static trace be performed for the given traffic selector
+ * starting at the given connect point.
+ *
+ * @param packet description of packet
+ * @param in point at which packet starts
+ * @return a trace result
+ */
+ StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in);
+}
diff --git a/apps/t3/app/src/main/java/org/onosproject/t3/api/package-info.java b/apps/t3/app/src/main/java/org/onosproject/t3/api/package-info.java
new file mode 100644
index 0000000..152d568
--- /dev/null
+++ b/apps/t3/app/src/main/java/org/onosproject/t3/api/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Troubleshooting toolkit for trellis fabrics API.
+ */
+package org.onosproject.t3.api;
diff --git a/apps/t3/app/src/main/java/org/onosproject/t3/cli/T3CliUtils.java b/apps/t3/app/src/main/java/org/onosproject/t3/cli/T3CliUtils.java
new file mode 100644
index 0000000..26c814e
--- /dev/null
+++ b/apps/t3/app/src/main/java/org/onosproject/t3/cli/T3CliUtils.java
@@ -0,0 +1,194 @@
+/*
+ * 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.commons.lang.StringUtils;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.flow.TrafficTreatment;
+import org.onosproject.net.group.GroupBucket;
+import org.onosproject.t3.api.GroupsInDevice;
+import org.onosproject.t3.api.StaticPacketTrace;
+
+import java.util.List;
+
+/**
+ * Class containing utility methods for T3 cli.
+ */
+final class T3CliUtils {
+
+ private T3CliUtils() {
+ //banning construction
+ }
+
+ 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";
+
+ /**
+ * Builds a string output for the given trace for a specific level of verbosity.
+ *
+ * @param trace the trace
+ * @param verbosity1 middle verbosity level
+ * @param verbosity2 high verbosity level
+ * @return a string representing the trace.
+ */
+ static String printTrace(StaticPacketTrace trace, boolean verbosity1, boolean verbosity2) {
+ StringBuilder tracePrint = new StringBuilder();
+ //Print based on verbosity
+ if (verbosity1) {
+ tracePrint = printTrace(trace, false, tracePrint);
+ } else if (verbosity2) {
+ tracePrint = printTrace(trace, true, tracePrint);
+ } else {
+ tracePrint.append("Paths");
+ tracePrint.append("\n");
+ List<List<ConnectPoint>> paths = trace.getCompletePaths();
+ for (List<ConnectPoint> path : paths) {
+ tracePrint.append(path);
+ tracePrint.append("\n");
+ }
+ }
+ tracePrint.append("Result: \n" + trace.resultMessage());
+ return tracePrint.toString();
+ }
+
+ //prints the trace
+ private static StringBuilder printTrace(StaticPacketTrace trace, boolean verbose, StringBuilder tracePrint) {
+ List<List<ConnectPoint>> paths = trace.getCompletePaths();
+ for (List<ConnectPoint> path : paths) {
+ tracePrint.append("Path " + path);
+ tracePrint.append("\n");
+ ConnectPoint previous = null;
+ if (path.size() == 1) {
+ ConnectPoint connectPoint = path.get(0);
+ tracePrint.append("Device " + connectPoint.deviceId());
+ tracePrint.append("\n");
+ tracePrint.append("Input from " + connectPoint);
+ tracePrint.append("\n");
+ tracePrint = printFlows(trace, verbose, connectPoint, tracePrint);
+ tracePrint = printGroups(trace, verbose, connectPoint, tracePrint);
+ tracePrint.append("\n");
+ } else {
+ for (ConnectPoint connectPoint : path) {
+ if (previous == null || !previous.deviceId().equals(connectPoint.deviceId())) {
+ tracePrint.append("Device " + connectPoint.deviceId());
+ tracePrint.append("\n");
+ tracePrint.append(" Input from " + connectPoint);
+ tracePrint.append("\n");
+ tracePrint = printFlows(trace, verbose, connectPoint, tracePrint);
+ } else {
+ tracePrint = printGroups(trace, verbose, connectPoint, tracePrint);
+ tracePrint.append(" Output through " + connectPoint);
+ tracePrint.append("\n");
+ }
+ previous = connectPoint;
+ }
+ }
+ tracePrint.append(StringUtils.leftPad("\n", 100, '-'));
+ }
+ return tracePrint;
+ }
+
+
+ //Prints the flows for a given trace and a specified level of verbosity
+ private static StringBuilder printFlows(StaticPacketTrace trace, boolean verbose, ConnectPoint connectPoint,
+ StringBuilder tracePrint) {
+ tracePrint.append(" Flows ");
+ tracePrint.append(trace.getFlowsForDevice(connectPoint.deviceId()).size());
+ tracePrint.append(" \n");
+ trace.getFlowsForDevice(connectPoint.deviceId()).forEach(f -> {
+ if (verbose) {
+ tracePrint.append(" " + String.format(FLOW_SHORT_FORMAT, f.state(), f.bytes(), f.packets(),
+ f.table(), f.priority(), f.selector().criteria(),
+ printTreatment(f.treatment())));
+ tracePrint.append("\n");
+ } else {
+ tracePrint.append(String.format(" flowId=%s, table=%s, selector=%s", f.id(), f.table(),
+ f.selector().criteria()));
+ tracePrint.append("\n");
+ }
+ });
+ return tracePrint;
+ }
+
+ //Prints the groups for a given trace and a specified level of verbosity
+ private static StringBuilder printGroups(StaticPacketTrace trace, boolean verbose, ConnectPoint connectPoint,
+ StringBuilder tracePrint) {
+ List<GroupsInDevice> groupsInDevice = trace.getGroupOuputs(connectPoint.deviceId());
+ if (groupsInDevice != null) {
+ tracePrint.append(" Groups");
+ tracePrint.append("\n");
+ groupsInDevice.forEach(output -> {
+ if (output.getOutput().equals(connectPoint)) {
+ output.getGroups().forEach(group -> {
+ if (verbose) {
+ tracePrint.append(" " + String.format(GROUP_FORMAT, Integer.toHexString(group.id().id()),
+ group.state(), group.type(), group.bytes(), group.packets(),
+ group.appId().name(), group.referenceCount()));
+ tracePrint.append("\n");
+ int i = 0;
+ for (GroupBucket bucket : group.buckets().buckets()) {
+ tracePrint.append(" " + String.format(GROUP_BUCKET_FORMAT,
+ Integer.toHexString(group.id().id()),
+ ++i, bucket.bytes(), bucket.packets(),
+ bucket.treatment().allInstructions()));
+ tracePrint.append("\n");
+ }
+ } else {
+ tracePrint.append(" groupId=" + group.id());
+ tracePrint.append("\n");
+ }
+ });
+ tracePrint.append(" Outgoing Packet " + output.getFinalPacket());
+ tracePrint.append("\n");
+ }
+ });
+ }
+ return tracePrint;
+ }
+
+ private static 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/app/src/main/java/org/onosproject/t3/cli/TroubleshootMcastCommand.java b/apps/t3/app/src/main/java/org/onosproject/t3/cli/TroubleshootMcastCommand.java
new file mode 100644
index 0000000..92ed92f
--- /dev/null
+++ b/apps/t3/app/src/main/java/org/onosproject/t3/cli/TroubleshootMcastCommand.java
@@ -0,0 +1,138 @@
+/*
+ * 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.cli;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.onlab.packet.EthType;
+import org.onlab.packet.IpPrefix;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.ConnectPoint;
+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.IPCriterion;
+import org.onosproject.t3.api.StaticPacketTrace;
+import org.onosproject.t3.api.TroubleshootService;
+import org.onosproject.t3.impl.Generator;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+/**
+ * Starts a Static Packet Trace for all the multicast routes in the system and prints the result.
+ */
+@Command(scope = "onos", name = "t3-troubleshoot-mcast",
+ description = "Traces all the mcast routes present in the system")
+public class TroubleshootMcastCommand extends AbstractShellCommand {
+
+
+ @Option(name = "-v", aliases = "--verbose", description = "Outputs trace for each mcast route")
+ private boolean verbosity1 = false;
+
+ @Option(name = "-vv", aliases = "--veryverbose", description = "Outputs middle level details of every trace")
+ private boolean verbosity2 = false;
+
+ @Option(name = "-vvv", aliases = "--veryveryverbose", description = "Outputs complete details of every trace")
+ private boolean verbosity3 = false;
+
+ @Option(name = "-vid", aliases = "--vlanId", description = "Vlan of incoming packet", valueToShowInHelp = "None")
+ String vlan = "None";
+
+
+ @Override
+ protected void execute() {
+ TroubleshootService service = get(TroubleshootService.class);
+ print("Tracing all Multicast routes in the System");
+
+ //Create the generator for the list of traces.
+ VlanId vlanId = vlan == null || vlan.isEmpty() ? VlanId.NONE : VlanId.vlanId(vlan);
+ Generator<Set<StaticPacketTrace>> generator = service.traceMcast(vlanId);
+ int totalTraces = 0;
+ List<StaticPacketTrace> failedTraces = new ArrayList<>();
+ StaticPacketTrace previousTrace = null;
+ while (generator.iterator().hasNext()) {
+ totalTraces++;
+ //Print also Route if possible or packet
+ Set<StaticPacketTrace> traces = generator.iterator().next();
+ if (!verbosity1 && !verbosity2 && !verbosity3) {
+ for (StaticPacketTrace trace : traces) {
+ previousTrace = printTrace(previousTrace, trace);
+ if (!trace.isSuccess()) {
+ print("Failure: %s", trace.resultMessage());
+ failedTraces.add(trace);
+ } else {
+ print("Success");
+ }
+ }
+ } else {
+ traces.forEach(trace -> {
+ print("Tracing packet: %s", trace.getInitialPacket());
+ print("%s", T3CliUtils.printTrace(trace, verbosity2, verbosity3));
+ print("%s", StringUtils.rightPad("", 125, '-'));
+ });
+ }
+ }
+
+ if (!verbosity1 && !verbosity2 && !verbosity3) {
+ if (failedTraces.size() != 0) {
+ print("%s", StringUtils.rightPad("", 125, '-'));
+ print("Failed Traces: %s", failedTraces.size());
+ }
+ previousTrace = null;
+ for (StaticPacketTrace trace : failedTraces) {
+ previousTrace = printTrace(previousTrace, trace);
+ print("Failure: %s", trace.resultMessage());
+ }
+ print("%s", StringUtils.rightPad("", 125, '-'));
+ print("Summary");
+ print("Total Traces %s, errors %s", totalTraces, failedTraces.size());
+ print("%s", StringUtils.rightPad("", 125, '-'));
+ }
+
+ }
+
+ private StaticPacketTrace printTrace(StaticPacketTrace previousTrace, StaticPacketTrace trace) {
+ if (previousTrace == null || !previousTrace.equals(trace)) {
+ print("%s", StringUtils.rightPad("", 125, '-'));
+ previousTrace = trace;
+ ConnectPoint initialConnectPoint = trace.getInitialConnectPoint();
+ TrafficSelector initialPacket = trace.getInitialPacket();
+ boolean isIPv4 = ((EthTypeCriterion) initialPacket.getCriterion(Criterion.Type.ETH_TYPE))
+ .ethType().equals(EthType.EtherType.IPV4.ethType()
+ );
+ IpPrefix group = ((IPCriterion) (isIPv4 ? trace.getInitialPacket()
+ .getCriterion(Criterion.Type.IPV4_DST) : trace.getInitialPacket()
+ .getCriterion(Criterion.Type.IPV6_DST))).ip();
+ print("Source %s, group %s", initialConnectPoint, group);
+ }
+ StringBuilder destinations = new StringBuilder();
+ if (trace.getCompletePaths().size() > 1) {
+ destinations.append("Sinks: ");
+ } else {
+ destinations.append("Sink: ");
+ }
+ trace.getCompletePaths().forEach(path -> {
+ destinations.append(path.get(path.size() - 1) + " ");
+ });
+ print("%s", destinations.toString());
+ return previousTrace;
+ }
+}
diff --git a/apps/t3/app/src/main/java/org/onosproject/t3/cli/TroubleshootPingAllCommand.java b/apps/t3/app/src/main/java/org/onosproject/t3/cli/TroubleshootPingAllCommand.java
new file mode 100644
index 0000000..f9d7376
--- /dev/null
+++ b/apps/t3/app/src/main/java/org/onosproject/t3/cli/TroubleshootPingAllCommand.java
@@ -0,0 +1,228 @@
+/*
+ * 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.cli;
+
+import org.apache.commons.lang.StringUtils;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.Host;
+import org.onosproject.net.flow.criteria.Criterion;
+import org.onosproject.net.flow.criteria.IPCriterion;
+import org.onosproject.t3.api.StaticPacketTrace;
+import org.onosproject.t3.api.TroubleshootService;
+import org.onosproject.t3.impl.Generator;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Set;
+
+import static java.lang.Thread.sleep;
+import static org.onlab.packet.EthType.EtherType;
+
+/**
+ * Starts a Static Packet Trace for a given input and prints the result.
+ */
+@Command(scope = "onos", name = "t3-troubleshoot-pingall",
+ description = "Traces a ping between all hosts in the system of a given ETH type")
+public class TroubleshootPingAllCommand extends AbstractShellCommand {
+
+ private static final String FMT_SHORT =
+ "id=%s, mac=%s, locations=%s, vlan=%s, ip(s)=%s";
+
+ @Option(name = "-et", aliases = "--ethType", description = "ETH Type", valueToShowInHelp = "ipv4")
+ String ethType = "ipv4";
+
+ @Option(name = "-v", aliases = "--verbose", description = "Outputs trace for each host to host combination")
+ private boolean verbosity1 = false;
+
+ @Option(name = "-vv", aliases = "--veryverbose", description = "Outputs details of every trace")
+ private boolean verbosity2 = false;
+
+ @Option(name = "-d", aliases = "--delay", description = "delay between host to host trace display")
+ private long delay = 0;
+
+ @Override
+ protected void execute() {
+ TroubleshootService service = get(TroubleshootService.class);
+
+ EtherType type = EtherType.valueOf(ethType.toUpperCase());
+
+ print("Tracing between all %s hosts", ethType);
+
+ if (!type.equals(EtherType.IPV4) && !type.equals(EtherType.IPV6)) {
+ print("Command only support IPv4 or IPv6");
+ } else {
+ //Create the generator for the list of traces.
+ Generator<Set<StaticPacketTrace>> generator = service.pingAllGenerator(type);
+ Host previousHost = null;
+ int totalTraces = 0;
+ List<StaticPacketTrace> failedTraces = new ArrayList<>();
+ boolean ipv4 = type.equals(EtherType.IPV4);
+ while (generator.iterator().hasNext()) {
+ Set<StaticPacketTrace> traces = generator.iterator().next();
+ totalTraces++;
+ for (StaticPacketTrace trace : traces) {
+ //no verbosity is mininet style output
+ if (!verbosity1 && !verbosity2) {
+ if (trace.getEndpointHosts().isPresent()) {
+ Host src = trace.getEndpointHosts().get().getLeft();
+ if (previousHost == null || !previousHost.equals(src)) {
+ print("%s", StringUtils.rightPad("", 125, '-'));
+ previousHost = printSrc(trace, ipv4, src);
+ }
+ String host = getDstString(trace, ipv4, src);
+ if (!trace.isSuccess()) {
+ host = host + " " + trace.resultMessage();
+ failedTraces.add(trace);
+ }
+ print("%s", host);
+ }
+ } else {
+ print("%s", StringUtils.leftPad("", 125, '-'));
+
+ if (trace.getInitialPacket() != null) {
+ if (verbosity1) {
+ printResultOnly(trace, ipv4);
+ } else if (verbosity2) {
+ printVerbose(trace);
+ }
+ } else {
+ if (trace.getEndpointHosts().isPresent()) {
+ Host source = trace.getEndpointHosts().get().getLeft();
+ Host destination = trace.getEndpointHosts().get().getRight();
+ print("Source %s --> Destination %s", source.id(), destination.id());
+ }
+ print("Error in obtaining trace: %s", trace.resultMessage());
+ }
+ }
+ }
+ try {
+ sleep(delay);
+ } catch (InterruptedException e) {
+ log.debug("interrupted while sleep");
+ }
+ }
+ print("%s", StringUtils.rightPad("", 125, '-'));
+ print("Failed Traces: %s", failedTraces.size());
+ print("%s", StringUtils.rightPad("", 125, '-'));
+ failedTraces.forEach(t -> {
+ if (t.getEndpointHosts().isPresent()) {
+ printSrc(t, ipv4, t.getEndpointHosts().get().getLeft());
+ String dst = getDstString(t, ipv4, t.getEndpointHosts().get().getRight());
+ dst = dst + " " + t.resultMessage();
+ print("%s", dst);
+ print("%s", StringUtils.rightPad("", 125, '-'));
+ }
+ });
+ print("Summary");
+ print("Total Traces %s, errors %s", totalTraces, failedTraces.size());
+ }
+ }
+
+ private String getDstString(StaticPacketTrace trace, boolean ipv4, Host src) {
+ String host;
+ IpAddress ipAddress = getIpAddress(trace, ipv4, src, false);
+ if (ipAddress == null) {
+ host = String.format(" %s %s", trace.getEndpointHosts().get().getRight().id(),
+ trace.isSuccess());
+ } else {
+ host = String.format(" %s (%s) %s",
+ trace.getEndpointHosts().get().getRight().id(), ipAddress,
+ trace.isSuccess());
+ }
+ return host;
+ }
+
+ private Host printSrc(StaticPacketTrace trace, boolean ipv4, Host src) {
+ Host previousHost;
+ IpAddress ipAddress = getIpAddress(trace, ipv4, src, true);
+ if (ipAddress == null) {
+ print("%s", src.id() + " -->");
+ } else {
+ print("%s (%s) -->", src.id(), ipAddress);
+ }
+ previousHost = src;
+ return previousHost;
+ }
+
+ private IpAddress getIpAddress(StaticPacketTrace trace, boolean ipv4, Host host, boolean src) {
+ IpAddress ipAddress;
+ if (ipv4) {
+ Criterion.Type type = src ? Criterion.Type.IPV4_SRC : Criterion.Type.IPV4_DST;
+ if (trace.getInitialPacket().getCriterion(type) != null) {
+ ipAddress = ((IPCriterion) trace.getInitialPacket()
+ .getCriterion(type)).ip().address();
+ } else {
+ ipAddress = host.ipAddresses().stream().filter(IpAddress::isIp4)
+ .findAny().orElseGet(null);
+ }
+ } else {
+ Criterion.Type type = src ? Criterion.Type.IPV6_SRC : Criterion.Type.IPV6_DST;
+ if (trace.getInitialPacket().getCriterion(type) != null) {
+ ipAddress = ((IPCriterion) trace.getInitialPacket()
+ .getCriterion(type)).ip().address();
+ } else {
+ ipAddress = host.ipAddresses().stream().filter(IpAddress::isIp6)
+ .findAny().orElseGet(null);
+ }
+ }
+ return ipAddress;
+ }
+
+ private void printResultOnly(StaticPacketTrace trace, boolean ipv4) {
+ if (trace.getEndpointHosts().isPresent()) {
+ Host source = trace.getEndpointHosts().get().getLeft();
+ Host destination = trace.getEndpointHosts().get().getRight();
+ IpAddress srcIP;
+ IpAddress dstIP;
+ if (ipv4 && trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_SRC) != null) {
+ srcIP = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_SRC)).ip().address();
+ dstIP = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_DST)).ip().address();
+ print("Source %s (%s) --> Destination %s (%s)", source.id(), srcIP, destination.id(), dstIP);
+ } else if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_SRC) != null) {
+ srcIP = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_SRC)).ip().address();
+ dstIP = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST)).ip().address();
+ print("Source %s (%s) --> Destination %s (%s)", source.id(), srcIP, destination.id(), dstIP);
+ } else {
+ print("Source %s --> Destination %s", source.id(), destination.id());
+ }
+ print("%s", trace.resultMessage());
+ } else {
+ print("Can't gather host information from trace");
+ print("%s", trace.resultMessage());
+ }
+ }
+
+ private void printVerbose(StaticPacketTrace trace) {
+ if (trace.getEndpointHosts().isPresent()) {
+ Host source = trace.getEndpointHosts().get().getLeft();
+ print("Source host %s", printHost(source));
+ Host destination = trace.getEndpointHosts().get().getRight();
+ print("Destination host %s", printHost(destination));
+ }
+ print("%s", trace.getInitialPacket());
+ print("%s", T3CliUtils.printTrace(trace, false, false));
+ }
+
+ private String printHost(Host host) {
+ return String.format(FMT_SHORT, host.id(), host.mac(),
+ host.locations(),
+ host.vlan(), host.ipAddresses());
+ }
+}
diff --git a/apps/t3/app/src/main/java/org/onosproject/t3/cli/TroubleshootSimpleTraceCommand.java b/apps/t3/app/src/main/java/org/onosproject/t3/cli/TroubleshootSimpleTraceCommand.java
new file mode 100644
index 0000000..75356dc
--- /dev/null
+++ b/apps/t3/app/src/main/java/org/onosproject/t3/cli/TroubleshootSimpleTraceCommand.java
@@ -0,0 +1,81 @@
+/*
+ * 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.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.cli.net.HostIdCompleter;
+import org.onosproject.net.HostId;
+import org.onosproject.t3.api.StaticPacketTrace;
+import org.onosproject.t3.api.TroubleshootService;
+
+import java.util.Set;
+
+import static org.onlab.packet.EthType.EtherType;
+
+/**
+ * Starts a Static Packet Trace for a given input and prints the result.
+ */
+@Command(scope = "onos", name = "t3-troubleshoot-simple",
+ description = "Given two hosts troubleshoots flows and groups between them, in case of segment routing")
+public class TroubleshootSimpleTraceCommand extends AbstractShellCommand {
+
+ // OSGi workaround to introduce package dependency
+ HostIdCompleter completer;
+ @Argument(index = 0, name = "one", description = "One host ID",
+ required = true, multiValued = false)
+ String srcHost = null;
+
+ @Argument(index = 1, name = "two", description = "Another host ID",
+ required = true, multiValued = false)
+ String dstHost = null;
+
+ @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 = "-et", aliases = "--ethType", description = "ETH Type", valueToShowInHelp = "ipv4")
+ String ethType = "ipv4";
+
+ @Override
+ protected void execute() {
+ TroubleshootService service = get(TroubleshootService.class);
+
+ EtherType type = EtherType.valueOf(ethType.toUpperCase());
+
+ //Printing the traced hosts
+ print("Tracing between: %s and %s", srcHost, dstHost);
+
+ //Build the traces
+ Set<StaticPacketTrace> traces = service.trace(HostId.hostId(srcHost), HostId.hostId(dstHost), type);
+ traces.forEach(trace -> {
+ if (trace.getInitialPacket() != null) {
+ print("Tracing Packet: %s", trace.getInitialPacket());
+ print("%s", T3CliUtils.printTrace(trace, verbosity1, verbosity2));
+ } else {
+ print("Cannot obtain trace between %s and %s", srcHost, dstHost);
+ print("Reason: %s", trace.resultMessage());
+ }
+ });
+
+
+ }
+}
diff --git a/apps/t3/app/src/main/java/org/onosproject/t3/cli/TroubleshootTraceCommand.java b/apps/t3/app/src/main/java/org/onosproject/t3/cli/TroubleshootTraceCommand.java
new file mode 100644
index 0000000..e8b759d
--- /dev/null
+++ b/apps/t3/app/src/main/java/org/onosproject/t3/cli/TroubleshootTraceCommand.java
@@ -0,0 +1,193 @@
+/*
+ * 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.cli;
+
+import com.google.common.base.Preconditions;
+import org.apache.karaf.shell.commands.Command;
+import org.apache.karaf.shell.commands.Option;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.MplsLabel;
+import org.onlab.packet.TpPort;
+import org.onlab.packet.VlanId;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.t3.api.StaticPacketTrace;
+import org.onosproject.t3.api.TroubleshootService;
+
+import static org.onlab.packet.EthType.EtherType;
+
+/**
+ * Starts a Static Packet Trace for a given input and prints the result.
+ */
+@Command(scope = "onos", name = "t3-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";
+
+ private static final String CONTROLLER = "CONTROLLER";
+
+ @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")
+ String dstIp = null;
+
+ @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 = "-ml", aliases = "--mplsLabel", description = "Mpls label of incoming packet")
+ String mplsLabel = null;
+
+ @Option(name = "-mb", aliases = "--mplsBos", description = "MPLS BOS")
+ String mplsBos = null;
+
+ @Option(name = "-ipp", aliases = "--ipProto", description = "IP Proto")
+ String ipProto = null;
+
+ @Option(name = "-udps", aliases = "--udpSrc", description = "UDP Source")
+ String udpSrc = null;
+
+ @Option(name = "-udpd", aliases = "--udpDst", description = "UDP Destination")
+ String udpDst = null;
+
+ @Override
+ protected void execute() {
+ TroubleshootService service = get(TroubleshootService.class);
+ String[] cpInfo = srcPort.split("/");
+ Preconditions.checkArgument(cpInfo.length == 2, "wrong format of source port");
+ ConnectPoint cp;
+ //Uses input port as a convenience to carry the Controller port, proper flood behaviour is handled in the
+ // troubleshoot manager.
+ if (cpInfo[1].equalsIgnoreCase(CONTROLLER)) {
+ cp = new ConnectPoint(DeviceId.deviceId(cpInfo[0]), PortNumber.CONTROLLER);
+ } else {
+ cp = ConnectPoint.deviceConnectPoint(srcPort);
+ }
+
+ EtherType type = EtherType.valueOf(ethType.toUpperCase());
+
+ //Input Port must be specified
+ TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder()
+ .matchInPort(cp.port());
+
+ if (srcIp != null) {
+ if (type.equals(EtherType.IPV6)) {
+ selectorBuilder.matchIPv6Src(IpAddress.valueOf(srcIp).toIpPrefix());
+ } else {
+ selectorBuilder.matchIPSrc(IpAddress.valueOf(srcIp).toIpPrefix());
+ }
+ }
+
+ if (srcMac != null) {
+ selectorBuilder.matchEthSrc(MacAddress.valueOf(srcMac));
+ }
+
+ //if EthType option is not specified using IPv4
+ selectorBuilder.matchEthType(type.ethType().toShort());
+
+ if (srcTcpPort != null) {
+ selectorBuilder.matchTcpSrc(TpPort.tpPort(Integer.parseInt(srcTcpPort)));
+ }
+
+ if (dstIp != null) {
+ if (type.equals(EtherType.IPV6)) {
+ selectorBuilder.matchIPv6Dst(IpAddress.valueOf(dstIp).toIpPrefix());
+ } else {
+ 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 (mplsLabel != null) {
+ selectorBuilder.matchMplsLabel(MplsLabel.mplsLabel(Integer.parseInt(mplsLabel)));
+ }
+
+ if (mplsBos != null) {
+ selectorBuilder.matchMplsBos(Boolean.valueOf(mplsBos));
+ }
+
+ if (ipProto != null) {
+ selectorBuilder.matchIPProtocol(Byte.valueOf(ipProto));
+ }
+
+ if (udpSrc != null) {
+ selectorBuilder.matchUdpSrc(TpPort.tpPort(Integer.parseInt(udpSrc)));
+ }
+
+ if (udpDst != null) {
+ selectorBuilder.matchUdpDst(TpPort.tpPort(Integer.parseInt(udpDst)));
+ }
+
+
+ TrafficSelector packet = selectorBuilder.build();
+
+ //Printing the created packet
+ print("Tracing packet: %s", packet.criteria());
+
+ //Build the trace
+ StaticPacketTrace trace = service.trace(packet, cp);
+
+ print("%s", T3CliUtils.printTrace(trace, verbosity1, verbosity2));
+
+ }
+}
diff --git a/apps/t3/app/src/main/java/org/onosproject/t3/cli/package-info.java b/apps/t3/app/src/main/java/org/onosproject/t3/cli/package-info.java
new file mode 100644
index 0000000..b956df2
--- /dev/null
+++ b/apps/t3/app/src/main/java/org/onosproject/t3/cli/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Troubleshooting toolkit for trellis fabrics CLI.
+ */
+package org.onosproject.t3.cli;
diff --git a/apps/t3/app/src/main/java/org/onosproject/t3/impl/Generator.java b/apps/t3/app/src/main/java/org/onosproject/t3/impl/Generator.java
new file mode 100644
index 0000000..82ad628
--- /dev/null
+++ b/apps/t3/app/src/main/java/org/onosproject/t3/impl/Generator.java
@@ -0,0 +1,148 @@
+/*
+ * 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 java.util.Iterator;
+import java.util.NoSuchElementException;
+
+/**
+ * Generator class that yields instances of T type objects as soon as they are ready.
+ *
+ * @param <T> type of the object.
+ */
+public abstract class Generator<T> implements Iterable<T> {
+
+ private class Condition {
+ private boolean isSet;
+
+ synchronized void set() {
+ isSet = true;
+ notifyAll();
+ }
+
+ synchronized void await() throws InterruptedException {
+ try {
+
+ if (isSet) {
+ return;
+ }
+
+ while (!isSet) {
+ wait();
+ }
+ } finally {
+ isSet = false;
+ }
+ }
+ }
+
+ private static ThreadGroup threadGroup;
+
+ private Thread producer;
+ private boolean hasFinished;
+ private final Condition itemAvailableOrHasFinished = new Condition();
+ private final Condition itemRequested = new Condition();
+ private T nextItem;
+ private boolean nextItemAvailable;
+ private RuntimeException exceptionRaisedByProducer;
+
+ @Override
+ public Iterator<T> iterator() {
+ return new Iterator<T>() {
+ @Override
+ public boolean hasNext() {
+ return waitForNext();
+ }
+
+ @Override
+ public T next() {
+ if (!waitForNext()) {
+ throw new NoSuchElementException();
+ }
+ nextItemAvailable = false;
+ return nextItem;
+ }
+
+ @Override
+ public void remove() {
+ throw new UnsupportedOperationException();
+ }
+
+ private boolean waitForNext() {
+ if (nextItemAvailable) {
+ return true;
+ }
+ if (hasFinished) {
+ return false;
+ }
+ if (producer == null) {
+ startProducer();
+ }
+ itemRequested.set();
+ try {
+ itemAvailableOrHasFinished.await();
+ } catch (InterruptedException e) {
+ hasFinished = true;
+ producer.interrupt();
+ try {
+ producer.join();
+ } catch (InterruptedException e1) {
+ // Interrupting the broken thread
+ Thread.currentThread().interrupt();
+ throw new IllegalStateException(e1);
+ }
+ }
+ if (exceptionRaisedByProducer != null) {
+ throw exceptionRaisedByProducer;
+ }
+ return !hasFinished;
+ }
+ };
+ }
+
+ protected abstract void run() throws InterruptedException;
+
+ void yield(T element) throws InterruptedException {
+ nextItem = element;
+ nextItemAvailable = true;
+ itemAvailableOrHasFinished.set();
+ itemRequested.await();
+ }
+
+ private void startProducer() {
+ assert producer == null;
+ synchronized (this) {
+ if (threadGroup == null) {
+ threadGroup = new ThreadGroup("onos-t3-generator");
+ }
+ }
+ producer = new Thread(threadGroup, () -> {
+ try {
+ itemRequested.await();
+ Generator.this.run();
+ } catch (InterruptedException e) {
+ // Remaining steps in run() will shut down thread.
+ } catch (RuntimeException e) {
+ exceptionRaisedByProducer = e;
+ }
+ hasFinished = true;
+ itemAvailableOrHasFinished.set();
+ });
+ producer.setDaemon(true);
+ producer.start();
+ }
+}
\ No newline at end of file
diff --git a/apps/t3/app/src/main/java/org/onosproject/t3/impl/McastGenerator.java b/apps/t3/app/src/main/java/org/onosproject/t3/impl/McastGenerator.java
new file mode 100644
index 0000000..a5022a2
--- /dev/null
+++ b/apps/t3/app/src/main/java/org/onosproject/t3/impl/McastGenerator.java
@@ -0,0 +1,92 @@
+/*
+ * 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.ImmutableSet;
+import org.onlab.packet.EthType;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.mcast.api.McastRouteData;
+import org.onosproject.mcast.api.MulticastRouteService;
+import org.onosproject.net.flow.DefaultTrafficSelector;
+import org.onosproject.net.flow.TrafficSelector;
+import org.onosproject.t3.api.StaticPacketTrace;
+import org.slf4j.Logger;
+
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the generator class that yields a set of Packet Traces.
+ */
+public class McastGenerator extends Generator<Set<StaticPacketTrace>> {
+
+ private static final Logger log = getLogger(McastGenerator.class);
+ protected static final MacAddress IPV4_ADDRESS = MacAddress.valueOf("01:00:5E:00:00:00");
+ protected static final MacAddress IPV6_ADDRESS = MacAddress.valueOf("33:33:00:00:00:00");
+
+ private final MulticastRouteService mcastService;
+ private final TroubleshootManager manager;
+ private final VlanId vlanId;
+
+ /**
+ * Creates a generator for obtaining traces of all configured multicast routes.
+ *
+ * @param service the host service
+ * @param manager the troubleshoot manager issuing the request.
+ * @param vlanId the multicast configured VlanId.
+ */
+ McastGenerator(MulticastRouteService service, TroubleshootManager manager, VlanId vlanId) {
+ this.mcastService = service;
+ this.manager = manager;
+ this.vlanId = vlanId;
+ }
+
+ @Override
+ protected void run() {
+ mcastService.getRoutes().forEach(route -> {
+ McastRouteData routeData = mcastService.routeData(route);
+ IpAddress group = route.group();
+ routeData.sources().forEach((host, sources) -> {
+ sources.forEach(source -> {
+ TrafficSelector.Builder selector = DefaultTrafficSelector.builder()
+ .matchVlanId(vlanId)
+ .matchInPort(source.port());
+ if (group.isIp4()) {
+ selector.matchEthDst(IPV4_ADDRESS)
+ .matchIPDst(group.toIpPrefix())
+ .matchEthType(EthType.EtherType.IPV4.ethType().toShort());
+ } else {
+ selector.matchEthDst(IPV6_ADDRESS)
+ .matchIPv6Dst(group.toIpPrefix())
+ .matchEthType(EthType.EtherType.IPV6.ethType().toShort());
+ }
+ try {
+ yield(ImmutableSet.of(manager.trace(selector.build(), source)));
+ } catch (InterruptedException e) {
+ log.warn("Interrupted generator", e.getMessage());
+ log.debug("exception", e);
+ }
+ });
+ });
+
+ });
+
+ }
+}
diff --git a/apps/t3/app/src/main/java/org/onosproject/t3/impl/PingAllGenerator.java b/apps/t3/app/src/main/java/org/onosproject/t3/impl/PingAllGenerator.java
new file mode 100644
index 0000000..bf4d4b4
--- /dev/null
+++ b/apps/t3/app/src/main/java/org/onosproject/t3/impl/PingAllGenerator.java
@@ -0,0 +1,84 @@
+/*
+ * 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.Sets;
+import org.onlab.packet.EthType;
+import org.onlab.packet.IpAddress;
+import org.onosproject.net.host.HostService;
+import org.onosproject.t3.api.StaticPacketTrace;
+import org.slf4j.Logger;
+
+import java.util.List;
+import java.util.Set;
+
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * Implementation of the generator class that yields a set of Packet Traces.
+ */
+public class PingAllGenerator extends Generator<Set<StaticPacketTrace>> {
+
+ private static final Logger log = getLogger(PingAllGenerator.class);
+
+ private final EthType.EtherType etherType;
+ private final HostService hostService;
+ private final TroubleshootManager manager;
+
+ /**
+ * Creates a generator for obtaining traces of pings between all the hosts in the network.
+ *
+ * @param etherType the type of traffic we are tracing.
+ * @param service the host service
+ * @param manager the troubleshoot manager issuing the request.
+ */
+ PingAllGenerator(EthType.EtherType etherType, HostService service, TroubleshootManager manager) {
+ this.etherType = etherType;
+ this.hostService = service;
+ this.manager = manager;
+ }
+
+ @Override
+ protected void run() throws InterruptedException {
+ hostService.getHosts().forEach(host -> {
+ List<IpAddress> ipAddresses = manager.getIpAddresses(host, etherType, false);
+ if (ipAddresses.size() > 0) {
+ //check if the host has only local IPs of that ETH type
+ boolean onlyLocalSrc = ipAddresses.size() == 1 && ipAddresses.get(0).isLinkLocal();
+ hostService.getHosts().forEach(hostToPing -> {
+ List<IpAddress> ipAddressesToPing = manager.getIpAddresses(hostToPing, etherType, false);
+ //check if the other host has only local IPs of that ETH type
+ boolean onlyLocalDst = ipAddressesToPing.size() == 1 && ipAddressesToPing.get(0).isLinkLocal();
+ boolean sameLocation = Sets.intersection(host.locations(), hostToPing.locations()).size() > 0;
+ //Trace is done only if they are both local and under the same location
+ // or not local and if they are not the same host.
+ if (((sameLocation && onlyLocalDst && onlyLocalSrc) ||
+ (!onlyLocalSrc && !onlyLocalDst && ipAddressesToPing.size() > 0))
+ && !host.equals(hostToPing)) {
+ try {
+ yield(manager.trace(host.id(), hostToPing.id(), etherType));
+ } catch (InterruptedException e) {
+ log.warn("Interrupted generator", e.getMessage());
+ log.debug("exception", e);
+ }
+ }
+ });
+ }
+ });
+
+ }
+}
diff --git a/apps/t3/app/src/main/java/org/onosproject/t3/impl/Subnet.java b/apps/t3/app/src/main/java/org/onosproject/t3/impl/Subnet.java
new file mode 100644
index 0000000..ac5a741
--- /dev/null
+++ b/apps/t3/app/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/app/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java b/apps/t3/app/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java
new file mode 100644
index 0000000..3426517
--- /dev/null
+++ b/apps/t3/app/src/main/java/org/onosproject/t3/impl/TroubleshootManager.java
@@ -0,0 +1,1385 @@
+/*
+ * 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 com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableList;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.Lists;
+import com.google.common.collect.Sets;
+import org.apache.commons.lang3.tuple.Pair;
+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.IpAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.mcast.api.MulticastRouteService;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.HostId;
+import org.onosproject.net.Link;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.config.ConfigException;
+import org.onosproject.net.config.NetworkConfigService;
+import org.onosproject.net.config.basics.InterfaceConfig;
+import org.onosproject.net.device.DeviceService;
+import org.onosproject.net.driver.DriverService;
+import org.onosproject.net.edge.EdgePortService;
+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.criteria.VlanIdCriterion;
+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.host.InterfaceIpAddress;
+import org.onosproject.net.intf.Interface;
+import org.onosproject.net.link.LinkService;
+import org.onosproject.routeservice.ResolvedRoute;
+import org.onosproject.routeservice.RouteService;
+import org.onosproject.segmentrouting.config.SegmentRoutingDeviceConfig;
+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.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Optional;
+import java.util.Set;
+import java.util.stream.Collectors;
+
+import static org.onlab.packet.EthType.EtherType;
+import static org.onosproject.net.flow.TrafficSelector.Builder;
+import static org.onosproject.net.flow.instructions.Instructions.GroupInstruction;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModEtherInstruction;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsHeaderInstruction;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModMplsLabelInstruction;
+import static org.onosproject.net.flow.instructions.L2ModificationInstruction.ModVlanIdInstruction;
+import static org.onosproject.t3.impl.TroubleshootUtils.compareMac;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * 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)
+public class TroubleshootManager implements TroubleshootService {
+
+ private static final Logger log = getLogger(TroubleshootManager.class);
+
+ static final String PACKET_TO_CONTROLLER = "Packet goes to the controller";
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected FlowRuleService flowRuleService;
+
+ @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;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected DeviceService deviceService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MastershipService mastershipService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected NetworkConfigService networkConfigService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected EdgePortService edgePortService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected RouteService routeService;
+
+ @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+ protected MulticastRouteService mcastService;
+
+ @Override
+ public List<StaticPacketTrace> pingAll(EtherType type) {
+ ImmutableList.Builder<StaticPacketTrace> tracesBuilder = ImmutableList.builder();
+ hostService.getHosts().forEach(host -> {
+ List<IpAddress> ipAddresses = getIpAddresses(host, type, false);
+ if (ipAddresses.size() > 0) {
+ //check if the host has only local IPs of that ETH type
+ boolean onlyLocalSrc = ipAddresses.size() == 1 && ipAddresses.get(0).isLinkLocal();
+ hostService.getHosts().forEach(hostToPing -> {
+ List<IpAddress> ipAddressesToPing = getIpAddresses(hostToPing, type, false);
+ //check if the other host has only local IPs of that ETH type
+ boolean onlyLocalDst = ipAddressesToPing.size() == 1 && ipAddressesToPing.get(0).isLinkLocal();
+ boolean sameLocation = Sets.intersection(host.locations(), hostToPing.locations()).size() > 0;
+ //Trace is done only if they are both local and under the same location
+ // or not local and if they are not the same host.
+ if (((sameLocation && onlyLocalDst && onlyLocalSrc) ||
+ (!onlyLocalSrc && !onlyLocalDst && ipAddressesToPing.size() > 0))
+ && !host.equals(hostToPing)) {
+ tracesBuilder.addAll(trace(host.id(), hostToPing.id(), type));
+ }
+ });
+ }
+ });
+ return tracesBuilder.build();
+ }
+
+ @Override
+ public Generator<Set<StaticPacketTrace>> pingAllGenerator(EtherType type) {
+ return new PingAllGenerator(type, hostService, this);
+ }
+
+ @Override
+ public Generator<Set<StaticPacketTrace>> traceMcast(VlanId vlanId) {
+ return new McastGenerator(mcastService, this, vlanId);
+ }
+
+ @Override
+ public Set<StaticPacketTrace> trace(HostId sourceHost, HostId destinationHost, EtherType etherType) {
+ Host source = hostService.getHost(sourceHost);
+ Host destination = hostService.getHost(destinationHost);
+
+ //Temporary trace to fail in case we don't have enough information or what is provided is incoherent
+ StaticPacketTrace failTrace = new StaticPacketTrace(null, null, Pair.of(source, destination));
+
+ if (source == null) {
+ failTrace.addResultMessage("Source Host " + sourceHost + " does not exist");
+ failTrace.setSuccess(false);
+
+ return ImmutableSet.of(failTrace);
+ }
+
+ if (destination == null) {
+ failTrace.addResultMessage("Destination Host " + destinationHost + " does not exist");
+ failTrace.setSuccess(false);
+ return ImmutableSet.of(failTrace);
+ }
+
+ TrafficSelector.Builder selectorBuilder = DefaultTrafficSelector.builder()
+ .matchEthType(etherType.ethType().toShort())
+ .matchEthDst(source.mac())
+ .matchVlanId(source.vlan());
+
+
+ try {
+ ImmutableSet.Builder<StaticPacketTrace> traces = ImmutableSet.builder();
+ //if the location deviceId is the same, the two hosts are under same subnet and vlan on the interface
+ // we are under same leaf so it's L2 Unicast.
+ if (areBridged(source, destination)) {
+ selectorBuilder.matchEthDst(destination.mac());
+ source.locations().forEach(hostLocation -> {
+ selectorBuilder.matchInPort(hostLocation.port());
+ StaticPacketTrace trace = trace(selectorBuilder.build(), hostLocation);
+ trace.addEndpointHosts(Pair.of(source, destination));
+ traces.add(trace);
+ });
+ //The destination host is not dual homed, if it is the other path might be done through routing.
+ if (destination.locations().size() == 1) {
+ return traces.build();
+ }
+ }
+ //handle the IPs for src and dst in case of L3
+ if (etherType.equals(EtherType.IPV4) || etherType.equals(EtherType.IPV6)) {
+
+ //Match on the source IP
+ if (!matchIP(source, failTrace, selectorBuilder, etherType, true)) {
+ return ImmutableSet.of(failTrace);
+ }
+
+ //Match on destination IP
+ if (!matchIP(destination, failTrace, selectorBuilder, etherType, false)) {
+ return ImmutableSet.of(failTrace);
+ }
+
+ } else {
+ failTrace.addResultMessage("Host based trace supports only IPv4 or IPv6 as EtherType, " +
+ "please use packet based");
+ failTrace.setSuccess(false);
+ return ImmutableSet.of(failTrace);
+ }
+
+ //l3 unicast, we get the dst mac of the leaf the source is connected to from netcfg
+ SegmentRoutingDeviceConfig segmentRoutingConfig = networkConfigService.getConfig(source.location()
+ .deviceId(), SegmentRoutingDeviceConfig.class);
+ if (segmentRoutingConfig != null) {
+ selectorBuilder.matchEthDst(segmentRoutingConfig.routerMac());
+ } else {
+ failTrace.addResultMessage("Can't get " + source.location().deviceId() +
+ " router MAC from segment routing config can't perform L3 tracing.");
+ failTrace.setSuccess(false);
+ }
+ source.locations().forEach(hostLocation -> {
+ selectorBuilder.matchInPort(hostLocation.port());
+ StaticPacketTrace trace = trace(selectorBuilder.build(), hostLocation);
+ trace.addEndpointHosts(Pair.of(source, destination));
+ traces.add(trace);
+ });
+ return traces.build();
+
+ } catch (ConfigException e) {
+ failTrace.addResultMessage("Can't get config " + e.getMessage());
+ return ImmutableSet.of(failTrace);
+ }
+ }
+
+ /**
+ * Matches src and dst IPs based on host information.
+ *
+ * @param host the host
+ * @param failTrace the trace to use in case of failure
+ * @param selectorBuilder the packet we are building to trace
+ * @param etherType the traffic type
+ * @param src is this src host or dst host
+ * @return true if properly matched
+ */
+ private boolean matchIP(Host host, StaticPacketTrace failTrace, Builder selectorBuilder,
+ EtherType etherType, boolean src) {
+ List<IpAddress> ips = getIpAddresses(host, etherType, true);
+
+ if (ips.size() > 0) {
+ if (etherType.equals(EtherType.IPV4)) {
+ if (src) {
+ selectorBuilder.matchIPSrc(ips.get(0).toIpPrefix());
+ } else {
+ selectorBuilder.matchIPDst(ips.get(0).toIpPrefix());
+ }
+ } else if (etherType.equals(EtherType.IPV6)) {
+ if (src) {
+ selectorBuilder.matchIPv6Src(ips.get(0).toIpPrefix());
+ } else {
+ selectorBuilder.matchIPv6Dst(ips.get(0).toIpPrefix());
+ }
+ }
+ } else {
+ failTrace.addResultMessage("Host " + host + " has no " + etherType + " address");
+ failTrace.setSuccess(false);
+ return false;
+ }
+ return true;
+ }
+
+ List<IpAddress> getIpAddresses(Host host, EtherType etherType, boolean checklocal) {
+ return host.ipAddresses().stream().filter(ipAddress -> {
+ boolean correctIp = false;
+ if (etherType.equals(EtherType.IPV4)) {
+ correctIp = ipAddress.isIp4();
+ } else if (etherType.equals(EtherType.IPV6)) {
+ correctIp = ipAddress.isIp6();
+ }
+ if (checklocal) {
+ correctIp = correctIp && !ipAddress.isLinkLocal();
+ }
+ return correctIp;
+ }).collect(Collectors.toList());
+ }
+
+ /**
+ * Checks that two hosts are bridged (L2Unicast).
+ *
+ * @param source the source host
+ * @param destination the destination host
+ * @return true if bridged.
+ * @throws ConfigException if config can't be properly retrieved
+ */
+ private boolean areBridged(Host source, Host destination) throws ConfigException {
+
+ //If the locations is not the same we don't even check vlan or subnets
+ if (Collections.disjoint(source.locations(), destination.locations())) {
+ return false;
+ }
+
+ if (!source.vlan().equals(VlanId.NONE) && !destination.vlan().equals(VlanId.NONE)
+ && !source.vlan().equals(destination.vlan())) {
+ return false;
+ }
+
+ InterfaceConfig interfaceCfgH1 = networkConfigService.getConfig(source.location(), InterfaceConfig.class);
+ InterfaceConfig interfaceCfgH2 = networkConfigService.getConfig(destination.location(), InterfaceConfig.class);
+ if (interfaceCfgH1 != null && interfaceCfgH2 != null) {
+
+ //following can be optimized but for clarity is left as is
+ Interface intfH1 = interfaceCfgH1.getInterfaces().stream().findFirst().get();
+ Interface intfH2 = interfaceCfgH2.getInterfaces().stream().findFirst().get();
+
+ if (source.vlan().equals(VlanId.NONE) && !destination.vlan().equals(VlanId.NONE)) {
+ return intfH1.vlanUntagged().equals(destination.vlan()) ||
+ intfH1.vlanNative().equals(destination.vlan());
+ }
+
+ if (!source.vlan().equals(VlanId.NONE) && destination.vlan().equals(VlanId.NONE)) {
+ return intfH2.vlanUntagged().equals(source.vlan()) ||
+ intfH2.vlanNative().equals(source.vlan());
+ }
+
+ if (!intfH1.vlanNative().equals(intfH2.vlanNative())) {
+ return false;
+ }
+
+ if (!intfH1.vlanUntagged().equals(intfH2.vlanUntagged())) {
+ return false;
+ }
+
+ List<InterfaceIpAddress> intersection = new ArrayList<>(intfH1.ipAddressesList());
+ intersection.retainAll(intfH2.ipAddressesList());
+ if (intersection.size() == 0) {
+ return false;
+ }
+ }
+ return true;
+ }
+
+ @Override
+ public StaticPacketTrace trace(TrafficSelector packet, ConnectPoint in) {
+ log.info("Tracing packet {} coming in through {}", packet, in);
+ //device must exist in ONOS
+ Preconditions.checkNotNull(deviceService.getDevice(in.deviceId()),
+ "Device " + in.deviceId() + " must exist in ONOS");
+
+ StaticPacketTrace trace = new StaticPacketTrace(packet, in);
+ boolean isDualHomed = getHosts(trace).stream().anyMatch(host -> host.locations().size() > 1);
+ //FIXME this can be done recursively
+ //Building output connect Points
+ List<ConnectPoint> path = new ArrayList<>();
+ trace = traceInDevice(trace, packet, in, isDualHomed, path);
+ trace = getTrace(path, in, trace, isDualHomed);
+ 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
+ * @param isDualHomed true if the trace we are doing starts or ends in a dual homed host
+ * @return the build trace for that packet.
+ */
+ private StaticPacketTrace getTrace(List<ConnectPoint> completePath, ConnectPoint in, StaticPacketTrace trace,
+ boolean isDualHomed) {
+
+ log.debug("------------------------------------------------------------");
+
+ //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());
+ completePath.add(in);
+ trace.addCompletePath(completePath);
+ trace.setSuccess(false);
+ 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");
+ trace.setSuccess(false);
+ return trace;
+ }
+
+ //If the trace has outputs we analyze them all
+ for (GroupsInDevice outputPath : trace.getGroupOuputs(in.deviceId())) {
+
+ ConnectPoint cp = outputPath.getOutput();
+ log.debug("Connect point in {}", in);
+ log.debug("Output path {}", cp);
+ log.debug("{}", outputPath.getFinalPacket());
+
+ //Hosts for the the given output
+ Set<Host> hostsList = hostService.getConnectedHosts(cp);
+ //Hosts queried from the original ip or mac
+ Set<Host> hosts = getHosts(trace);
+
+ if (in.equals(cp) && trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID) != null &&
+ outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID) != null
+ && ((VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID)).vlanId()
+ .equals(((VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID))
+ .vlanId())) {
+ if (trace.getGroupOuputs(in.deviceId()).size() == 1 &&
+ computePath(completePath, trace, outputPath.getOutput())) {
+ trace.addResultMessage("Connect point out " + cp + " is same as initial input " + in);
+ trace.setSuccess(false);
+ }
+ } else if (!Collections.disjoint(hostsList, hosts)) {
+ //If the two host collections contain the same item it means we reached the proper output
+ log.debug("Stopping here because host is expected destination {}, reached through", completePath);
+ if (computePath(completePath, trace, outputPath.getOutput())) {
+ trace.addResultMessage("Reached required destination Host " + cp);
+ trace.setSuccess(true);
+ }
+ break;
+ } else if (cp.port().equals(PortNumber.CONTROLLER)) {
+
+ //Getting the master when the packet gets sent as packet in
+ NodeId master = mastershipService.getMasterFor(cp.deviceId());
+ trace.addResultMessage(PACKET_TO_CONTROLLER + " " + master.id());
+ computePath(completePath, trace, outputPath.getOutput());
+ handleVlanToController(outputPath, trace);
+
+ } else if (linkService.getEgressLinks(cp).size() > 0) {
+
+ //TODO this can be optimized if we use a Tree structure for paths.
+ //if we already have outputs let's check if the one we are considering starts from one of the devices
+ // in any of the ones we have.
+ if (trace.getCompletePaths().size() > 0) {
+ ConnectPoint inputForOutput = null;
+ List<ConnectPoint> previousPath = new ArrayList<>();
+ for (List<ConnectPoint> path : trace.getCompletePaths()) {
+ for (ConnectPoint connect : path) {
+ //if the path already contains the input for the output we've found we use it
+ if (connect.equals(in)) {
+ inputForOutput = connect;
+ previousPath = path;
+ break;
+ }
+ }
+ }
+
+ //we use the pre-existing path up to the point we fork to a new output
+ if (inputForOutput != null && completePath.contains(inputForOutput)) {
+ List<ConnectPoint> temp = new ArrayList<>(previousPath);
+ temp = temp.subList(0, previousPath.indexOf(inputForOutput) + 1);
+ if (completePath.containsAll(temp)) {
+ completePath = temp;
+ }
+ }
+ }
+
+ //let's add the ouput for the input
+ completePath.add(cp);
+ //let's compute the links for the given output
+ Set<Link> links = linkService.getEgressLinks(cp);
+ log.debug("Egress Links {}", links);
+ //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
+ 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, isDualHomed, completePath);
+ //continue the trace along the path
+ getTrace(completePath, dst, trace, isDualHomed);
+ }
+ } else if (edgePortService.isEdgePoint(outputPath.getOutput()) &&
+ trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST) != null &&
+ ((EthCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.ETH_DST))
+ .mac().isMulticast()) {
+ trace.addResultMessage("Packet is multicast and reached output " + outputPath.getOutput() +
+ " which is enabled and is edge port");
+ trace.setSuccess(true);
+ computePath(completePath, trace, outputPath.getOutput());
+ if (!hasOtherOutput(in.deviceId(), trace, outputPath.getOutput())) {
+ return trace;
+ }
+ } else if (deviceService.getPort(cp) != null && deviceService.getPort(cp).isEnabled()) {
+ EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
+ .getCriterion(Criterion.Type.ETH_TYPE);
+ //We treat as correct output only if it's not LLDP or BDDP
+ if (!(ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
+ && !ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
+ if (computePath(completePath, trace, outputPath.getOutput())) {
+ if (hostsList.isEmpty()) {
+ trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
+ .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
+ cp + " with no hosts connected ");
+ } else {
+ IpAddress ipAddress = null;
+ if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_DST) != null) {
+ ipAddress = ((IPCriterion) trace.getInitialPacket()
+ .getCriterion(Criterion.Type.IPV4_DST)).ip().address();
+ } else if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST) != null) {
+ ipAddress = ((IPCriterion) trace.getInitialPacket()
+ .getCriterion(Criterion.Type.IPV6_DST)).ip().address();
+ }
+ if (ipAddress != null) {
+ IpAddress finalIpAddress = ipAddress;
+ if (hostsList.stream().anyMatch(host -> host.ipAddresses().contains(finalIpAddress)) ||
+ hostService.getHostsByIp(finalIpAddress).isEmpty()) {
+ trace.addResultMessage("Packet is " +
+ ((EthTypeCriterion) outputPath.getFinalPacket()
+ .getCriterion(Criterion.Type.ETH_TYPE)).ethType() +
+ " and reached " + cp + " with hosts " + hostsList);
+ } else {
+ trace.addResultMessage("Wrong output " + cp + " for required destination ip " +
+ ipAddress);
+ trace.setSuccess(false);
+ }
+ } else {
+ trace.addResultMessage("Packet is " + ((EthTypeCriterion) outputPath.getFinalPacket()
+ .getCriterion(Criterion.Type.ETH_TYPE)).ethType() + " and reached " +
+ cp + " with hosts " + hostsList);
+ }
+ }
+ trace.setSuccess(true);
+ }
+ }
+
+ } else {
+ computePath(completePath, trace, cp);
+ trace.setSuccess(false);
+ if (deviceService.getPort(cp) == null) {
+ //Port is not existent on device.
+ log.warn("Port {} is not available on device.", cp);
+ trace.addResultMessage("Port " + cp + "is not available on device. Packet is dropped");
+ } else {
+ //No links means that the packet gets dropped.
+ log.warn("No links out of {}", cp);
+ trace.addResultMessage("No links depart from " + cp + ". Packet is dropped");
+ }
+ }
+ }
+ return trace;
+ }
+
+
+ /**
+ * If the initial packet comes tagged with a Vlan we output it with that to ONOS.
+ * If ONOS applied a vlan we remove it.
+ *
+ * @param outputPath the output
+ * @param trace the trace we are building
+ */
+
+ private void handleVlanToController(GroupsInDevice outputPath, StaticPacketTrace trace) {
+
+ VlanIdCriterion initialVid = (VlanIdCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.VLAN_VID);
+ VlanIdCriterion finalVid = (VlanIdCriterion) outputPath.getFinalPacket().getCriterion(Criterion.Type.VLAN_VID);
+
+ if (initialVid != null && !initialVid.equals(finalVid) && initialVid.vlanId().equals(VlanId.NONE)) {
+
+ Set<Criterion> finalCriteria = new HashSet<>(outputPath.getFinalPacket().criteria());
+ //removing the final vlanId
+ finalCriteria.remove(finalVid);
+ Builder packetUpdated = DefaultTrafficSelector.builder();
+ finalCriteria.forEach(packetUpdated::add);
+ //Initial was none so we set it to that
+ packetUpdated.add(Criteria.matchVlanId(VlanId.NONE));
+ //Update final packet
+ outputPath.setFinalPacket(packetUpdated.build());
+ }
+ }
+
+ /**
+ * Checks if the device has other outputs than the given connect point.
+ *
+ * @param inDeviceId the device
+ * @param trace the trace we are building
+ * @param cp an output connect point
+ * @return true if the device has other outputs.
+ */
+ private boolean hasOtherOutput(DeviceId inDeviceId, StaticPacketTrace trace, ConnectPoint cp) {
+ return trace.getGroupOuputs(inDeviceId).stream().filter(groupsInDevice -> {
+ return !groupsInDevice.getOutput().equals(cp);
+ }).count() > 0;
+ }
+
+ /**
+ * 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 boolean computePath(List<ConnectPoint> completePath, StaticPacketTrace trace, ConnectPoint output) {
+ List<ConnectPoint> traverseList = new ArrayList<>();
+ if (!completePath.contains(trace.getInitialConnectPoint())) {
+ traverseList.add(trace.getInitialConnectPoint());
+ }
+
+ if (output != null && trace.getInitialConnectPoint().deviceId().equals(output.deviceId())) {
+ trace.addCompletePath(ImmutableList.of(trace.getInitialConnectPoint(), output));
+ return true;
+ }
+
+ traverseList.addAll(completePath);
+ if (output != null && !completePath.contains(output)) {
+ traverseList.add(output);
+ }
+ if (!trace.getCompletePaths().contains(traverseList)) {
+ trace.addCompletePath(ImmutableList.copyOf(traverseList));
+ return true;
+ }
+ return false;
+ }
+
+ /**
+ * 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.
+ * @param isDualHomed true if the trace we are doing starts or ends in a dual homed host
+ * @param completePath the path up until this device
+ * @return updated trace
+ */
+ private StaticPacketTrace traceInDevice(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in,
+ boolean isDualHomed, List<ConnectPoint> completePath) {
+
+ boolean multipleRoutes = false;
+ if (trace.getGroupOuputs(in.deviceId()) != null) {
+ multipleRoutes = multipleRoutes(trace);
+ }
+ if (trace.getGroupOuputs(in.deviceId()) != null && !isDualHomed && !multipleRoutes) {
+ log.debug("Trace already contains device and given outputs");
+ return trace;
+ }
+
+ log.debug("Packet {} coming in from {}", packet, in);
+
+ //if device is not available exit here.
+ if (!deviceService.isAvailable(in.deviceId())) {
+ trace.addResultMessage("Device is offline " + in.deviceId());
+ computePath(completePath, trace, null);
+ return trace;
+ }
+
+ //handle when the input is the controller
+ //NOTE, we are using the input port as a convenience to carry the CONTROLLER port number even if
+ // a packet in from the controller will not actually traverse the pipeline and have no such notion
+ // as the input port.
+ if (in.port().equals(PortNumber.CONTROLLER)) {
+ StaticPacketTrace outputTrace = inputFromController(trace, in);
+ if (outputTrace != null) {
+ return trace;
+ }
+ }
+
+ List<FlowEntry> flows = new ArrayList<>();
+ List<FlowEntry> outputFlows = new ArrayList<>();
+ List<Instruction> deferredInstructions = new ArrayList<>();
+
+ FlowEntry nextTableIdEntry = findNextTableIdEntry(in.deviceId(), -1);
+ if (nextTableIdEntry == null) {
+ trace.addResultMessage("No flow rules for device " + in.deviceId() + ". Aborting");
+ computePath(completePath, trace, null);
+ trace.setSuccess(false);
+ 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");
+
+ 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 matching flow rules for device " + in.deviceId() + ". Aborting");
+ computePath(completePath, trace, null);
+ trace.setSuccess(false);
+ 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 if (((IndexTableId) tableId).id() == 40) {
+ //if the table is 40 OFDPA skips to table 60
+ log.debug("A miss on Table 40 on OFDPA means that we skip directly to table 60");
+ tableId = IndexTableId.of(60);
+ } else {
+ tableId = nextTableIdEntry.table();
+ }
+
+ } else if (flowEntry == null) {
+ trace.addResultMessage("Packet has no match on table " + tableId + " in device " +
+ in.deviceId() + ". Dropping");
+ computePath(completePath, trace, null);
+ trace.setSuccess(false);
+ 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 immediate actions of this flow rule.
+ packet = updatePacket(packet, flowEntry.treatment().immediate()).build();
+
+ //save the deferred rules for later
+ deferredInstructions.addAll(flowEntry.treatment().deferred());
+
+ //If the flow requires to clear deferred actions we do so for all the ones we encountered.
+ if (flowEntry.treatment().clearedDeferred()) {
+ deferredInstructions.clear();
+ }
+
+ //On table 10 OFDPA needs two rules to apply the vlan if none and then to transition to the next table.
+ if (needsSecondTable10Flow(flowEntry, isOfdpaHardware)) {
+
+ //Let's get the packet vlanId instruction
+ VlanIdCriterion packetVlanIdCriterion =
+ (VlanIdCriterion) packet.getCriterion(Criterion.Type.VLAN_VID);
+
+ //Let's get the flow entry vlan mod instructions
+ ModVlanIdInstruction entryModVlanIdInstruction = (ModVlanIdInstruction) flowEntry.treatment()
+ .immediate().stream()
+ .filter(instruction -> instruction instanceof ModVlanIdInstruction)
+ .findFirst().orElse(null);
+
+ //If the entry modVlan is not null we need to make sure that the packet has been updated and there
+ // is a flow rule that matches on same criteria and with updated vlanId
+ if (entryModVlanIdInstruction != null) {
+
+ FlowEntry secondVlanFlow = getSecondFlowEntryOnTable10(packet, in,
+ packetVlanIdCriterion, entryModVlanIdInstruction);
+
+ //We found the flow that we expected
+ if (secondVlanFlow != null) {
+ flows.add(secondVlanFlow);
+ } else {
+ trace.addResultMessage("Missing forwarding rule for tagged packet on " + in);
+ computePath(completePath, trace, null);
+ return trace;
+ }
+ }
+
+ }
+
+ }
+ }
+
+ //Creating a modifiable builder for the output packet
+ Builder builder = DefaultTrafficSelector.builder();
+ packet.criteria().forEach(builder::add);
+
+ //Adding all the flows to the trace
+ trace.addFlowsForDevice(in.deviceId(), ImmutableList.copyOf(flows));
+
+ List<PortNumber> outputPorts = new ArrayList<>();
+ List<FlowEntry> outputFlowEntries = handleFlows(trace, packet, in, outputFlows, builder, outputPorts);
+
+
+ log.debug("Handling Groups");
+ //Analyze Groups
+ List<Group> groups = new ArrayList<>();
+
+ Collection<FlowEntry> nonOutputFlows = flows;
+ nonOutputFlows.removeAll(outputFlowEntries);
+
+ //Handling groups pointed at by immediate instructions
+ for (FlowEntry entry : flows) {
+ getGroupsFromInstructions(trace, groups, entry.treatment().immediate(),
+ entry.deviceId(), builder, outputPorts, in, completePath);
+ }
+
+ //If we have deferred instructions at this point we handle them.
+ if (deferredInstructions.size() > 0) {
+ builder = handleDeferredActions(trace, packet, in, deferredInstructions, outputPorts, groups, completePath);
+
+ }
+ packet = builder.build();
+
+ log.debug("Output Packet {}", packet);
+ return trace;
+ }
+
+ private List<FlowEntry> handleFlows(StaticPacketTrace trace, TrafficSelector packet, ConnectPoint in,
+ List<FlowEntry> outputFlows, Builder builder, List<PortNumber> outputPorts) {
+ //TODO optimization
+ //outputFlows contains also last rule of device, so we need filtering for OUTPUT instructions.
+ List<FlowEntry> outputFlowEntries = outputFlows.stream().filter(flow -> flow.treatment()
+ .allInstructions().stream().filter(instruction -> instruction.type()
+ .equals(Instruction.Type.OUTPUT)).count() > 0).collect(Collectors.toList());
+
+ if (outputFlowEntries.size() > 1) {
+ trace.addResultMessage("More than one flow rule with OUTPUT instruction");
+ log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
+ }
+
+ if (outputFlowEntries.size() == 1) {
+
+ OutputInstruction outputInstruction = (OutputInstruction) outputFlowEntries.get(0).treatment()
+ .allInstructions().stream()
+ .filter(instruction -> {
+ return instruction.type().equals(Instruction.Type.OUTPUT);
+ }).findFirst().get();
+
+ //FIXME using GroupsInDevice for output even if flows.
+ buildOutputFromDevice(trace, in, builder, outputPorts, outputInstruction, ImmutableList.of());
+
+ }
+ return outputFlowEntries;
+ }
+
+ private boolean multipleRoutes(StaticPacketTrace trace) {
+ boolean multipleRoutes = false;
+ IPCriterion ipCriterion = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV4_DST));
+ IpAddress ip = null;
+ if (ipCriterion != null) {
+ ip = ipCriterion.ip().address();
+ } else if (trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST) != null) {
+ ip = ((IPCriterion) trace.getInitialPacket().getCriterion(Criterion.Type.IPV6_DST)).ip().address();
+ }
+ if (ip != null) {
+ Optional<ResolvedRoute> optionalRoute = routeService.longestPrefixLookup(ip);
+ if (optionalRoute.isPresent()) {
+ ResolvedRoute route = optionalRoute.get();
+ route.prefix();
+ multipleRoutes = routeService.getAllResolvedRoutes(route.prefix()).size() > 1;
+ }
+ }
+ return multipleRoutes;
+ }
+
+ /**
+ * Handles the specific case where the Input is the controller.
+ * Note that the in port is used as a convenience to store the port of the controller even if the packet in
+ * from a controller should not have a physical input port. The in port from the Controller is used to make sure
+ * the flood to all active physical ports of the device.
+ *
+ * @param trace the trace
+ * @param in the controller port
+ * @return the augmented trace.
+ */
+ private StaticPacketTrace inputFromController(StaticPacketTrace trace, ConnectPoint in) {
+ EthTypeCriterion ethTypeCriterion = (EthTypeCriterion) trace.getInitialPacket()
+ .getCriterion(Criterion.Type.ETH_TYPE);
+ //If the packet is LLDP or BDDP we flood it on all active ports of the switch.
+ if (ethTypeCriterion != null && (ethTypeCriterion.ethType().equals(EtherType.LLDP.ethType())
+ || ethTypeCriterion.ethType().equals(EtherType.BDDP.ethType()))) {
+ //get the active ports
+ List<Port> enabledPorts = deviceService.getPorts(in.deviceId()).stream()
+ .filter(Port::isEnabled)
+ .collect(Collectors.toList());
+ //build an output from each one
+ enabledPorts.forEach(port -> {
+ GroupsInDevice output = new GroupsInDevice(new ConnectPoint(port.element().id(), port.number()),
+ ImmutableList.of(), trace.getInitialPacket());
+ trace.addGroupOutputPath(in.deviceId(), output);
+ });
+ return trace;
+ }
+ return null;
+ }
+
+ private boolean needsSecondTable10Flow(FlowEntry flowEntry, boolean isOfdpaHardware) {
+ return isOfdpaHardware && flowEntry.table().equals(IndexTableId.of(10))
+ && flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID) != null
+ && ((VlanIdCriterion) flowEntry.selector().getCriterion(Criterion.Type.VLAN_VID))
+ .vlanId().equals(VlanId.NONE);
+ }
+
+ /**
+ * Method that finds a flow rule on table 10 that matches the packet and the VLAN of the already
+ * found rule on table 10. This is because OFDPA needs two rules on table 10, first to apply the rule,
+ * second to transition to following table
+ *
+ * @param packet the incoming packet
+ * @param in the input connect point
+ * @param packetVlanIdCriterion the vlan criterion from the packet
+ * @param entryModVlanIdInstruction the entry vlan instruction
+ * @return the second flow entry that matched
+ */
+ private FlowEntry getSecondFlowEntryOnTable10(TrafficSelector packet, ConnectPoint in,
+ VlanIdCriterion packetVlanIdCriterion,
+ ModVlanIdInstruction entryModVlanIdInstruction) {
+ FlowEntry secondVlanFlow = null;
+ //Check the packet has been update from the first rule.
+ if (packetVlanIdCriterion.vlanId().equals(entryModVlanIdInstruction.vlanId())) {
+ //find a rule on the same table that matches the vlan and
+ // also all the other elements of the flow such as input port
+ secondVlanFlow = Lists.newArrayList(flowRuleService.getFlowEntriesByState(in.deviceId(),
+ FlowEntry.FlowEntryState.ADDED).iterator())
+ .stream()
+ .filter(entry -> {
+ return entry.table().equals(IndexTableId.of(10));
+ })
+ .filter(entry -> {
+ VlanIdCriterion criterion = (VlanIdCriterion) entry.selector()
+ .getCriterion(Criterion.Type.VLAN_VID);
+ return criterion != null && match(packet, entry)
+ && criterion.vlanId().equals(entryModVlanIdInstruction.vlanId());
+ }).findFirst().orElse(null);
+
+ }
+ return secondVlanFlow;
+ }
+
+
+ /**
+ * 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())) {
+ //TODO update with parsing with eth MPLS pop Instruction for treating label an bos
+ Instruction ethInstruction = Instructions.popMpls(((EthTypeCriterion) trace.getInitialPacket()
+ .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
+ //FIXME what do we use as L3_Unicast mpls Label ?
+ //translateInstruction(builder, ethInstruction);
+ 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.getFlowEntriesByState(deviceId, FlowEntry.FlowEntryState.ADDED)
+ .iterator()).stream()
+ .filter(f -> ((IndexTableId) f.table()).id() > currentId).min(comparator).orElse(null);
+ }
+
+ private Builder handleDeferredActions(StaticPacketTrace trace, TrafficSelector packet,
+ ConnectPoint in, List<Instruction> deferredInstructions,
+ List<PortNumber> outputPorts, List<Group> groups,
+ List<ConnectPoint> completePath) {
+
+ //Update the packet with the deferred instructions
+ Builder builder = updatePacket(packet, deferredInstructions);
+
+ //Gather any output instructions from the deferred instruction
+ List<Instruction> outputFlowInstruction = deferredInstructions.stream().filter(instruction -> {
+ return instruction.type().equals(Instruction.Type.OUTPUT);
+ }).collect(Collectors.toList());
+
+ //We are considering deferred instructions from flows, there can only be one output.
+ if (outputFlowInstruction.size() > 1) {
+ trace.addResultMessage("More than one flow rule with OUTPUT instruction");
+ log.warn("There cannot be more than one flow entry with OUTPUT instruction for {}", packet);
+ }
+ //If there is one output let's go through that
+ if (outputFlowInstruction.size() == 1) {
+ buildOutputFromDevice(trace, in, builder, outputPorts, (OutputInstruction) outputFlowInstruction.get(0),
+ ImmutableList.of());
+ }
+ //If there is no output let's see if there any deferred instruction point to groups.
+ if (outputFlowInstruction.size() == 0) {
+ getGroupsFromInstructions(trace, groups, deferredInstructions,
+ in.deviceId(), builder, outputPorts, in, completePath);
+ }
+ return builder;
+ }
+
+ /**
+ * 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,
+ Builder builder, List<PortNumber> outputPorts,
+ ConnectPoint in, List<ConnectPoint> completePath) {
+ 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)) {
+ buildOutputFromDevice(trace, in, builder, outputPorts,
+ (OutputInstruction) instruction, ImmutableList.copyOf(groupsForDevice));
+ //clearing the groups because we start from the top.
+ groupsForDevice.clear();
+ } 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);
+ trace.setSuccess(false);
+ break;
+ }
+ if (group.buckets().buckets().size() == 0) {
+ trace.addResultMessage("Group " + group.id() + " has no buckets");
+ trace.setSuccess(false);
+ computePath(completePath, trace, null);
+ break;
+ }
+
+ //Cycle in each of the group's buckets and add them to the groups for this Device.
+ for (GroupBucket bucket : group.buckets().buckets()) {
+
+ //add the group to the traversed groups
+ if (!groupsForDevice.contains(group)) {
+ groupsForDevice.add(group);
+ }
+
+ getGroupsFromInstructions(trace, groupsForDevice, bucket.treatment().allInstructions(),
+ deviceId, builder, outputPorts, in, completePath);
+ }
+ }
+ }
+
+ /**
+ * Check if the output is the input port, if so adds a dop result message, otherwise builds
+ * a possible output from this device.
+ *
+ * @param trace the trace
+ * @param in the input connect point
+ * @param builder the packet builder
+ * @param outputPorts the list of output ports for this device
+ * @param outputInstruction the output instruction
+ * @param groupsForDevice the groups we output from
+ */
+ private void buildOutputFromDevice(StaticPacketTrace trace, ConnectPoint in, Builder builder,
+ List<PortNumber> outputPorts, OutputInstruction outputInstruction,
+ List<Group> groupsForDevice) {
+ ConnectPoint output = new ConnectPoint(in.deviceId(), outputInstruction.port());
+
+ outputPorts.add(outputInstruction.port());
+
+ GroupsInDevice device = new GroupsInDevice(output, groupsForDevice, builder.build());
+ if (trace.getGroupOuputs(output.deviceId()) != null
+ && trace.getGroupOuputs(output.deviceId()).contains(device)) {
+ return;
+ }
+ trace.addGroupOutputPath(in.deviceId(),
+ new GroupsInDevice(output, groupsForDevice, builder.build()));
+ }
+
+ /**
+ * 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 Builder updatePacket(TrafficSelector packet, List<Instruction> instructions) {
+ Builder newSelector = DefaultTrafficSelector.builder();
+ packet.criteria().forEach(newSelector::add);
+ //FIXME optimize
+ for (Instruction instruction : instructions) {
+ newSelector = 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 Builder translateInstruction(Builder newSelector, Instruction instruction) {
+ log.debug("Translating instruction {}", instruction);
+ log.debug("New Selector {}", newSelector.build());
+ //TODO add as required
+ Criterion criterion = null;
+ switch (instruction.type()) {
+ case L2MODIFICATION:
+ L2ModificationInstruction l2Instruction = (L2ModificationInstruction) instruction;
+ switch (l2Instruction.subtype()) {
+ case VLAN_ID:
+ ModVlanIdInstruction vlanIdInstruction =
+ (ModVlanIdInstruction) instruction;
+ VlanId id = vlanIdInstruction.vlanId();
+ criterion = Criteria.matchVlanId(id);
+ break;
+ case VLAN_POP:
+ criterion = Criteria.matchVlanId(VlanId.NONE);
+ break;
+ case MPLS_PUSH:
+ ModMplsHeaderInstruction mplsEthInstruction =
+ (ModMplsHeaderInstruction) instruction;
+ criterion = Criteria.matchEthType(mplsEthInstruction.ethernetType().toShort());
+ break;
+ case MPLS_POP:
+ ModMplsHeaderInstruction mplsPopInstruction =
+ (ModMplsHeaderInstruction) instruction;
+ criterion = Criteria.matchEthType(mplsPopInstruction.ethernetType().toShort());
+
+ //When popping MPLS we remove label and BOS
+ TrafficSelector temporaryPacket = newSelector.build();
+ if (temporaryPacket.getCriterion(Criterion.Type.MPLS_LABEL) != null) {
+ Builder noMplsSelector = DefaultTrafficSelector.builder();
+ temporaryPacket.criteria().stream().filter(c -> {
+ return !c.type().equals(Criterion.Type.MPLS_LABEL) &&
+ !c.type().equals(Criterion.Type.MPLS_BOS);
+ }).forEach(noMplsSelector::add);
+ newSelector = noMplsSelector;
+ }
+
+ break;
+ case MPLS_LABEL:
+ ModMplsLabelInstruction mplsLabelInstruction =
+ (ModMplsLabelInstruction) instruction;
+ criterion = Criteria.matchMplsLabel(mplsLabelInstruction.label());
+ newSelector.matchMplsBos(true);
+ break;
+ case ETH_DST:
+ ModEtherInstruction modEtherDstInstruction =
+ (ModEtherInstruction) instruction;
+ criterion = Criteria.matchEthDst(modEtherDstInstruction.mac());
+ break;
+ case ETH_SRC:
+ ModEtherInstruction modEtherSrcInstruction =
+ (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.getFlowEntriesByState(in.deviceId(), FlowEntry.FlowEntryState.ADDED)
+ .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) {
+ return flowEntry.selector().criteria().stream().allMatch(criterion -> {
+ Criterion.Type type = criterion.type();
+ //If the criterion 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)) {
+ return matchIp(packet, (IPCriterion) criterion);
+ //we check that the packet contains the criterion provided by the flow rule.
+ } else if (type.equals(Criterion.Type.ETH_SRC_MASKED)) {
+ return matchMac(packet, (EthCriterion) criterion, false);
+ } else if (type.equals(Criterion.Type.ETH_DST_MASKED)) {
+ return matchMac(packet, (EthCriterion) criterion, true);
+ } else {
+ return packet.criteria().contains(criterion);
+ }
+ });
+ }
+
+ /**
+ * Checks if the packet has an dst or src IP and if that IP matches the subnet of the ip criterion.
+ *
+ * @param packet the incoming packet
+ * @param criterion the criterion to match
+ * @return true if match
+ */
+ private boolean matchIp(TrafficSelector packet, IPCriterion criterion) {
+ IPCriterion matchCriterion = (IPCriterion) packet.getCriterion(criterion.type());
+ //if the packet does not have an IPv4 or IPv6 criterion we return true
+ if (matchCriterion == null) {
+ return false;
+ }
+ try {
+ log.debug("Checking if {} is under {}", matchCriterion.ip(), criterion.ip());
+ Subnet subnet = Subnet.createInstance(criterion.ip().toString());
+ return subnet.isInSubnet(matchCriterion.ip().address().toInetAddress());
+ } catch (UnknownHostException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Checks if the packet has a dst or src MAC and if that Mac matches the mask of the mac criterion.
+ *
+ * @param packet the incoming packet
+ * @param hitCriterion the criterion to match
+ * @param dst true if we are checking DST MAC
+ * @return true if match
+ */
+ private boolean matchMac(TrafficSelector packet, EthCriterion hitCriterion, boolean dst) {
+ //Packet can have only one EthCriterion
+ EthCriterion matchCriterion;
+ if (dst) {
+ matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
+ return criterion1.type().equals(Criterion.Type.ETH_DST_MASKED) ||
+ criterion1.type().equals(Criterion.Type.ETH_DST);
+ }).findFirst().orElse(null);
+ } else {
+ matchCriterion = (EthCriterion) packet.criteria().stream().filter(criterion1 -> {
+ return criterion1.type().equals(Criterion.Type.ETH_SRC_MASKED) ||
+ criterion1.type().equals(Criterion.Type.ETH_SRC);
+ }).findFirst().orElse(null);
+ }
+ //if the packet does not have an ETH criterion we return true
+ if (matchCriterion == null) {
+ return true;
+ }
+ log.debug("Checking if {} is under {}/{}", matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
+ return compareMac(matchCriterion.mac(), hitCriterion.mac(), hitCriterion.mask());
+ }
+}
diff --git a/apps/t3/app/src/main/java/org/onosproject/t3/impl/TroubleshootUtils.java b/apps/t3/app/src/main/java/org/onosproject/t3/impl/TroubleshootUtils.java
new file mode 100644
index 0000000..57758b3
--- /dev/null
+++ b/apps/t3/app/src/main/java/org/onosproject/t3/impl/TroubleshootUtils.java
@@ -0,0 +1,79 @@
+/*
+ * 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 org.onlab.packet.MacAddress;
+
+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();
+
+ /**
+ * Checks if the Mac Address is inside a range between the min MAC and the mask.
+ * @param macAddress the MAC address to check
+ * @param minAddr the min MAC address
+ * @param maskAddr the mask
+ * @return true if in range, false otherwise.
+ */
+ static boolean compareMac(MacAddress macAddress, MacAddress minAddr, MacAddress maskAddr) {
+ byte[] mac = macAddress.toBytes();
+ byte[] min = minAddr.toBytes();
+ byte[] mask = maskAddr.toBytes();
+ boolean inRange = true;
+
+ int i = 0;
+
+ //if mask is 00 stop
+ while (inRange && i < mask.length && (mask[i] & 0xFF) != 0) {
+ int ibmac = mac[i] & 0xFF;
+ int ibmin = min[i] & 0xFF;
+ int ibmask = mask[i] & 0xFF;
+ if (ibmask == 255) {
+ inRange = ibmac == ibmin;
+ } else if (ibmac < ibmin || ibmac >= ibmask) {
+ inRange = false;
+ break;
+ }
+ i++;
+ }
+
+ return inRange;
+ }
+}
diff --git a/apps/t3/app/src/main/java/org/onosproject/t3/impl/package-info.java b/apps/t3/app/src/main/java/org/onosproject/t3/impl/package-info.java
new file mode 100644
index 0000000..dfcc294
--- /dev/null
+++ b/apps/t3/app/src/main/java/org/onosproject/t3/impl/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Troubleshooting toolkit for trellis fabrics implementation.
+ */
+package org.onosproject.t3.impl;
diff --git a/apps/t3/app/src/main/java/org/onosproject/t3/package-info.java b/apps/t3/app/src/main/java/org/onosproject/t3/package-info.java
new file mode 100644
index 0000000..2dae6e2
--- /dev/null
+++ b/apps/t3/app/src/main/java/org/onosproject/t3/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * 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.
+ */
+
+/**
+ * Troubleshooting toolkit for trellis fabrics.
+ */
+package org.onosproject.t3;
diff --git a/apps/t3/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/t3/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
new file mode 100644
index 0000000..48df042
--- /dev/null
+++ b/apps/t3/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -0,0 +1,47 @@
+<!--
+ ~ 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>
+ <action class="org.onosproject.t3.cli.TroubleshootSimpleTraceCommand"/>
+ <completers>
+ <ref component-id="hostIdCompleter"/>
+ </completers>
+ <optional-completers>
+ <entry key="-et" value-ref="ethTypeCompleter"/>
+ </optional-completers>
+ </command>
+ <command>
+ <action class="org.onosproject.t3.cli.TroubleshootPingAllCommand"/>
+ <optional-completers>
+ <entry key="-et" value-ref="ethTypeCompleter"/>
+ </optional-completers>
+ </command>
+ <command>
+ <action class="org.onosproject.t3.cli.TroubleshootMcastCommand"/>
+ </command>
+ </command-bundle>
+
+ <bean id="hostIdCompleter" class="org.onosproject.cli.net.HostIdCompleter"/>
+ <bean id="ethTypeCompleter" class="org.onosproject.cli.net.EthTypeCompleter"/>
+
+</blueprint>
+
+
diff --git a/apps/t3/app/src/test/java/org/onosproject/t3/impl/T3TestObjects.java b/apps/t3/app/src/test/java/org/onosproject/t3/impl/T3TestObjects.java
new file mode 100644
index 0000000..8501400
--- /dev/null
+++ b/apps/t3/app/src/test/java/org/onosproject/t3/impl/T3TestObjects.java
@@ -0,0 +1,760 @@
+/*
+ * 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.MplsLabel;
+import org.onlab.packet.VlanId;
+import org.onosproject.core.DefaultApplicationId;
+import org.onosproject.core.GroupId;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+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 T3TestObjects() {
+ //banning construction
+ }
+
+ 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;
+ private static final String HOST_DUAL_HOMED_MAC = "00:00:00:00:00:03";
+ private static final String HOST_DUAL_HOMED_VLAN = "None";
+ private static final String HOST_DUAL_HOMED = HOST_DUAL_HOMED_MAC + "/" + HOST_DUAL_HOMED_VLAN;
+
+ //offline device
+ static final DeviceId OFFLINE_DEVICE = DeviceId.deviceId("offlineDevice");
+
+ //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);
+
+ //same output as input
+ static final DeviceId SAME_OUTPUT_FLOW_DEVICE = DeviceId.deviceId("sameOutputDevice");
+
+ private static final TrafficTreatment SAME_OUTPUT_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.portNumber(1)).build();
+ private static final FlowRule SAME_OUTPUT_FLOW = DefaultFlowEntry.builder().forDevice(SAME_OUTPUT_FLOW_DEVICE)
+ .forTable(0)
+ .withPriority(100)
+ .withSelector(SINGLE_FLOW_SELECTOR)
+ .withTreatment(SAME_OUTPUT_FLOW_TREATMENT)
+ .fromApp(new DefaultApplicationId(0, "TestApp"))
+ .makePermanent()
+ .build();
+ static final FlowEntry SAME_OUTPUT_FLOW_ENTRY = new DefaultFlowEntry(SAME_OUTPUT_FLOW);
+
+ static final ConnectPoint SAME_OUTPUT_FLOW_CP = ConnectPoint.deviceConnectPoint(SAME_OUTPUT_FLOW_DEVICE + "/" + 1);
+
+ //ARP
+ static final DeviceId ARP_FLOW_DEVICE = DeviceId.deviceId("ArpDevice");
+
+ private static final TrafficSelector ARP_FLOW_SELECTOR = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.portNumber(1))
+ .matchEthType(EthType.EtherType.ARP.ethType().toShort())
+ .build();
+
+ private static final TrafficTreatment ARP_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.CONTROLLER).build();
+ private static final FlowRule ARP_FLOW = DefaultFlowEntry.builder().forDevice(ARP_FLOW_DEVICE)
+ .forTable(0)
+ .withPriority(100)
+ .withSelector(ARP_FLOW_SELECTOR)
+ .withTreatment(ARP_FLOW_TREATMENT)
+ .fromApp(new DefaultApplicationId(0, "TestApp"))
+ .makePermanent()
+ .build();
+ static final FlowEntry ARP_FLOW_ENTRY = new DefaultFlowEntry(ARP_FLOW);
+
+ static final ConnectPoint ARP_FLOW_CP = ConnectPoint.deviceConnectPoint(ARP_FLOW_DEVICE + "/" + 1);
+
+
+ //Dual Flow Test
+ static final DeviceId DUAL_FLOW_DEVICE = DeviceId.deviceId("DualFlowDevice");
+ 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()
+ .pushMpls()
+ .setMpls(MplsLabel.mplsLabel(100))
+ .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 TrafficTreatment OUTPUT_GROUP_TREATMENT = DefaultTrafficTreatment.builder()
+ .popMpls(EthType.EtherType.IPV4.ethType())
+ .setOutput(PortNumber.portNumber(2)).build();
+
+ private static final GroupBucket BUCKET = DefaultGroupBucket.createSelectGroupBucket(OUTPUT_GROUP_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);
+
+ //HW Double Rule on 10
+
+ static final DeviceId HARDWARE_DEVICE_10 = DeviceId.deviceId("HardwareDevice10");
+
+ static final ConnectPoint HARDWARE_DEVICE_10_IN_CP = ConnectPoint.deviceConnectPoint(HARDWARE_DEVICE_10 + "/" + 1);
+
+ static final ConnectPoint HARDWARE_DEVICE_10_OUT_CP = ConnectPoint.deviceConnectPoint(HARDWARE_DEVICE_10 + "/" + 2);
+
+ private static final TrafficSelector HARDWARE_10_FLOW_SELECTOR = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.portNumber(1))
+ .matchIPSrc(IpPrefix.valueOf("127.0.0.1/32"))
+ .matchIPDst(IpPrefix.valueOf("127.0.0.2/32"))
+ .matchVlanId(VlanId.NONE)
+ .build();
+
+ private static final TrafficTreatment HARDWARE_10_TRANSITION_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+ .setVlanId(VlanId.vlanId("10"))
+ .transition(20)
+ .build();
+
+ private static final FlowRule HARDWARE_DEVICE_10_FLOW = DefaultFlowEntry.builder().forDevice(HARDWARE_DEVICE_10)
+ .forTable(10)
+ .withPriority(100)
+ .withSelector(HARDWARE_10_FLOW_SELECTOR)
+ .withTreatment(HARDWARE_10_TRANSITION_FLOW_TREATMENT)
+ .fromApp(new DefaultApplicationId(0, "TestApp"))
+ .makePermanent()
+ .build();
+
+ static final FlowEntry HARDWARE_10_FLOW_ENTRY = new DefaultFlowEntry(HARDWARE_DEVICE_10_FLOW);
+
+ private static final TrafficSelector HARDWARE_10_SECOND_SELECTOR = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.portNumber(1))
+ .matchVlanId(VlanId.vlanId("10"))
+ .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_10_SECOND_FLOW = DefaultFlowEntry.builder().forDevice(HARDWARE_DEVICE_10)
+ .forTable(10)
+ .withPriority(100)
+ .withSelector(HARDWARE_10_SECOND_SELECTOR)
+ .withTreatment(HARDWARE_10_TRANSITION_FLOW_TREATMENT)
+ .fromApp(new DefaultApplicationId(0, "TestApp"))
+ .makePermanent()
+ .build();
+
+ static final FlowEntry HARDWARE_10_SECOND_FLOW_ENTRY = new DefaultFlowEntry(HARDWARE_10_SECOND_FLOW);
+
+ private static final FlowRule HARDWARE_10_OUTPUT_FLOW = DefaultFlowEntry.builder().forDevice(HARDWARE_DEVICE_10)
+ .forTable(20)
+ .withPriority(100)
+ .withSelector(SINGLE_FLOW_SELECTOR)
+ .withTreatment(OUTPUT_FLOW_TREATMENT)
+ .fromApp(new DefaultApplicationId(0, "TestApp"))
+ .makePermanent()
+ .build();
+
+ static final FlowEntry HARDWARE_10_OUTPUT_FLOW_ENTRY = new DefaultFlowEntry(HARDWARE_10_OUTPUT_FLOW);
+
+ //Dual Links
+ // - (1) Device 1 (2-3) - (1-4) Device 2 (2-3) - (1-2) Device 3 (3) -
+ static final DeviceId DUAL_LINK_1 = DeviceId.deviceId("DualLink1");
+ static final DeviceId DUAL_LINK_2 = DeviceId.deviceId("DualLink2");
+ static final DeviceId DUAL_LINK_3 = DeviceId.deviceId("DualLink3");
+
+ static final ConnectPoint DUAL_LINK_1_CP_1_IN = ConnectPoint.deviceConnectPoint(DUAL_LINK_1 + "/" + 1);
+ static final ConnectPoint DUAL_LINK_1_CP_2_OUT = ConnectPoint.deviceConnectPoint(DUAL_LINK_1 + "/" + 2);
+ static final ConnectPoint DUAL_LINK_1_CP_3_OUT = ConnectPoint.deviceConnectPoint(DUAL_LINK_1 + "/" + 3);
+ static final ConnectPoint DUAL_LINK_2_CP_1_IN = ConnectPoint.deviceConnectPoint(DUAL_LINK_2 + "/" + 1);
+ static final ConnectPoint DUAL_LINK_2_CP_4_IN = ConnectPoint.deviceConnectPoint(DUAL_LINK_2 + "/" + 4);
+ static final ConnectPoint DUAL_LINK_2_CP_2_OUT = ConnectPoint.deviceConnectPoint(DUAL_LINK_2 + "/" + 2);
+ static final ConnectPoint DUAL_LINK_2_CP_3_OUT = ConnectPoint.deviceConnectPoint(DUAL_LINK_2 + "/" + 3);
+ static final ConnectPoint DUAL_LINK_3_CP_1_IN = ConnectPoint.deviceConnectPoint(DUAL_LINK_3 + "/" + 1);
+ static final ConnectPoint DUAL_LINK_3_CP_2_IN = ConnectPoint.deviceConnectPoint(DUAL_LINK_3 + "/" + 2);
+ static final ConnectPoint DUAL_LINK_3_CP_3_OUT = ConnectPoint.deviceConnectPoint(DUAL_LINK_3 + "/" + 3);
+
+ //match on port 1 and point to group for device 1 and 2
+ private static final TrafficTreatment DUAL_LINK_1_GROUP_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+ .pushMpls()
+ .setMpls(MplsLabel.mplsLabel(100))
+ .group(GROUP_ID)
+ .build();
+ private static final FlowRule DUAL_LINK_1_GROUP_FLOW = DefaultFlowEntry.builder().forDevice(DUAL_LINK_1)
+ .forTable(0)
+ .withPriority(100)
+ .withSelector(SINGLE_FLOW_SELECTOR)
+ .withTreatment(DUAL_LINK_1_GROUP_FLOW_TREATMENT)
+ .fromApp(new DefaultApplicationId(0, "TestApp"))
+ .makePermanent()
+ .build();
+ static final FlowEntry DUAL_LINK_1_GROUP_FLOW_ENTRY = new DefaultFlowEntry(DUAL_LINK_1_GROUP_FLOW);
+
+ //Match on port 4 and point to group for device 2
+ private static final TrafficSelector DUAL_LINK_2_FLOW_SELECTOR = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.portNumber(4))
+ .matchIPSrc(IpPrefix.valueOf("127.0.0.1/32"))
+ .matchIPDst(IpPrefix.valueOf("127.0.0.2/32"))
+ .build();
+
+ private static final FlowRule DUAL_LINK_2_GROUP_FLOW = DefaultFlowEntry.builder().forDevice(DUAL_LINK_2)
+ .forTable(0)
+ .withPriority(100)
+ .withSelector(DUAL_LINK_2_FLOW_SELECTOR)
+ .withTreatment(DUAL_LINK_1_GROUP_FLOW_TREATMENT)
+ .fromApp(new DefaultApplicationId(0, "TestApp"))
+ .makePermanent()
+ .build();
+ static final FlowEntry DUAL_LINK_2_GROUP_FLOW_ENTRY = new DefaultFlowEntry(DUAL_LINK_2_GROUP_FLOW);
+
+ //Flows for device 3 to ouput on port 3
+ private static final TrafficTreatment DUAL_LINK_1_OUTPUT_TREATMENT = DefaultTrafficTreatment.builder()
+ .popMpls(EthType.EtherType.IPV4.ethType())
+ .setOutput(PortNumber.portNumber(3)).build();
+
+ private static final TrafficSelector DUAL_LINK_3_FLOW_SELECTOR_1 = 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 FlowRule DUAL_LINK_3_FLOW_1 = DefaultFlowEntry.builder().forDevice(DUAL_LINK_3)
+ .forTable(0)
+ .withPriority(100)
+ .withSelector(DUAL_LINK_3_FLOW_SELECTOR_1)
+ .withTreatment(DUAL_LINK_1_OUTPUT_TREATMENT)
+ .fromApp(new DefaultApplicationId(0, "TestApp"))
+ .makePermanent()
+ .build();
+
+ static final FlowEntry DUAL_LINK_3_FLOW_ENTRY = new DefaultFlowEntry(DUAL_LINK_3_FLOW_1);
+
+ private static final TrafficSelector DUAL_LINK_3_FLOW_SELECTOR_2 = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.portNumber(2))
+ .matchIPSrc(IpPrefix.valueOf("127.0.0.1/32"))
+ .matchIPDst(IpPrefix.valueOf("127.0.0.2/32"))
+ .build();
+ private static final FlowRule DUAL_LINK_3_FLOW_2 = DefaultFlowEntry.builder().forDevice(DUAL_LINK_3)
+ .forTable(0)
+ .withPriority(100)
+ .withSelector(DUAL_LINK_3_FLOW_SELECTOR_2)
+ .withTreatment(DUAL_LINK_1_OUTPUT_TREATMENT)
+ .fromApp(new DefaultApplicationId(0, "TestApp"))
+ .makePermanent()
+ .build();
+
+ static final FlowEntry DUAL_LINK_3_FLOW_ENTRY_2 = new DefaultFlowEntry(DUAL_LINK_3_FLOW_2);
+
+ //Group with two buckets to output on port 2 and 3 of device 1 and 2
+
+ private static final GroupBucket BUCKET_2_DUAL =
+ DefaultGroupBucket.createSelectGroupBucket(DUAL_LINK_1_OUTPUT_TREATMENT);
+
+ private static final GroupBuckets BUCKETS_DUAL = new GroupBuckets(ImmutableList.of(BUCKET, BUCKET_2_DUAL));
+
+ static final Group DUAL_LINK_GROUP = new DefaultGroup(GROUP_ID, DUAL_LINK_1, Group.Type.SELECT, BUCKETS_DUAL);
+
+ //Clear Deferred
+ static final DeviceId DEFERRED_1 = DeviceId.deviceId("Deferred");
+
+ static final ConnectPoint DEFERRED_CP_1_IN = ConnectPoint.deviceConnectPoint(DEFERRED_1 + "/" + 1);
+ static final ConnectPoint DEFERRED_CP_2_OUT = ConnectPoint.deviceConnectPoint(DEFERRED_1 + "/" + 2);
+
+ //match on port 1 and apply deferred actions
+ private static final TrafficTreatment DEFERRED_1_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+ .transition(10)
+ .deferred()
+ .pushMpls()
+ .setMpls(MplsLabel.mplsLabel(100))
+ .build();
+ private static final FlowRule DEFERRED_FLOW = DefaultFlowEntry.builder().forDevice(DEFERRED_1)
+ .forTable(0)
+ .withPriority(100)
+ .withSelector(SINGLE_FLOW_SELECTOR)
+ .withTreatment(DEFERRED_1_FLOW_TREATMENT)
+ .fromApp(new DefaultApplicationId(0, "TestApp"))
+ .makePermanent()
+ .build();
+ static final FlowEntry DEFERRED_FLOW_ENTRY = new DefaultFlowEntry(DEFERRED_FLOW);
+
+ //Multicast Flow and Group Test
+ static final DeviceId MULTICAST_GROUP_FLOW_DEVICE = DeviceId.deviceId("MulticastGroupFlowDevice");
+
+ private static final TrafficSelector MULTICAST_FLOW_SELECTOR = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.portNumber(1))
+ .matchIPDst(IpPrefix.valueOf("224.0.0.1/32"))
+ .matchEthDst(MacAddress.valueOf("01:00:5e:00:00:01"))
+ .build();
+
+ private static final FlowRule MULTICAST_GROUP_FLOW =
+ DefaultFlowEntry.builder().forDevice(MULTICAST_GROUP_FLOW_DEVICE)
+ .forTable(0)
+ .withPriority(100)
+ .withSelector(MULTICAST_FLOW_SELECTOR)
+ .withTreatment(GROUP_FLOW_TREATMENT)
+ .fromApp(new DefaultApplicationId(0, "TestApp"))
+ .makePermanent()
+ .build();
+
+ static final FlowEntry MULTICAST_GROUP_FLOW_ENTRY = new DefaultFlowEntry(MULTICAST_GROUP_FLOW);
+
+ static final Group MULTICAST_GROUP = new DefaultGroup(GROUP_ID, MULTICAST_GROUP_FLOW_DEVICE,
+ Group.Type.SELECT, BUCKETS_MULTIPLE);
+
+ static final ConnectPoint MULTICAST_IN_CP = ConnectPoint.deviceConnectPoint(MULTICAST_GROUP_FLOW_DEVICE + "/" + 1);
+
+ static final ConnectPoint MULTICAST_OUT_CP = ConnectPoint.deviceConnectPoint(MULTICAST_GROUP_FLOW_DEVICE + "/" + 3);
+
+ static final ConnectPoint MULTICAST_OUT_CP_2 =
+ ConnectPoint.deviceConnectPoint(MULTICAST_GROUP_FLOW_DEVICE + "/" + 2);
+
+ //match on port 1, clear deferred actions and output
+ private static final TrafficTreatment DEFERRED_CLEAR_1_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+ .wipeDeferred()
+ .setOutput(PortNumber.portNumber(2))
+ .build();
+ private static final FlowRule DEFERRED_CLEAR_FLOW = DefaultFlowEntry.builder().forDevice(DEFERRED_1)
+ .forTable(10)
+ .withPriority(100)
+ .withSelector(SINGLE_FLOW_SELECTOR)
+ .withTreatment(DEFERRED_CLEAR_1_FLOW_TREATMENT)
+ .fromApp(new DefaultApplicationId(0, "TestApp"))
+ .makePermanent()
+ .build();
+ static final FlowEntry DEFERRED_CLEAR_FLOW_ENTRY = new DefaultFlowEntry(DEFERRED_CLEAR_FLOW);
+
+ //LLDP
+
+ static final DeviceId LLDP_FLOW_DEVICE = DeviceId.deviceId("LldpDevice");
+
+ private static final TrafficSelector LLDP_FLOW_SELECTOR = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.portNumber(1))
+ .matchEthType(EthType.EtherType.LLDP.ethType().toShort())
+ .build();
+
+ private static final TrafficTreatment LLDP_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.CONTROLLER).build();
+ private static final FlowRule LLDP_FLOW = DefaultFlowEntry.builder().forDevice(LLDP_FLOW_DEVICE)
+ .forTable(0)
+ .withPriority(100)
+ .withSelector(LLDP_FLOW_SELECTOR)
+ .withTreatment(LLDP_FLOW_TREATMENT)
+ .fromApp(new DefaultApplicationId(0, "TestApp"))
+ .makePermanent()
+ .build();
+ static final FlowEntry LLDP_FLOW_ENTRY = new DefaultFlowEntry(LLDP_FLOW);
+
+ static final ConnectPoint LLDP_FLOW_CP = ConnectPoint.deviceConnectPoint(LLDP_FLOW_DEVICE + "/" + 1);
+
+ //No Buckets
+
+ static final DeviceId NO_BUCKET_DEVICE = DeviceId.deviceId("nobucket");
+
+ private static final TrafficSelector NO_BUCKET_SELECTOR = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.portNumber(1))
+ .build();
+
+ private static final GroupId NO_BUCKET_GROUP_ID = GroupId.valueOf(1);
+
+ private static final TrafficTreatment NO_BUCKET_TREATMENT = DefaultTrafficTreatment.builder()
+ .group(NO_BUCKET_GROUP_ID)
+ .build();
+ private static final FlowRule NO_BUCKET_FLOW = DefaultFlowEntry.builder().forDevice(NO_BUCKET_DEVICE)
+ .forTable(0)
+ .withPriority(100)
+ .withSelector(NO_BUCKET_SELECTOR)
+ .withTreatment(NO_BUCKET_TREATMENT)
+ .fromApp(new DefaultApplicationId(0, "TestApp"))
+ .makePermanent()
+ .build();
+ static final FlowEntry NO_BUCKET_ENTRY = new DefaultFlowEntry(NO_BUCKET_FLOW);
+
+ private static final GroupBuckets NO_BUCKETS = new GroupBuckets(ImmutableList.of());
+
+ static final Group NO_BUCKET_GROUP =
+ new DefaultGroup(NO_BUCKET_GROUP_ID, NO_BUCKET_DEVICE, Group.Type.SELECT, NO_BUCKETS);
+
+ static final ConnectPoint NO_BUCKET_CP = ConnectPoint.deviceConnectPoint(NO_BUCKET_DEVICE + "/" + 1);
+
+ //Dual Homing
+
+ static final DeviceId DUAL_HOME_DEVICE_1 = DeviceId.deviceId("DualHomeDevice1");
+
+ static final DeviceId DUAL_HOME_DEVICE_2 = DeviceId.deviceId("DualHomeDevice2");
+
+ static final DeviceId DUAL_HOME_DEVICE_3 = DeviceId.deviceId("DualHomeDevice3");
+
+ static final ConnectPoint DUAL_HOME_CP_1_1 = ConnectPoint.deviceConnectPoint(DUAL_HOME_DEVICE_1 + "/" + 1);
+ static final ConnectPoint DUAL_HOME_CP_1_2 = ConnectPoint.deviceConnectPoint(DUAL_HOME_DEVICE_1 + "/" + 2);
+ static final ConnectPoint DUAL_HOME_CP_1_3 = ConnectPoint.deviceConnectPoint(DUAL_HOME_DEVICE_1 + "/" + 3);
+
+ static final ConnectPoint DUAL_HOME_CP_2_1 = ConnectPoint.deviceConnectPoint(DUAL_HOME_DEVICE_2 + "/" + 1);
+ static final ConnectPoint DUAL_HOME_CP_2_2 = ConnectPoint.deviceConnectPoint(DUAL_HOME_DEVICE_2 + "/" + 2);
+
+ static final ConnectPoint DUAL_HOME_CP_3_1 = ConnectPoint.deviceConnectPoint(DUAL_HOME_DEVICE_3 + "/" + 1);
+ static final ConnectPoint DUAL_HOME_CP_3_2 = ConnectPoint.deviceConnectPoint(DUAL_HOME_DEVICE_3 + "/" + 2);
+
+
+ private static final TrafficSelector DUAL_HOME_INPUT_FLOW_SELECTOR = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.portNumber(1))
+ .build();
+
+ private static final GroupId DUAL_HOME_GROUP_ID = GroupId.valueOf(1);
+
+ private static final TrafficTreatment DUAL_HOME_GROUP_TREATMENT = DefaultTrafficTreatment.builder()
+ .group(DUAL_HOME_GROUP_ID)
+ .build();
+ private static final FlowRule DUAL_HOME_INPUT_FLOW = DefaultFlowEntry.builder().forDevice(DUAL_HOME_DEVICE_1)
+ .forTable(0)
+ .withPriority(100)
+ .withSelector(DUAL_HOME_INPUT_FLOW_SELECTOR)
+ .withTreatment(DUAL_HOME_GROUP_TREATMENT)
+ .fromApp(new DefaultApplicationId(0, "TestApp"))
+ .makePermanent()
+ .build();
+ static final FlowEntry DUAL_HOME_FLOW_ENTRY = new DefaultFlowEntry(DUAL_HOME_INPUT_FLOW);
+
+ private static final TrafficTreatment DUAL_HOME_OUTPUT_1_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.portNumber(2)).build();
+ private static final TrafficTreatment DUAL_HOME_OUTPUT_2_FLOW_TREATMENT = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.portNumber(3)).build();
+
+ private static final GroupBucket BUCKET_1_DUAL_HOMED =
+ DefaultGroupBucket.createSelectGroupBucket(DUAL_HOME_OUTPUT_1_FLOW_TREATMENT);
+
+ private static final GroupBucket BUCKET_2_DUAL_HOMED =
+ DefaultGroupBucket.createSelectGroupBucket(DUAL_HOME_OUTPUT_2_FLOW_TREATMENT);
+
+ private static final GroupBuckets BUCKETS_MULTIPLE_DUAL = new GroupBuckets(ImmutableList.of(BUCKET_1_DUAL_HOMED,
+ BUCKET_2_DUAL_HOMED));
+
+ static final Group DUAL_HOME_GROUP = new DefaultGroup(DUAL_HOME_GROUP_ID, DUAL_HOME_DEVICE_1,
+ Group.Type.SELECT, BUCKETS_MULTIPLE_DUAL);
+
+ private static final TrafficTreatment DUAL_HOME_TREATMENT = DefaultTrafficTreatment.builder()
+ .setOutput(PortNumber.portNumber("2"))
+ .build();
+ private static final FlowRule DUAL_HOME_OUT_FLOW = DefaultFlowEntry.builder().forDevice(DUAL_HOME_DEVICE_2)
+ .forTable(0)
+ .withPriority(100)
+ .withSelector(DUAL_HOME_INPUT_FLOW_SELECTOR)
+ .withTreatment(DUAL_HOME_TREATMENT)
+ .fromApp(new DefaultApplicationId(0, "TestApp"))
+ .makePermanent()
+ .build();
+ static final FlowEntry DUAL_HOME_OUT_FLOW_ENTRY = new DefaultFlowEntry(DUAL_HOME_OUT_FLOW);
+
+ //helper elements
+
+ static final String MASTER_1 = "Master1";
+
+ static final Host H1 = new DefaultHost(ProviderId.NONE, HostId.hostId(HOST_ONE), MacAddress.valueOf(100),
+ VlanId.NONE, new HostLocation(SINGLE_FLOW_DEVICE, PortNumber.portNumber(2), 0),
+ ImmutableSet.of(IpAddress.valueOf("127.0.0.2")));
+
+ 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 Host DUAL_HOME_H = new DefaultHost(ProviderId.NONE, HostId.hostId(HOST_DUAL_HOMED),
+ MacAddress.valueOf(HOST_DUAL_HOMED_MAC),
+ VlanId.NONE, ImmutableSet.of(new HostLocation(DUAL_HOME_DEVICE_2, PortNumber.portNumber(2), 0),
+ new HostLocation(DUAL_HOME_DEVICE_3, PortNumber.portNumber(2), 0)),
+ ImmutableSet.of(IpAddress.valueOf("127.0.0.4")), true, DefaultAnnotations.builder().build());
+
+ static final TrafficSelector PACKET_OK = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.portNumber(1))
+ .matchEthType(EthType.EtherType.IPV4.ethType().toShort())
+ .matchIPSrc(IpPrefix.valueOf("127.0.0.1/32"))
+ .matchIPDst(IpPrefix.valueOf("127.0.0.2/32"))
+ .matchVlanId(VlanId.NONE)
+ .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_ARP = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.portNumber(1))
+ .matchIPDst(IpPrefix.valueOf("255.255.255.255/32"))
+ .matchEthType(EthType.EtherType.ARP.ethType().toShort())
+ .build();
+
+ static final TrafficSelector PACKET_LLDP = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.portNumber(1))
+ .matchEthType(EthType.EtherType.LLDP.ethType().toShort())
+ .build();
+
+ static final TrafficSelector PACKET_OK_MULTICAST = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.portNumber(1))
+ .matchEthType(EthType.EtherType.IPV4.ethType().toShort())
+ .matchEthDst(MacAddress.valueOf("01:00:5e:00:00:01"))
+ .matchIPDst(IpPrefix.valueOf("224.0.0.1/32"))
+ .build();
+
+ static final TrafficSelector PACKET_DUAL_HOME = DefaultTrafficSelector.builder()
+ .matchInPort(PortNumber.portNumber(1))
+ .matchEthType(EthType.EtherType.IPV4.ethType().toShort())
+ .matchIPSrc(IpPrefix.valueOf("127.0.0.1/32"))
+ .matchIPDst(IpPrefix.valueOf("127.0.0.4/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/app/src/test/java/org/onosproject/t3/impl/TroubleshootManagerTest.java b/apps/t3/app/src/test/java/org/onosproject/t3/impl/TroubleshootManagerTest.java
new file mode 100644
index 0000000..8ac76c8
--- /dev/null
+++ b/apps/t3/app/src/test/java/org/onosproject/t3/impl/TroubleshootManagerTest.java
@@ -0,0 +1,669 @@
+/*
+ * 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.ChassisId;
+import org.onlab.packet.EthType;
+import org.onlab.packet.IpAddress;
+import org.onlab.packet.MacAddress;
+import org.onlab.packet.VlanId;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.mastership.MastershipServiceAdapter;
+import org.onosproject.net.ConnectPoint;
+import org.onosproject.net.DefaultAnnotations;
+import org.onosproject.net.DefaultDevice;
+import org.onosproject.net.DefaultLink;
+import org.onosproject.net.DefaultPort;
+import org.onosproject.net.Device;
+import org.onosproject.net.DeviceId;
+import org.onosproject.net.Host;
+import org.onosproject.net.Link;
+import org.onosproject.net.Port;
+import org.onosproject.net.PortNumber;
+import org.onosproject.net.device.DeviceServiceAdapter;
+import org.onosproject.net.driver.DefaultDriver;
+import org.onosproject.net.driver.Driver;
+import org.onosproject.net.driver.DriverServiceAdapter;
+import org.onosproject.net.edge.EdgePortServiceAdapter;
+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.routeservice.ResolvedRoute;
+import org.onosproject.routeservice.RouteServiceAdapter;
+import org.onosproject.t3.api.StaticPacketTrace;
+import org.slf4j.Logger;
+
+import java.util.HashMap;
+import java.util.Optional;
+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.net.Device.Type.SWITCH;
+import static org.onosproject.t3.impl.T3TestObjects.*;
+import static org.onosproject.t3.impl.TroubleshootManager.PACKET_TO_CONTROLLER;
+import static org.slf4j.LoggerFactory.getLogger;
+
+/**
+ * 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();
+ mngr.deviceService = new TestDeviceService();
+ mngr.mastershipService = new TestMastershipService();
+ mngr.edgePortService = new TestEdgePortService();
+ mngr.routeService = new TestRouteService();
+
+ 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);
+ assertNotNull("Device Service should not be null", mngr.deviceService);
+ }
+
+ /**
+ * Tests failure on non existent device.
+ */
+ @Test(expected = NullPointerException.class)
+ public void nonExistentDevice() {
+ StaticPacketTrace traceFail = mngr.trace(PACKET_OK, ConnectPoint.deviceConnectPoint("nonexistent" + "/1"));
+ }
+
+ /**
+ * Tests failure on offline device.
+ */
+ @Test
+ public void offlineDevice() {
+ StaticPacketTrace traceFail = mngr.trace(PACKET_OK, ConnectPoint.deviceConnectPoint(OFFLINE_DEVICE + "/1"));
+ assertNotNull("Trace should not be null", traceFail);
+ assertNull("Trace should have 0 output", traceFail.getGroupOuputs(SINGLE_FLOW_DEVICE));
+ }
+
+ /**
+ * Tests failure on same output.
+ */
+ @Test
+ public void sameOutput() {
+ StaticPacketTrace traceFail = mngr.trace(PACKET_OK, SAME_OUTPUT_FLOW_CP);
+ assertNotNull("Trace should not be null", traceFail);
+ assertTrue("Trace should be unsuccessful",
+ traceFail.resultMessage().contains("is same as initial input"));
+ log.info("trace {}", traceFail.resultMessage());
+ }
+
+ /**
+ * Tests ARP to controller.
+ */
+ @Test
+ public void arpToController() {
+ StaticPacketTrace traceSuccess = mngr.trace(PACKET_ARP, ARP_FLOW_CP);
+ assertNotNull("Trace should not be null", traceSuccess);
+ assertTrue("Trace should be successful",
+ traceSuccess.resultMessage().contains(PACKET_TO_CONTROLLER));
+ assertTrue("Master should be Master1",
+ traceSuccess.resultMessage().contains(MASTER_1));
+ ConnectPoint connectPoint = traceSuccess.getGroupOuputs(ARP_FLOW_DEVICE).get(0).getOutput();
+ assertEquals("Packet Should go to CONTROLLER", PortNumber.CONTROLLER, connectPoint.port());
+ assertNull("VlanId should be null", traceSuccess.getGroupOuputs(ARP_FLOW_DEVICE).get(0)
+ .getFinalPacket().getCriterion(Criterion.Type.VLAN_VID));
+ log.info("trace {}", traceSuccess.resultMessage());
+ }
+
+
+ /**
+ * 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 group with no buckets.
+ */
+ @Test
+ public void noBucketsTest() throws Exception {
+
+ StaticPacketTrace traceFail = mngr.trace(PACKET_OK, NO_BUCKET_CP);
+ assertNotNull("Trace should not be null", traceFail);
+ assertTrue("Trace should be unsuccessful",
+ traceFail.resultMessage().contains("no buckets"));
+ 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, 1);
+
+ testFailure(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, 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
+ testFailure(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, 1);
+
+ assertTrue("Wrong Output Group", traceSuccess.getGroupOuputs(GROUP_FLOW_DEVICE)
+ .get(0).getGroups().contains(GROUP));
+ assertEquals("Packet should not have MPLS Label", EthType.EtherType.IPV4.ethType(),
+ ((EthTypeCriterion) traceSuccess.getGroupOuputs(GROUP_FLOW_DEVICE)
+ .get(0).getFinalPacket().getCriterion(Criterion.Type.ETH_TYPE)).ethType());
+ assertNull("Packet should not have MPLS Label", traceSuccess.getGroupOuputs(GROUP_FLOW_DEVICE)
+ .get(0).getFinalPacket().getCriterion(Criterion.Type.MPLS_LABEL));
+ assertNull("Packet should not have MPLS Label", traceSuccess.getGroupOuputs(GROUP_FLOW_DEVICE)
+ .get(0).getFinalPacket().getCriterion(Criterion.Type.MPLS_BOS));
+
+ }
+
+ /**
+ * 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, 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, 1);
+
+ log.info("{}", traceSuccess);
+
+ 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, 1);
+
+ assertEquals("wrong ETH type", EthType.EtherType.IPV4.ethType(),
+ ((EthTypeCriterion) traceSuccess.getGroupOuputs(HARDWARE_DEVICE).get(0).getFinalPacket()
+ .getCriterion(Criterion.Type.ETH_TYPE)).ethType());
+
+ }
+
+ /**
+ * Test that HW has two rules on table 10 for untagged packets.
+ */
+ @Test
+ public void hardwareTable10Test() throws Exception {
+
+ StaticPacketTrace traceSuccess = testSuccess(PACKET_OK, HARDWARE_DEVICE_10_IN_CP,
+ HARDWARE_DEVICE_10, HARDWARE_DEVICE_10_OUT_CP, 1, 1);
+
+ assertTrue("Second flow rule is absent", traceSuccess.getFlowsForDevice(HARDWARE_DEVICE_10)
+ .contains(HARDWARE_10_SECOND_FLOW_ENTRY));
+
+ }
+
+ /**
+ * Test dual links between 3 topology elements.
+ */
+ @Test
+ public void dualLinks() throws Exception {
+
+ StaticPacketTrace traceSuccess = testSuccess(PACKET_OK, DUAL_LINK_1_CP_1_IN,
+ DUAL_LINK_3, DUAL_LINK_3_CP_3_OUT, 4, 1);
+
+ //TODO tests
+
+ }
+
+ /**
+ * Test proper clear deferred behaviour.
+ */
+ @Test
+ public void clearDeferred() throws Exception {
+
+ StaticPacketTrace traceSuccess = testSuccess(PACKET_OK, DEFERRED_CP_1_IN,
+ DEFERRED_1, DEFERRED_CP_2_OUT, 1, 1);
+
+ assertNull("MPLS should have been not applied due to clear deferred", traceSuccess
+ .getGroupOuputs(DEFERRED_1).get(0).getFinalPacket().getCriterion(Criterion.Type.MPLS_LABEL));
+
+ }
+
+
+ /**
+ * Test LLDP output to controller.
+ */
+ @Test
+ public void lldpToController() {
+ StaticPacketTrace traceSuccess = mngr.trace(PACKET_LLDP, LLDP_FLOW_CP);
+ assertNotNull("Trace should not be null", traceSuccess);
+ assertTrue("Trace should be successful",
+ traceSuccess.resultMessage().contains("Packet goes to the controller"));
+ assertTrue("Master should be Master1",
+ traceSuccess.resultMessage().contains(MASTER_1));
+ ConnectPoint connectPoint = traceSuccess.getGroupOuputs(LLDP_FLOW_DEVICE).get(0).getOutput();
+ assertEquals("Packet Should go to CONTROLLER", PortNumber.CONTROLLER, connectPoint.port());
+ log.info("trace {}", traceSuccess.resultMessage());
+ }
+
+ /**
+ * Test multicast in single device.
+ */
+ @Test
+ public void multicastTest() throws Exception {
+
+ StaticPacketTrace traceSuccess = mngr.trace(PACKET_OK_MULTICAST, MULTICAST_IN_CP);
+
+ log.info("trace {}", traceSuccess);
+
+ log.info("trace {}", traceSuccess.resultMessage());
+
+ assertNotNull("trace should not be null", traceSuccess);
+ assertEquals("Trace should have " + 2 + " output", 2,
+ traceSuccess.getGroupOuputs(MULTICAST_GROUP_FLOW_DEVICE).size());
+ assertEquals("Trace should only have " + 2 + "output", 2,
+ traceSuccess.getCompletePaths().size());
+ assertTrue("Trace should be successful",
+ traceSuccess.resultMessage().contains("reached output"));
+ assertEquals("Incorrect Output CP", MULTICAST_OUT_CP_2,
+ traceSuccess.getGroupOuputs(MULTICAST_GROUP_FLOW_DEVICE).get(0).getOutput());
+ assertEquals("Incorrect Output CP", MULTICAST_OUT_CP,
+ traceSuccess.getGroupOuputs(MULTICAST_GROUP_FLOW_DEVICE).get(1).getOutput());
+
+ }
+
+ /**
+ * Tests dual homing of a host.
+ */
+ @Test
+ public void dualhomedTest() throws Exception {
+ StaticPacketTrace traceSuccess = mngr.trace(PACKET_DUAL_HOME, DUAL_HOME_CP_1_1);
+
+ assertNotNull("trace should not be null", traceSuccess);
+ assertTrue("Should have 2 output paths", traceSuccess.getCompletePaths().size() == 2);
+ assertTrue("Should contain proper path", traceSuccess.getCompletePaths()
+ .contains(ImmutableList.of(DUAL_HOME_CP_1_1, DUAL_HOME_CP_1_2, DUAL_HOME_CP_2_1, DUAL_HOME_CP_2_2)));
+ assertTrue("Should contain proper path", traceSuccess.getCompletePaths()
+ .contains(ImmutableList.of(DUAL_HOME_CP_1_1, DUAL_HOME_CP_1_3, DUAL_HOME_CP_3_1, DUAL_HOME_CP_3_2)));
+
+ }
+
+
+ private StaticPacketTrace testSuccess(TrafficSelector packet, ConnectPoint in, DeviceId deviceId, ConnectPoint out,
+ int paths, int outputs) {
+ 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 " + outputs + " output", outputs,
+ 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 testFailure(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> getFlowEntriesByState(DeviceId deviceId, FlowEntry.FlowEntryState state) {
+ 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);
+ } else if (deviceId.equals(SAME_OUTPUT_FLOW_DEVICE)) {
+ return ImmutableList.of(SAME_OUTPUT_FLOW_ENTRY);
+ } else if (deviceId.equals(ARP_FLOW_DEVICE)) {
+ return ImmutableList.of(ARP_FLOW_ENTRY);
+ } else if (deviceId.equals(DUAL_LINK_1)) {
+ return ImmutableList.of(DUAL_LINK_1_GROUP_FLOW_ENTRY);
+ } else if (deviceId.equals(DUAL_LINK_2)) {
+ return ImmutableList.of(DUAL_LINK_1_GROUP_FLOW_ENTRY, DUAL_LINK_2_GROUP_FLOW_ENTRY);
+ } else if (deviceId.equals(DUAL_LINK_3)) {
+ return ImmutableList.of(DUAL_LINK_3_FLOW_ENTRY, DUAL_LINK_3_FLOW_ENTRY_2);
+ } else if (deviceId.equals(DEFERRED_1)) {
+ return ImmutableList.of(DEFERRED_FLOW_ENTRY, DEFERRED_CLEAR_FLOW_ENTRY);
+ } else if (deviceId.equals(HARDWARE_DEVICE_10)) {
+ return ImmutableList.of(HARDWARE_10_FLOW_ENTRY, HARDWARE_10_SECOND_FLOW_ENTRY,
+ HARDWARE_10_OUTPUT_FLOW_ENTRY);
+ } else if (deviceId.equals(LLDP_FLOW_DEVICE)) {
+ return ImmutableList.of(LLDP_FLOW_ENTRY);
+ } else if (deviceId.equals(MULTICAST_GROUP_FLOW_DEVICE)) {
+ return ImmutableList.of(MULTICAST_GROUP_FLOW_ENTRY);
+ } else if (deviceId.equals(NO_BUCKET_DEVICE)) {
+ return ImmutableList.of(NO_BUCKET_ENTRY);
+ } else if (deviceId.equals(DUAL_HOME_DEVICE_1)) {
+ return ImmutableList.of(DUAL_HOME_FLOW_ENTRY);
+ } else if (deviceId.equals(DUAL_HOME_DEVICE_2) || deviceId.equals(DUAL_HOME_DEVICE_3)) {
+ return ImmutableList.of(DUAL_HOME_OUT_FLOW_ENTRY);
+ }
+ return ImmutableList.of();
+ }
+ }
+
+ private class TestDriverService extends DriverServiceAdapter {
+ @Override
+ public Driver getDriver(DeviceId deviceId) {
+ if (deviceId.equals(HARDWARE_DEVICE) || deviceId.equals(HARDWARE_DEVICE_10)) {
+ 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);
+ } else if (deviceId.equals(DUAL_LINK_1) || deviceId.equals(DUAL_LINK_2)) {
+ return ImmutableList.of(DUAL_LINK_GROUP);
+ } else if (deviceId.equals(MULTICAST_GROUP_FLOW_DEVICE)) {
+ return ImmutableList.of(MULTICAST_GROUP);
+ } else if (deviceId.equals(NO_BUCKET_DEVICE)) {
+ return ImmutableList.of(NO_BUCKET_GROUP);
+ } else if (deviceId.equals(DUAL_HOME_DEVICE_1)) {
+ return ImmutableList.of(DUAL_HOME_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);
+ } else if (connectPoint.equals(DUAL_LINK_1_CP_2_OUT) || connectPoint.equals(DUAL_LINK_1_CP_3_OUT) ||
+ connectPoint.equals(DUAL_LINK_2_CP_2_OUT) || connectPoint.equals(DUAL_LINK_2_CP_3_OUT)) {
+ return ImmutableSet.of();
+ }
+ if (connectPoint.equals(SINGLE_FLOW_OUT_CP) ||
+ connectPoint.equals(DUAL_FLOW_OUT_CP) ||
+ connectPoint.equals(GROUP_FLOW_OUT_CP) ||
+ connectPoint.equals(HARDWARE_DEVICE_OUT_CP) ||
+ connectPoint.equals(HARDWARE_DEVICE_10_OUT_CP) ||
+ connectPoint.equals(DEFERRED_CP_2_OUT) ||
+ connectPoint.equals(DUAL_LINK_3_CP_3_OUT)) {
+ return ImmutableSet.of(H1);
+ }
+ if (connectPoint.equals(DUAL_HOME_CP_2_2) || connectPoint.equals(DUAL_HOME_CP_3_2)) {
+ return ImmutableSet.of(DUAL_HOME_H);
+ }
+ return ImmutableSet.of();
+ }
+
+ @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);
+ } else if (mac.equals(DUAL_HOME_H.mac())) {
+ return ImmutableSet.of(DUAL_HOME_H);
+ }
+ 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);
+ } else if ((DUAL_HOME_H.ipAddresses().contains(ip))) {
+ return ImmutableSet.of(DUAL_HOME_H);
+ }
+ 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());
+ } else if (connectPoint.equals(DUAL_LINK_1_CP_2_OUT)) {
+ return ImmutableSet.of(DefaultLink.builder()
+ .providerId(ProviderId.NONE)
+ .type(Link.Type.DIRECT)
+ .src(DUAL_LINK_1_CP_2_OUT)
+ .dst(DUAL_LINK_2_CP_1_IN)
+ .build());
+ } else if (connectPoint.equals(DUAL_LINK_1_CP_3_OUT)) {
+ return ImmutableSet.of(DefaultLink.builder()
+ .providerId(ProviderId.NONE)
+ .type(Link.Type.DIRECT)
+ .src(DUAL_LINK_1_CP_3_OUT)
+ .dst(DUAL_LINK_2_CP_4_IN)
+ .build());
+ } else if (connectPoint.equals(DUAL_LINK_2_CP_2_OUT)) {
+ return ImmutableSet.of(DefaultLink.builder()
+ .providerId(ProviderId.NONE)
+ .type(Link.Type.DIRECT)
+ .src(DUAL_LINK_2_CP_2_OUT)
+ .dst(DUAL_LINK_3_CP_1_IN)
+ .build());
+ } else if (connectPoint.equals(DUAL_LINK_2_CP_3_OUT)) {
+ return ImmutableSet.of(DefaultLink.builder()
+ .providerId(ProviderId.NONE)
+ .type(Link.Type.DIRECT)
+ .src(DUAL_LINK_2_CP_3_OUT)
+ .dst(DUAL_LINK_3_CP_2_IN)
+ .build());
+ } else if (connectPoint.equals(DUAL_HOME_CP_1_2)) {
+ return ImmutableSet.of(DefaultLink.builder()
+ .providerId(ProviderId.NONE)
+ .type(Link.Type.DIRECT)
+ .src(DUAL_HOME_CP_1_2)
+ .dst(DUAL_HOME_CP_2_1)
+ .build());
+ } else if (connectPoint.equals(DUAL_HOME_CP_1_3)) {
+ return ImmutableSet.of(DefaultLink.builder()
+ .providerId(ProviderId.NONE)
+ .type(Link.Type.DIRECT)
+ .src(DUAL_HOME_CP_1_3)
+ .dst(DUAL_HOME_CP_3_1)
+ .build());
+ }
+ return ImmutableSet.of();
+ }
+ }
+
+ private class TestDeviceService extends DeviceServiceAdapter {
+ @Override
+ public Device getDevice(DeviceId deviceId) {
+ if (deviceId.equals(DeviceId.deviceId("nonexistent"))) {
+ return null;
+ }
+ return new DefaultDevice(ProviderId.NONE, DeviceId.deviceId("test"), SWITCH,
+ "test", "test", "test", "test", new ChassisId(),
+ DefaultAnnotations.builder().set("foo", "bar").build());
+ }
+
+ @Override
+ public Port getPort(ConnectPoint cp) {
+ return new DefaultPort(null, cp.port(), true, DefaultAnnotations.builder().build());
+ }
+
+ @Override
+ public boolean isAvailable(DeviceId deviceId) {
+ return !deviceId.equals(OFFLINE_DEVICE);
+ }
+ }
+
+ private class TestEdgePortService extends EdgePortServiceAdapter {
+
+ @Override
+ public boolean isEdgePoint(ConnectPoint point) {
+ return point.equals(MULTICAST_OUT_CP) ||
+ point.equals(MULTICAST_OUT_CP_2);
+ }
+ }
+
+ private class TestRouteService extends RouteServiceAdapter {
+ @Override
+ public Optional<ResolvedRoute> longestPrefixLookup(IpAddress ip) {
+ return Optional.empty();
+ }
+ }
+
+ private class TestMastershipService extends MastershipServiceAdapter {
+ @Override
+ public NodeId getMasterFor(DeviceId deviceId) {
+ return NodeId.nodeId(MASTER_1);
+ }
+ }
+}
\ No newline at end of file
diff --git a/apps/t3/app/src/test/java/org/onosproject/t3/impl/TroubleshootUtilsTest.java b/apps/t3/app/src/test/java/org/onosproject/t3/impl/TroubleshootUtilsTest.java
new file mode 100644
index 0000000..c8195c0
--- /dev/null
+++ b/apps/t3/app/src/test/java/org/onosproject/t3/impl/TroubleshootUtilsTest.java
@@ -0,0 +1,51 @@
+/*
+ * 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 org.junit.Test;
+import org.onlab.packet.MacAddress;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+/**
+ * Test for util methods of the Trellis Troubleshoot Toolkit.
+ */
+public class TroubleshootUtilsTest {
+
+ @Test
+ public void testMacMatch() {
+
+ MacAddress min = MacAddress.valueOf("01:00:5E:00:00:00");
+ MacAddress mask = MacAddress.valueOf("FF:FF:FF:80:00:00");
+ MacAddress macOk = MacAddress.valueOf("01:00:5E:00:00:01");
+
+ assertTrue("False on correct match", TroubleshootUtils.compareMac(macOk, min, mask));
+
+ MacAddress macWrong = MacAddress.valueOf("01:00:5E:80:00:00");
+
+ assertFalse("True on false match", TroubleshootUtils.compareMac(macWrong, min, mask));
+
+ MacAddress maskEmpty = MacAddress.valueOf("00:00:00:00:00:00");
+
+ assertTrue("False on empty Mask", TroubleshootUtils.compareMac(macOk, min, maskEmpty));
+
+ MacAddress maskFull = MacAddress.valueOf("FF:FF:FF:FF:FF:FF");
+
+ assertFalse("True on full Mask", TroubleshootUtils.compareMac(macOk, min, maskFull));
+
+ }
+}
\ No newline at end of file