[ONOS-7752] check north-south reachability for troubleshoot purpose

Change-Id: I973047c0fcd4fb241ad010fbbef9d016510b8deb
diff --git a/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/InstancePortService.java b/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/InstancePortService.java
index f5ebe11..7dcbd32 100644
--- a/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/InstancePortService.java
+++ b/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/InstancePortService.java
@@ -66,4 +66,12 @@
      * @return set of instance ports; empty list if no port exists
      */
     Set<InstancePort> instancePorts(String osNetId);
+
+    /**
+     * Returns the floating IP with the supplied instance port.
+     *
+     * @param osPortId openstack port id
+     * @return openstack floating IP
+     */
+    IpAddress floatingIp(String osPortId);
 }
diff --git a/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/OpenstackNetworkService.java b/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/OpenstackNetworkService.java
index 71fc440..71d9bb1 100644
--- a/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/OpenstackNetworkService.java
+++ b/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/OpenstackNetworkService.java
@@ -156,7 +156,7 @@
     String networkType(String netId);
 
     /**
-     * Returns gateway ip address upplied port ID.
+     * Returns gateway ip address with supplied port ID.
      *
      * @param portId openstack port id
      * @return gateway ip address
diff --git a/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/OpenstackRouterService.java b/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/OpenstackRouterService.java
index a1a8d30..18bf967 100644
--- a/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/OpenstackRouterService.java
+++ b/apps/openstacknetworking/api/src/main/java/org/onosproject/openstacknetworking/api/OpenstackRouterService.java
@@ -68,6 +68,7 @@
 
     /**
      * Returns the floating IP with the supplied floating IP ID.
+     *
      * @param floatingIpId floating ip id
      * @return openstack floating ip
      */
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/InstancePortManager.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/InstancePortManager.java
index 3f204fb..645a86d 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/InstancePortManager.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/InstancePortManager.java
@@ -45,6 +45,7 @@
 import org.onosproject.openstacknetworking.api.InstancePortService;
 import org.onosproject.openstacknetworking.api.InstancePortStore;
 import org.onosproject.openstacknetworking.api.InstancePortStoreDelegate;
+import org.onosproject.openstacknetworking.api.OpenstackRouterService;
 import org.slf4j.Logger;
 
 import java.util.Objects;
@@ -101,6 +102,9 @@
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
     protected HostService hostService;
 
+    @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
+    protected OpenstackRouterService routerService;
+
     private final InstancePortStoreDelegate
                             delegate = new InternalInstancePortStoreDelegate();
     private final InternalHostListener
@@ -224,6 +228,17 @@
         return ImmutableSet.copyOf(ports);
     }
 
+    @Override
+    public IpAddress floatingIp(String osPortId) {
+        checkNotNull(osPortId, ERR_NULL_INSTANCE_PORT_ID);
+
+        return routerService.floatingIps().stream()
+                .filter(fip -> osPortId.equals(fip.getPortId()))
+                .filter(fip -> fip.getFloatingIpAddress() != null)
+                .map(fip -> IpAddress.valueOf(fip.getFloatingIpAddress()))
+                .findFirst().orElse(null);
+    }
+
     private boolean isInstancePortInUse(String portId) {
         // TODO add checking logic
         return false;
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingIcmpHandler.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingIcmpHandler.java
index b8eb339..764cefe 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingIcmpHandler.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/impl/OpenstackRoutingIcmpHandler.java
@@ -250,7 +250,7 @@
         return osRouter.getExternalGatewayInfo();
     }
 
-    private void handleEchoReply(IPv4 ipPacket, ICMP icmp) {
+    private boolean handleEchoReply(IPv4 ipPacket, ICMP icmp) {
         String icmpInfoKey = icmpInfoKey(icmp,
                 IPv4.fromIPv4Address(ipPacket.getDestinationAddress()),
                 IPv4.fromIPv4Address(ipPacket.getSourceAddress()));
@@ -259,8 +259,10 @@
         if (icmpInfoMap.get(icmpInfoKey) != null) {
             processReplyFromExternal(ipPacket, icmpInfoMap.get(icmpInfoKey).value());
             icmpInfoMap.remove(icmpInfoKey);
+            return true;
         } else {
-            log.warn("No ICMP Info for ICMP packet");
+            log.debug("No ICMP Info for ICMP packet");
+            return false;
         }
     }
 
@@ -472,8 +474,9 @@
                     context.block();
                     break;
                 case TYPE_ECHO_REPLY:
-                    handleEchoReply(ipPacket, icmp);
-                    context.block();
+                    if (handleEchoReply(ipPacket, icmp)) {
+                        context.block();
+                    }
                     break;
                 default:
                     break;
diff --git a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/InstancePortServiceAdapter.java b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/InstancePortServiceAdapter.java
index 885c7d8..b0efdfa 100644
--- a/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/InstancePortServiceAdapter.java
+++ b/apps/openstacknetworking/app/src/test/java/org/onosproject/openstacknetworking/impl/InstancePortServiceAdapter.java
@@ -54,6 +54,11 @@
     }
 
     @Override
+    public IpAddress floatingIp(String osPortId) {
+        return null;
+    }
+
+    @Override
     public void addListener(InstancePortListener listener) {
 
     }
diff --git a/apps/openstacktroubleshoot/api/pom.xml b/apps/openstacktroubleshoot/api/pom.xml
index 2b5b420..d79df17 100644
--- a/apps/openstacktroubleshoot/api/pom.xml
+++ b/apps/openstacktroubleshoot/api/pom.xml
@@ -35,5 +35,11 @@
             <groupId>org.onosproject</groupId>
             <artifactId>onos-api</artifactId>
         </dependency>
+
+        <dependency>
+            <groupId>org.onosproject</groupId>
+            <artifactId>onos-apps-openstacknetworking-api</artifactId>
+            <version>${project.version}</version>
+        </dependency>
     </dependencies>
 </project>
diff --git a/apps/openstacktroubleshoot/api/src/main/java/org/onosproject/openstacktroubleshoot/api/OpenstackTroubleshootService.java b/apps/openstacktroubleshoot/api/src/main/java/org/onosproject/openstacktroubleshoot/api/OpenstackTroubleshootService.java
index 272e257..fc1e735 100644
--- a/apps/openstacktroubleshoot/api/src/main/java/org/onosproject/openstacktroubleshoot/api/OpenstackTroubleshootService.java
+++ b/apps/openstacktroubleshoot/api/src/main/java/org/onosproject/openstacktroubleshoot/api/OpenstackTroubleshootService.java
@@ -15,22 +15,28 @@
  */
 package org.onosproject.openstacktroubleshoot.api;
 
-import org.onlab.packet.IpAddress;
 import org.onosproject.openstacknetworking.api.InstancePort;
 
-import java.util.Map;
-
 /**
  * Openstack troubleshoot interface.
  */
 public interface OpenstackTroubleshootService {
 
     /**
-     * Checks all east-west VMs' connectivity.
-     *
-     * @return reachability map
+     * Troubleshoot traffic direction.
      */
-    Map<String, Reachability> probeEastWestBulk();
+    enum Direction {
+
+        /**
+         * Signifies that EAST_WEST troubleshoot case.
+         */
+        EAST_WEST,
+
+        /**
+         * Signifies that NORTH_SOUTH troubleshoot case.
+         */
+        NORTH_SOUTH,
+    }
 
     /**
      * Checks a single VM-to-Vm connectivity.
@@ -43,18 +49,10 @@
                                InstancePort dstInstancePort);
 
     /**
-     * Checks all north-south router to VMs' connectivity.
-     *
-     * @return reachability map
-     */
-    Map<String, Reachability> probeNorthSouth();
-
-    /**
      * Checks a single router-to-VM connectivity.
      *
-     * @param netId network ID
-     * @param ip destination VM IP address
+     * @param dstInstancePort destination instance port
      * @return reachability
      */
-    Reachability probeNorthSouth(String netId, IpAddress ip);
+    Reachability probeNorthSouth(InstancePort dstInstancePort);
 }
diff --git a/apps/openstacktroubleshoot/app/src/main/java/org/onosproject/openstacktroubleshoot/cli/ActiveFloatingIpCompleter.java b/apps/openstacktroubleshoot/app/src/main/java/org/onosproject/openstacktroubleshoot/cli/ActiveFloatingIpCompleter.java
new file mode 100644
index 0000000..4687209
--- /dev/null
+++ b/apps/openstacktroubleshoot/app/src/main/java/org/onosproject/openstacktroubleshoot/cli/ActiveFloatingIpCompleter.java
@@ -0,0 +1,49 @@
+/*
+ * 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.openstacktroubleshoot.cli;
+
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.stream.Collectors;
+
+/**
+ * Active VM floating IP address completer.
+ */
+public class ActiveFloatingIpCompleter implements Completer {
+
+    @Override
+    public int complete(String buffer, int cursor, List<String> candidates) {
+        StringsCompleter delegate = new StringsCompleter();
+        InstancePortService service = AbstractShellCommand.get(InstancePortService.class);
+        Set<String> set = service.instancePorts().stream()
+                .filter(p -> p.state() == InstancePort.State.ACTIVE)
+                .filter(p -> service.floatingIp(p.portId()) != null)
+                .map(p -> service.floatingIp(p.portId()).toString())
+                .collect(Collectors.toSet());
+
+        SortedSet<String> strings = delegate.getStrings();
+        strings.addAll(set);
+
+        return delegate.complete(buffer, cursor, candidates);
+    }
+}
diff --git a/apps/openstacktroubleshoot/app/src/main/java/org/onosproject/openstacktroubleshoot/cli/OpenstackEastWestProbeCommand.java b/apps/openstacktroubleshoot/app/src/main/java/org/onosproject/openstacktroubleshoot/cli/OpenstackEastWestProbeCommand.java
index 696cb11..0b832da 100644
--- a/apps/openstacktroubleshoot/app/src/main/java/org/onosproject/openstacktroubleshoot/cli/OpenstackEastWestProbeCommand.java
+++ b/apps/openstacktroubleshoot/app/src/main/java/org/onosproject/openstacktroubleshoot/cli/OpenstackEastWestProbeCommand.java
@@ -26,11 +26,12 @@
 import org.onosproject.openstacktroubleshoot.api.OpenstackTroubleshootService;
 import org.onosproject.openstacktroubleshoot.api.Reachability;
 
-import java.util.Map;
 import java.util.Optional;
 import java.util.Set;
 import java.util.stream.Collectors;
 
+import static org.onosproject.openstacknetworking.api.InstancePort.State.ACTIVE;
+
 /**
  * Checks the east-west VMs connectivity.
  */
@@ -57,8 +58,7 @@
         OpenstackTroubleshootService tsService =
                 get(OpenstackTroubleshootService.class);
 
-        InstancePortService instPortService =
-                get(InstancePortService.class);
+        InstancePortService instPortService = get(InstancePortService.class);
 
         if (tsService == null) {
             error("Failed to troubleshoot openstack networking.");
@@ -72,15 +72,28 @@
 
         if (isAll) {
             printHeader();
-            Map<String, Reachability> map = tsService.probeEastWestBulk();
-            map.values().forEach(this::printReachability);
+            // send ICMP PACKET_OUT to all connect VMs whose instance port state is ACTIVE
+            Set<InstancePort> activePorts = instPortService.instancePorts().stream()
+                    .filter(p -> p.state() == ACTIVE)
+                    .collect(Collectors.toSet());
+
+            activePorts.forEach(srcPort ->
+                    activePorts.forEach(dstPort ->
+                            printReachability(tsService.probeEastWest(srcPort, dstPort))
+                    )
+            );
         } else {
             if (vmIps.length > 2) {
                 print("Too many VM IPs. The number of IP should be limited to 2.");
                 return;
             }
 
-            IpAddress srcIp = IpAddress.valueOf(vmIps[0]);
+            IpAddress srcIp = getIpAddress(vmIps[0]);
+
+            if (srcIp == null) {
+                return;
+            }
+
             InstancePort srcPort = instPort(instPortService, srcIp);
 
             if (srcPort == null) {
@@ -91,7 +104,13 @@
             final Set<IpAddress> dstIps = Sets.newConcurrentHashSet();
 
             if (vmIps.length == 2) {
-                dstIps.add(IpAddress.valueOf(vmIps[1]));
+                IpAddress dstIp = getIpAddress(vmIps[1]);
+
+                if (dstIp == null) {
+                    return;
+                }
+
+                dstIps.add(dstIp);
             }
 
             if (vmIps.length == 1) {
@@ -130,4 +149,13 @@
         String result = r.isReachable() ? REACHABLE : UNREACHABLE;
         print(FORMAT, r.srcIp().toString(), ARROW, r.dstIp().toString(), result);
     }
+
+    private IpAddress getIpAddress(String ipString) {
+        try {
+            return IpAddress.valueOf(vmIps[0]);
+        } catch (IllegalArgumentException e) {
+            error("Invalid IP address string.");
+            return null;
+        }
+    }
 }
diff --git a/apps/openstacktroubleshoot/app/src/main/java/org/onosproject/openstacktroubleshoot/cli/OpenstackNorthSouthProbeCommand.java b/apps/openstacktroubleshoot/app/src/main/java/org/onosproject/openstacktroubleshoot/cli/OpenstackNorthSouthProbeCommand.java
new file mode 100644
index 0000000..77c24bb
--- /dev/null
+++ b/apps/openstacktroubleshoot/app/src/main/java/org/onosproject/openstacktroubleshoot/cli/OpenstackNorthSouthProbeCommand.java
@@ -0,0 +1,123 @@
+/*
+ * 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.openstacktroubleshoot.cli;
+
+import com.google.common.collect.Sets;
+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.cluster.ClusterService;
+import org.onosproject.cluster.NodeId;
+import org.onosproject.mastership.MastershipService;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeService;
+import org.onosproject.openstacktroubleshoot.api.OpenstackTroubleshootService;
+import org.onosproject.openstacktroubleshoot.api.Reachability;
+
+import java.util.Set;
+
+import static org.onosproject.openstacknetworking.api.InstancePort.State.ACTIVE;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
+
+/**
+ * Checks the north-south VM connectivity.
+ */
+@Command(scope = "onos", name = "openstack-check-north-south",
+        description = "Checks the north-south VMs connectivity")
+public class OpenstackNorthSouthProbeCommand extends AbstractShellCommand {
+
+    private static final String REACHABLE = "Reachable :)";
+    private static final String UNREACHABLE = "Unreachable :(";
+    private static final String ARROW = "->";
+
+    private static final String FORMAT = "%-20s%-5s%-20s%-20s";
+
+    @Option(name = "-a", aliases = "--all", description = "Apply this command to all VMs",
+            required = false, multiValued = false)
+    private boolean isAll = false;
+
+    @Argument(index = 0, name = "vmIps", description = "VMs' IP addresses",
+            required = false, multiValued = true)
+    private String[] vmIps = null;
+
+    @Override
+    protected void execute() {
+        OpenstackTroubleshootService tsService = get(OpenstackTroubleshootService.class);
+        InstancePortService instPortService = get(InstancePortService.class);
+        OpenstackNodeService osNodeService = get(OpenstackNodeService.class);
+        MastershipService mastershipService = get(MastershipService.class);
+        ClusterService clusterService = get(ClusterService.class);
+
+        if (tsService == null || osNodeService == null ||
+                instPortService == null || mastershipService == null) {
+            error("Failed to troubleshoot openstack networking.");
+            return;
+        }
+
+        if ((!isAll && vmIps == null) || (isAll && vmIps != null)) {
+            print("Please specify one of VM IP address or -a option.");
+            return;
+        }
+
+        NodeId localNodeId = clusterService.getLocalNode().id();
+
+        for (OpenstackNode gw : osNodeService.completeNodes(GATEWAY)) {
+            if (!localNodeId.equals(mastershipService.getMasterFor(gw.intgBridge()))) {
+                error("Current node is not the master for all gateway nodes. Please enforce mastership first!");
+                return;
+            }
+        }
+
+        if (isAll) {
+            printHeader();
+
+            // send ICMP PACKET_OUT to all connect VMs whose instance port state is ACTIVE
+            instPortService.instancePorts().stream()
+                    .filter(p -> p.state() == ACTIVE)
+                    .filter(p -> instPortService.floatingIp(p.portId()) != null)
+                    .forEach(port -> printReachability(tsService.probeNorthSouth(port)));
+        } else {
+
+            final Set<InstancePort> ports = Sets.newConcurrentHashSet();
+
+            for (String ip : vmIps) {
+                instPortService.instancePorts().stream()
+                        .filter(p -> p.state().equals(InstancePort.State.ACTIVE))
+                        .filter(p -> instPortService.floatingIp(p.portId()) != null)
+                        .filter(p -> ip.equals(instPortService.floatingIp(p.portId()).toString()))
+                        .forEach(ports::add);
+            }
+
+            printHeader();
+            ports.forEach(port -> printReachability(tsService.probeNorthSouth(port)));
+        }
+    }
+
+    private void printHeader() {
+        print(FORMAT, "Source IP", "", "Destination IP", "Reachability");
+    }
+
+    private void printReachability(Reachability r) {
+        if (r == null) {
+            return;
+        }
+        String result = r.isReachable() ? REACHABLE : UNREACHABLE;
+        print(FORMAT, r.srcIp().toString(), ARROW, r.dstIp().toString(), result);
+    }
+}
diff --git a/apps/openstacktroubleshoot/app/src/main/java/org/onosproject/openstacktroubleshoot/impl/OpenstackTroubleshootManager.java b/apps/openstacktroubleshoot/app/src/main/java/org/onosproject/openstacktroubleshoot/impl/OpenstackTroubleshootManager.java
index 2354076..13e9579 100644
--- a/apps/openstacktroubleshoot/app/src/main/java/org/onosproject/openstacktroubleshoot/impl/OpenstackTroubleshootManager.java
+++ b/apps/openstacktroubleshoot/app/src/main/java/org/onosproject/openstacktroubleshoot/impl/OpenstackTroubleshootManager.java
@@ -29,6 +29,7 @@
 import org.onlab.packet.IPv4;
 import org.onlab.packet.IpAddress;
 import org.onlab.packet.IpPrefix;
+import org.onlab.packet.MacAddress;
 import org.onlab.util.KryoNamespace;
 import org.onosproject.cluster.ClusterService;
 import org.onosproject.cluster.LeadershipService;
@@ -36,6 +37,7 @@
 import org.onosproject.core.ApplicationId;
 import org.onosproject.core.CoreService;
 import org.onosproject.mastership.MastershipService;
+import org.onosproject.net.DeviceId;
 import org.onosproject.net.flow.DefaultTrafficSelector;
 import org.onosproject.net.flow.DefaultTrafficTreatment;
 import org.onosproject.net.flow.FlowEntry;
@@ -53,6 +55,7 @@
 import org.onosproject.openstacknetworking.api.InstancePortService;
 import org.onosproject.openstacknetworking.api.OpenstackFlowRuleService;
 import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
+import org.onosproject.openstacknode.api.OpenstackNode;
 import org.onosproject.openstacknode.api.OpenstackNodeService;
 import org.onosproject.openstacktroubleshoot.api.OpenstackTroubleshootService;
 import org.onosproject.openstacktroubleshoot.api.Reachability;
@@ -65,12 +68,11 @@
 import org.slf4j.LoggerFactory;
 
 import java.nio.ByteBuffer;
-import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.concurrent.ExecutorService;
 import java.util.function.BooleanSupplier;
 import java.util.function.Predicate;
-import java.util.stream.Collectors;
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static java.util.concurrent.Executors.newSingleThreadScheduledExecutor;
@@ -83,13 +85,15 @@
 import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_DST;
 import static org.onosproject.net.flow.criteria.Criterion.Type.IPV4_SRC;
 import static org.onosproject.openstacknetworking.api.Constants.ACL_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_EXTERNAL_ROUTER_MAC;
 import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC;
 import static org.onosproject.openstacknetworking.api.Constants.FORWARDING_TABLE;
+import static org.onosproject.openstacknetworking.api.Constants.GW_COMMON_TABLE;
 import static org.onosproject.openstacknetworking.api.Constants.OPENSTACK_NETWORKING_APP_ID;
 import static org.onosproject.openstacknetworking.api.Constants.PRIORITY_ICMP_PROBE_RULE;
 import static org.onosproject.openstacknetworking.api.Constants.VTAG_TABLE;
 import static org.onosproject.openstacknetworking.api.InstancePort.State.ACTIVE;
-import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.COMPUTE;
+import static org.onosproject.openstacknode.api.OpenstackNode.NodeType.GATEWAY;
 import static org.onosproject.openstacktroubleshoot.util.OpenstackTroubleshootUtil.getSegId;
 
 /**
@@ -111,6 +115,8 @@
     private static final int PREFIX_LENGTH = 32;
     private static final int ICMP_PROCESSOR_PRIORITY = 99;
 
+    private static final MacAddress LOCAL_MAC = MacAddress.valueOf("11:22:33:44:55:66");
+
     private static final String ICMP_COUNTER_NAME = "icmp-id-counter";
 
     @Reference(cardinality = ReferenceCardinality.MANDATORY_UNARY)
@@ -195,68 +201,6 @@
     }
 
     @Override
-    public Map<String, Reachability> probeEastWestBulk() {
-
-        // install flow rules to enforce ICMP_REQUEST to be tagged and direct to ACL table
-        eventExecutor.execute(() -> setAllVidTagRule(true));
-
-        // install flow rules to enforce forwarding ICMP_REPLY to controller
-        eventExecutor.execute(() -> setAllIcmpReplyRule(true));
-
-        icmpReachabilityMap.clear();
-
-        // send ICMP PACKET_OUT to all connect VMs whose instance port state is ACTIVE
-        Set<InstancePort> activePorts = instancePortService.instancePorts().stream()
-                                                .filter(p -> p.state() == ACTIVE)
-                                                .collect(Collectors.toSet());
-
-        timeoutSupplier(activePorts.size(), VID_TAG_RULE_INSTALL_TIMEOUT_MS, this::checkAllVidTagRules);
-        timeoutSupplier(activePorts.size(), ICMP_RULE_INSTALL_TIMEOUT_MS, this::checkAllIcmpReplyRules);
-
-        for (InstancePort srcPort : activePorts) {
-
-            // we only let the master of the switch where the source host
-            // is attached to send out ICMP request packet
-            if (!mastershipService.isLocalMaster(srcPort.deviceId())) {
-                continue;
-            }
-
-            for (InstancePort dstPort : activePorts) {
-                // if the source and destination ports are identical, we do
-                // not probe the reachability
-                if (srcPort.equals(dstPort)) {
-                    continue;
-                }
-
-                // if the two ports are located in different types of networks,
-                // we do not probe the reachability
-                if (!osNetworkService.networkType(srcPort.networkId())
-                        .equals(osNetworkService.networkType(dstPort.networkId()))) {
-                    continue;
-                }
-
-                sendIcmpEchoRequest(srcPort, dstPort);
-            }
-        }
-
-        long count = icmpReachabilityMap.asJavaMap().values().stream()
-                                        .filter(r -> !r.isReachable()).count();
-
-        BooleanSupplier checkReachability = () -> icmpReachabilityMap.asJavaMap()
-                .values().stream().allMatch(Reachability::isReachable);
-
-        timeoutSupplier(count, ICMP_REPLY_TIMEOUT_MS, checkReachability);
-
-        // uninstall ICMP_REQUEST VID tagging rules
-        eventExecutor.execute(() -> setAllVidTagRule(false));
-
-        // uninstall ICMP_REPLY enforcing rules
-        eventExecutor.execute(() -> setAllIcmpReplyRule(false));
-
-        return icmpReachabilityMap.asJavaMap();
-    }
-
-    @Override
     public Reachability probeEastWest(InstancePort srcPort, InstancePort dstPort) {
 
         Reachability.Builder rBuilder = DefaultReachability.builder()
@@ -270,20 +214,28 @@
         }  else {
             if (srcPort.state() == ACTIVE && dstPort.state() == ACTIVE) {
 
+                // if the two ports are located in different types of networks,
+                // we immediately return unreachable state
+                if (!osNetworkService.networkType(srcPort.networkId())
+                        .equals(osNetworkService.networkType(dstPort.networkId()))) {
+                    rBuilder.isReachable(false);
+                    return rBuilder.build();
+                }
+
                 // install flow rules to enforce ICMP_REQUEST to be tagged and direct to ACL table
                 eventExecutor.execute(() -> setVidTagRule(srcPort, true));
 
                 // install flow rules to enforce forwarding ICMP_REPLY to controller
-                eventExecutor.execute(() -> setIcmpReplyRule(srcPort, true));
+                eventExecutor.execute(() -> setEastWestIcmpReplyRule(srcPort, true));
 
                 timeoutPredicate(1, VID_TAG_RULE_INSTALL_TIMEOUT_MS,
                         this::checkVidTagRule, srcPort.ipAddress().toString());
 
                 timeoutPredicate(1, ICMP_RULE_INSTALL_TIMEOUT_MS,
-                        this::checkIcmpReplyRule, srcPort.ipAddress().toString());
+                        this::checkEastWestIcmpReplyRule, srcPort.ipAddress().toString());
 
                 // send out ICMP ECHO request
-                sendIcmpEchoRequest(srcPort, dstPort);
+                sendIcmpEchoRequest(srcPort, dstPort, null, Direction.EAST_WEST);
 
                 BooleanSupplier checkReachability = () -> icmpReachabilityMap.asJavaMap()
                         .values().stream().allMatch(Reachability::isReachable);
@@ -294,7 +246,7 @@
                 eventExecutor.execute(() -> setVidTagRule(srcPort, false));
 
                 // uninstall ICMP_REPLY enforcing rules
-                eventExecutor.execute(() -> setIcmpReplyRule(srcPort, false));
+                eventExecutor.execute(() -> setEastWestIcmpReplyRule(srcPort, false));
 
                 return icmpReachabilityMap.asJavaMap()
                                           .get(String.valueOf(icmpIdCounter.get()));
@@ -307,50 +259,48 @@
     }
 
     @Override
-    public Map<String, Reachability> probeNorthSouth() {
-        // TODO: require implementation
-        return null;
-    }
+    public Reachability probeNorthSouth(InstancePort port) {
+        Optional<OpenstackNode> gw = osNodeService.completeNodes(GATEWAY).stream().findFirst();
 
-    @Override
-    public Reachability probeNorthSouth(String netId, IpAddress ip) {
-        // TODO: require implementation
-        return null;
-    }
-
-    /**
-     * Checks whether all of ICMP reply rules are added or not.
-     *
-     * @return true if all of ICMP reply rules are added, false otherwise
-     */
-    private boolean checkAllIcmpReplyRules() {
-
-        Set<InstancePort> activePorts = instancePortService.instancePorts().stream()
-                .filter(p -> p.state() == ACTIVE).collect(Collectors.toSet());
-
-        for (InstancePort port : activePorts) {
-            if (!checkIcmpReplyRule(port.ipAddress().toString())) {
-                return false;
-            }
+        if (!gw.isPresent()) {
+            log.warn("Gateway is not available to troubleshoot north-south traffic.");
+            return null;
         }
 
-        return true;
+        // install flow rules to enforce forwarding ICMP_REPLY to controller
+        eventExecutor.execute(() -> setNorthSouthIcmpReplyRule(port, gw.get(), true));
+
+        timeoutPredicate(1, ICMP_RULE_INSTALL_TIMEOUT_MS,
+                this::checkNorthSouthIcmpReplyRule, port.ipAddress().toString());
+
+        // send out ICMP ECHO request
+        sendIcmpEchoRequest(null, port, gw.get(), Direction.NORTH_SOUTH);
+
+        BooleanSupplier checkReachability = () -> icmpReachabilityMap.asJavaMap()
+                .values().stream().allMatch(Reachability::isReachable);
+
+        timeoutSupplier(1, ICMP_REPLY_TIMEOUT_MS, checkReachability);
+
+        // uninstall ICMP_REPLY enforcing rules
+        eventExecutor.execute(() -> setNorthSouthIcmpReplyRule(port, gw.get(), false));
+
+        return icmpReachabilityMap.asJavaMap().get(String.valueOf(icmpIdCounter.get()));
     }
 
     /**
-     * Checks whether ICMP reply rule is added or not.
+     * Checks whether east-west ICMP reply rule is added or not.
      *
-     * @param dstIp destination IP address
+     * @param ip    IP address
      * @return true if ICMP reply rule is added, false otherwise
      */
-    private boolean checkIcmpReplyRule(String dstIp) {
+    private boolean checkEastWestIcmpReplyRule(String ip) {
         for (FlowEntry entry : flowRuleService.getFlowEntriesById(appId)) {
             TrafficSelector selector = entry.selector();
 
-            IPCriterion dstIpCriterion = (IPCriterion) selector.getCriterion(IPV4_DST);
+            IPCriterion ipCriterion = (IPCriterion) selector.getCriterion(IPV4_DST);
 
-            if (dstIpCriterion != null &&
-                    dstIp.equals(dstIpCriterion.ip().address().toString()) &&
+            if (ipCriterion != null &&
+                    ip.equals(ipCriterion.ip().address().toString()) &&
                     entry.state() == ADDED) {
                 return true;
             }
@@ -360,21 +310,25 @@
     }
 
     /**
-     * Checks whether all of ICMP request VID tagging rules are added or not.
+     * Checks whether north-south ICMP reply rule is added or not.
      *
-     * @return true if the rule is added, false otherwise
+     * @param ip    IP address
+     * @return true if ICMP reply rule is added, false otherwise
      */
-    private boolean checkAllVidTagRules() {
-        Set<InstancePort> activePorts = instancePortService.instancePorts().stream()
-                .filter(p -> p.state() == ACTIVE).collect(Collectors.toSet());
+    private boolean checkNorthSouthIcmpReplyRule(String ip) {
+        for (FlowEntry entry : flowRuleService.getFlowEntriesById(appId)) {
+            TrafficSelector selector = entry.selector();
 
-        for (InstancePort port : activePorts) {
-            if (!checkVidTagRule(port.ipAddress().toString())) {
-                return false;
+            IPCriterion ipCriterion = (IPCriterion) selector.getCriterion(IPV4_SRC);
+
+            if (ipCriterion != null &&
+                    ip.equals(ipCriterion.ip().address().toString()) &&
+                    entry.state() == ADDED) {
+                return true;
             }
         }
 
-        return true;
+        return false;
     }
 
     /**
@@ -400,19 +354,6 @@
     }
 
     /**
-     * Installs/uninstalls all of the flow rules to match ingress fake ICMP requests.
-     *
-     * @param install   installation flag
-     */
-    private void setAllVidTagRule(boolean install) {
-        osNodeService.nodes(COMPUTE).forEach(n ->
-                instancePortService.instancePorts().stream()
-                        .filter(p -> p.deviceId().equals(n.intgBridge()))
-                        .forEach(p -> setVidTagRule(p, install))
-        );
-    }
-
-    /**
      * Installs/uninstalls a flow rule to match ingress fake ICMP request packets,
      * and tags VNI/VID, direct the tagged packet to ACL table.
      *
@@ -440,25 +381,48 @@
     }
 
     /**
-     * Installs/uninstalls all of the flow rules to match ICMP reply packets.
+     * Installs/uninstalls a flow rule to match north-south ICMP reply packets,
+     * direct all ICMP reply packets to the controller.
      *
+     * @param port      instance port
+     * @param gw        gateway node
      * @param install   installation flag
      */
-    private void setAllIcmpReplyRule(boolean install) {
-        osNodeService.nodes(COMPUTE).forEach(n ->
-            instancePortService.instancePorts().stream()
-                    .filter(p -> p.deviceId().equals(n.intgBridge()))
-                    .forEach(p -> setIcmpReplyRule(p, install))
-        );
+    private void setNorthSouthIcmpReplyRule(InstancePort port, OpenstackNode gw,
+                                            boolean install) {
+        TrafficSelector selector = DefaultTrafficSelector.builder()
+                .matchEthType(Ethernet.TYPE_IPV4)
+                .matchIPSrc(IpPrefix.valueOf(port.ipAddress(), PREFIX_LENGTH))
+                .matchIPProtocol(IPv4.PROTOCOL_ICMP)
+                .matchIcmpType(ICMP.TYPE_ECHO_REPLY)
+                .matchTunnelId(getSegId(osNetworkService, port))
+                .build();
+
+        TrafficTreatment treatment = DefaultTrafficTreatment.builder()
+                .setIpSrc(instancePortService.floatingIp(port.portId()))
+                .setEthSrc(port.macAddress())
+                .setEthDst(DEFAULT_EXTERNAL_ROUTER_MAC)
+                .punt()
+                .build();
+
+        osFlowRuleService.setRule(
+                appId,
+                gw.intgBridge(),
+                selector,
+                treatment,
+                PRIORITY_ICMP_PROBE_RULE,
+                GW_COMMON_TABLE,
+                install);
     }
 
     /**
-     * Installs/uninstalls a flow rule to match ICMP reply packets, direct all
-     * ICMP reply packets to the controller.
+     * Installs/uninstalls a flow rule to match east-west ICMP reply packets,
+     * direct all ICMP reply packets to the controller.
      *
+     * @param port      instance port
      * @param install   installation flag
      */
-    private void setIcmpReplyRule(InstancePort port, boolean install) {
+    private void setEastWestIcmpReplyRule(InstancePort port, boolean install) {
         TrafficSelector selector = DefaultTrafficSelector.builder()
                 .matchEthType(Ethernet.TYPE_IPV4)
                 .matchIPDst(IpPrefix.valueOf(port.ipAddress(), PREFIX_LENGTH))
@@ -486,14 +450,16 @@
      * @param srcPort   source instance port
      * @param dstPort   destination instance port
      */
-    private void sendIcmpEchoRequest(InstancePort srcPort, InstancePort dstPort) {
+    private void sendIcmpEchoRequest(InstancePort srcPort, InstancePort dstPort,
+                                     OpenstackNode gateway, Direction direction) {
 
         short icmpSeq = INITIAL_SEQ;
 
         short icmpId = (short) icmpIdCounter.incrementAndGet();
 
         for (int i = 0; i < MAX_ICMP_GEN; i++) {
-            packetService.emit(buildIcmpOutputPacket(srcPort, dstPort, icmpId, icmpSeq));
+            packetService.emit(buildIcmpOutputPacket(srcPort, dstPort, gateway,
+                    icmpId, icmpSeq, direction));
             icmpSeq++;
         }
     }
@@ -508,12 +474,30 @@
      */
     private OutboundPacket buildIcmpOutputPacket(InstancePort srcPort,
                                                  InstancePort dstPort,
+                                                 OpenstackNode gateway,
                                                  short icmpId,
-                                                 short icmpSeq) {
+                                                 short icmpSeq,
+                                                 Direction direction) {
 
-        // TODO: need to encapsulate the frame into VXLAN/VLAN and transit the
-        // packet to TABLE 0 in order to force the packet to go through all pipelines
-        Ethernet ethFrame = constructIcmpPacket(srcPort, dstPort, icmpId, icmpSeq);
+        Ethernet ethFrame;
+        IpAddress srcIp;
+        IpAddress dstIp;
+        DeviceId deviceId;
+
+        if (direction == Direction.EAST_WEST) {
+            ethFrame = constructEastWestIcmpPacket(srcPort, dstPort, icmpId, icmpSeq);
+            srcIp = srcPort.ipAddress();
+            dstIp = dstPort.ipAddress();
+            deviceId = srcPort.deviceId();
+        } else if (direction == Direction.NORTH_SOUTH) {
+            ethFrame = constructNorthSouthIcmpPacket(dstPort, icmpId, icmpSeq);
+            srcIp = clusterService.getLocalNode().ip();
+            dstIp = instancePortService.floatingIp(dstPort.portId());
+            deviceId = gateway.intgBridge();
+        } else {
+            log.warn("Invalid traffic direction {}", direction);
+            return null;
+        }
 
         TrafficTreatment.Builder tBuilder = DefaultTrafficTreatment.builder();
 
@@ -523,8 +507,8 @@
         tBuilder.setOutput(TABLE);
 
         Reachability reachability = DefaultReachability.builder()
-                                            .srcIp(srcPort.ipAddress())
-                                            .dstIp(dstPort.ipAddress())
+                                            .srcIp(srcIp)
+                                            .dstIp(dstIp)
                                             .isReachable(false)
                                             .build();
 
@@ -532,7 +516,7 @@
         icmpIds.add(String.valueOf(icmpId));
 
         return new DefaultOutboundPacket(
-                        srcPort.deviceId(),
+                        deviceId,
                         tBuilder.build(),
                         ByteBuffer.wrap(ethFrame.serialize()));
     }
@@ -540,35 +524,29 @@
     /**
      * Constructs an ICMP packet with given source and destination IP/MAC.
      *
-     * @param srcPort   source instance port
-     * @param dstPort   destination instance port
+     * @param srcIp     source IP address
+     * @param dstIp     destination IP address
+     * @param srcMac    source MAC address
+     * @param dstMac    destination MAC address
      * @param icmpId    ICMP identifier
      * @param icmpSeq   ICMP sequence number
      * @return an ethernet frame which contains ICMP payload
      */
-    private Ethernet constructIcmpPacket(InstancePort srcPort,
-                                         InstancePort dstPort,
+    private Ethernet constructIcmpPacket(IpAddress srcIp, IpAddress dstIp,
+                                         MacAddress srcMac, MacAddress dstMac,
                                          short icmpId, short icmpSeq) {
+
         // Ethernet frame
         Ethernet ethFrame = new Ethernet();
 
         ethFrame.setEtherType(TYPE_IPV4);
-        ethFrame.setSourceMACAddress(srcPort.macAddress());
-
-        boolean isRemote = !srcPort.deviceId().equals(dstPort.deviceId());
-
-        if (isRemote) {
-            // if the source and destination VMs are located in different OVS,
-            // we will assign fake gateway MAC as the destination MAC
-            ethFrame.setDestinationMACAddress(DEFAULT_GATEWAY_MAC);
-        } else {
-            ethFrame.setDestinationMACAddress(dstPort.macAddress());
-        }
+        ethFrame.setSourceMACAddress(srcMac);
+        ethFrame.setDestinationMACAddress(dstMac);
 
         // IP packet
         IPv4 iPacket = new IPv4();
-        iPacket.setDestinationAddress(dstPort.ipAddress().toString());
-        iPacket.setSourceAddress(srcPort.ipAddress().toString());
+        iPacket.setDestinationAddress(dstIp.toString());
+        iPacket.setSourceAddress(srcIp.toString());
         iPacket.setTtl(TTL);
         iPacket.setProtocol(IPv4.PROTOCOL_ICMP);
 
@@ -608,6 +586,56 @@
     }
 
     /**
+     * Constructs an east-west ICMP packet with given source and destination IP/MAC.
+     *
+     * @param srcPort   source instance port
+     * @param dstPort   destination instance port
+     * @param icmpId    ICMP identifier
+     * @param icmpSeq   ICMP sequence number
+     * @return an ethernet frame which contains ICMP payload
+     */
+    private Ethernet constructEastWestIcmpPacket(InstancePort srcPort,
+                                                 InstancePort dstPort,
+                                                 short icmpId, short icmpSeq) {
+        boolean isRemote = true;
+
+        if (srcPort.deviceId().equals(dstPort.deviceId()) &&
+                osNetworkService.gatewayIp(srcPort.portId())
+                        .equals(osNetworkService.gatewayIp(dstPort.portId()))) {
+            isRemote = false;
+        }
+
+        // if the source and destination VMs are located in different OVS,
+        // we will assign fake gateway MAC as the destination MAC
+        MacAddress dstMac = isRemote ? DEFAULT_GATEWAY_MAC : dstPort.macAddress();
+
+        return constructIcmpPacket(srcPort.ipAddress(), dstPort.ipAddress(),
+                                    srcPort.macAddress(), dstMac, icmpId, icmpSeq);
+    }
+
+    /**
+     * Constructs a north-south ICMP packet with the given destination IP/MAC.
+     *
+     * @param dstPort   destination instance port
+     * @param icmpId    ICMP identifier
+     * @param icmpSeq   ICMP sequence number
+     * @return an ethernet frame which contains ICMP payload
+     */
+    private Ethernet constructNorthSouthIcmpPacket(InstancePort dstPort,
+                                                   short icmpId, short icmpSeq) {
+
+        IpAddress localIp = clusterService.getLocalNode().ip();
+        IpAddress fip = instancePortService.floatingIp(dstPort.portId());
+
+        if (fip != null) {
+            return constructIcmpPacket(localIp, fip, LOCAL_MAC, dstPort.macAddress(),
+                    icmpId, icmpSeq);
+        } else {
+            return null;
+        }
+    }
+
+    /**
      * Handles ICMP ECHO REPLY packets.
      *
      * @param ipPacket  IP packet
diff --git a/apps/openstacktroubleshoot/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/openstacktroubleshoot/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index c7fd715..fa3dabd 100644
--- a/apps/openstacktroubleshoot/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/apps/openstacktroubleshoot/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -21,7 +21,14 @@
                 <ref component-id="activeVmIpCompleter"/>
             </completers>
         </command>
+        <command>
+            <action class="org.onosproject.openstacktroubleshoot.cli.OpenstackNorthSouthProbeCommand"/>
+            <completers>
+                <ref component-id="activeFloatingIpCompleter"/>
+            </completers>
+        </command>
     </command-bundle>
 
     <bean id="activeVmIpCompleter" class="org.onosproject.openstacktroubleshoot.cli.ActiveVmIpCompleter"/>
+    <bean id="activeFloatingIpCompleter" class="org.onosproject.openstacktroubleshoot.cli.ActiveFloatingIpCompleter"/>
 </blueprint>