Support flow trace CLI in openstack networking app.

Change-Id: I39d8e5febf9244e1908459d64ce089670ff38234
This commit is contained in:
Daniel Park 2018-09-17 17:43:25 +09:00 committed by Daniel Park
parent 66bf3824ea
commit a73c236a87
8 changed files with 339 additions and 0 deletions

View File

@ -4,6 +4,7 @@ BUNDLES = [
'//lib:httpclient-osgi',
'//lib:httpcore-osgi',
'//lib:commons-codec',
'//lib:sshd-core',
]
onos_app (

View File

@ -4,6 +4,7 @@ BUNDLES = [
"@httpclient_osgi//jar",
"@httpcore_osgi//jar",
"@commons_codec//jar",
"@sshd_core//jar",
]
onos_app(

View File

@ -24,6 +24,11 @@ COMPILE_DEPS = [
'//lib:btf',
'//lib:msg-simple',
'//lib:snakeyaml',
'//lib:sshd-core',
]
EXCLUDED_BUNDLES = [
'//lib:sshd-core',
]
TEST_DEPS = [

View File

@ -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 + [

View File

@ -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);
}
}

View File

@ -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);
}
}

View File

@ -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<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));
}

View File

@ -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>