Implements flow tracer in Openstack Networking UI

Change-Id: I6747de157e63b4d6ffa67274b623b29db311d2e3
This commit is contained in:
Daniel Park 2018-07-16 17:29:34 +09:00 committed by Jian Li
parent 50ac0983c8
commit 577b69c945
21 changed files with 523 additions and 596 deletions

View File

@ -139,4 +139,29 @@ public interface OpenstackNetworkService
*/
Set<ExternalPeerRouter> externalPeerRouters();
/**
* Returns ip prefix with supplied port ID.
*
* @param portId openstack port id
* @return ip prefix
*/
IpPrefix ipPrefix(String portId);
/**
* Returns network type with supplied network ID.
*
* @param netId openstack network id
* @return network type
*/
String networkType(String netId);
/**
* Returns gateway ip address upplied port ID.
*
* @param portId openstack port id
* @return gateway ip address
*/
String gatewayIp(String portId);
}

View File

@ -567,6 +567,64 @@ public class OpenstackNetworkManager
public Set<ExternalPeerRouter> externalPeerRouters() {
return ImmutableSet.copyOf(externalPeerRouterMap.asJavaMap().values());
}
@Override
public IpPrefix ipPrefix(String portId) {
checkNotNull(portId);
Port port = port(portId);
checkNotNull(port);
IpAddress ipAddress = port.getFixedIps().stream()
.map(ip -> IpAddress.valueOf(ip.getIpAddress()))
.findAny().orElse(null);
checkNotNull(ipAddress);
Network network = network(port.getNetworkId());
checkNotNull(network);
return subnets(network.getId()).stream()
.map(s -> IpPrefix.valueOf(s.getCidr()))
.filter(prefix -> prefix.contains(ipAddress))
.findAny().orElse(null);
}
@Override
public String networkType(String netId) {
Network network = network(netId);
checkNotNull(network);
return network.getNetworkType().toString();
}
@Override
public String gatewayIp(String portId) {
checkNotNull(portId);
Port port = port(portId);
checkNotNull(port);
IpAddress ipAddress = port.getFixedIps().stream()
.map(ip -> IpAddress.valueOf(ip.getIpAddress()))
.findAny().orElse(null);
checkNotNull(ipAddress);
Network network = network(port.getNetworkId());
checkNotNull(network);
return subnets(network.getId()).stream()
.filter(s -> IpPrefix.valueOf(s.getCidr()).contains(ipAddress))
.map(s -> s.getGateway())
.findAny().orElse(null);
}
private boolean isNetworkInUse(String netId) {
return !subnets(netId).isEmpty() && !ports(netId).isEmpty();
}

View File

@ -103,6 +103,21 @@ public class OpenstackNetworkServiceAdapter implements OpenstackNetworkService {
return ImmutableSet.of();
}
@Override
public IpPrefix ipPrefix(String portId) {
return null;
}
@Override
public String networkType(String netId) {
return null;
}
@Override
public String gatewayIp(String portId) {
return null;
}
@Override
public void addListener(OpenstackNetworkListener listener) {

View File

@ -7,6 +7,13 @@ COMPILE_DEPS = [
'//lib:jersey-client',
'//cli:onos-cli',
'//lib:org.apache.karaf.shell.console',
'//lib:sshd-core',
'//apps/openstacknode/api:onos-apps-openstacknode-api',
'//apps/openstacknetworking/api:onos-apps-openstacknetworking-api',
]
EXCLUDED_BUNDLES = [
'//lib:sshd-core',
]
TEST_DEPS = [
@ -18,11 +25,6 @@ TEST_DEPS = [
osgi_jar_with_tests (
deps = COMPILE_DEPS,
test_deps = TEST_DEPS,
web_context = '/onos/openstacknetworkingui',
api_title = 'OpenStack Networking UI REST API',
api_version = '0.9',
api_description = 'OpenStack Networking UI REST API',
api_package = 'org.onosproject.openstacknetworkingui.web',
)
onos_app (
@ -31,4 +33,8 @@ onos_app (
category = 'GUI',
url = 'http://onosproject.org',
description = 'Openstack Networking UI Service',
excluded_bundles = EXCLUDED_BUNDLES,
required_apps = [
'org.onosproject.openstacknetworking',
]
)

View File

@ -37,13 +37,6 @@
<onos.app.category>UI</onos.app.category>
<onos.app.url>http://onosproject.org</onos.app.url>
<onos.app.readme>OpenStack Networking UI Application</onos.app.readme>
<web.context>/onos/openstacknetworkingui</web.context>
<api.version>1.0.0</api.version>
<api.title>OpenStack Networking UI REST API</api.title>
<api.description>
APIs for interacting with OpenStack Networking Monitoring server.
</api.description>
<api.package>org.onosproject.openstacknetworkingui.web</api.package>
</properties>
<dependencies>
@ -70,6 +63,23 @@
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-apps-openstacknetworking-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.onosproject</groupId>
<artifactId>onos-apps-openstacknode-api</artifactId>
<version>${project.version}</version>
</dependency>
<dependency>
<groupId>org.apache.sshd</groupId>
<artifactId>sshd-core</artifactId>
</dependency>
<dependency>
<groupId>org.apache.karaf.shell</groupId>
<artifactId>org.apache.karaf.shell.console</artifactId>

View File

@ -1,5 +1,5 @@
/*
* Copyright 2017-present Open Networking Foundation
* 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.
@ -13,8 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.onosproject.openstacknetworkingui;
/**
* CLI handlers of OpenStack Networking UI service.
* Interface class for parsing flow trace string result to json with respect to OVS version.
*/
package org.onosproject.openstacknetworkingui.cli;
public interface FlowTraceResultParser {
String flowTraceResultInJson(String outputStream, String hostName);
}

View File

@ -15,7 +15,6 @@
*/
package org.onosproject.openstacknetworkingui;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Streams;
import org.apache.felix.scr.annotations.Activate;
@ -117,32 +116,6 @@ public class OpenstackNetworkingUiManager implements OpenstackNetworkingUiServic
log.info("Stopped");
}
@Override
public void sendMessage(String type, ObjectNode payload) {
messageHandler.sendMessagetoUi(type, payload);
}
@Override
public void setRestServerIp(String ipAddress) {
messageHandler.setRestUrl(ipAddress);
}
@Override
public String restServerUrl() {
return messageHandler.restUrl();
}
@Override
public void setRestServerAuthInfo(String id, String password) {
messageHandler.setRestAuthInfo(id, password);
}
@Override
public String restServerAuthInfo() {
return messageHandler.restAuthInfo();
}
private Optional<Port> vxlanPort(DeviceId deviceId) {
return deviceService.getPorts(deviceId)
.stream()
@ -178,5 +151,4 @@ public class OpenstackNetworkingUiManager implements OpenstackNetworkingUiServic
private LinkDescription createLinkDescription(ConnectPoint srcConnectPoint, ConnectPoint dstConnectPoint) {
return new DefaultLinkDescription(srcConnectPoint, dstConnectPoint, Type.DIRECT, true);
}
}

View File

@ -15,38 +15,38 @@
*/
package org.onosproject.openstacknetworkingui;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
import com.google.common.base.Charsets;
import com.google.common.base.Strings;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Lists;
import com.google.common.collect.Sets;
import com.google.common.collect.Streams;
import org.apache.commons.io.IOUtils;
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.osgi.ServiceDirectory;
import org.onlab.util.DefaultHashMap;
import org.onosproject.cluster.ClusterService;
import org.onosproject.cluster.NodeId;
import org.onosproject.mastership.MastershipService;
import org.onosproject.net.AnnotationKeys;
import org.onosproject.net.Annotations;
import org.onlab.packet.IpAddress;
import org.onosproject.net.Device;
import org.onosproject.net.DeviceId;
import org.onosproject.net.Element;
import org.onosproject.net.Host;
import org.onosproject.net.HostId;
import org.onosproject.net.Path;
import org.onosproject.net.config.NetworkConfigService;
import org.onosproject.net.config.basics.BasicDeviceConfig;
import org.onosproject.net.config.basics.BasicElementConfig;
import org.onosproject.net.device.DeviceEvent;
import org.onosproject.net.device.DeviceService;
import org.onosproject.net.driver.Driver;
import org.onosproject.net.driver.DriverService;
import org.onosproject.net.host.HostService;
import org.onosproject.net.topology.PathService;
import org.onosproject.openstacknetworking.api.InstancePort;
import org.onosproject.openstacknetworking.api.InstancePortService;
import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
import org.onosproject.openstacknode.api.OpenstackNode;
import org.onosproject.openstacknode.api.OpenstackNodeService;
import org.onosproject.openstacknode.api.OpenstackSshAuth;
import org.onosproject.ui.JsonUtils;
import org.onosproject.ui.RequestHandler;
import org.onosproject.ui.UiConnection;
@ -59,32 +59,21 @@ import org.onosproject.ui.topo.TopoJson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.client.Client;
import javax.ws.rs.client.ClientBuilder;
import javax.ws.rs.client.Entity;
import javax.ws.rs.client.Invocation;
import javax.ws.rs.client.WebTarget;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.nio.charset.StandardCharsets;
import java.util.Base64;
import java.io.OutputStream;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
import static com.google.common.base.Strings.isNullOrEmpty;
import static org.onosproject.net.AnnotationKeys.DRIVER;
import static java.util.concurrent.Executors.newSingleThreadExecutor;
import static org.onlab.util.Tools.groupedThreads;
import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
import static org.onosproject.net.Device.Type.SWITCH;
import static org.onosproject.net.config.basics.BasicElementConfig.LOC_TYPE_GEO;
import static org.onosproject.net.config.basics.BasicElementConfig.LOC_TYPE_GRID;
import static org.onosproject.net.device.DeviceEvent.Type.DEVICE_REMOVED;
import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC_STR;
/**
* OpenStack Networking UI message handler.
@ -99,24 +88,32 @@ public class OpenstackNetworkingUiMessageHandler extends UiMessageHandler {
private static final String SRC_IP = "srcIp";
private static final String DST_IP = "dstIp";
private static final String ANNOTATION_SEGMENT_ID = "segId";
private static final String AUTHORIZATION = "Authorization";
private static final String COMMAND = "command";
private static final String FLOW_TRACE = "flowtrace";
private static final String REVERSE = "reverse";
private static final String TRANSACTION_ID = "transaction_id";
private static final String TRANSACTION_VALUE = "sona";
private static final String APP_REST_URL = "app_rest_url";
private static final String MATCHING_FIELDS = "matchingfields";
private static final String SOURCE_IP = "source_ip";
private static final String DESTINATION_IP = "destination_ip";
private static final String TO_GATEWAY = "to_gateway";
private static final String IP_PROTOCOL = "ip_protocol";
private static final String HTTP = "http://";
private static final String OPENSTACK_NETWORKING_UI_RESULT = ":8181/onos/openstacknetworkingui/result";
private static final String ID = "id";
private static final String MODE = "mode";
private static final String MOUSE = "mouse";
private static final String TRACE_RESULT = "traceResult";
private static final String IS_SUCCESS = "isSuccess";
private static final String TRACE_SUCCESS = "traceSuccess";
private static final String FLOW_TRACE_RESULT = "flowTraceResult";
private static final String SRC_DEVICE_ID = "srcDeviceId";
private static final String DST_DEVICE_ID = "dstDeviceId";
private static final String SW_VERSION = "sw";
private static final String OVS_VERSION_2_8 = "2.8";
private static final String OVS_VERSION_2_6 = "2.6";
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,in_port=";
private static final String NW_SRC = "nw_src=";
private static final String COMMA = ",";
private static final long TIMEOUT_MS = 5000;
private static final long WAIT_OUTPUT_STREAM_SECOND = 2;
private static final int SSH_PORT = 22;
private enum Mode { IDLE, MOUSE }
@ -125,21 +122,16 @@ public class OpenstackNetworkingUiMessageHandler extends UiMessageHandler {
private DeviceService deviceService;
private HostService hostService;
private PathService pathService;
private ClusterService clusterService;
private DriverService driverService;
private MastershipService mastershipService;
private OpenstackNodeService osNodeService;
private InstancePortService instancePortService;
private OpenstackNetworkService osNetService;
private String restUrl;
private String restAuthInfo;
private Mode currentMode = Mode.IDLE;
private Element elementOfNote;
private final Client client = ClientBuilder.newClient();
private static Map<String, ObjectNode> metaUi = new ConcurrentHashMap<>();
private static final DefaultHashMap<DeviceEvent.Type, String> DEVICE_EVENT =
new DefaultHashMap<>("updateDevice");
static {
DEVICE_EVENT.put(DEVICE_REMOVED, "removeDevice");
}
private final ExecutorService eventExecutor = newSingleThreadExecutor(
groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
@Override
public void init(UiConnection connection, ServiceDirectory directory) {
@ -147,107 +139,11 @@ public class OpenstackNetworkingUiMessageHandler extends UiMessageHandler {
deviceService = directory.get(DeviceService.class);
hostService = directory.get(HostService.class);
pathService = directory.get(PathService.class);
clusterService = directory.get(ClusterService.class);
driverService = directory.get((DriverService.class));
mastershipService = directory.get(MastershipService.class);
// Removes non switch devices such as an ovsdb device
removeNonSwitchDevices();
osNodeService = directory.get(OpenstackNodeService.class);
instancePortService = directory.get(InstancePortService.class);
osNetService = directory.get(OpenstackNetworkService.class);
}
private void removeNonSwitchDevices() {
Streams.stream(deviceService.getAvailableDevices())
.filter(device -> device.type() != SWITCH)
.forEach(device -> sendMessage(deviceMessage(new DeviceEvent(DEVICE_REMOVED, device))));
}
// Produces a device event message to the client.
protected ObjectNode deviceMessage(DeviceEvent event) {
Device device = event.subject();
String uiType = device.annotations().value(AnnotationKeys.UI_TYPE);
String driverName = device.annotations().value(DRIVER);
Driver driver = driverName == null ? null : driverService.getDriver(driverName);
String devType = uiType != null ? uiType :
(driver != null ? driver.getProperty(AnnotationKeys.UI_TYPE) : null);
if (devType == null) {
devType = device.type().toString().toLowerCase();
}
String name = device.annotations().value(AnnotationKeys.NAME);
name = isNullOrEmpty(name) ? device.id().toString() : name;
ObjectNode payload = objectNode()
.put("id", device.id().toString())
.put("type", devType)
.put("online", deviceService.isAvailable(device.id()))
.put("master", master(device.id()));
payload.set("labels", labels("", name, device.id().toString()));
payload.set("props", props(device.annotations()));
BasicDeviceConfig cfg = get(NetworkConfigService.class)
.getConfig(device.id(), BasicDeviceConfig.class);
if (!addLocation(cfg, payload)) {
addMetaUi(device.id().toString(), payload);
}
String type = DEVICE_EVENT.get(event.type());
return JsonUtils.envelope(type, payload);
}
// Returns the name of the master node for the specified device id.
private String master(DeviceId deviceId) {
NodeId master = mastershipService.getMasterFor(deviceId);
return master != null ? master.toString() : "";
}
// Encodes the specified list of labels a JSON array.
private ArrayNode labels(String... labels) {
ArrayNode json = arrayNode();
for (String label : labels) {
json.add(label);
}
return json;
}
private boolean addLocation(BasicElementConfig cfg, ObjectNode payload) {
if (cfg != null) {
String locType = cfg.locType();
boolean isGeo = Objects.equals(locType, LOC_TYPE_GEO);
boolean isGrid = Objects.equals(locType, LOC_TYPE_GRID);
if (isGeo || isGrid) {
try {
ObjectNode loc = objectNode()
.put("locType", locType)
.put("latOrY", isGeo ? cfg.latitude() : cfg.gridY())
.put("longOrX", isGeo ? cfg.longitude() : cfg.gridX());
payload.set("location", loc);
return true;
} catch (NumberFormatException e) {
log.warn("Invalid location data: {}", cfg);
}
}
}
return false;
}
// Adds meta UI information for the specified object.
private void addMetaUi(String id, ObjectNode payload) {
ObjectNode meta = metaUi.get(id);
if (meta != null) {
payload.set("metaUi", meta);
}
}
// Produces JSON structure from annotations.
private JsonNode props(Annotations annotations) {
ObjectNode props = objectNode();
if (annotations != null) {
for (String key : annotations.keys()) {
props.put(key, annotations.value(key));
}
}
return props;
}
@Override
protected Collection<RequestHandler> createRequestHandlers() {
return ImmutableSet.of(
@ -258,22 +154,6 @@ public class OpenstackNetworkingUiMessageHandler extends UiMessageHandler {
);
}
public void setRestUrl(String ipAddress) {
restUrl = "http://" + ipAddress + ":8000/trace_request";
}
public String restUrl() {
return restUrl;
}
public void setRestAuthInfo(String id, String password) {
restAuthInfo = Base64.getEncoder().encodeToString(id.concat(":").concat(password).getBytes());
}
public String restAuthInfo() {
return restAuthInfo;
}
private final class DisplayStartHandler extends RequestHandler {
public DisplayStartHandler() {
@ -310,75 +190,18 @@ public class OpenstackNetworkingUiMessageHandler extends UiMessageHandler {
public void process(ObjectNode payload) {
String srcIp = string(payload, SRC_IP);
String dstIp = string(payload, DST_IP);
log.debug("SendEvent called with src IP: {}, dst IP: {}", srcIp, dstIp);
ObjectNode objectNode = getFlowTraceRequestAsJson(srcIp, dstIp);
InputStream byteArrayInputStream
= new ByteArrayInputStream(objectNode.toString().getBytes());
Invocation.Builder builder = getClientBuilder(restUrl);
if (builder == null) {
log.error("Fail to get the client builder for the trace from {} to {}", srcIp, dstIp);
return;
}
try {
Response response = builder.header(AUTHORIZATION, restAuthInfo.toString())
.post(Entity.entity(IOUtils.toString(byteArrayInputStream, StandardCharsets.UTF_8),
MediaType.APPLICATION_JSON_TYPE));
log.debug("Response from server: {}", response);
if (response.getStatus() != 200) {
log.error("FlowTraceRequest failed because of {}", response);
}
} catch (IOException e) {
log.error("Exception occured because of {}", e.toString());
}
String srcDeviceId = string(payload, SRC_DEVICE_ID);
String dstDeviceId = string(payload, DST_DEVICE_ID);
log.info("Flow trace request called with src IP: {}, dst IP: {}, src device ID: {}, dst device Id: {}",
srcIp,
dstIp,
srcDeviceId,
dstDeviceId);
processFlowTraceRequest(srcIp, dstIp, srcDeviceId, dstDeviceId);
}
}
private ObjectNode getFlowTraceRequestAsJson(String srcIp, String dstIp) {
ObjectMapper mapper = new ObjectMapper();
String controllerUrl = HTTP + clusterService.getLocalNode().ip()
+ OPENSTACK_NETWORKING_UI_RESULT;
ObjectNode objectNode = mapper.createObjectNode();
objectNode.put(COMMAND, FLOW_TRACE)
.put(REVERSE, false)
.put(TRANSACTION_ID, TRANSACTION_VALUE)
.put(APP_REST_URL, controllerUrl);
if (srcIp.equals(dstIp)) {
objectNode.putObject(MATCHING_FIELDS)
.put(SOURCE_IP, srcIp)
.put(DESTINATION_IP, dstIp)
.put(TO_GATEWAY, true)
.put(IP_PROTOCOL, 1);
} else {
objectNode.putObject(MATCHING_FIELDS)
.put(SOURCE_IP, srcIp)
.put(DESTINATION_IP, dstIp);
}
return objectNode;
}
private Invocation.Builder getClientBuilder(String url) {
if (Strings.isNullOrEmpty(url)) {
log.warn("URL in not set");
return null;
}
WebTarget wt = client.target(url);
return wt.request(MediaType.APPLICATION_JSON_TYPE);
}
private final class DisplayUpdateHandler extends RequestHandler {
public DisplayUpdateHandler() {
super(OPENSTACK_NETWORKING_UI_UPDATE);
@ -572,4 +395,166 @@ public class OpenstackNetworkingUiMessageHandler extends UiMessageHandler {
private NodeBadge createBadge(int n) {
return NodeBadge.number(Status.INFO, n, "Openstack Node");
}
private void processFlowTraceRequest(String srcIp, String dstIp, String srcDeviceId, String dstDeviceId) {
boolean traceSuccess = true;
ObjectMapper mapper = new ObjectMapper();
ObjectNode traceResult = mapper.createObjectNode();
ArrayNode traceResultArray = traceResult.putArray(TRACE_RESULT);
OpenstackNode srcOpenstackNode = osNodeService.node(DeviceId.deviceId(srcDeviceId));
if (srcOpenstackNode == null) {
return;
}
if (srcOpenstackNode.sshAuthInfo() == null) {
log.error("Openstack node {} has no SSH authentication information..",
srcOpenstackNode.hostname());
return;
}
String traceResultForward = sendTraceRequestToNode(srcIp, dstIp, srcOpenstackNode);
if (traceResultForward == null) {
return;
}
log.debug("traceResultForward raw data: {}", traceResultForward);
ObjectNode traceResultForwardJson = null;
Device srcDevice = deviceService.getDevice(srcOpenstackNode.intgBridge());
if (srcDevice.annotations().value(SW_VERSION).startsWith(OVS_VERSION_2_8)) {
traceResultForwardJson = Ovs28FlowTraceResultParser.flowTraceResultInJson(
traceResultForward, srcOpenstackNode.hostname());
} else {
log.error("Currently OVS version {} is not supported",
deviceService.getDevice(srcOpenstackNode.intgBridge()));
}
if (traceResultForwardJson == null) {
return;
}
traceResultArray.add(traceResultForwardJson);
log.debug("traceResultForward Json: {}", traceResultForwardJson);
if (!traceResultForwardJson.get(IS_SUCCESS).asBoolean()) {
traceSuccess = false;
}
//TODO implements trace result in backward
traceResult.put(TRACE_SUCCESS, traceSuccess);
log.debug("traceResult Json: {}", traceResult);
sendMessagetoUi(FLOW_TRACE_RESULT, traceResult);
}
private String sendTraceRequestToNode(String srcIp, String dstIp, OpenstackNode openstackNode) {
String traceResult = null;
OpenstackSshAuth sshAuth = openstackNode.sshAuthInfo();
try (SshClient client = SshClient.setUpDefaultClient()) {
client.start();
try (ClientSession session = client
.connect(sshAuth.id(), openstackNode.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)) {
String requestString = traceRequestString(srcIp, dstIp, openstackNode);
if (requestString == null) {
return null;
}
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.trim();
}
private String traceRequestString(String srcIp, String dstIp, OpenstackNode openstackNode) {
Optional<InstancePort> instancePort = instancePortService.instancePorts().stream()
.filter(port -> port.ipAddress().getIp4Address().toString().equals(srcIp)
&& port.deviceId().equals(openstackNode.intgBridge()))
.findAny();
if (!instancePort.isPresent()) {
return null;
}
String requestString = DEFAULT_REQUEST_STRING
+ instancePort.get().portNumber().toString()
+ COMMA
+ NW_SRC
+ srcIp
+ COMMA;
if (osNetService.networkType(instancePort.get().networkId()).equals(VXLAN)) {
if (srcIp.equals(dstIp)) {
dstIp = osNetService.gatewayIp(instancePort.get().portId());
requestString = requestString + DL_DST + DEFAULT_GATEWAY_MAC_STR + COMMA;
} else if (!osNetService.ipPrefix(instancePort.get().portId()).contains(IpAddress.valueOf(dstIp))) {
requestString = requestString + DL_DST + DEFAULT_GATEWAY_MAC_STR + COMMA;
}
} else if (osNetService.networkType(instancePort.get().networkId()).equals(FLAT)) {
if (srcIp.equals(dstIp)) {
dstIp = osNetService.gatewayIp(instancePort.get().portId());
}
}
requestString = requestString + NW_DST + dstIp + "\n";
return requestString;
}
}

View File

@ -16,6 +16,7 @@
package org.onosproject.openstacknetworkingui;
import org.onlab.osgi.DefaultServiceDirectory;
import org.onosproject.net.DeviceId;
import org.onosproject.net.HostId;
import org.onosproject.net.host.HostService;
import org.onosproject.ui.UiTopoOverlay;
@ -38,6 +39,7 @@ public class OpenstackNetworkingUiOverlay extends UiTopoOverlay {
private static final String SUMMARY_VERSION = "0.9";
private static final String VNI = "VNI";
private static final String ANNOTATION_SEGMENT_ID = "segId";
private static final String DEVICE_ID = "DeviceId";
private static final String NOT_AVAILABLE = "N/A";
@ -68,9 +70,11 @@ public class OpenstackNetworkingUiOverlay extends UiTopoOverlay {
@Override
public void modifyHostDetails(PropertyPanel pp, HostId hostId) {
String vni = hostService.getHost(hostId).annotations().value(ANNOTATION_SEGMENT_ID);
DeviceId deviceId = hostService.getHost(hostId).location().deviceId();
pp.removeProps(VLAN);
pp.addProp(SONA, VNI, vni == null ? NOT_AVAILABLE : vni)
pp.addProp(VNI, VNI, vni == null ? NOT_AVAILABLE : vni)
.addProp(DEVICE_ID, DEVICE_ID, deviceId.toString())
.addButton(FLOW_TRACE_BUTTON)
.addButton(RESET_BUTTON)
.addButton(TO_GATEWAY_BUTTON)

View File

@ -15,48 +15,8 @@
*/
package org.onosproject.openstacknetworkingui;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* Service for OpenStack Networking UI.
*/
public interface OpenstackNetworkingUiService {
/**
* Sends message to OpenStack Networking UI.
*
* @param type event type
* @param payload payload
*/
void sendMessage(String type, ObjectNode payload);
/**
* Sets the REST server ip address.
*
* @param ipAddress rest server ip address
*/
void setRestServerIp(String ipAddress);
/**
* Gets the REST server url.
*
* @return REST server url
*/
String restServerUrl();
/**
* Sets the REST server authorization information.
*
* @param id id
* @param password password
*/
void setRestServerAuthInfo(String id, String password);
/**
* Gets the REST server authorization information.
*
* @return REST server authorization information as String
*/
String restServerAuthInfo();
}

View File

@ -0,0 +1,133 @@
/*
* 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.openstacknetworkingui;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ArrayNode;
import com.fasterxml.jackson.databind.node.ObjectNode;
/**
* Parser class for flow trace result string from OVS28.
*/
public final class Ovs28FlowTraceResultParser {
private static final String TRACE_NODE_NAME = "traceNodeName";
private static final String IS_SUCCESS = "isSuccess";
private static final String FLOW_RULES = "flowRules";
private static final String TABLE = "table";
private static final String PRIORITY = "priority";
private static final String SELECTOR = "selector";
private static final String ACTIONS = "actions";
private static final String BRIDGE = "bridge";
private static final String DATAPATH = "Datapath";
private static final String DROP = "drop";
private static final String COMMA = ",";
private static final String DOT = "\\.";
private static final String NEW_LINE = "\n";
private Ovs28FlowTraceResultParser() {
}
public static ObjectNode flowTraceResultInJson(String outputStream, String hostName) {
if (outputStream == null || hostName == null) {
return null;
}
ObjectMapper mapper = new ObjectMapper();
ObjectNode jsonNode = mapper.createObjectNode();
int flowRuleStartLineNum = 0;
int flowRuleEndLineNum = 0;
jsonNode.put(TRACE_NODE_NAME, hostName);
String[] lines = outputStream.split(NEW_LINE);
for (int i = 0; i < lines.length; i++) {
if (lines[i].startsWith(BRIDGE)) {
flowRuleStartLineNum = i + 2;
}
if (lines[i].startsWith(DATAPATH)) {
flowRuleEndLineNum = i;
break;
}
}
ArrayNode arrayNode = jsonNode.putArray(FLOW_RULES);
for (int i = flowRuleStartLineNum; i < flowRuleEndLineNum; i = i + 2) {
if (!isNewFlowTable(lines[i])) {
break;
}
ObjectNode flowRule = arrayNode.addObject();
flowRule.put(TABLE, tableNum(lines[i]));
flowRule.put(PRIORITY, priority(lines[i]));
flowRule.put(SELECTOR, selector(lines[i]));
String actions = action(lines[i + 1]);
if (!isNewFlowTable(lines[i + 2])) {
actions = actions + "\n" + action(lines[i + 2]);
i = i + 1;
}
flowRule.put(ACTIONS, actions);
}
if (lines[flowRuleEndLineNum].contains(DROP)) {
jsonNode.put(IS_SUCCESS, false);
} else {
jsonNode.put(IS_SUCCESS, true);
}
return jsonNode;
}
private static boolean isNewFlowTable(String line) {
return line.contains(PRIORITY);
}
private static String tableNum(String line) {
return line.split(DOT)[0];
}
private static String priority(String line) {
return line.split(PRIORITY)[1].trim().split(COMMA)[0];
}
private static String selector(String line) {
if (!hasSelector(line)) {
return "";
}
String selectorString = line.trim().split(PRIORITY)[0].split(" ")[1];
return selectorString.replaceAll(COMMA, NEW_LINE);
}
private static boolean hasSelector(String line) {
String tableNum = tableNum(line);
return !line.replaceFirst(tableNum + DOT + " ", "").startsWith(PRIORITY);
}
private static String action(String line) {
return line.trim();
}
}

View File

@ -1,34 +0,0 @@
/*
* Copyright 2017-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.openstacknetworkingui.cli;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.openstacknetworkingui.OpenstackNetworkingUiService;
/**
* Gets the REST server authorization information.
*/
@Command(scope = "onos", name = "openstacknetworking-ui-get-restserver-auth",
description = "Gets the REST server authorization information")
public class GetRestServerAuthInfoCommand extends AbstractShellCommand {
@Override
protected void execute() {
OpenstackNetworkingUiService service = AbstractShellCommand.get(OpenstackNetworkingUiService.class);
print("Encoded information for the REST server authorization: %s", service.restServerAuthInfo());
}
}

View File

@ -1,35 +0,0 @@
/*
* Copyright 2017-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.openstacknetworkingui.cli;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.openstacknetworkingui.OpenstackNetworkingUiService;
/**
* Gets the REST server url.
*/
@Command(scope = "onos", name = "openstacknetworking-ui-get-restserver-url",
description = "Gets the REST server url")
public class GetRestServerCommand extends AbstractShellCommand {
@Override
protected void execute() {
OpenstackNetworkingUiService service = AbstractShellCommand.get(OpenstackNetworkingUiService.class);
print("REST server url : %s", service.restServerUrl());
}
}

View File

@ -1,45 +0,0 @@
/*
* Copyright 2017-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.openstacknetworkingui.cli;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.openstacknetworkingui.OpenstackNetworkingUiService;
/**
* Sets the REST server authorization information.
*/
@Command(scope = "onos", name = "openstacknetworking-ui-set-restserver-auth",
description = "Sets the REST server authorization information")
public class SetRestServerAuthInfoCommand extends AbstractShellCommand {
@Argument(index = 0, name = "restServerAuthId", description = "REST server authorization id",
required = true, multiValued = false)
private String restServerAuthId = null;
@Argument(index = 1, name = "restServerAuthPass", description = "REST server authorization password",
required = true, multiValued = false)
private String restServerAuthPass = null;
@Override
protected void execute() {
OpenstackNetworkingUiService service = AbstractShellCommand.get(OpenstackNetworkingUiService.class);
service.setRestServerAuthInfo(restServerAuthId, restServerAuthPass);
print("Id and password for the REST server authorization are %s and %s.", restServerAuthId, restServerAuthPass);
print("Encoded result as based 64 format: %s", service.restServerAuthInfo());
}
}

View File

@ -1,40 +0,0 @@
/*
* Copyright 2017-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.openstacknetworkingui.cli;
import org.apache.karaf.shell.commands.Argument;
import org.apache.karaf.shell.commands.Command;
import org.onosproject.cli.AbstractShellCommand;
import org.onosproject.openstacknetworkingui.OpenstackNetworkingUiService;
/**
* Sets the REST server ip address.
*/
@Command(scope = "onos", name = "openstacknetworking-ui-set-restserver-ip",
description = "Sets the REST server ip address")
public class SetRestServerCommand extends AbstractShellCommand {
@Argument(index = 0, name = "restServerIp", description = "REST server ip address",
required = true, multiValued = false)
private String restServerIp = null;
@Override
protected void execute() {
OpenstackNetworkingUiService service = AbstractShellCommand.get(OpenstackNetworkingUiService.class);
service.setRestServerIp(restServerIp);
print("The REST server url is set to %s", service.restServerUrl());
}
}

View File

@ -1,76 +0,0 @@
/*
* Copyright 2017-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.openstacknetworkingui.web;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.fasterxml.jackson.databind.node.ObjectNode;
import org.onlab.osgi.DefaultServiceDirectory;
import org.onosproject.openstacknetworkingui.OpenstackNetworkingUiService;
import org.onosproject.rest.AbstractWebResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import javax.ws.rs.Consumes;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.core.Context;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.UriInfo;
import java.io.IOException;
import java.io.InputStream;
import static javax.ws.rs.core.Response.status;
import static org.onlab.util.Tools.readTreeFromStream;
/**
* Handles REST API from monitoring server.
*/
@Path("result")
public class FlowTraceWebResource extends AbstractWebResource {
protected final Logger log = LoggerFactory.getLogger(getClass());
private final OpenstackNetworkingUiService uiService =
DefaultServiceDirectory.getService(OpenstackNetworkingUiService.class);
private static final String FLOW_TRACE_RESULT = "flowTraceResult";
@Context
private UriInfo uriInfo;
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public Response flowTraceResponse(InputStream inputStream) throws IOException {
try {
JsonNode jsonNode = readTreeFromStream(mapper().enable(SerializationFeature.INDENT_OUTPUT), inputStream);
ObjectNode objectNode = jsonNode.deepCopy();
log.debug("FlowTraceResponse: {}", jsonNode.toString());
uiService.sendMessage(FLOW_TRACE_RESULT, objectNode);
} catch (IOException e) {
log.error("Exception occured because of {}", e.toString());
}
return status(Response.Status.OK).build();
}
}

View File

@ -1,20 +0,0 @@
/*
* Copyright 2017-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.
*/
/**
* Web implementation of OpenStack Networking UI service.
*/
package org.onosproject.openstacknetworkingui.web;

View File

@ -15,17 +15,5 @@
-->
<blueprint xmlns="http://www.osgi.org/xmlns/blueprint/v1.0.0">
<command-bundle xmlns="http://karaf.apache.org/xmlns/shell/v1.1.0">
<command>
<action class="org.onosproject.openstacknetworkingui.cli.SetRestServerCommand"/>
</command>
<command>
<action class="org.onosproject.openstacknetworkingui.cli.GetRestServerCommand"/>
</command>
<command>
<action class="org.onosproject.openstacknetworkingui.cli.GetRestServerAuthInfoCommand"/>
</command>
<command>
<action class="org.onosproject.openstacknetworkingui.cli.SetRestServerAuthInfoCommand"/>
</command>
</command-bundle>
</blueprint>

View File

@ -22,10 +22,12 @@
var $log, tov, sts, flash, ds, wss;
var traceSrc = null;
var traceDst = null;
var srcDeviceId = null;
var dstDeviceId = null;
var traceInfoDialogId = 'traceInfoDialogId',
traceInfoDialogOpt = {
width: 200,
width: 300,
edge: 'left',
margin: 20,
hideMargin: -20
@ -64,11 +66,13 @@
cb: function (data) {
if (traceSrc == null && data.navPath == 'host') {
traceSrc = data.title;
traceSrc = data.propValues.ip;
srcDeviceId = data.propValues.DeviceId
flash.flash('Src ' + traceSrc + ' selected. Please select the dst');
} else if (traceDst == null && data.title != traceSrc && data.navPath == 'host') {
traceDst = data.title;
traceDst = data.propValues.ip;
dstDeviceId = data.propValues.DeviceId;
openTraceInfoDialog();
flash.flash('Dst ' + traceDst + ' selected. Press Request button');
}
@ -95,6 +99,7 @@
if (traceSrc != null && data.title == traceSrc && data.navPath == 'host') {
//Set traceSrc to traceDst in case trace to gateway
traceDst = traceSrc;
dstDeviceId = 'toGateway';
openTraceInfoDialog();
flash.flash('Trace to Gateway');
}
@ -107,6 +112,7 @@
if (traceSrc != null && data.title == traceSrc && data.navPath == 'host') {
//Set traceDst to 8.8.8.8 to check external connection
traceDst = '8.8.8.8';
dstDeviceId = 'toExternal';
openTraceInfoDialog();
flash.flash('Trace to External')
}
@ -184,6 +190,16 @@
tBodySelection.append('td').text(dst).attr("class", "value");
}
texts.select('table').select('tbody').append('tr');
tBodySelection = texts.select('table').select('tbody').select('tr:nth-child(3)');
tBodySelection.append('td').text('SrcDeviceId').attr("class", "label");
tBodySelection.append('td').text(srcDeviceId).attr("class", "value");
texts.select('table').select('tbody').append('tr');
tBodySelection = texts.select('table').select('tbody').select('tr:nth-child(4)');
tBodySelection.append('td').text('DstDeviceId').attr("class", "label");
tBodySelection.append('td').text(dstDeviceId).attr("class", "value");
texts.append('hr');
return texts;
@ -191,7 +207,7 @@
}
function flowTraceResultBtn() {
sts.sendFlowTraceRequest(traceSrc, traceDst);
sts.sendFlowTraceRequest(traceSrc, traceDst, srcDeviceId, dstDeviceId);
ds.closeDialog();
traceSrc = null;
traceDst = null;

View File

@ -56,10 +56,12 @@
wss.sendEvent(displayStop);
}
function sendFlowTraceRequest(src, dst) {
function sendFlowTraceRequest(src, dst, srcDeviceId, dstDeviceId) {
wss.sendEvent(flowTraceRequest, {
srcIp: src,
dstIp: dst
dstIp: dst,
srcDeviceId: srcDeviceId,
dstDeviceId: dstDeviceId,
});
flash.flash('sendFlowTraceRequest called');
}
@ -106,7 +108,7 @@
margin: 20,
hideMargin: -20
}
var traceSuccess = data.trace_success == true ? "SUCCESS" : "FALSE";
var traceSuccess = data.traceSuccess == true ? "SUCCESS" : "FALSE";
ds.openDialog(flowTraceResultDialogId, flowTraceResultDialogOpt)
.setTitle('Flow Trace Result: ' + traceSuccess)
.addContent(createTraceResultInfoDiv(data))
@ -136,15 +138,15 @@
var tbodySelection = texts.select('.table-body').select('table').select('tbody');
var rowNum = 1;
data.trace_result.forEach(function(result) {
result.flow_rules.forEach(function(flowRule) {
data.traceResult.forEach(function(result) {
result.flowRules.forEach(function(flowRule) {
tbodySelection.append('tr');
var tbodyTrSelection = tbodySelection.select('tr:nth-child(' + rowNum + ')');
tbodyTrSelection.append('td').text(result.trace_node_name);
tbodyTrSelection.append('td').text(result.traceNodeName);
tbodyTrSelection.append('td').text(flowRule.table);
tbodyTrSelection.append('td').text(flowRule.priority);
tbodyTrSelection.append('td').text(jsonToSring(flowRule.selector));
tbodyTrSelection.append('td').text(jsonToSring(flowRule.actions));
tbodyTrSelection.append('td').text(flowRule.selector);
tbodyTrSelection.append('td').text(flowRule.actions);
if (jsonToSring(flowRule.actions).includes("drop")) {
tbodyTrSelection.attr("class", "drop");
}

View File

@ -47,7 +47,6 @@
<init-param>
<param-name>jersey.config.server.provider.classnames</param-name>
<param-value>
org.onosproject.openstacknetworkingui.web.FlowTraceWebResource
</param-value>
</init-param>
<load-on-startup>1</load-on-startup>