blob: 3982d222214b256ec4e6e46048b13316771447fb [file] [log] [blame]
daniel park128c52c2017-09-04 13:15:51 +09001/*
2 * Copyright 2017-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.openstacknetworkingui;
17
18import com.fasterxml.jackson.databind.ObjectMapper;
Daniel Park819f4e82018-07-02 14:22:31 +090019import com.fasterxml.jackson.databind.node.ArrayNode;
daniel park128c52c2017-09-04 13:15:51 +090020import com.fasterxml.jackson.databind.node.ObjectNode;
Daniel Park577b69c2018-07-16 17:29:34 +090021import com.google.common.base.Charsets;
daniel park128c52c2017-09-04 13:15:51 +090022import com.google.common.base.Strings;
23import com.google.common.collect.ImmutableSet;
24import com.google.common.collect.Lists;
25import com.google.common.collect.Sets;
26import com.google.common.collect.Streams;
Daniel Park577b69c2018-07-16 17:29:34 +090027import org.apache.sshd.client.SshClient;
28import org.apache.sshd.client.channel.ClientChannel;
29import org.apache.sshd.client.channel.ClientChannelEvent;
30import org.apache.sshd.client.future.OpenFuture;
31import org.apache.sshd.client.session.ClientSession;
32import org.apache.sshd.common.util.io.NoCloseInputStream;
daniel park128c52c2017-09-04 13:15:51 +090033import org.onlab.osgi.ServiceDirectory;
Daniel Park577b69c2018-07-16 17:29:34 +090034import org.onlab.packet.IpAddress;
daniel park128c52c2017-09-04 13:15:51 +090035import org.onosproject.net.Device;
36import org.onosproject.net.DeviceId;
37import org.onosproject.net.Element;
38import org.onosproject.net.Host;
39import org.onosproject.net.HostId;
40import org.onosproject.net.Path;
41import org.onosproject.net.device.DeviceService;
42import org.onosproject.net.host.HostService;
43import org.onosproject.net.topology.PathService;
Daniel Park577b69c2018-07-16 17:29:34 +090044import org.onosproject.openstacknetworking.api.InstancePort;
45import org.onosproject.openstacknetworking.api.InstancePortService;
46import org.onosproject.openstacknetworking.api.OpenstackNetworkService;
47import org.onosproject.openstacknode.api.OpenstackNode;
48import org.onosproject.openstacknode.api.OpenstackNodeService;
49import org.onosproject.openstacknode.api.OpenstackSshAuth;
daniel park128c52c2017-09-04 13:15:51 +090050import org.onosproject.ui.JsonUtils;
51import org.onosproject.ui.RequestHandler;
52import org.onosproject.ui.UiConnection;
53import org.onosproject.ui.UiMessageHandler;
daniel park128c52c2017-09-04 13:15:51 +090054import org.onosproject.ui.topo.Highlights;
55import org.onosproject.ui.topo.HostHighlight;
56import org.onosproject.ui.topo.NodeBadge;
57import org.onosproject.ui.topo.NodeBadge.Status;
58import org.onosproject.ui.topo.TopoJson;
59import org.slf4j.Logger;
60import org.slf4j.LoggerFactory;
61
daniel park128c52c2017-09-04 13:15:51 +090062import java.io.ByteArrayInputStream;
Daniel Park577b69c2018-07-16 17:29:34 +090063import java.io.ByteArrayOutputStream;
daniel park128c52c2017-09-04 13:15:51 +090064import java.io.InputStream;
Daniel Park577b69c2018-07-16 17:29:34 +090065import java.io.OutputStream;
daniel park128c52c2017-09-04 13:15:51 +090066import java.util.Collection;
67import java.util.List;
Daniel Park577b69c2018-07-16 17:29:34 +090068import java.util.Optional;
daniel park128c52c2017-09-04 13:15:51 +090069import java.util.Set;
Daniel Park577b69c2018-07-16 17:29:34 +090070import java.util.concurrent.ExecutorService;
71import java.util.concurrent.TimeUnit;
daniel park128c52c2017-09-04 13:15:51 +090072
Daniel Park577b69c2018-07-16 17:29:34 +090073import static java.util.concurrent.Executors.newSingleThreadExecutor;
74import static org.onlab.util.Tools.groupedThreads;
daniel park128c52c2017-09-04 13:15:51 +090075import static org.onosproject.net.DefaultEdgeLink.createEdgeLink;
Daniel Park577b69c2018-07-16 17:29:34 +090076import static org.onosproject.openstacknetworking.api.Constants.DEFAULT_GATEWAY_MAC_STR;
daniel park128c52c2017-09-04 13:15:51 +090077
78/**
79 * OpenStack Networking UI message handler.
80 */
81public class OpenstackNetworkingUiMessageHandler extends UiMessageHandler {
82
83 private static final String OPENSTACK_NETWORKING_UI_START = "openstackNetworkingUiStart";
84 private static final String OPENSTACK_NETWORKING_UI_UPDATE = "openstackNetworkingUiUpdate";
85 private static final String OPENSTACK_NETWORKING_UI_STOP = "openstackNetworkingUiStop";
86 private static final String ANNOTATION_NETWORK_ID = "networkId";
87 private static final String FLOW_TRACE_REQUEST = "flowTraceRequest";
88 private static final String SRC_IP = "srcIp";
89 private static final String DST_IP = "dstIp";
90 private static final String ANNOTATION_SEGMENT_ID = "segId";
daniel park128c52c2017-09-04 13:15:51 +090091
92 private static final String ID = "id";
93 private static final String MODE = "mode";
94 private static final String MOUSE = "mouse";
Daniel Park577b69c2018-07-16 17:29:34 +090095 private static final String TRACE_RESULT = "traceResult";
96 private static final String IS_SUCCESS = "isSuccess";
97 private static final String TRACE_SUCCESS = "traceSuccess";
98 private static final String FLOW_TRACE_RESULT = "flowTraceResult";
99 private static final String SRC_DEVICE_ID = "srcDeviceId";
100 private static final String DST_DEVICE_ID = "dstDeviceId";
Daniel Park577b69c2018-07-16 17:29:34 +0900101 private static final String OVS_VERSION_2_8 = "2.8";
102 private static final String OVS_VERSION_2_6 = "2.6";
103 private static final String FLAT = "FLAT";
104 private static final String VXLAN = "VXLAN";
105 private static final String VLAN = "VLAN";
106 private static final String DL_DST = "dl_dst=";
107 private static final String NW_DST = "nw_dst=";
108 private static final String DEFAULT_REQUEST_STRING = "sudo ovs-appctl ofproto/trace br-int ip,in_port=";
109 private static final String NW_SRC = "nw_src=";
110 private static final String COMMA = ",";
111
112
113 private static final long TIMEOUT_MS = 5000;
114 private static final long WAIT_OUTPUT_STREAM_SECOND = 2;
115 private static final int SSH_PORT = 22;
daniel park128c52c2017-09-04 13:15:51 +0900116
117 private enum Mode { IDLE, MOUSE }
118
119 private final Logger log = LoggerFactory.getLogger(getClass());
120
121 private DeviceService deviceService;
122 private HostService hostService;
123 private PathService pathService;
Daniel Park577b69c2018-07-16 17:29:34 +0900124 private OpenstackNodeService osNodeService;
125 private InstancePortService instancePortService;
126 private OpenstackNetworkService osNetService;
daniel park128c52c2017-09-04 13:15:51 +0900127 private Mode currentMode = Mode.IDLE;
128 private Element elementOfNote;
daniel park128c52c2017-09-04 13:15:51 +0900129
Daniel Park577b69c2018-07-16 17:29:34 +0900130 private final ExecutorService eventExecutor = newSingleThreadExecutor(
131 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
daniel park128c52c2017-09-04 13:15:51 +0900132
133 @Override
134 public void init(UiConnection connection, ServiceDirectory directory) {
135 super.init(connection, directory);
136 deviceService = directory.get(DeviceService.class);
137 hostService = directory.get(HostService.class);
138 pathService = directory.get(PathService.class);
Daniel Park577b69c2018-07-16 17:29:34 +0900139 osNodeService = directory.get(OpenstackNodeService.class);
140 instancePortService = directory.get(InstancePortService.class);
141 osNetService = directory.get(OpenstackNetworkService.class);
daniel park128c52c2017-09-04 13:15:51 +0900142 }
143
daniel park128c52c2017-09-04 13:15:51 +0900144 @Override
145 protected Collection<RequestHandler> createRequestHandlers() {
146 return ImmutableSet.of(
147 new DisplayStartHandler(),
148 new DisplayUpdateHandler(),
149 new DisplayStopHandler(),
150 new FlowTraceRequestHandler()
151 );
152 }
153
daniel park128c52c2017-09-04 13:15:51 +0900154 private final class DisplayStartHandler extends RequestHandler {
155
156 public DisplayStartHandler() {
157 super(OPENSTACK_NETWORKING_UI_START);
158 }
159
160 @Override
161 public void process(ObjectNode payload) {
162 String mode = string(payload, MODE);
163
164 log.debug("Start Display: mode [{}]", mode);
165 clearState();
166 clearForMode();
167
168 switch (mode) {
169 case MOUSE:
170 currentMode = Mode.MOUSE;
Daniel Parkbbdf4ba2018-07-23 16:23:42 +0900171 eventExecutor.execute(() -> sendMouseData());
172
daniel park128c52c2017-09-04 13:15:51 +0900173 break;
174
175 default:
176 currentMode = Mode.IDLE;
177 break;
178 }
179 }
180 }
181
182 private final class FlowTraceRequestHandler extends RequestHandler {
183 public FlowTraceRequestHandler() {
184 super(FLOW_TRACE_REQUEST);
185 }
186
187 @Override
188 public void process(ObjectNode payload) {
189 String srcIp = string(payload, SRC_IP);
190 String dstIp = string(payload, DST_IP);
Daniel Park577b69c2018-07-16 17:29:34 +0900191 String srcDeviceId = string(payload, SRC_DEVICE_ID);
192 String dstDeviceId = string(payload, DST_DEVICE_ID);
193 log.info("Flow trace request called with src IP: {}, dst IP: {}, src device ID: {}, dst device Id: {}",
194 srcIp,
195 dstIp,
196 srcDeviceId,
197 dstDeviceId);
Daniel Parkbbdf4ba2018-07-23 16:23:42 +0900198 eventExecutor.execute(() -> processFlowTraceRequest(srcIp, dstIp, srcDeviceId, dstDeviceId));
daniel park128c52c2017-09-04 13:15:51 +0900199 }
200 }
201
daniel park128c52c2017-09-04 13:15:51 +0900202 private final class DisplayUpdateHandler extends RequestHandler {
203 public DisplayUpdateHandler() {
204 super(OPENSTACK_NETWORKING_UI_UPDATE);
205 }
206
207 @Override
208 public void process(ObjectNode payload) {
209 String id = string(payload, ID);
210 log.debug("Update Display: id [{}]", id);
211 if (!Strings.isNullOrEmpty(id)) {
Daniel Parkbbdf4ba2018-07-23 16:23:42 +0900212 eventExecutor.execute(() -> updateForMode(id));
daniel park128c52c2017-09-04 13:15:51 +0900213 } else {
Daniel Parkbbdf4ba2018-07-23 16:23:42 +0900214 eventExecutor.execute(() -> clearForMode());
daniel park128c52c2017-09-04 13:15:51 +0900215 }
216 }
217 }
218
219 private final class DisplayStopHandler extends RequestHandler {
220 public DisplayStopHandler() {
221 super(OPENSTACK_NETWORKING_UI_STOP);
222 }
223
224 @Override
225 public void process(ObjectNode payload) {
226 log.debug("Stop Display");
227 clearState();
228 clearForMode();
229 }
230 }
231
232 // === ------------
233
234 private void clearState() {
235 currentMode = Mode.IDLE;
236 elementOfNote = null;
237 }
238
239 private void updateForMode(String id) {
240
241 try {
242 HostId hid = HostId.hostId(id);
243 elementOfNote = hostService.getHost(hid);
244
245 } catch (Exception e) {
246 try {
247 DeviceId did = DeviceId.deviceId(id);
248 elementOfNote = deviceService.getDevice(did);
249
250 } catch (Exception e2) {
251 log.debug("Unable to process ID [{}]", id);
252 elementOfNote = null;
253 }
254 }
255
256 switch (currentMode) {
257 case MOUSE:
258 sendMouseData();
259 break;
260
261 default:
262 break;
263 }
264
265 }
266
267 private void clearForMode() {
268 sendHighlights(new Highlights());
269 }
270
271 private void sendHighlights(Highlights highlights) {
272 sendMessage(TopoJson.highlightsMessage(highlights));
273 }
274
Daniel Parkbbdf4ba2018-07-23 16:23:42 +0900275 /**
276 * Sends JSON-based message to UI.
277 * @param type type
278 * @param payload payload
279 */
daniel park128c52c2017-09-04 13:15:51 +0900280 public void sendMessagetoUi(String type, ObjectNode payload) {
281 sendMessage(JsonUtils.envelope(type, payload));
282 }
283
284 private int getVni(Host host) {
285 String vni = host.annotations().value(ANNOTATION_SEGMENT_ID);
286
Daniel Parkbbdf4ba2018-07-23 16:23:42 +0900287 return vni == null ? 0 : Integer.parseInt(vni);
daniel park128c52c2017-09-04 13:15:51 +0900288 }
289
290 private void sendMouseData() {
291 Highlights highlights = new Highlights();
292
293 if (elementOfNote != null && elementOfNote instanceof Device) {
294 DeviceId deviceId = (DeviceId) elementOfNote.id();
295
296 List<OpenstackLink> edgeLinks = edgeLinks(deviceId);
297
298 edgeLinks.forEach(edgeLink -> {
299 highlights.add(edgeLink.highlight(OpenstackLink.RequestType.DEVICE_SELECTED));
300 });
301
302 hostService.getConnectedHosts(deviceId).forEach(host -> {
303 HostHighlight hostHighlight = new HostHighlight(host.id().toString());
304 hostHighlight.setBadge(createBadge(getVni(host)));
305 highlights.add(hostHighlight);
306 });
307
308 sendHighlights(highlights);
309
310 } else if (elementOfNote != null && elementOfNote instanceof Host) {
311
312 HostId hostId = HostId.hostId(elementOfNote.id().toString());
313 if (!hostMadeFromOpenstack(hostId)) {
314 return;
315 }
316
317 List<OpenstackLink> openstackLinks = linksInSameNetwork(hostId);
318
319 openstackLinks.forEach(openstackLink -> {
320 highlights.add(openstackLink.highlight(OpenstackLink.RequestType.HOST_SELECTED));
321 });
322
323 hostHighlightsInSameNetwork(hostId).forEach(highlights::add);
324
325 sendHighlights(highlights);
326
327 }
328 }
329
330 private boolean hostMadeFromOpenstack(HostId hostId) {
331 return hostService.getHost(hostId).annotations()
332 .value(ANNOTATION_NETWORK_ID) == null ? false : true;
333 }
334
335 private String networkId(HostId hostId) {
336 return hostService.getHost(hostId).annotations().value(ANNOTATION_NETWORK_ID);
337 }
338
339 private Set<HostHighlight> hostHighlightsInSameNetwork(HostId hostId) {
340
341 Set<HostHighlight> hostHighlights = Sets.newHashSet();
342 Streams.stream(hostService.getHosts())
343 .filter(host -> isHostInSameNetwork(host, networkId(hostId)))
344 .forEach(host -> {
345 HostHighlight hostHighlight = new HostHighlight(host.id().toString());
346 hostHighlight.setBadge(createBadge(getVni(host)));
347 hostHighlights.add(hostHighlight);
348 });
349
350 return hostHighlights;
351 }
352
353 private List<OpenstackLink> edgeLinks(DeviceId deviceId) {
354 OpenstackLinkMap openstackLinkMap = new OpenstackLinkMap();
355
356 hostService.getConnectedHosts(deviceId).forEach(host -> {
357 openstackLinkMap.add(createEdgeLink(host, true));
358 openstackLinkMap.add(createEdgeLink(host, false));
359 });
360
361 List<OpenstackLink> edgeLinks = Lists.newArrayList();
362
363 openstackLinkMap.biLinks().forEach(edgeLinks::add);
364
365 return edgeLinks;
366 }
367
368 private List<OpenstackLink> linksInSameNetwork(HostId hostId) {
369 OpenstackLinkMap linkMap = new OpenstackLinkMap();
370
371 Streams.stream(hostService.getHosts())
372 .filter(host -> isHostInSameNetwork(host, networkId(hostId)))
373 .forEach(host -> {
374 linkMap.add(createEdgeLink(host, true));
375 linkMap.add(createEdgeLink(host, false));
376
377 Set<Path> paths = pathService.getPaths(hostId,
378 host.id());
379
380 if (!paths.isEmpty()) {
381 paths.forEach(path -> path.links().forEach(linkMap::add));
382 }
383 });
384
385 List<OpenstackLink> openstackLinks = Lists.newArrayList();
386
387 linkMap.biLinks().forEach(openstackLinks::add);
388
389 return openstackLinks;
390 }
391
392 private boolean isHostInSameNetwork(Host host, String networkId) {
393 return hostService.getHost(host.id()).annotations()
394 .value(ANNOTATION_NETWORK_ID).equals(networkId);
395 }
396
397 private NodeBadge createBadge(int n) {
398 return NodeBadge.number(Status.INFO, n, "Openstack Node");
399 }
Daniel Park577b69c2018-07-16 17:29:34 +0900400
401 private void processFlowTraceRequest(String srcIp, String dstIp, String srcDeviceId, String dstDeviceId) {
402 boolean traceSuccess = true;
403
404 ObjectMapper mapper = new ObjectMapper();
405
406 ObjectNode traceResult = mapper.createObjectNode();
407
408 ArrayNode traceResultArray = traceResult.putArray(TRACE_RESULT);
409
410 OpenstackNode srcOpenstackNode = osNodeService.node(DeviceId.deviceId(srcDeviceId));
411 if (srcOpenstackNode == null) {
412 return;
413 }
414
415 if (srcOpenstackNode.sshAuthInfo() == null) {
416 log.error("Openstack node {} has no SSH authentication information..",
417 srcOpenstackNode.hostname());
418 return;
419 }
420
421 String traceResultForward = sendTraceRequestToNode(srcIp, dstIp, srcOpenstackNode);
422 if (traceResultForward == null) {
423 return;
424 }
425
426 log.debug("traceResultForward raw data: {}", traceResultForward);
427
428 ObjectNode traceResultForwardJson = null;
429
430 Device srcDevice = deviceService.getDevice(srcOpenstackNode.intgBridge());
Daniel Parke910e402018-07-26 11:25:01 +0900431 if (srcDevice.swVersion().startsWith(OVS_VERSION_2_8)) {
Daniel Park577b69c2018-07-16 17:29:34 +0900432 traceResultForwardJson = Ovs28FlowTraceResultParser.flowTraceResultInJson(
Daniel Parkbbdf4ba2018-07-23 16:23:42 +0900433 traceResultForward.trim(), srcOpenstackNode.hostname());
Daniel Park577b69c2018-07-16 17:29:34 +0900434 } else {
435 log.error("Currently OVS version {} is not supported",
436 deviceService.getDevice(srcOpenstackNode.intgBridge()));
437 }
438
439 if (traceResultForwardJson == null) {
440 return;
441 }
442
443 traceResultArray.add(traceResultForwardJson);
444
445 log.debug("traceResultForward Json: {}", traceResultForwardJson);
446
447 if (!traceResultForwardJson.get(IS_SUCCESS).asBoolean()) {
448 traceSuccess = false;
449 }
450
451 //TODO implements trace result in backward
452
453 traceResult.put(TRACE_SUCCESS, traceSuccess);
454 log.debug("traceResult Json: {}", traceResult);
455
456 sendMessagetoUi(FLOW_TRACE_RESULT, traceResult);
457
458 }
459
460 private String sendTraceRequestToNode(String srcIp, String dstIp, OpenstackNode openstackNode) {
461 String traceResult = null;
462 OpenstackSshAuth sshAuth = openstackNode.sshAuthInfo();
463
464 try (SshClient client = SshClient.setUpDefaultClient()) {
465 client.start();
466
467 try (ClientSession session = client
468 .connect(sshAuth.id(), openstackNode.managementIp().getIp4Address().toString(), SSH_PORT)
469 .verify(TIMEOUT_MS, TimeUnit.SECONDS).getSession()) {
470 session.addPasswordIdentity(sshAuth.password());
471 session.auth().verify(TIMEOUT_MS, TimeUnit.SECONDS);
472
473
474 try (ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_SHELL)) {
475
476 String requestString = traceRequestString(srcIp, dstIp, openstackNode);
477 if (requestString == null) {
478 return null;
479 }
480
481 log.debug("requestString: {}", requestString);
482 final InputStream inputStream =
483 new ByteArrayInputStream(requestString.getBytes());
484
485 OutputStream outputStream = new ByteArrayOutputStream();
486 OutputStream errStream = new ByteArrayOutputStream();
487
488 channel.setIn(new NoCloseInputStream(inputStream));
489 channel.setErr(errStream);
490 channel.setOut(outputStream);
491
492 Collection<ClientChannelEvent> eventList = Lists.newArrayList();
493 eventList.add(ClientChannelEvent.OPENED);
494
495 OpenFuture channelFuture = channel.open();
496
497 if (channelFuture.await(TIMEOUT_MS, TimeUnit.SECONDS)) {
498
499 long timeoutExpiredMs = System.currentTimeMillis() + TIMEOUT_MS;
500
501 while (!channelFuture.isOpened()) {
502 if ((timeoutExpiredMs - System.currentTimeMillis()) <= 0) {
503 log.error("Failed to open channel");
504 return null;
505 }
506 }
507 TimeUnit.SECONDS.sleep(WAIT_OUTPUT_STREAM_SECOND);
508
509 traceResult = ((ByteArrayOutputStream) outputStream).toString(Charsets.UTF_8.name());
510
511 channel.close();
512 }
513 } finally {
514 session.close();
515 }
516 } finally {
517 client.stop();
518 }
519
520 } catch (Exception e) {
521 log.error("Exception occurred because of {}", e.toString());
522 }
523
Daniel Parkbbdf4ba2018-07-23 16:23:42 +0900524 return traceResult;
Daniel Park577b69c2018-07-16 17:29:34 +0900525 }
526
527 private String traceRequestString(String srcIp, String dstIp, OpenstackNode openstackNode) {
528
529 Optional<InstancePort> instancePort = instancePortService.instancePorts().stream()
530 .filter(port -> port.ipAddress().getIp4Address().toString().equals(srcIp)
531 && port.deviceId().equals(openstackNode.intgBridge()))
532 .findAny();
533
534 if (!instancePort.isPresent()) {
535 return null;
536 }
537
538 String requestString = DEFAULT_REQUEST_STRING
539 + instancePort.get().portNumber().toString()
540 + COMMA
541 + NW_SRC
542 + srcIp
543 + COMMA;
544
545 if (osNetService.networkType(instancePort.get().networkId()).equals(VXLAN)) {
546 if (srcIp.equals(dstIp)) {
547 dstIp = osNetService.gatewayIp(instancePort.get().portId());
548 requestString = requestString + DL_DST + DEFAULT_GATEWAY_MAC_STR + COMMA;
549 } else if (!osNetService.ipPrefix(instancePort.get().portId()).contains(IpAddress.valueOf(dstIp))) {
550 requestString = requestString + DL_DST + DEFAULT_GATEWAY_MAC_STR + COMMA;
551 }
552 } else if (osNetService.networkType(instancePort.get().networkId()).equals(FLAT)) {
553 if (srcIp.equals(dstIp)) {
554 dstIp = osNetService.gatewayIp(instancePort.get().portId());
555 }
556 }
557
558 requestString = requestString + NW_DST + dstIp + "\n";
559
560 return requestString;
561 }
daniel park128c52c2017-09-04 13:15:51 +0900562}