diff --git a/apps/openstacknetworking/BUCK b/apps/openstacknetworking/BUCK index ec7fad0ad4..01778eaae4 100644 --- a/apps/openstacknetworking/BUCK +++ b/apps/openstacknetworking/BUCK @@ -4,6 +4,7 @@ BUNDLES = [ '//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 6da2cbd0a1..bf1a806463 100644 --- a/apps/openstacknetworking/BUILD +++ b/apps/openstacknetworking/BUILD @@ -4,6 +4,7 @@ BUNDLES = [ "@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 24ed61804f..a227e74e22 100644 --- a/apps/openstacknetworking/app/BUCK +++ b/apps/openstacknetworking/app/BUCK @@ -24,6 +24,11 @@ COMPILE_DEPS = [ '//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 c33828a0d3..0ce947d0a2 100644 --- a/apps/openstacknetworking/app/BUILD +++ b/apps/openstacknetworking/app/BUILD @@ -24,6 +24,11 @@ COMPILE_DEPS = CORE_DEPS + JACKSON + KRYO + CLI + REST + [ "@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 0000000000..a41a64380a --- /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 candidates) { + StringsCompleter delegate = new StringsCompleter(); + InstancePortService instancePortService = + AbstractShellCommand.get(InstancePortService.class); + + Set set = instancePortService.instancePorts().stream() + .map(InstancePort::ipAddress) + .collect(Collectors.toSet()); + set.add(IpAddress.valueOf(EXTERNAL_IP)); + + SortedSet strings = delegate.getStrings(); + + Iterator 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 0000000000..69ba0dcd8a --- /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 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 e1edb6d870..d06065c138 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.JsonMappingException; 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.HttpTransportMetricsImpl; 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.openstacknetworking.impl.DefaultInstancePort; 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.ByteArrayInputStream; 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.Objects; 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 @@ public final class OpenstackNetworkingUtil { 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 @@ public final class OpenstackNetworkingUtil { 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 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 ce36d9a64e..bb8bbdd0fa 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 @@ + + + + + + @@ -109,4 +115,5 @@ +