blob: 99e9aeb9cde230eb14861c787b03c20205b503d4 [file] [log] [blame]
Thomas Vachuska7d638d32014-11-07 10:24:43 -08001/*
2 * Copyright 2014 Open Networking Laboratory
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.onlab.onos.gui;
17
Thomas Vachuskadea45ff2014-11-12 18:35:46 -080018import com.fasterxml.jackson.databind.node.ArrayNode;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080019import com.fasterxml.jackson.databind.node.ObjectNode;
Thomas Vachuska7d638d32014-11-07 10:24:43 -080020import org.eclipse.jetty.websocket.WebSocket;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080021import org.onlab.onos.cluster.ClusterEvent;
22import org.onlab.onos.cluster.ClusterEventListener;
23import org.onlab.onos.cluster.ControllerNode;
Thomas Vachuska4830d392014-11-09 17:09:56 -080024import org.onlab.onos.core.ApplicationId;
25import org.onlab.onos.core.CoreService;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080026import org.onlab.onos.net.Device;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080027import org.onlab.onos.net.Host;
28import org.onlab.onos.net.HostId;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080029import org.onlab.onos.net.Link;
30import org.onlab.onos.net.device.DeviceEvent;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080031import org.onlab.onos.net.device.DeviceListener;
Thomas Vachuska4830d392014-11-09 17:09:56 -080032import org.onlab.onos.net.flow.DefaultTrafficSelector;
33import org.onlab.onos.net.flow.DefaultTrafficTreatment;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080034import org.onlab.onos.net.host.HostEvent;
35import org.onlab.onos.net.host.HostListener;
Thomas Vachuska4830d392014-11-09 17:09:56 -080036import org.onlab.onos.net.intent.HostToHostIntent;
37import org.onlab.onos.net.intent.Intent;
38import org.onlab.onos.net.intent.IntentEvent;
Thomas Vachuska4830d392014-11-09 17:09:56 -080039import org.onlab.onos.net.intent.IntentListener;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080040import org.onlab.onos.net.link.LinkEvent;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080041import org.onlab.onos.net.link.LinkListener;
Thomas Vachuska7d638d32014-11-07 10:24:43 -080042import org.onlab.osgi.ServiceDirectory;
43
44import java.io.IOException;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -080045import java.util.Set;
Thomas Vachuska22e34922014-11-14 00:40:55 -080046import java.util.Timer;
47import java.util.TimerTask;
Thomas Vachuska7d638d32014-11-07 10:24:43 -080048
Thomas Vachuskae7591e52014-11-13 21:31:15 -080049import static com.google.common.base.Strings.isNullOrEmpty;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080050import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_ADDED;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080051import static org.onlab.onos.net.DeviceId.deviceId;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080052import static org.onlab.onos.net.HostId.hostId;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080053import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
Thomas Vachuska4830d392014-11-09 17:09:56 -080054import static org.onlab.onos.net.host.HostEvent.Type.HOST_ADDED;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080055import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080056
Thomas Vachuska7d638d32014-11-07 10:24:43 -080057/**
58 * Web socket capable of interacting with the GUI topology view.
59 */
Thomas Vachuska7c27ad72014-11-14 16:20:10 -080060public class TopologyViewWebSocket
61 extends TopologyViewMessages
Thomas Vachuskaba5621e2014-11-12 01:47:19 -080062 implements WebSocket.OnTextMessage, WebSocket.OnControl {
63
64 private static final long MAX_AGE_MS = 15000;
65
66 private static final byte PING = 0x9;
67 private static final byte PONG = 0xA;
68 private static final byte[] PING_DATA = new byte[]{(byte) 0xde, (byte) 0xad};
Thomas Vachuska7d638d32014-11-07 10:24:43 -080069
Thomas Vachuska4830d392014-11-09 17:09:56 -080070 private static final String APP_ID = "org.onlab.onos.gui";
Thomas Vachuska4830d392014-11-09 17:09:56 -080071
Thomas Vachuska7c27ad72014-11-14 16:20:10 -080072 private static final long TRAFFIC_FREQUENCY_SEC = 1000;
Thomas Vachuska22e34922014-11-14 00:40:55 -080073
Thomas Vachuska4830d392014-11-09 17:09:56 -080074 private final ApplicationId appId;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080075
Thomas Vachuska7d638d32014-11-07 10:24:43 -080076 private Connection connection;
Thomas Vachuskaba5621e2014-11-12 01:47:19 -080077 private FrameConnection control;
Thomas Vachuska7d638d32014-11-07 10:24:43 -080078
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080079 private final ClusterEventListener clusterListener = new InternalClusterListener();
Thomas Vachuska690e5f62014-11-09 08:26:47 -080080 private final DeviceListener deviceListener = new InternalDeviceListener();
81 private final LinkListener linkListener = new InternalLinkListener();
82 private final HostListener hostListener = new InternalHostListener();
Thomas Vachuska4830d392014-11-09 17:09:56 -080083 private final IntentListener intentListener = new InternalIntentListener();
Thomas Vachuska690e5f62014-11-09 08:26:47 -080084
Thomas Vachuska4830d392014-11-09 17:09:56 -080085 // Intents that are being monitored for the GUI
Thomas Vachuska22e34922014-11-14 00:40:55 -080086 private ObjectNode monitorRequest;
87 private final Timer timer = new Timer("intent-traffic-monitor");
88 private final TimerTask timerTask = new IntentTrafficMonitor();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080089
Thomas Vachuskaba5621e2014-11-12 01:47:19 -080090 private long lastActive = System.currentTimeMillis();
Thomas Vachuskadea45ff2014-11-12 18:35:46 -080091 private boolean listenersRemoved = false;
Thomas Vachuskaba5621e2014-11-12 01:47:19 -080092
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -080093 private TopologyViewIntentFilter intentFilter;
94
Thomas Vachuska7d638d32014-11-07 10:24:43 -080095 /**
96 * Creates a new web-socket for serving data to GUI topology view.
97 *
98 * @param directory service directory
99 */
Thomas Vachuska7c27ad72014-11-14 16:20:10 -0800100 public TopologyViewWebSocket(ServiceDirectory directory) {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800101 super(directory);
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800102
103 intentFilter = new TopologyViewIntentFilter(intentService, deviceService,
104 hostService, linkService);
Thomas Vachuska4830d392014-11-09 17:09:56 -0800105 appId = directory.get(CoreService.class).registerApplication(APP_ID);
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800106 }
107
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800108 /**
109 * Issues a close on the connection.
110 */
111 synchronized void close() {
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800112 removeListeners();
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800113 if (connection.isOpen()) {
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800114 connection.close();
115 }
116 }
117
118 /**
119 * Indicates if this connection is idle.
Thomas Vachuska3266abf2014-11-13 09:28:46 -0800120 *
121 * @return true if idle or closed
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800122 */
123 synchronized boolean isIdle() {
124 boolean idle = (System.currentTimeMillis() - lastActive) > MAX_AGE_MS;
125 if (idle || !connection.isOpen()) {
126 return true;
127 }
128 try {
129 control.sendControl(PING, PING_DATA, 0, PING_DATA.length);
130 } catch (IOException e) {
131 log.warn("Unable to send ping message due to: ", e);
132 }
133 return false;
134 }
135
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800136 @Override
137 public void onOpen(Connection connection) {
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800138 log.info("GUI client connected");
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800139 this.connection = connection;
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800140 this.control = (FrameConnection) connection;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800141 addListeners();
Thomas Vachuska22e34922014-11-14 00:40:55 -0800142 timer.schedule(timerTask, TRAFFIC_FREQUENCY_SEC, TRAFFIC_FREQUENCY_SEC);
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800143
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800144 sendAllInstances();
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800145 sendAllDevices();
146 sendAllLinks();
Thomas Vachuska4830d392014-11-09 17:09:56 -0800147 sendAllHosts();
148 }
149
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800150 @Override
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800151 public synchronized void onClose(int closeCode, String message) {
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800152 removeListeners();
Thomas Vachuska22e34922014-11-14 00:40:55 -0800153 timer.cancel();
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800154 log.info("GUI client disconnected");
155 }
156
157 @Override
158 public boolean onControl(byte controlCode, byte[] data, int offset, int length) {
159 lastActive = System.currentTimeMillis();
160 return true;
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800161 }
162
163 @Override
164 public void onMessage(String data) {
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800165 lastActive = System.currentTimeMillis();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800166 try {
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800167 processMessage((ObjectNode) mapper.reader().readTree(data));
Thomas Vachuska4830d392014-11-09 17:09:56 -0800168 } catch (Exception e) {
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800169 log.warn("Unable to parse GUI request {} due to {}", data, e);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800170 }
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800171 }
172
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800173 // Processes the specified event.
174 private void processMessage(ObjectNode event) {
175 String type = string(event, "event", "unknown");
176 if (type.equals("requestDetails")) {
177 requestDetails(event);
178 } else if (type.equals("updateMeta")) {
179 updateMetaUi(event);
180 } else if (type.equals("addHostIntent")) {
181 createHostIntent(event);
182 } else if (type.equals("requestTraffic")) {
183 requestTraffic(event);
184 } else if (type.equals("requestAllTraffic")) {
185 requestAllTraffic(event);
186 } else if (type.equals("cancelTraffic")) {
187 cancelTraffic(event);
188 }
189 }
190
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800191 // Sends the specified data to the client.
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800192 private synchronized void sendMessage(ObjectNode data) {
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800193 try {
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800194 if (connection.isOpen()) {
195 connection.sendMessage(data.toString());
196 }
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800197 } catch (IOException e) {
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800198 log.warn("Unable to send message {} to GUI due to {}", data, e);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800199 }
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800200 }
201
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800202 // Sends all controller nodes to the client as node-added messages.
203 private void sendAllInstances() {
204 for (ControllerNode node : clusterService.getNodes()) {
205 sendMessage(instanceMessage(new ClusterEvent(INSTANCE_ADDED, node)));
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800206 }
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800207 }
208
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800209 // Sends all devices to the client as device-added messages.
210 private void sendAllDevices() {
211 for (Device device : deviceService.getDevices()) {
212 sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800213 }
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800214 }
215
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800216 // Sends all links to the client as link-added messages.
217 private void sendAllLinks() {
218 for (Link link : linkService.getLinks()) {
219 sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link)));
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800220 }
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800221 }
222
223 // Sends all hosts to the client as host-added messages.
224 private void sendAllHosts() {
225 for (Host host : hostService.getHosts()) {
226 sendMessage(hostMessage(new HostEvent(HOST_ADDED, host)));
227 }
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800228 }
229
230 // Sends back device or host details.
Thomas Vachuskaf1fae002014-11-11 18:22:02 -0800231 private void requestDetails(ObjectNode event) {
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800232 ObjectNode payload = payload(event);
Thomas Vachuskaf1fae002014-11-11 18:22:02 -0800233 String type = string(payload, "class", "unknown");
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800234 long sid = number(event, "sid");
235
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800236 if (type.equals("device")) {
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800237 sendMessage(deviceDetails(deviceId(string(payload, "id")), sid));
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800238 } else if (type.equals("host")) {
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800239 sendMessage(hostDetails(hostId(string(payload, "id")), sid));
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800240 }
241 }
242
Thomas Vachuska4830d392014-11-09 17:09:56 -0800243 // Creates host-to-host intent.
244 private void createHostIntent(ObjectNode event) {
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800245 ObjectNode payload = payload(event);
246 long id = number(event, "sid");
Thomas Vachuska4830d392014-11-09 17:09:56 -0800247 // TODO: add protection against device ids and non-existent hosts.
248 HostId one = hostId(string(payload, "one"));
249 HostId two = hostId(string(payload, "two"));
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800250
Thomas Vachuska4830d392014-11-09 17:09:56 -0800251 HostToHostIntent hostIntent = new HostToHostIntent(appId, one, two,
252 DefaultTrafficSelector.builder().build(),
253 DefaultTrafficTreatment.builder().build());
Thomas Vachuska22e34922014-11-14 00:40:55 -0800254 monitorRequest = event;
Thomas Vachuska4830d392014-11-09 17:09:56 -0800255 intentService.submit(hostIntent);
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800256 }
257
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800258 // Subscribes for host traffic messages.
259 private synchronized void requestAllTraffic(ObjectNode event) {
260 ObjectNode payload = payload(event);
261 long sid = number(event, "sid");
262 monitorRequest = event;
263 sendMessage(trafficSummaryMessage(sid));
264 }
265
266 // Subscribes for host traffic messages.
Thomas Vachuska22e34922014-11-14 00:40:55 -0800267 private synchronized void requestTraffic(ObjectNode event) {
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800268 ObjectNode payload = payload(event);
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800269 if (!payload.has("ids")) {
270 return;
271 }
272
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800273 long sid = number(event, "sid");
Thomas Vachuska22e34922014-11-14 00:40:55 -0800274 monitorRequest = event;
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800275
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800276 // Get the set of selected hosts and their intents.
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800277 ArrayNode ids = (ArrayNode) payload.path("ids");
278 Set<Host> hosts = getHosts(ids);
279 Set<Device> devices = getDevices(ids);
280 Set<Intent> intents = intentFilter.findPathIntents(hosts, devices);
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800281
282 // If there is a hover node, include it in the hosts and find intents.
283 String hover = string(payload, "hover");
284 Set<Intent> hoverIntents;
285 if (!isNullOrEmpty(hover)) {
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800286 addHover(hosts, devices, hover);
287 hoverIntents = intentFilter.findPathIntents(hosts, devices);
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800288 intents.removeAll(hoverIntents);
289
290 // Send an initial message to highlight all links of all monitored intents.
291 sendMessage(trafficMessage(sid,
292 new TrafficClass("primary", hoverIntents),
293 new TrafficClass("secondary", intents)));
294
295 } else {
296 // Send an initial message to highlight all links of all monitored intents.
297 sendMessage(trafficMessage(sid, new TrafficClass("primary", intents)));
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800298 }
299 }
300
301 // Cancels sending traffic messages.
302 private void cancelTraffic(ObjectNode event) {
Thomas Vachuskae7591e52014-11-13 21:31:15 -0800303 sendMessage(trafficMessage(number(event, "sid")));
Thomas Vachuska22e34922014-11-14 00:40:55 -0800304 monitorRequest = null;
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800305 }
306
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800307 // Adds all internal listeners.
308 private void addListeners() {
309 clusterService.addListener(clusterListener);
310 deviceService.addListener(deviceListener);
311 linkService.addListener(linkListener);
312 hostService.addListener(hostListener);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800313 intentService.addListener(intentListener);
314 }
315
316 // Removes all internal listeners.
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800317 private synchronized void removeListeners() {
318 if (!listenersRemoved) {
319 listenersRemoved = true;
320 clusterService.removeListener(clusterListener);
321 deviceService.removeListener(deviceListener);
322 linkService.removeListener(linkListener);
323 hostService.removeListener(hostListener);
Thomas Vachuskadea45ff2014-11-12 18:35:46 -0800324 intentService.removeListener(intentListener);
325 }
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800326 }
327
328 // Cluster event listener.
329 private class InternalClusterListener implements ClusterEventListener {
330 @Override
331 public void event(ClusterEvent event) {
332 sendMessage(instanceMessage(event));
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800333 }
334 }
335
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800336 // Device event listener.
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800337 private class InternalDeviceListener implements DeviceListener {
338 @Override
339 public void event(DeviceEvent event) {
340 sendMessage(deviceMessage(event));
341 }
342 }
343
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800344 // Link event listener.
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800345 private class InternalLinkListener implements LinkListener {
346 @Override
347 public void event(LinkEvent event) {
348 sendMessage(linkMessage(event));
349 }
350 }
351
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800352 // Host event listener.
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800353 private class InternalHostListener implements HostListener {
354 @Override
355 public void event(HostEvent event) {
356 sendMessage(hostMessage(event));
357 }
358 }
359
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800360 // Intent event listener.
Thomas Vachuska4830d392014-11-09 17:09:56 -0800361 private class InternalIntentListener implements IntentListener {
362 @Override
363 public void event(IntentEvent event) {
Thomas Vachuska22e34922014-11-14 00:40:55 -0800364 if (monitorRequest != null) {
365 requestTraffic(monitorRequest);
Thomas Vachuska4830d392014-11-09 17:09:56 -0800366 }
367 }
368 }
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800369
Thomas Vachuska22e34922014-11-14 00:40:55 -0800370 private class IntentTrafficMonitor extends TimerTask {
371 @Override
372 public void run() {
373 if (monitorRequest != null) {
Thomas Vachuska5fedb7a2014-11-20 00:55:08 -0800374 String type = string(monitorRequest, "event", "unknown");
375 if (type.equals("requestAllTraffic")) {
376 requestAllTraffic(monitorRequest);
377 } else {
378 requestTraffic(monitorRequest);
379 }
Thomas Vachuska22e34922014-11-14 00:40:55 -0800380 }
381 }
382 }
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800383}
384