mirror of
https://github.com/opennetworkinglab/onos.git
synced 2026-05-05 20:26:16 +02:00
Support flow trace CLI in openstack networking app.
Change-Id: I39d8e5febf9244e1908459d64ce089670ff38234
This commit is contained in:
parent
66bf3824ea
commit
a73c236a87
@ -4,6 +4,7 @@ BUNDLES = [
|
||||
'//lib:httpclient-osgi',
|
||||
'//lib:httpcore-osgi',
|
||||
'//lib:commons-codec',
|
||||
'//lib:sshd-core',
|
||||
]
|
||||
|
||||
onos_app (
|
||||
|
||||
@ -4,6 +4,7 @@ BUNDLES = [
|
||||
"@httpclient_osgi//jar",
|
||||
"@httpcore_osgi//jar",
|
||||
"@commons_codec//jar",
|
||||
"@sshd_core//jar",
|
||||
]
|
||||
|
||||
onos_app(
|
||||
|
||||
@ -24,6 +24,11 @@ COMPILE_DEPS = [
|
||||
'//lib:btf',
|
||||
'//lib:msg-simple',
|
||||
'//lib:snakeyaml',
|
||||
'//lib:sshd-core',
|
||||
]
|
||||
|
||||
EXCLUDED_BUNDLES = [
|
||||
'//lib:sshd-core',
|
||||
]
|
||||
|
||||
TEST_DEPS = [
|
||||
|
||||
@ -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 + [
|
||||
|
||||
@ -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);
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
@ -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);
|
||||
}
|
||||
}
|
||||
@ -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));
|
||||
}
|
||||
|
||||
@ -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>
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user