blob: 3b869b66ead1510b2cd7ab464ac787e74bb47930 [file] [log] [blame]
/*
* 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;
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.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.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.device.DeviceService;
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;
import org.onosproject.ui.UiMessageHandler;
import org.onosproject.ui.topo.Highlights;
import org.onosproject.ui.topo.HostHighlight;
import org.onosproject.ui.topo.NodeBadge;
import org.onosproject.ui.topo.NodeBadge.Status;
import org.onosproject.ui.topo.TopoJson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Collection;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.TimeUnit;
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.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC_STR;
/**
* OpenStack Networking UI message handler.
*/
public class OpenstackNetworkingUiMessageHandler extends UiMessageHandler {
private static final String OPENSTACK_NETWORKING_UI_START = "openstackNetworkingUiStart";
private static final String OPENSTACK_NETWORKING_UI_UPDATE = "openstackNetworkingUiUpdate";
private static final String OPENSTACK_NETWORKING_UI_STOP = "openstackNetworkingUiStop";
private static final String ANNOTATION_NETWORK_ID = "networkId";
private static final String FLOW_TRACE_REQUEST = "flowTraceRequest";
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 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 UPLINK = "uplink";
private static final String OVS_VERSION_2_8 = "2.8";
private static final String OVS_VERSION_2_7 = "2.7";
private static final String OVS_VERSION_2_6 = "2.6";
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;
private enum Mode { IDLE, MOUSE }
private final Logger log = LoggerFactory.getLogger(getClass());
private DeviceService deviceService;
private HostService hostService;
private PathService pathService;
private OpenstackNodeService osNodeService;
private InstancePortService instancePortService;
private OpenstackNetworkService osNetService;
private Mode currentMode = Mode.IDLE;
private Element elementOfNote;
private final ExecutorService eventExecutor = newSingleThreadExecutor(
groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
@Override
public void init(UiConnection connection, ServiceDirectory directory) {
super.init(connection, directory);
deviceService = directory.get(DeviceService.class);
hostService = directory.get(HostService.class);
pathService = directory.get(PathService.class);
osNodeService = directory.get(OpenstackNodeService.class);
instancePortService = directory.get(InstancePortService.class);
osNetService = directory.get(OpenstackNetworkService.class);
}
@Override
protected Collection<RequestHandler> createRequestHandlers() {
return ImmutableSet.of(
new DisplayStartHandler(),
new DisplayUpdateHandler(),
new DisplayStopHandler(),
new FlowTraceRequestHandler()
);
}
private final class DisplayStartHandler extends RequestHandler {
public DisplayStartHandler() {
super(OPENSTACK_NETWORKING_UI_START);
}
@Override
public void process(ObjectNode payload) {
String mode = string(payload, MODE);
log.debug("Start Display: mode [{}]", mode);
clearState();
clearForMode();
switch (mode) {
case MOUSE:
currentMode = Mode.MOUSE;
eventExecutor.execute(OpenstackNetworkingUiMessageHandler.this::sendMouseData);
break;
default:
currentMode = Mode.IDLE;
break;
}
}
}
private final class FlowTraceRequestHandler extends RequestHandler {
public FlowTraceRequestHandler() {
super(FLOW_TRACE_REQUEST);
}
@Override
public void process(ObjectNode payload) {
String srcIp = string(payload, SRC_IP);
String dstIp = string(payload, DST_IP);
String srcDeviceId = string(payload, SRC_DEVICE_ID);
String dstDeviceId = string(payload, DST_DEVICE_ID);
boolean uplink = bool(payload, UPLINK);
log.info("Flow trace request called with" +
"src IP: {}, dst IP: {}, src device ID: {}, dst device Id: {}, uplink: ",
srcIp,
dstIp,
srcDeviceId,
dstDeviceId,
uplink);
eventExecutor.execute(() -> processFlowTraceRequest(srcIp, dstIp, srcDeviceId, dstDeviceId, uplink));
}
}
private final class DisplayUpdateHandler extends RequestHandler {
public DisplayUpdateHandler() {
super(OPENSTACK_NETWORKING_UI_UPDATE);
}
@Override
public void process(ObjectNode payload) {
String id = string(payload, ID);
log.debug("Update Display: id [{}]", id);
if (!Strings.isNullOrEmpty(id)) {
eventExecutor.execute(() -> updateForMode(id));
} else {
eventExecutor.execute(OpenstackNetworkingUiMessageHandler.this::clearForMode);
}
}
}
private final class DisplayStopHandler extends RequestHandler {
public DisplayStopHandler() {
super(OPENSTACK_NETWORKING_UI_STOP);
}
@Override
public void process(ObjectNode payload) {
log.debug("Stop Display");
clearState();
clearForMode();
}
}
private void clearState() {
currentMode = Mode.IDLE;
elementOfNote = null;
}
private void updateForMode(String id) {
try {
HostId hid = HostId.hostId(id);
elementOfNote = hostService.getHost(hid);
} catch (Exception e) {
try {
DeviceId did = DeviceId.deviceId(id);
elementOfNote = deviceService.getDevice(did);
} catch (Exception e2) {
log.debug("Unable to process ID [{}]", id);
elementOfNote = null;
}
}
switch (currentMode) {
case MOUSE:
sendMouseData();
break;
default:
break;
}
}
private void clearForMode() {
sendHighlights(new Highlights());
}
private void sendHighlights(Highlights highlights) {
sendMessage(TopoJson.highlightsMessage(highlights));
}
/**
* Sends JSON-based message to UI.
* @param type type
* @param payload payload
*/
public void sendMessagetoUi(String type, ObjectNode payload) {
sendMessage(JsonUtils.envelope(type, payload));
}
private int getVni(Host host) {
String vni = host.annotations().value(ANNOTATION_SEGMENT_ID);
return vni == null ? 0 : Integer.parseInt(vni);
}
private void sendMouseData() {
Highlights highlights = new Highlights();
if (elementOfNote instanceof Device) {
DeviceId deviceId = (DeviceId) elementOfNote.id();
List<OpenstackLink> edgeLinks = edgeLinks(deviceId);
edgeLinks.forEach(edgeLink ->
highlights.add(edgeLink.highlight(OpenstackLink.RequestType.DEVICE_SELECTED)));
hostService.getConnectedHosts(deviceId).forEach(host -> {
HostHighlight hostHighlight = new HostHighlight(host.id().toString());
hostHighlight.setBadge(createBadge(getVni(host)));
highlights.add(hostHighlight);
});
sendHighlights(highlights);
} else if (elementOfNote instanceof Host) {
HostId hostId = HostId.hostId(elementOfNote.id().toString());
if (!hostMadeFromOpenstack(hostId)) {
return;
}
List<OpenstackLink> openstackLinks = linksInSameNetwork(hostId);
openstackLinks.forEach(openstackLink ->
highlights.add(openstackLink.highlight(OpenstackLink.RequestType.HOST_SELECTED)));
hostHighlightsInSameNetwork(hostId).forEach(highlights::add);
sendHighlights(highlights);
}
}
private boolean hostMadeFromOpenstack(HostId hostId) {
return hostService.getHost(hostId).annotations().value(ANNOTATION_NETWORK_ID) != null;
}
private String networkId(HostId hostId) {
return hostService.getHost(hostId).annotations().value(ANNOTATION_NETWORK_ID);
}
private Set<HostHighlight> hostHighlightsInSameNetwork(HostId hostId) {
Set<HostHighlight> hostHighlights = Sets.newHashSet();
Streams.stream(hostService.getHosts())
.filter(host -> isHostInSameNetwork(host, networkId(hostId)))
.forEach(host -> {
HostHighlight hostHighlight = new HostHighlight(host.id().toString());
hostHighlight.setBadge(createBadge(getVni(host)));
hostHighlights.add(hostHighlight);
});
return hostHighlights;
}
private List<OpenstackLink> edgeLinks(DeviceId deviceId) {
OpenstackLinkMap openstackLinkMap = new OpenstackLinkMap();
hostService.getConnectedHosts(deviceId).forEach(host -> {
openstackLinkMap.add(createEdgeLink(host, true));
openstackLinkMap.add(createEdgeLink(host, false));
});
List<OpenstackLink> edgeLinks = Lists.newArrayList();
edgeLinks.addAll(openstackLinkMap.biLinks());
return edgeLinks;
}
private List<OpenstackLink> linksInSameNetwork(HostId hostId) {
OpenstackLinkMap linkMap = new OpenstackLinkMap();
Streams.stream(hostService.getHosts())
.filter(host -> isHostInSameNetwork(host, networkId(hostId)))
.forEach(host -> {
linkMap.add(createEdgeLink(host, true));
linkMap.add(createEdgeLink(host, false));
Set<Path> paths = pathService.getPaths(hostId,
host.id());
if (!paths.isEmpty()) {
paths.forEach(path -> path.links().forEach(linkMap::add));
}
});
List<OpenstackLink> openstackLinks = Lists.newArrayList();
openstackLinks.addAll(linkMap.biLinks());
return openstackLinks;
}
private boolean isHostInSameNetwork(Host host, String networkId) {
return hostService.getHost(host.id()).annotations()
.value(ANNOTATION_NETWORK_ID).equals(networkId);
}
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 uplink) {
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) {
log.error("There's no openstack node information for device {}", srcDeviceId);
return;
}
if (srcOpenstackNode.sshAuthInfo() == null) {
log.error("Openstack node {} has no SSH authentication information..",
srcOpenstackNode.hostname());
return;
}
String traceResultString = sendTraceRequestToNode(srcIp, dstIp, srcOpenstackNode, uplink);
if (traceResultString == null) {
return;
}
log.debug("traceResultString raw data: {}", traceResultString);
ObjectNode traceResultJson = null;
Device srcDevice = deviceService.getDevice(srcOpenstackNode.intgBridge());
if (srcDevice.swVersion().startsWith(OVS_VERSION_2_8) ||
srcDevice.swVersion().startsWith(OVS_VERSION_2_7)) {
traceResultJson = Ovs28FlowTraceResultParser.flowTraceResultInJson(
traceResultString.trim(), srcOpenstackNode.hostname());
} else {
log.error("Currently OVS version {} is not supported",
deviceService.getDevice(srcOpenstackNode.intgBridge()));
}
if (traceResultJson == null) {
return;
}
traceResultArray.add(traceResultJson);
log.debug("traceResultForward Json: {}", traceResultJson);
if (!traceResultJson.get(IS_SUCCESS).asBoolean()) {
traceSuccess = false;
}
traceResult.put(TRACE_SUCCESS, traceSuccess);
traceResult.put(SRC_IP, srcIp);
traceResult.put(DST_IP, dstIp);
traceResult.put(SRC_DEVICE_ID, srcDeviceId);
traceResult.put(DST_DEVICE_ID, dstDeviceId);
traceResult.put(UPLINK, uplink);
log.debug("traceResult Json: {}", traceResult);
sendMessagetoUi(FLOW_TRACE_RESULT, traceResult);
}
private String sendTraceRequestToNode(String srcIp, String dstIp, OpenstackNode openstackNode, boolean uplink) {
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 = traceRequestString(srcIp, dstIp,
instancePort.get(), osNetService, uplink);
return sendTraceRequestToNode(requestString, openstackNode);
}
private 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();
}
private 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;
}
}