blob: e4bfccdd238eb3526b18d4cbf43b37eaefc2b4ed [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 Vachuskad472c6e2014-11-07 19:11:05 -080018import com.fasterxml.jackson.databind.node.ObjectNode;
Thomas Vachuska7d638d32014-11-07 10:24:43 -080019import org.eclipse.jetty.websocket.WebSocket;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080020import org.onlab.onos.cluster.ClusterEvent;
21import org.onlab.onos.cluster.ClusterEventListener;
22import org.onlab.onos.cluster.ControllerNode;
Thomas Vachuska4830d392014-11-09 17:09:56 -080023import org.onlab.onos.core.ApplicationId;
24import org.onlab.onos.core.CoreService;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080025import org.onlab.onos.mastership.MastershipEvent;
26import org.onlab.onos.mastership.MastershipListener;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080027import org.onlab.onos.net.Device;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080028import org.onlab.onos.net.Host;
29import org.onlab.onos.net.HostId;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080030import org.onlab.onos.net.Link;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080031import org.onlab.onos.net.Path;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080032import org.onlab.onos.net.device.DeviceEvent;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080033import org.onlab.onos.net.device.DeviceListener;
Thomas Vachuska4830d392014-11-09 17:09:56 -080034import org.onlab.onos.net.flow.DefaultTrafficSelector;
35import org.onlab.onos.net.flow.DefaultTrafficTreatment;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080036import org.onlab.onos.net.host.HostEvent;
37import org.onlab.onos.net.host.HostListener;
Thomas Vachuska4830d392014-11-09 17:09:56 -080038import org.onlab.onos.net.intent.HostToHostIntent;
39import org.onlab.onos.net.intent.Intent;
40import org.onlab.onos.net.intent.IntentEvent;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080041import org.onlab.onos.net.intent.IntentId;
Thomas Vachuska4830d392014-11-09 17:09:56 -080042import org.onlab.onos.net.intent.IntentListener;
Thomas Vachuska4830d392014-11-09 17:09:56 -080043import org.onlab.onos.net.intent.PathIntent;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080044import org.onlab.onos.net.link.LinkEvent;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080045import org.onlab.onos.net.link.LinkListener;
Thomas Vachuska7d638d32014-11-07 10:24:43 -080046import org.onlab.osgi.ServiceDirectory;
47
48import java.io.IOException;
Thomas Vachuska4830d392014-11-09 17:09:56 -080049import java.util.List;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080050import java.util.Map;
Thomas Vachuska4830d392014-11-09 17:09:56 -080051import java.util.concurrent.ConcurrentHashMap;
Thomas Vachuska7d638d32014-11-07 10:24:43 -080052
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080053import static org.onlab.onos.cluster.ClusterEvent.Type.INSTANCE_ADDED;
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080054import static org.onlab.onos.net.DeviceId.deviceId;
Thomas Vachuska690e5f62014-11-09 08:26:47 -080055import static org.onlab.onos.net.HostId.hostId;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080056import static org.onlab.onos.net.device.DeviceEvent.Type.DEVICE_ADDED;
Thomas Vachuska4830d392014-11-09 17:09:56 -080057import static org.onlab.onos.net.host.HostEvent.Type.HOST_ADDED;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080058import static org.onlab.onos.net.link.LinkEvent.Type.LINK_ADDED;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080059
Thomas Vachuska7d638d32014-11-07 10:24:43 -080060/**
61 * Web socket capable of interacting with the GUI topology view.
62 */
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080063public class TopologyWebSocket
Thomas Vachuskaba5621e2014-11-12 01:47:19 -080064 extends TopologyMessages
65 implements WebSocket.OnTextMessage, WebSocket.OnControl {
66
67 private static final long MAX_AGE_MS = 15000;
68
69 private static final byte PING = 0x9;
70 private static final byte PONG = 0xA;
71 private static final byte[] PING_DATA = new byte[]{(byte) 0xde, (byte) 0xad};
Thomas Vachuska7d638d32014-11-07 10:24:43 -080072
Thomas Vachuska4830d392014-11-09 17:09:56 -080073 private static final String APP_ID = "org.onlab.onos.gui";
Thomas Vachuska4830d392014-11-09 17:09:56 -080074
75 private final ApplicationId appId;
Thomas Vachuskad472c6e2014-11-07 19:11:05 -080076
Thomas Vachuska7d638d32014-11-07 10:24:43 -080077 private Connection connection;
Thomas Vachuskaba5621e2014-11-12 01:47:19 -080078 private FrameConnection control;
Thomas Vachuska7d638d32014-11-07 10:24:43 -080079
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080080 private final ClusterEventListener clusterListener = new InternalClusterListener();
Thomas Vachuska690e5f62014-11-09 08:26:47 -080081 private final DeviceListener deviceListener = new InternalDeviceListener();
82 private final LinkListener linkListener = new InternalLinkListener();
83 private final HostListener hostListener = new InternalHostListener();
84 private final MastershipListener mastershipListener = new InternalMastershipListener();
Thomas Vachuska4830d392014-11-09 17:09:56 -080085 private final IntentListener intentListener = new InternalIntentListener();
Thomas Vachuska690e5f62014-11-09 08:26:47 -080086
Thomas Vachuska4830d392014-11-09 17:09:56 -080087 // Intents that are being monitored for the GUI
88 private static Map<IntentId, Long> intentsToMonitor = new ConcurrentHashMap<>();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -080089
Thomas Vachuskaba5621e2014-11-12 01:47:19 -080090 private long lastActive = System.currentTimeMillis();
91
Thomas Vachuska7d638d32014-11-07 10:24:43 -080092 /**
93 * Creates a new web-socket for serving data to GUI topology view.
94 *
95 * @param directory service directory
96 */
97 public TopologyWebSocket(ServiceDirectory directory) {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -080098 super(directory);
Thomas Vachuska4830d392014-11-09 17:09:56 -080099 appId = directory.get(CoreService.class).registerApplication(APP_ID);
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800100 }
101
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800102 /**
103 * Issues a close on the connection.
104 */
105 synchronized void close() {
106 if (connection.isOpen()) {
107 removeListeners();
108 connection.close();
109 }
110 }
111
112 /**
113 * Indicates if this connection is idle.
114 */
115 synchronized boolean isIdle() {
116 boolean idle = (System.currentTimeMillis() - lastActive) > MAX_AGE_MS;
117 if (idle || !connection.isOpen()) {
118 return true;
119 }
120 try {
121 control.sendControl(PING, PING_DATA, 0, PING_DATA.length);
122 } catch (IOException e) {
123 log.warn("Unable to send ping message due to: ", e);
124 }
125 return false;
126 }
127
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800128 @Override
129 public void onOpen(Connection connection) {
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800130 log.info("GUI client connected");
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800131 this.connection = connection;
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800132 this.control = (FrameConnection) connection;
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800133 addListeners();
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800134
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800135 sendAllInstances();
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800136 sendAllDevices();
137 sendAllLinks();
Thomas Vachuska4830d392014-11-09 17:09:56 -0800138 sendAllHosts();
139 }
140
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800141 @Override
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800142 public synchronized void onClose(int closeCode, String message) {
143 if (connection.isOpen()) {
144 removeListeners();
145 }
146 log.info("GUI client disconnected");
147 }
148
149 @Override
150 public boolean onControl(byte controlCode, byte[] data, int offset, int length) {
151 lastActive = System.currentTimeMillis();
152 return true;
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800153 }
154
155 @Override
156 public void onMessage(String data) {
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800157 lastActive = System.currentTimeMillis();
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800158 try {
159 ObjectNode event = (ObjectNode) mapper.reader().readTree(data);
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800160 String type = string(event, "event", "unknown");
Thomas Vachuskaf1fae002014-11-11 18:22:02 -0800161 if (type.equals("requestDetails")) {
162 requestDetails(event);
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800163 } else if (type.equals("updateMeta")) {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800164 updateMetaUi(event);
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800165 } else if (type.equals("requestPath")) {
Thomas Vachuska4830d392014-11-09 17:09:56 -0800166 createHostIntent(event);
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800167 } else if (type.equals("requestTraffic")) {
168 sendTraffic(event);
169 } else if (type.equals("cancelTraffic")) {
170 cancelTraffic(event);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800171 }
Thomas Vachuska4830d392014-11-09 17:09:56 -0800172 } catch (Exception e) {
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800173 log.warn("Unable to parse GUI request {} due to {}", data, e);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800174 }
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800175 }
176
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800177 // Sends the specified data to the client.
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800178 private synchronized void sendMessage(ObjectNode data) {
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800179 try {
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800180 if (connection.isOpen()) {
181 connection.sendMessage(data.toString());
182 }
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800183 } catch (IOException e) {
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800184 log.warn("Unable to send message {} to GUI due to {}", data, e);
Thomas Vachuskad1be50d2014-11-08 16:10:20 -0800185 }
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800186 }
187
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800188 // Sends all controller nodes to the client as node-added messages.
189 private void sendAllInstances() {
190 for (ControllerNode node : clusterService.getNodes()) {
191 sendMessage(instanceMessage(new ClusterEvent(INSTANCE_ADDED, node)));
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800192 }
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800193 }
194
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800195 // Sends all devices to the client as device-added messages.
196 private void sendAllDevices() {
197 for (Device device : deviceService.getDevices()) {
198 sendMessage(deviceMessage(new DeviceEvent(DEVICE_ADDED, device)));
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800199 }
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800200 }
201
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800202 // Sends all links to the client as link-added messages.
203 private void sendAllLinks() {
204 for (Link link : linkService.getLinks()) {
205 sendMessage(linkMessage(new LinkEvent(LINK_ADDED, link)));
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800206 }
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800207 }
208
209 // Sends all hosts to the client as host-added messages.
210 private void sendAllHosts() {
211 for (Host host : hostService.getHosts()) {
212 sendMessage(hostMessage(new HostEvent(HOST_ADDED, host)));
213 }
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800214 }
215
216 // Sends back device or host details.
Thomas Vachuskaf1fae002014-11-11 18:22:02 -0800217 private void requestDetails(ObjectNode event) {
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800218 ObjectNode payload = payload(event);
Thomas Vachuskaf1fae002014-11-11 18:22:02 -0800219 String type = string(payload, "class", "unknown");
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800220 if (type.equals("device")) {
221 sendMessage(deviceDetails(deviceId(string(payload, "id")),
222 number(event, "sid")));
223 } else if (type.equals("host")) {
224 sendMessage(hostDetails(hostId(string(payload, "id")),
225 number(event, "sid")));
226 }
227 }
228
Thomas Vachuska4830d392014-11-09 17:09:56 -0800229 // Creates host-to-host intent.
230 private void createHostIntent(ObjectNode event) {
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800231 ObjectNode payload = payload(event);
232 long id = number(event, "sid");
Thomas Vachuska4830d392014-11-09 17:09:56 -0800233 // TODO: add protection against device ids and non-existent hosts.
234 HostId one = hostId(string(payload, "one"));
235 HostId two = hostId(string(payload, "two"));
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800236
Thomas Vachuska4830d392014-11-09 17:09:56 -0800237 HostToHostIntent hostIntent = new HostToHostIntent(appId, one, two,
238 DefaultTrafficSelector.builder().build(),
239 DefaultTrafficTreatment.builder().build());
240 intentsToMonitor.put(hostIntent.id(), number(event, "sid"));
241 intentService.submit(hostIntent);
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800242 }
243
244 // Sends traffic message.
245 private void sendTraffic(ObjectNode event) {
246 ObjectNode payload = payload(event);
247 long id = number(event, "sid");
248 IntentId intentId = IntentId.valueOf(payload.path("intentId").asLong());
249
250 if (payload != null) {
251 payload.put("traffic", true);
252 sendMessage(envelope("showPath", id, payload));
253 } else {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800254 sendMessage(warning(id, "No path found"));
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800255 }
256 }
257
258 // Cancels sending traffic messages.
259 private void cancelTraffic(ObjectNode event) {
260 // TODO: implement this
261 }
262
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800263
264 // Adds all internal listeners.
265 private void addListeners() {
266 clusterService.addListener(clusterListener);
267 deviceService.addListener(deviceListener);
268 linkService.addListener(linkListener);
269 hostService.addListener(hostListener);
270 mastershipService.addListener(mastershipListener);
271 intentService.addListener(intentListener);
272 }
273
274 // Removes all internal listeners.
275 private void removeListeners() {
276 clusterService.removeListener(clusterListener);
277 deviceService.removeListener(deviceListener);
278 linkService.removeListener(linkListener);
279 hostService.removeListener(hostListener);
280 mastershipService.removeListener(mastershipListener);
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800281 intentService.removeListener(intentListener);
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800282 }
283
284 // Cluster event listener.
285 private class InternalClusterListener implements ClusterEventListener {
286 @Override
287 public void event(ClusterEvent event) {
288 sendMessage(instanceMessage(event));
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800289 }
290 }
291
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800292 // Device event listener.
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800293 private class InternalDeviceListener implements DeviceListener {
294 @Override
295 public void event(DeviceEvent event) {
296 sendMessage(deviceMessage(event));
297 }
298 }
299
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800300 // Link event listener.
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800301 private class InternalLinkListener implements LinkListener {
302 @Override
303 public void event(LinkEvent event) {
304 sendMessage(linkMessage(event));
305 }
306 }
307
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800308 // Host event listener.
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800309 private class InternalHostListener implements HostListener {
310 @Override
311 public void event(HostEvent event) {
312 sendMessage(hostMessage(event));
313 }
314 }
315
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800316 // Mastership event listener.
Thomas Vachuska690e5f62014-11-09 08:26:47 -0800317 private class InternalMastershipListener implements MastershipListener {
318 @Override
319 public void event(MastershipEvent event) {
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800320 // TODO: Is DeviceEvent.Type.DEVICE_MASTERSHIP_CHANGED the same?
Thomas Vachuskaba5621e2014-11-12 01:47:19 -0800321
Thomas Vachuskad472c6e2014-11-07 19:11:05 -0800322 }
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800323 }
Thomas Vachuska4830d392014-11-09 17:09:56 -0800324
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800325 // Intent event listener.
Thomas Vachuska4830d392014-11-09 17:09:56 -0800326 private class InternalIntentListener implements IntentListener {
327 @Override
328 public void event(IntentEvent event) {
329 Intent intent = event.subject();
330 Long sid = intentsToMonitor.get(intent.id());
331 if (sid != null) {
332 List<Intent> installable = intentService.getInstallableIntents(intent.id());
333 if (installable != null && !installable.isEmpty()) {
334 PathIntent pathIntent = (PathIntent) installable.iterator().next();
335 Path path = pathIntent.path();
Thomas Vachuska0f6baee2014-11-11 15:02:32 -0800336 ObjectNode payload = pathMessage(path, "host")
337 .put("intentId", intent.id().toString());
Thomas Vachuska4830d392014-11-09 17:09:56 -0800338 sendMessage(envelope("showPath", sid, payload));
339 }
340 }
341 }
342 }
Thomas Vachuskaa7c3dd12014-11-11 09:10:19 -0800343
Thomas Vachuska7d638d32014-11-07 10:24:43 -0800344}
345