blob: 8668096636289f0e5ef7bc6aa5a6a9311628ee66 [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";
101 private static final String SW_VERSION = "sw";
102 private static final String OVS_VERSION_2_8 = "2.8";
103 private static final String OVS_VERSION_2_6 = "2.6";
104 private static final String FLAT = "FLAT";
105 private static final String VXLAN = "VXLAN";
106 private static final String VLAN = "VLAN";
107 private static final String DL_DST = "dl_dst=";
108 private static final String NW_DST = "nw_dst=";
109 private static final String DEFAULT_REQUEST_STRING = "sudo ovs-appctl ofproto/trace br-int ip,in_port=";
110 private static final String NW_SRC = "nw_src=";
111 private static final String COMMA = ",";
112
113
114 private static final long TIMEOUT_MS = 5000;
115 private static final long WAIT_OUTPUT_STREAM_SECOND = 2;
116 private static final int SSH_PORT = 22;
daniel park128c52c2017-09-04 13:15:51 +0900117
118 private enum Mode { IDLE, MOUSE }
119
120 private final Logger log = LoggerFactory.getLogger(getClass());
121
122 private DeviceService deviceService;
123 private HostService hostService;
124 private PathService pathService;
Daniel Park577b69c2018-07-16 17:29:34 +0900125 private OpenstackNodeService osNodeService;
126 private InstancePortService instancePortService;
127 private OpenstackNetworkService osNetService;
daniel park128c52c2017-09-04 13:15:51 +0900128 private String restUrl;
129 private String restAuthInfo;
130 private Mode currentMode = Mode.IDLE;
131 private Element elementOfNote;
daniel park128c52c2017-09-04 13:15:51 +0900132
Daniel Park577b69c2018-07-16 17:29:34 +0900133 private final ExecutorService eventExecutor = newSingleThreadExecutor(
134 groupedThreads(this.getClass().getSimpleName(), "event-handler", log));
daniel park128c52c2017-09-04 13:15:51 +0900135
136 @Override
137 public void init(UiConnection connection, ServiceDirectory directory) {
138 super.init(connection, directory);
139 deviceService = directory.get(DeviceService.class);
140 hostService = directory.get(HostService.class);
141 pathService = directory.get(PathService.class);
Daniel Park577b69c2018-07-16 17:29:34 +0900142 osNodeService = directory.get(OpenstackNodeService.class);
143 instancePortService = directory.get(InstancePortService.class);
144 osNetService = directory.get(OpenstackNetworkService.class);
daniel park128c52c2017-09-04 13:15:51 +0900145 }
146
daniel park128c52c2017-09-04 13:15:51 +0900147 @Override
148 protected Collection<RequestHandler> createRequestHandlers() {
149 return ImmutableSet.of(
150 new DisplayStartHandler(),
151 new DisplayUpdateHandler(),
152 new DisplayStopHandler(),
153 new FlowTraceRequestHandler()
154 );
155 }
156
daniel park128c52c2017-09-04 13:15:51 +0900157 private final class DisplayStartHandler extends RequestHandler {
158
159 public DisplayStartHandler() {
160 super(OPENSTACK_NETWORKING_UI_START);
161 }
162
163 @Override
164 public void process(ObjectNode payload) {
165 String mode = string(payload, MODE);
166
167 log.debug("Start Display: mode [{}]", mode);
168 clearState();
169 clearForMode();
170
171 switch (mode) {
172 case MOUSE:
173 currentMode = Mode.MOUSE;
174 sendMouseData();
175 break;
176
177 default:
178 currentMode = Mode.IDLE;
179 break;
180 }
181 }
182 }
183
184 private final class FlowTraceRequestHandler extends RequestHandler {
185 public FlowTraceRequestHandler() {
186 super(FLOW_TRACE_REQUEST);
187 }
188
189 @Override
190 public void process(ObjectNode payload) {
191 String srcIp = string(payload, SRC_IP);
192 String dstIp = string(payload, DST_IP);
Daniel Park577b69c2018-07-16 17:29:34 +0900193 String srcDeviceId = string(payload, SRC_DEVICE_ID);
194 String dstDeviceId = string(payload, DST_DEVICE_ID);
195 log.info("Flow trace request called with src IP: {}, dst IP: {}, src device ID: {}, dst device Id: {}",
196 srcIp,
197 dstIp,
198 srcDeviceId,
199 dstDeviceId);
daniel park128c52c2017-09-04 13:15:51 +0900200
Daniel Park577b69c2018-07-16 17:29:34 +0900201 processFlowTraceRequest(srcIp, dstIp, srcDeviceId, dstDeviceId);
daniel park128c52c2017-09-04 13:15:51 +0900202 }
203 }
204
daniel park128c52c2017-09-04 13:15:51 +0900205 private final class DisplayUpdateHandler extends RequestHandler {
206 public DisplayUpdateHandler() {
207 super(OPENSTACK_NETWORKING_UI_UPDATE);
208 }
209
210 @Override
211 public void process(ObjectNode payload) {
212 String id = string(payload, ID);
213 log.debug("Update Display: id [{}]", id);
214 if (!Strings.isNullOrEmpty(id)) {
215 updateForMode(id);
216 } else {
217 clearForMode();
218 }
219 }
220 }
221
222 private final class DisplayStopHandler extends RequestHandler {
223 public DisplayStopHandler() {
224 super(OPENSTACK_NETWORKING_UI_STOP);
225 }
226
227 @Override
228 public void process(ObjectNode payload) {
229 log.debug("Stop Display");
230 clearState();
231 clearForMode();
232 }
233 }
234
235 // === ------------
236
237 private void clearState() {
238 currentMode = Mode.IDLE;
239 elementOfNote = null;
240 }
241
242 private void updateForMode(String id) {
243
244 try {
245 HostId hid = HostId.hostId(id);
246 elementOfNote = hostService.getHost(hid);
247
248 } catch (Exception e) {
249 try {
250 DeviceId did = DeviceId.deviceId(id);
251 elementOfNote = deviceService.getDevice(did);
252
253 } catch (Exception e2) {
254 log.debug("Unable to process ID [{}]", id);
255 elementOfNote = null;
256 }
257 }
258
259 switch (currentMode) {
260 case MOUSE:
261 sendMouseData();
262 break;
263
264 default:
265 break;
266 }
267
268 }
269
270 private void clearForMode() {
271 sendHighlights(new Highlights());
272 }
273
274 private void sendHighlights(Highlights highlights) {
275 sendMessage(TopoJson.highlightsMessage(highlights));
276 }
277
278 public void sendMessagetoUi(String type, ObjectNode payload) {
279 sendMessage(JsonUtils.envelope(type, payload));
280 }
281
282 private int getVni(Host host) {
283 String vni = host.annotations().value(ANNOTATION_SEGMENT_ID);
284
285 return vni == null ? 0 : Integer.valueOf(vni).intValue();
286 }
287
288 private void sendMouseData() {
289 Highlights highlights = new Highlights();
290
291 if (elementOfNote != null && elementOfNote instanceof Device) {
292 DeviceId deviceId = (DeviceId) elementOfNote.id();
293
294 List<OpenstackLink> edgeLinks = edgeLinks(deviceId);
295
296 edgeLinks.forEach(edgeLink -> {
297 highlights.add(edgeLink.highlight(OpenstackLink.RequestType.DEVICE_SELECTED));
298 });
299
300 hostService.getConnectedHosts(deviceId).forEach(host -> {
301 HostHighlight hostHighlight = new HostHighlight(host.id().toString());
302 hostHighlight.setBadge(createBadge(getVni(host)));
303 highlights.add(hostHighlight);
304 });
305
306 sendHighlights(highlights);
307
308 } else if (elementOfNote != null && elementOfNote instanceof Host) {
309
310 HostId hostId = HostId.hostId(elementOfNote.id().toString());
311 if (!hostMadeFromOpenstack(hostId)) {
312 return;
313 }
314
315 List<OpenstackLink> openstackLinks = linksInSameNetwork(hostId);
316
317 openstackLinks.forEach(openstackLink -> {
318 highlights.add(openstackLink.highlight(OpenstackLink.RequestType.HOST_SELECTED));
319 });
320
321 hostHighlightsInSameNetwork(hostId).forEach(highlights::add);
322
323 sendHighlights(highlights);
324
325 }
326 }
327
328 private boolean hostMadeFromOpenstack(HostId hostId) {
329 return hostService.getHost(hostId).annotations()
330 .value(ANNOTATION_NETWORK_ID) == null ? false : true;
331 }
332
333 private String networkId(HostId hostId) {
334 return hostService.getHost(hostId).annotations().value(ANNOTATION_NETWORK_ID);
335 }
336
337 private Set<HostHighlight> hostHighlightsInSameNetwork(HostId hostId) {
338
339 Set<HostHighlight> hostHighlights = Sets.newHashSet();
340 Streams.stream(hostService.getHosts())
341 .filter(host -> isHostInSameNetwork(host, networkId(hostId)))
342 .forEach(host -> {
343 HostHighlight hostHighlight = new HostHighlight(host.id().toString());
344 hostHighlight.setBadge(createBadge(getVni(host)));
345 hostHighlights.add(hostHighlight);
346 });
347
348 return hostHighlights;
349 }
350
351 private List<OpenstackLink> edgeLinks(DeviceId deviceId) {
352 OpenstackLinkMap openstackLinkMap = new OpenstackLinkMap();
353
354 hostService.getConnectedHosts(deviceId).forEach(host -> {
355 openstackLinkMap.add(createEdgeLink(host, true));
356 openstackLinkMap.add(createEdgeLink(host, false));
357 });
358
359 List<OpenstackLink> edgeLinks = Lists.newArrayList();
360
361 openstackLinkMap.biLinks().forEach(edgeLinks::add);
362
363 return edgeLinks;
364 }
365
366 private List<OpenstackLink> linksInSameNetwork(HostId hostId) {
367 OpenstackLinkMap linkMap = new OpenstackLinkMap();
368
369 Streams.stream(hostService.getHosts())
370 .filter(host -> isHostInSameNetwork(host, networkId(hostId)))
371 .forEach(host -> {
372 linkMap.add(createEdgeLink(host, true));
373 linkMap.add(createEdgeLink(host, false));
374
375 Set<Path> paths = pathService.getPaths(hostId,
376 host.id());
377
378 if (!paths.isEmpty()) {
379 paths.forEach(path -> path.links().forEach(linkMap::add));
380 }
381 });
382
383 List<OpenstackLink> openstackLinks = Lists.newArrayList();
384
385 linkMap.biLinks().forEach(openstackLinks::add);
386
387 return openstackLinks;
388 }
389
390 private boolean isHostInSameNetwork(Host host, String networkId) {
391 return hostService.getHost(host.id()).annotations()
392 .value(ANNOTATION_NETWORK_ID).equals(networkId);
393 }
394
395 private NodeBadge createBadge(int n) {
396 return NodeBadge.number(Status.INFO, n, "Openstack Node");
397 }
Daniel Park577b69c2018-07-16 17:29:34 +0900398
399 private void processFlowTraceRequest(String srcIp, String dstIp, String srcDeviceId, String dstDeviceId) {
400 boolean traceSuccess = true;
401
402 ObjectMapper mapper = new ObjectMapper();
403
404 ObjectNode traceResult = mapper.createObjectNode();
405
406 ArrayNode traceResultArray = traceResult.putArray(TRACE_RESULT);
407
408 OpenstackNode srcOpenstackNode = osNodeService.node(DeviceId.deviceId(srcDeviceId));
409 if (srcOpenstackNode == null) {
410 return;
411 }
412
413 if (srcOpenstackNode.sshAuthInfo() == null) {
414 log.error("Openstack node {} has no SSH authentication information..",
415 srcOpenstackNode.hostname());
416 return;
417 }
418
419 String traceResultForward = sendTraceRequestToNode(srcIp, dstIp, srcOpenstackNode);
420 if (traceResultForward == null) {
421 return;
422 }
423
424 log.debug("traceResultForward raw data: {}", traceResultForward);
425
426 ObjectNode traceResultForwardJson = null;
427
428 Device srcDevice = deviceService.getDevice(srcOpenstackNode.intgBridge());
429 if (srcDevice.annotations().value(SW_VERSION).startsWith(OVS_VERSION_2_8)) {
430 traceResultForwardJson = Ovs28FlowTraceResultParser.flowTraceResultInJson(
431 traceResultForward, srcOpenstackNode.hostname());
432 } else {
433 log.error("Currently OVS version {} is not supported",
434 deviceService.getDevice(srcOpenstackNode.intgBridge()));
435 }
436
437 if (traceResultForwardJson == null) {
438 return;
439 }
440
441 traceResultArray.add(traceResultForwardJson);
442
443 log.debug("traceResultForward Json: {}", traceResultForwardJson);
444
445 if (!traceResultForwardJson.get(IS_SUCCESS).asBoolean()) {
446 traceSuccess = false;
447 }
448
449 //TODO implements trace result in backward
450
451 traceResult.put(TRACE_SUCCESS, traceSuccess);
452 log.debug("traceResult Json: {}", traceResult);
453
454 sendMessagetoUi(FLOW_TRACE_RESULT, traceResult);
455
456 }
457
458 private String sendTraceRequestToNode(String srcIp, String dstIp, OpenstackNode openstackNode) {
459 String traceResult = null;
460 OpenstackSshAuth sshAuth = openstackNode.sshAuthInfo();
461
462 try (SshClient client = SshClient.setUpDefaultClient()) {
463 client.start();
464
465 try (ClientSession session = client
466 .connect(sshAuth.id(), openstackNode.managementIp().getIp4Address().toString(), SSH_PORT)
467 .verify(TIMEOUT_MS, TimeUnit.SECONDS).getSession()) {
468 session.addPasswordIdentity(sshAuth.password());
469 session.auth().verify(TIMEOUT_MS, TimeUnit.SECONDS);
470
471
472 try (ClientChannel channel = session.createChannel(ClientChannel.CHANNEL_SHELL)) {
473
474 String requestString = traceRequestString(srcIp, dstIp, openstackNode);
475 if (requestString == null) {
476 return null;
477 }
478
479 log.debug("requestString: {}", requestString);
480 final InputStream inputStream =
481 new ByteArrayInputStream(requestString.getBytes());
482
483 OutputStream outputStream = new ByteArrayOutputStream();
484 OutputStream errStream = new ByteArrayOutputStream();
485
486 channel.setIn(new NoCloseInputStream(inputStream));
487 channel.setErr(errStream);
488 channel.setOut(outputStream);
489
490 Collection<ClientChannelEvent> eventList = Lists.newArrayList();
491 eventList.add(ClientChannelEvent.OPENED);
492
493 OpenFuture channelFuture = channel.open();
494
495 if (channelFuture.await(TIMEOUT_MS, TimeUnit.SECONDS)) {
496
497 long timeoutExpiredMs = System.currentTimeMillis() + TIMEOUT_MS;
498
499 while (!channelFuture.isOpened()) {
500 if ((timeoutExpiredMs - System.currentTimeMillis()) <= 0) {
501 log.error("Failed to open channel");
502 return null;
503 }
504 }
505 TimeUnit.SECONDS.sleep(WAIT_OUTPUT_STREAM_SECOND);
506
507 traceResult = ((ByteArrayOutputStream) outputStream).toString(Charsets.UTF_8.name());
508
509 channel.close();
510 }
511 } finally {
512 session.close();
513 }
514 } finally {
515 client.stop();
516 }
517
518 } catch (Exception e) {
519 log.error("Exception occurred because of {}", e.toString());
520 }
521
522 return traceResult.trim();
523 }
524
525 private String traceRequestString(String srcIp, String dstIp, OpenstackNode openstackNode) {
526
527 Optional<InstancePort> instancePort = instancePortService.instancePorts().stream()
528 .filter(port -> port.ipAddress().getIp4Address().toString().equals(srcIp)
529 && port.deviceId().equals(openstackNode.intgBridge()))
530 .findAny();
531
532 if (!instancePort.isPresent()) {
533 return null;
534 }
535
536 String requestString = DEFAULT_REQUEST_STRING
537 + instancePort.get().portNumber().toString()
538 + COMMA
539 + NW_SRC
540 + srcIp
541 + COMMA;
542
543 if (osNetService.networkType(instancePort.get().networkId()).equals(VXLAN)) {
544 if (srcIp.equals(dstIp)) {
545 dstIp = osNetService.gatewayIp(instancePort.get().portId());
546 requestString = requestString + DL_DST + DEFAULT_GATEWAY_MAC_STR + COMMA;
547 } else if (!osNetService.ipPrefix(instancePort.get().portId()).contains(IpAddress.valueOf(dstIp))) {
548 requestString = requestString + DL_DST + DEFAULT_GATEWAY_MAC_STR + COMMA;
549 }
550 } else if (osNetService.networkType(instancePort.get().networkId()).equals(FLAT)) {
551 if (srcIp.equals(dstIp)) {
552 dstIp = osNetService.gatewayIp(instancePort.get().portId());
553 }
554 }
555
556 requestString = requestString + NW_DST + dstIp + "\n";
557
558 return requestString;
559 }
daniel park128c52c2017-09-04 13:15:51 +0900560}