blob: 761c2a30252b715f174f497a2950d23394f6f74d [file] [log] [blame]
Thomas Vachuska3553b302015-03-07 14:49:43 -08001/*
2 * Copyright 2015 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.onosproject.ui.impl;
17
18import com.fasterxml.jackson.databind.ObjectMapper;
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -070019import com.fasterxml.jackson.databind.node.ArrayNode;
Thomas Vachuska3553b302015-03-07 14:49:43 -080020import com.fasterxml.jackson.databind.node.ObjectNode;
21import org.eclipse.jetty.websocket.WebSocket;
22import org.onlab.osgi.ServiceDirectory;
Thomas Vachuskafc52fec2015-05-18 19:13:56 -070023import org.onlab.osgi.ServiceNotFoundException;
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -070024import org.onosproject.cluster.ClusterService;
25import org.onosproject.cluster.ControllerNode;
Thomas Vachuska3553b302015-03-07 14:49:43 -080026import org.onosproject.ui.UiConnection;
27import org.onosproject.ui.UiExtensionService;
Thomas Vachuska329af532015-03-10 02:08:33 -070028import org.onosproject.ui.UiMessageHandlerFactory;
Simon Hunta0ddb022015-05-01 09:53:01 -070029import org.onosproject.ui.UiMessageHandler;
Simon Hunte05cae42015-07-23 17:35:24 -070030import org.onosproject.ui.UiTopoOverlayFactory;
Thomas Vachuska3553b302015-03-07 14:49:43 -080031import org.slf4j.Logger;
32import org.slf4j.LoggerFactory;
33
34import java.io.IOException;
35import java.util.HashMap;
36import java.util.Map;
37
38/**
39 * Web socket capable of interacting with the GUI.
40 */
41public class UiWebSocket
42 implements UiConnection, WebSocket.OnTextMessage, WebSocket.OnControl {
43
44 private static final Logger log = LoggerFactory.getLogger(UiWebSocket.class);
45
Thomas Vachuska1a989c12015-06-09 18:29:22 -070046 private static final long MAX_AGE_MS = 30_000;
Thomas Vachuska3553b302015-03-07 14:49:43 -080047
48 private static final byte PING = 0x9;
49 private static final byte PONG = 0xA;
50 private static final byte[] PING_DATA = new byte[]{(byte) 0xde, (byte) 0xad};
51
52 private final ServiceDirectory directory;
53
54 private Connection connection;
55 private FrameConnection control;
56
57 private final ObjectMapper mapper = new ObjectMapper();
58
59 private long lastActive = System.currentTimeMillis();
60
Simon Hunta0ddb022015-05-01 09:53:01 -070061 private Map<String, UiMessageHandler> handlers;
Simon Hunte05cae42015-07-23 17:35:24 -070062 private TopoOverlayCache overlayCache;
Thomas Vachuska3553b302015-03-07 14:49:43 -080063
64 /**
65 * Creates a new web-socket for serving data to GUI.
66 *
67 * @param directory service directory
68 */
69 public UiWebSocket(ServiceDirectory directory) {
70 this.directory = directory;
71 }
72
73 /**
74 * Issues a close on the connection.
75 */
76 synchronized void close() {
Simon Hunte05cae42015-07-23 17:35:24 -070077 destroyHandlersAndOverlays();
Thomas Vachuska3553b302015-03-07 14:49:43 -080078 if (connection.isOpen()) {
79 connection.close();
80 }
81 }
82
83 /**
84 * Indicates if this connection is idle.
85 *
86 * @return true if idle or closed
87 */
88 synchronized boolean isIdle() {
Simon Huntda580882015-05-12 20:58:18 -070089 long quietFor = System.currentTimeMillis() - lastActive;
90 boolean idle = quietFor > MAX_AGE_MS;
Thomas Vachuska3553b302015-03-07 14:49:43 -080091 if (idle || (connection != null && !connection.isOpen())) {
Simon Huntda580882015-05-12 20:58:18 -070092 log.debug("IDLE (or closed) websocket [{} ms]", quietFor);
Thomas Vachuska3553b302015-03-07 14:49:43 -080093 return true;
94 } else if (connection != null) {
95 try {
96 control.sendControl(PING, PING_DATA, 0, PING_DATA.length);
97 } catch (IOException e) {
98 log.warn("Unable to send ping message due to: ", e);
99 }
100 }
101 return false;
102 }
103
104 @Override
105 public void onOpen(Connection connection) {
Thomas Vachuska3553b302015-03-07 14:49:43 -0800106 this.connection = connection;
107 this.control = (FrameConnection) connection;
Thomas Vachuskafc52fec2015-05-18 19:13:56 -0700108 try {
Simon Hunte05cae42015-07-23 17:35:24 -0700109 createHandlersAndOverlays();
Thomas Vachuskafc52fec2015-05-18 19:13:56 -0700110 sendInstanceData();
111 log.info("GUI client connected");
112
113 } catch (ServiceNotFoundException e) {
Brian O'Connor75deea62015-06-24 16:09:17 -0400114 log.warn("Unable to open GUI connection; services have been shut-down", e);
Thomas Vachuskafc52fec2015-05-18 19:13:56 -0700115 this.connection.close();
116 this.connection = null;
117 this.control = null;
118 }
Thomas Vachuska3553b302015-03-07 14:49:43 -0800119 }
120
121 @Override
122 public synchronized void onClose(int closeCode, String message) {
Simon Hunte05cae42015-07-23 17:35:24 -0700123 destroyHandlersAndOverlays();
Simon Huntda580882015-05-12 20:58:18 -0700124 log.info("GUI client disconnected [close-code={}, message={}]",
125 closeCode, message);
Thomas Vachuska3553b302015-03-07 14:49:43 -0800126 }
127
128 @Override
129 public boolean onControl(byte controlCode, byte[] data, int offset, int length) {
130 lastActive = System.currentTimeMillis();
131 return true;
132 }
133
134 @Override
135 public void onMessage(String data) {
Simon Hunte05cae42015-07-23 17:35:24 -0700136 log.debug("onMessage: {}", data);
Thomas Vachuska3553b302015-03-07 14:49:43 -0800137 lastActive = System.currentTimeMillis();
138 try {
139 ObjectNode message = (ObjectNode) mapper.reader().readTree(data);
Thomas Vachuska329af532015-03-10 02:08:33 -0700140 String type = message.path("event").asText("unknown");
Simon Hunta0ddb022015-05-01 09:53:01 -0700141 UiMessageHandler handler = handlers.get(type);
Thomas Vachuska3553b302015-03-07 14:49:43 -0800142 if (handler != null) {
143 handler.process(message);
144 } else {
145 log.warn("No GUI message handler for type {}", type);
146 }
147 } catch (Exception e) {
148 log.warn("Unable to parse GUI message {} due to {}", data, e);
149 log.debug("Boom!!!", e);
150 }
151 }
152
153 @Override
Thomas Vachuska35fa3d42015-04-30 10:11:47 -0700154 public synchronized void sendMessage(ObjectNode message) {
Thomas Vachuska3553b302015-03-07 14:49:43 -0800155 try {
156 if (connection.isOpen()) {
157 connection.sendMessage(message.toString());
158 }
159 } catch (IOException e) {
160 log.warn("Unable to send message {} to GUI due to {}", message, e);
161 log.debug("Boom!!!", e);
162 }
163 }
164
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700165 @Override
Thomas Vachuska35fa3d42015-04-30 10:11:47 -0700166 public synchronized void sendMessage(String type, long sid, ObjectNode payload) {
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700167 ObjectNode message = mapper.createObjectNode();
168 message.put("event", type);
169 if (sid > 0) {
170 message.put("sid", sid);
171 }
172 message.set("payload", payload);
173 sendMessage(message);
174
175 }
176
Thomas Vachuska3553b302015-03-07 14:49:43 -0800177 // Creates new message handlers.
Simon Hunte05cae42015-07-23 17:35:24 -0700178 private synchronized void createHandlersAndOverlays() {
179 log.debug("creating handlers and overlays...");
Thomas Vachuska3553b302015-03-07 14:49:43 -0800180 handlers = new HashMap<>();
Simon Hunte05cae42015-07-23 17:35:24 -0700181 overlayCache = new TopoOverlayCache();
182
Thomas Vachuska3553b302015-03-07 14:49:43 -0800183 UiExtensionService service = directory.get(UiExtensionService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700184 service.getExtensions().forEach(ext -> {
185 UiMessageHandlerFactory factory = ext.messageHandlerFactory();
186 if (factory != null) {
187 factory.newHandlers().forEach(handler -> {
188 handler.init(this, directory);
189 handler.messageTypes().forEach(type -> handlers.put(type, handler));
Simon Hunte05cae42015-07-23 17:35:24 -0700190
191 // need to inject the overlay cache into topology message handler
192 if (handler instanceof TopologyViewMessageHandler) {
193 ((TopologyViewMessageHandler) handler).setOverlayCache(overlayCache);
194 }
Thomas Vachuska329af532015-03-10 02:08:33 -0700195 });
196 }
Simon Hunte05cae42015-07-23 17:35:24 -0700197
198 UiTopoOverlayFactory overlayFactory = ext.topoOverlayFactory();
199 if (overlayFactory != null) {
200 overlayFactory.newOverlays().forEach(overlayCache::add);
201 }
Thomas Vachuska329af532015-03-10 02:08:33 -0700202 });
Simon Hunte05cae42015-07-23 17:35:24 -0700203 log.debug("#handlers = {}, #overlays = {}", handlers.size(),
204 overlayCache.size());
Thomas Vachuska3553b302015-03-07 14:49:43 -0800205 }
206
207 // Destroys message handlers.
Simon Hunte05cae42015-07-23 17:35:24 -0700208 private synchronized void destroyHandlersAndOverlays() {
209 log.debug("destroying handlers and overlays...");
Thomas Vachuska3553b302015-03-07 14:49:43 -0800210 handlers.forEach((type, handler) -> handler.destroy());
211 handlers.clear();
Simon Hunte05cae42015-07-23 17:35:24 -0700212
213 if (overlayCache != null) {
214 overlayCache.destroy();
215 overlayCache = null;
216 }
Thomas Vachuska3553b302015-03-07 14:49:43 -0800217 }
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700218
219 // Sends cluster node/instance information to allow GUI to fail-over.
220 private void sendInstanceData() {
221 ClusterService service = directory.get(ClusterService.class);
222 ArrayNode instances = mapper.createArrayNode();
223
224 for (ControllerNode node : service.getNodes()) {
225 ObjectNode instance = mapper.createObjectNode()
226 .put("id", node.id().toString())
227 .put("ip", node.ip().toString())
228 .put("uiAttached", node.equals(service.getLocalNode()));
229 instances.add(instance);
230 }
231
232 ObjectNode payload = mapper.createObjectNode();
Thomas Vachuska20084b72015-03-11 13:46:50 -0700233 payload.set("clusterNodes", instances);
234 sendMessage("bootstrap", 0, payload);
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700235 }
236
Thomas Vachuska3553b302015-03-07 14:49:43 -0800237}
238