Support flow trace CLI in openstack networking app.

Change-Id: I39d8e5febf9244e1908459d64ce089670ff38234
diff --git a/apps/openstacknetworking/BUCK b/apps/openstacknetworking/BUCK
index ec7fad0..01778ea 100644
--- a/apps/openstacknetworking/BUCK
+++ b/apps/openstacknetworking/BUCK
@@ -4,6 +4,7 @@
     '//lib:httpclient-osgi',
     '//lib:httpcore-osgi',
     '//lib:commons-codec',
+    '//lib:sshd-core',
 ]
 
 onos_app (
diff --git a/apps/openstacknetworking/BUILD b/apps/openstacknetworking/BUILD
index 6da2cbd..bf1a806 100644
--- a/apps/openstacknetworking/BUILD
+++ b/apps/openstacknetworking/BUILD
@@ -4,6 +4,7 @@
     "@httpclient_osgi//jar",
     "@httpcore_osgi//jar",
     "@commons_codec//jar",
+    "@sshd_core//jar",
 ]
 
 onos_app(
diff --git a/apps/openstacknetworking/app/BUCK b/apps/openstacknetworking/app/BUCK
index 24ed618..a227e74 100644
--- a/apps/openstacknetworking/app/BUCK
+++ b/apps/openstacknetworking/app/BUCK
@@ -24,6 +24,11 @@
     '//lib:btf',
     '//lib:msg-simple',
     '//lib:snakeyaml',
+    '//lib:sshd-core',
+]
+
+EXCLUDED_BUNDLES = [
+  '//lib:sshd-core',
 ]
 
 TEST_DEPS = [
diff --git a/apps/openstacknetworking/app/BUILD b/apps/openstacknetworking/app/BUILD
index c33828a..0ce947d 100644
--- a/apps/openstacknetworking/app/BUILD
+++ b/apps/openstacknetworking/app/BUILD
@@ -24,6 +24,11 @@
     "@btf//jar",
     "@msg_simple//jar",
     "@snakeyaml//jar",
+    "@sshd_core//jar",
+]
+
+EXCLUDED_BUNDLES = [
+    "@sshd_core//jar",
 ]
 
 TEST_DEPS = TEST_ADAPTERS + TEST_REST + [
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/InstanceIpAddressCompleter.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/InstanceIpAddressCompleter.java
new file mode 100644
index 0000000..a41a643
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/InstanceIpAddressCompleter.java
@@ -0,0 +1,60 @@
+/*
+ * 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.openstacknetworking.cli;
+
+import org.apache.karaf.shell.console.Completer;
+import org.apache.karaf.shell.console.completer.StringsCompleter;
+import org.onlab.packet.IpAddress;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortService;
+
+import java.util.Iterator;
+import java.util.List;
+import java.util.Set;
+import java.util.SortedSet;
+import java.util.stream.Collectors;
+
+/**
+ * Instance port ip address completer.
+ */
+public class InstanceIpAddressCompleter implements Completer {
+    private static final String EXTERNAL_IP = "8.8.8.8";
+
+    @Override
+    public int complete(String buffer, int cursor, List<String> candidates) {
+        StringsCompleter delegate = new StringsCompleter();
+        InstancePortService instancePortService =
+                AbstractShellCommand.get(InstancePortService.class);
+
+        Set<IpAddress> set = instancePortService.instancePorts().stream()
+                .map(InstancePort::ipAddress)
+                .collect(Collectors.toSet());
+        set.add(IpAddress.valueOf(EXTERNAL_IP));
+
+        SortedSet<String> strings = delegate.getStrings();
+
+        Iterator<IpAddress> it = set.iterator();
+
+        while (it.hasNext()) {
+            strings.add(it.next().toString());
+        }
+
+        return delegate.complete(buffer, cursor, candidates);
+
+
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackFlowTraceCommand.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackFlowTraceCommand.java
new file mode 100644
index 0000000..69ba0dc
--- /dev/null
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/cli/OpenstackFlowTraceCommand.java
@@ -0,0 +1,93 @@
+/*
+ * 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.openstacknetworking.cli;
+
+import org.apache.karaf.shell.commands.Argument;
+import org.apache.karaf.shell.commands.Command;
+import org.onosproject.cli.AbstractShellCommand;
+import org.onosproject.openstacknetworking.api.InstancePort;
+import org.onosproject.openstacknetworking.api.InstancePortAdminService;
+import org.onosproject.openstacknetworking.api.OpenstackNetworkAdminService;
+import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackNodeAdminService;
+
+import java.util.Optional;
+
+import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.sendTraceRequestToNode;
+import static org.onosproject.openstacknetworking.util.OpenstackNetworkingUtil.traceRequestString;
+
+/**
+ * Requests flow trace command.
+ */
+@Command(scope = "onos", name = "openstack-flow-trace",
+        description = "Requests flow trace command")
+public class OpenstackFlowTraceCommand extends AbstractShellCommand {
+
+    @Argument(index = 0, name = "src ip address", description = "src ip address",
+            required = true, multiValued = false)
+    private String srcIp = null;
+
+    @Argument(index = 1, name = "dst ip address", description = "dst ip address",
+            required = true, multiValued = false)
+    private String dstIp = null;
+
+    private static final String NO_ELEMENT = "There's no instance port information with given ip address";
+    private static final String FLOW_TRACE_REQUEST_STRING_UPLINK = "Flow trace request string for uplink: ";
+    private static final String FLOW_TRACE_REQUEST_STRING_DOWNLINK = "Flow trace request string for downlink: ";
+
+
+    @Override
+    protected void execute() {
+        OpenstackNodeAdminService osNodeService = AbstractShellCommand.get(OpenstackNodeAdminService.class);
+        InstancePortAdminService instancePortService = AbstractShellCommand.get(InstancePortAdminService.class);
+        OpenstackNetworkAdminService osNetService = AbstractShellCommand.get(OpenstackNetworkAdminService.class);
+
+        Optional<InstancePort> srcInstance = instancePortService.instancePorts().stream()
+                .filter(port -> port.ipAddress().toString().equals(srcIp)).findAny();
+
+        if (!srcInstance.isPresent()) {
+            print(NO_ELEMENT);
+            return;
+        }
+
+        OpenstackNode srcNode = osNodeService.node(srcInstance.get().deviceId());
+        if (srcNode == null || srcNode.sshAuthInfo() == null) {
+            log.error("Openstack node {} is null or has no SSH authentication information.\n" +
+                            " Please refers to the sample network-cfg.json in OpenstackNode app to push" +
+                            "SSH authentication information", srcNode.hostname());
+
+            return;
+        }
+
+        if (dstIp.equals(osNetService.gatewayIp(srcInstance.get().portId()))) {
+            dstIp = srcIp;
+        }
+
+        //print uplink flow trace result
+        String requestStringUplink = traceRequestString(srcIp, dstIp, srcInstance.get(),
+                osNetService, true);
+
+        print(FLOW_TRACE_REQUEST_STRING_UPLINK + requestStringUplink);
+
+        String requestStringDownlink = traceRequestString(srcIp, dstIp, srcInstance.get(),
+                osNetService, false);
+        print(FLOW_TRACE_REQUEST_STRING_DOWNLINK + requestStringDownlink);
+
+        String traceResult = sendTraceRequestToNode(requestStringUplink + '\n'
+                + requestStringDownlink, srcNode);
+        print(traceResult);
+    }
+}
diff --git a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtil.java b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtil.java
index e1edb6d..d06065c 100644
--- a/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtil.java
+++ b/apps/openstacknetworking/app/src/main/java/org/onosproject/openstacknetworking/util/OpenstackNetworkingUtil.java
@@ -21,7 +21,9 @@
 import com.fasterxml.jackson.databind.JsonNode;
 import com.fasterxml.jackson.databind.ObjectMapper;
 import com.fasterxml.jackson.databind.node.ObjectNode;
+import com.google.common.base.Charsets;
 import com.google.common.base.Strings;
+import com.google.common.collect.Lists;
 import org.apache.commons.codec.binary.Hex;
 import org.apache.http.HttpException;
 import org.apache.http.HttpRequest;
@@ -34,6 +36,13 @@
 import org.apache.http.impl.io.SessionInputBufferImpl;
 import org.apache.http.impl.io.SessionOutputBufferImpl;
 import org.apache.http.io.HttpMessageWriter;
+import org.apache.sshd.client.SshClient;
+import org.apache.sshd.client.channel.ClientChannel;
+import org.apache.sshd.client.channel.ClientChannelEvent;
+import org.apache.sshd.client.future.OpenFuture;
+import org.apache.sshd.client.session.ClientSession;
+import org.apache.sshd.common.util.io.NoCloseInputStream;
+import org.onlab.packet.IpAddress;
 import org.onosproject.cfg.ConfigProperty;
 import org.onosproject.net.DeviceId;
 import org.onosproject.net.device.DeviceService;
@@ -45,6 +54,7 @@
 import org.onosproject.openstacknode.api.OpenstackAuth;
 import org.onosproject.openstacknode.api.OpenstackAuth.Perspective;
 import org.onosproject.openstacknode.api.OpenstackNode;
+import org.onosproject.openstacknode.api.OpenstackSshAuth;
 import org.openstack4j.api.OSClient;
 import org.openstack4j.api.client.IOSClientBuilder;
 import org.openstack4j.api.exceptions.AuthenticationException;
@@ -73,7 +83,9 @@
 import java.io.ByteArrayOutputStream;
 import java.io.IOException;
 import java.io.InputStream;
+import java.io.OutputStream;
 import java.security.cert.X509Certificate;
+import java.util.Collection;
 import java.util.HashMap;
 import java.util.Iterator;
 import java.util.Map;
@@ -81,11 +93,13 @@
 import java.util.Optional;
 import java.util.Set;
 import java.util.TreeMap;
+import java.util.concurrent.TimeUnit;
 
 import static com.fasterxml.jackson.databind.SerializationFeature.INDENT_OUTPUT;
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Strings.isNullOrEmpty;
 import static org.onosproject.net.AnnotationKeys.PORT_NAME;
+import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC_STR;
 import static org.onosproject.openstacknetworking.api.Constants.PCISLOT;
 import static org.onosproject.openstacknetworking.api.Constants.PCI_VENDOR_INFO;
 import static org.onosproject.openstacknetworking.api.Constants.PORT_NAME_PREFIX_VM;
@@ -124,6 +138,21 @@
 
     private static final String ERR_FLOW = "Failed set flows for floating IP %s: ";
 
+    private static final String FLAT = "FLAT";
+    private static final String VXLAN = "VXLAN";
+    private static final String VLAN = "VLAN";
+    private static final String DL_DST = "dl_dst=";
+    private static final String NW_DST = "nw_dst=";
+    private static final String DEFAULT_REQUEST_STRING = "sudo ovs-appctl ofproto/trace br-int ip";
+    private static final String IN_PORT = "in_port=";
+    private static final String NW_SRC = "nw_src=";
+    private static final String COMMA = ",";
+    private static final String TUN_ID = "tun_id=";
+
+    private static final long TIMEOUT_MS = 5000;
+    private static final long WAIT_OUTPUT_STREAM_SECOND = 2;
+    private static final int SSH_PORT = 22;
+
     /**
      * Prevents object instantiation from external.
      */
@@ -664,6 +693,144 @@
         return null;
     }
 
+    /**
+     * Creates flow trace request string.
+     *
+     * @param srcIp src ip address
+     * @param dstIp dst ip address
+     * @param srcInstancePort src instance port
+     * @param osNetService openstack networking service
+     * @param uplink true if this request if uplink
+     * @return flow trace request string
+     */
+    public static String traceRequestString(String srcIp,
+                                            String dstIp,
+                                            InstancePort srcInstancePort,
+                                            OpenstackNetworkService osNetService, boolean uplink) {
+
+        StringBuilder requestStringBuilder = new StringBuilder(DEFAULT_REQUEST_STRING);
+
+        if (uplink) {
+
+            requestStringBuilder.append(COMMA)
+                    .append(IN_PORT)
+                    .append(srcInstancePort.portNumber().toString())
+                    .append(COMMA)
+                    .append(NW_SRC)
+                    .append(srcIp)
+                    .append(COMMA);
+
+            if (osNetService.networkType(srcInstancePort.networkId()).equals(VXLAN) ||
+                    osNetService.networkType(srcInstancePort.networkId()).equals(VLAN)) {
+                if (srcIp.equals(dstIp)) {
+                    dstIp = osNetService.gatewayIp(srcInstancePort.portId());
+                    requestStringBuilder.append(DL_DST)
+                            .append(DEFAULT_GATEWAY_MAC_STR).append(COMMA);
+                } else if (!osNetService.ipPrefix(srcInstancePort.portId()).contains(IpAddress.valueOf(dstIp))) {
+                    requestStringBuilder.append(DL_DST)
+                            .append(DEFAULT_GATEWAY_MAC_STR)
+                            .append(COMMA);
+                }
+            } else {
+                if (srcIp.equals(dstIp)) {
+                    dstIp = osNetService.gatewayIp(srcInstancePort.portId());
+                }
+            }
+
+            requestStringBuilder.append(NW_DST)
+                    .append(dstIp)
+                    .append("\n");
+        } else {
+            requestStringBuilder.append(COMMA)
+                    .append(NW_SRC)
+                    .append(dstIp)
+                    .append(COMMA);
+
+            if (osNetService.networkType(srcInstancePort.networkId()).equals(VXLAN) ||
+                    osNetService.networkType(srcInstancePort.networkId()).equals(VLAN)) {
+                requestStringBuilder.append(TUN_ID)
+                        .append(osNetService.segmentId(srcInstancePort.networkId()))
+                        .append(COMMA);
+            }
+            requestStringBuilder.append(NW_DST)
+                    .append(srcIp)
+                    .append("\n");
+
+        }
+
+        return requestStringBuilder.toString();
+    }
+
+    /**
+     * Sends flow trace string to node.
+     *
+     * @param requestString reqeust string
+     * @param node src node
+     * @return flow trace result in string format
+     */
+    public static String sendTraceRequestToNode(String requestString,
+                                                OpenstackNode node) {
+        String traceResult = null;
+        OpenstackSshAuth sshAuth = node.sshAuthInfo();
+
+        try (SshClient client = SshClient.setUpDefaultClient()) {
+            client.start();
+
+            try (ClientSession session = client
+                    .connect(sshAuth.id(), node.managementIp().getIp4Address().toString(), SSH_PORT)
+                    .verify(TIMEOUT_MS, TimeUnit.SECONDS).getSession()) {
+                session.addPasswordIdentity(sshAuth.password());
+                session.auth().verify(TIMEOUT_MS, TimeUnit.SECONDS);
+
+
+                try (ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_SHELL)) {
+
+                    log.debug("requestString: {}", requestString);
+                    final InputStream inputStream =
+                            new ByteArrayInputStream(requestString.getBytes());
+
+                    OutputStream outputStream = new ByteArrayOutputStream();
+                    OutputStream errStream = new ByteArrayOutputStream();
+
+                    channel.setIn(new NoCloseInputStream(inputStream));
+                    channel.setErr(errStream);
+                    channel.setOut(outputStream);
+
+                    Collection<ClientChannelEvent> eventList = Lists.newArrayList();
+                    eventList.add(ClientChannelEvent.OPENED);
+
+                    OpenFuture channelFuture = channel.open();
+
+                    if (channelFuture.await(TIMEOUT_MS, TimeUnit.SECONDS)) {
+
+                        long timeoutExpiredMs = System.currentTimeMillis() + TIMEOUT_MS;
+
+                        while (!channelFuture.isOpened()) {
+                            if ((timeoutExpiredMs - System.currentTimeMillis()) <= 0) {
+                                log.error("Failed to open channel");
+                                return null;
+                            }
+                        }
+                        TimeUnit.SECONDS.sleep(WAIT_OUTPUT_STREAM_SECOND);
+
+                        traceResult = ((ByteArrayOutputStream) outputStream).toString(Charsets.UTF_8.name());
+
+                        channel.close();
+                    }
+                } finally {
+                    session.close();
+                }
+            } finally {
+                client.stop();
+            }
+
+        } catch (Exception e) {
+            log.error("Exception occurred because of {}", e.toString());
+        }
+
+        return traceResult;
+    }
+
     private static boolean isDirectPort(String portName) {
         return portNamePrefixMap().values().stream().anyMatch(p -> portName.startsWith(p));
     }
diff --git a/apps/openstacknetworking/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml b/apps/openstacknetworking/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
index ce36d9a..bb8bbdd 100644
--- a/apps/openstacknetworking/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
+++ b/apps/openstacknetworking/app/src/main/resources/OSGI-INF/blueprint/shell-config.xml
@@ -101,6 +101,12 @@
                 <ref component-id="directPortCompleter"/>
             </completers>
         </command>
+        <command>
+            <action class="org.onosproject.openstacknetworking.cli.OpenstackFlowTraceCommand" />
+            <completers>
+                <ref component-id="instanceIpAddressCompleter"/>
+            </completers>
+        </command>
     </command-bundle>
 
     <bean id="ipAddressCompleter" class="org.onosproject.openstacknetworking.cli.IpAddressCompleter"/>
@@ -109,4 +115,5 @@
     <bean id="vlanIdCompleter" class="org.onosproject.openstacknetworking.cli.VlanIdCompleter"/>
     <bean id="arpModeCompleter" class="org.onosproject.openstacknetworking.cli.ArpModeCompleter"/>
     <bean id="instancePortIdCompleter" class="org.onosproject.openstacknetworking.cli.InstancePortIdCompleter"/>
+    <bean id="instanceIpAddressCompleter" class="org.onosproject.openstacknetworking.cli.InstanceIpAddressCompleter"/>
 </blueprint>