Support flow trace CLI in openstack networking app.
Change-Id: I39d8e5febf9244e1908459d64ce089670ff38234
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>