blob: 4ddfc739e7f13801bbedfeb9c9bcbd70259d9cdb [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;
Simon Hunt7092cc42016-04-06 18:40:17 -070031import org.onosproject.ui.impl.topo.UiTopoSession;
Simon Hunt629b99e2015-07-27 17:38:33 -070032import org.onosproject.ui.topo.TopoConstants;
Thomas Vachuska3553b302015-03-07 14:49:43 -080033import org.slf4j.Logger;
34import org.slf4j.LoggerFactory;
35
36import java.io.IOException;
37import java.util.HashMap;
38import java.util.Map;
39
40/**
Simon Hunt7092cc42016-04-06 18:40:17 -070041 * Web socket capable of interacting with the Web UI.
Thomas Vachuska3553b302015-03-07 14:49:43 -080042 */
43public class UiWebSocket
44 implements UiConnection, WebSocket.OnTextMessage, WebSocket.OnControl {
45
46 private static final Logger log = LoggerFactory.getLogger(UiWebSocket.class);
47
Thomas Vachuska1a989c12015-06-09 18:29:22 -070048 private static final long MAX_AGE_MS = 30_000;
Thomas Vachuska3553b302015-03-07 14:49:43 -080049
50 private static final byte PING = 0x9;
51 private static final byte PONG = 0xA;
52 private static final byte[] PING_DATA = new byte[]{(byte) 0xde, (byte) 0xad};
53
Simon Hunt7092cc42016-04-06 18:40:17 -070054 private final ObjectMapper mapper = new ObjectMapper();
Thomas Vachuska3553b302015-03-07 14:49:43 -080055 private final ServiceDirectory directory;
Simon Hunt7092cc42016-04-06 18:40:17 -070056 private final UiTopoSession topoSession;
Thomas Vachuska3553b302015-03-07 14:49:43 -080057
58 private Connection connection;
59 private FrameConnection control;
Thomas Vachuska0af26912016-03-21 21:37:30 -070060 private String userName;
Thomas Vachuska3553b302015-03-07 14:49:43 -080061
Thomas Vachuska3553b302015-03-07 14:49:43 -080062 private long lastActive = System.currentTimeMillis();
63
Simon Hunta0ddb022015-05-01 09:53:01 -070064 private Map<String, UiMessageHandler> handlers;
Simon Hunte05cae42015-07-23 17:35:24 -070065 private TopoOverlayCache overlayCache;
Thomas Vachuska3553b302015-03-07 14:49:43 -080066
67 /**
68 * Creates a new web-socket for serving data to GUI.
69 *
70 * @param directory service directory
Thomas Vachuska0af26912016-03-21 21:37:30 -070071 * @param userName user name of the logged-in user
Thomas Vachuska3553b302015-03-07 14:49:43 -080072 */
Thomas Vachuska0af26912016-03-21 21:37:30 -070073 public UiWebSocket(ServiceDirectory directory, String userName) {
Thomas Vachuska3553b302015-03-07 14:49:43 -080074 this.directory = directory;
Thomas Vachuska0af26912016-03-21 21:37:30 -070075 this.userName = userName;
Simon Hunt7092cc42016-04-06 18:40:17 -070076 this.topoSession = new UiTopoSession(this);
Thomas Vachuska0af26912016-03-21 21:37:30 -070077 }
78
79 @Override
80 public String userName() {
81 return userName;
Thomas Vachuska3553b302015-03-07 14:49:43 -080082 }
83
84 /**
85 * Issues a close on the connection.
86 */
87 synchronized void close() {
Simon Hunte05cae42015-07-23 17:35:24 -070088 destroyHandlersAndOverlays();
Thomas Vachuska3553b302015-03-07 14:49:43 -080089 if (connection.isOpen()) {
90 connection.close();
91 }
92 }
93
94 /**
95 * Indicates if this connection is idle.
96 *
97 * @return true if idle or closed
98 */
99 synchronized boolean isIdle() {
Simon Huntda580882015-05-12 20:58:18 -0700100 long quietFor = System.currentTimeMillis() - lastActive;
101 boolean idle = quietFor > MAX_AGE_MS;
Thomas Vachuska3553b302015-03-07 14:49:43 -0800102 if (idle || (connection != null && !connection.isOpen())) {
Simon Huntda580882015-05-12 20:58:18 -0700103 log.debug("IDLE (or closed) websocket [{} ms]", quietFor);
Thomas Vachuska3553b302015-03-07 14:49:43 -0800104 return true;
105 } else if (connection != null) {
106 try {
107 control.sendControl(PING, PING_DATA, 0, PING_DATA.length);
108 } catch (IOException e) {
109 log.warn("Unable to send ping message due to: ", e);
110 }
111 }
112 return false;
113 }
114
115 @Override
Satish K598c28d2015-11-24 17:20:40 +0530116 public synchronized void onOpen(Connection connection) {
Thomas Vachuska3553b302015-03-07 14:49:43 -0800117 this.connection = connection;
118 this.control = (FrameConnection) connection;
Thomas Vachuskafc52fec2015-05-18 19:13:56 -0700119 try {
Simon Hunt7092cc42016-04-06 18:40:17 -0700120 topoSession.init();
Simon Hunte05cae42015-07-23 17:35:24 -0700121 createHandlersAndOverlays();
Thomas Vachuskafc52fec2015-05-18 19:13:56 -0700122 sendInstanceData();
123 log.info("GUI client connected");
124
125 } catch (ServiceNotFoundException e) {
Brian O'Connor75deea62015-06-24 16:09:17 -0400126 log.warn("Unable to open GUI connection; services have been shut-down", e);
Thomas Vachuskafc52fec2015-05-18 19:13:56 -0700127 this.connection.close();
128 this.connection = null;
129 this.control = null;
130 }
Thomas Vachuska3553b302015-03-07 14:49:43 -0800131 }
132
133 @Override
134 public synchronized void onClose(int closeCode, String message) {
Simon Hunt7092cc42016-04-06 18:40:17 -0700135 topoSession.destroy();
Simon Hunte05cae42015-07-23 17:35:24 -0700136 destroyHandlersAndOverlays();
Simon Huntda580882015-05-12 20:58:18 -0700137 log.info("GUI client disconnected [close-code={}, message={}]",
138 closeCode, message);
Thomas Vachuska3553b302015-03-07 14:49:43 -0800139 }
140
141 @Override
142 public boolean onControl(byte controlCode, byte[] data, int offset, int length) {
143 lastActive = System.currentTimeMillis();
144 return true;
145 }
146
147 @Override
148 public void onMessage(String data) {
Simon Hunte05cae42015-07-23 17:35:24 -0700149 log.debug("onMessage: {}", data);
Thomas Vachuska3553b302015-03-07 14:49:43 -0800150 lastActive = System.currentTimeMillis();
151 try {
152 ObjectNode message = (ObjectNode) mapper.reader().readTree(data);
Thomas Vachuska329af532015-03-10 02:08:33 -0700153 String type = message.path("event").asText("unknown");
Simon Hunta0ddb022015-05-01 09:53:01 -0700154 UiMessageHandler handler = handlers.get(type);
Thomas Vachuska3553b302015-03-07 14:49:43 -0800155 if (handler != null) {
156 handler.process(message);
157 } else {
158 log.warn("No GUI message handler for type {}", type);
159 }
160 } catch (Exception e) {
161 log.warn("Unable to parse GUI message {} due to {}", data, e);
162 log.debug("Boom!!!", e);
163 }
164 }
165
166 @Override
Thomas Vachuska35fa3d42015-04-30 10:11:47 -0700167 public synchronized void sendMessage(ObjectNode message) {
Thomas Vachuska3553b302015-03-07 14:49:43 -0800168 try {
169 if (connection.isOpen()) {
170 connection.sendMessage(message.toString());
171 }
172 } catch (IOException e) {
173 log.warn("Unable to send message {} to GUI due to {}", message, e);
174 log.debug("Boom!!!", e);
175 }
176 }
177
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700178 @Override
Thomas Vachuska35fa3d42015-04-30 10:11:47 -0700179 public synchronized void sendMessage(String type, long sid, ObjectNode payload) {
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700180 ObjectNode message = mapper.createObjectNode();
181 message.put("event", type);
182 if (sid > 0) {
183 message.put("sid", sid);
184 }
Thomas Vachuskafa74dd72016-03-20 19:11:12 -0700185 message.set("payload", payload != null ? payload : mapper.createObjectNode());
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700186 sendMessage(message);
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700187 }
188
Thomas Vachuska3553b302015-03-07 14:49:43 -0800189 // Creates new message handlers.
Simon Hunte05cae42015-07-23 17:35:24 -0700190 private synchronized void createHandlersAndOverlays() {
191 log.debug("creating handlers and overlays...");
Thomas Vachuska3553b302015-03-07 14:49:43 -0800192 handlers = new HashMap<>();
Simon Hunte05cae42015-07-23 17:35:24 -0700193 overlayCache = new TopoOverlayCache();
194
Thomas Vachuska3553b302015-03-07 14:49:43 -0800195 UiExtensionService service = directory.get(UiExtensionService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700196 service.getExtensions().forEach(ext -> {
197 UiMessageHandlerFactory factory = ext.messageHandlerFactory();
198 if (factory != null) {
199 factory.newHandlers().forEach(handler -> {
Thomas Vachuskac4178cc2015-12-10 11:43:32 -0800200 try {
201 handler.init(this, directory);
202 handler.messageTypes().forEach(type -> handlers.put(type, handler));
Simon Hunte05cae42015-07-23 17:35:24 -0700203
Thomas Vachuskac4178cc2015-12-10 11:43:32 -0800204 // need to inject the overlay cache into topology message handler
205 if (handler instanceof TopologyViewMessageHandler) {
206 ((TopologyViewMessageHandler) handler).setOverlayCache(overlayCache);
207 }
208 } catch (Exception e) {
209 log.warn("Unable to setup handler {} due to", handler, e);
Simon Hunte05cae42015-07-23 17:35:24 -0700210 }
Thomas Vachuska329af532015-03-10 02:08:33 -0700211 });
212 }
Simon Hunte05cae42015-07-23 17:35:24 -0700213
214 UiTopoOverlayFactory overlayFactory = ext.topoOverlayFactory();
215 if (overlayFactory != null) {
216 overlayFactory.newOverlays().forEach(overlayCache::add);
217 }
Thomas Vachuska329af532015-03-10 02:08:33 -0700218 });
Simon Hunte05cae42015-07-23 17:35:24 -0700219 log.debug("#handlers = {}, #overlays = {}", handlers.size(),
220 overlayCache.size());
Thomas Vachuska3553b302015-03-07 14:49:43 -0800221 }
222
223 // Destroys message handlers.
Simon Hunte05cae42015-07-23 17:35:24 -0700224 private synchronized void destroyHandlersAndOverlays() {
225 log.debug("destroying handlers and overlays...");
Thomas Vachuska3553b302015-03-07 14:49:43 -0800226 handlers.forEach((type, handler) -> handler.destroy());
227 handlers.clear();
Simon Hunte05cae42015-07-23 17:35:24 -0700228
229 if (overlayCache != null) {
230 overlayCache.destroy();
231 overlayCache = null;
232 }
Thomas Vachuska3553b302015-03-07 14:49:43 -0800233 }
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700234
235 // Sends cluster node/instance information to allow GUI to fail-over.
236 private void sendInstanceData() {
237 ClusterService service = directory.get(ClusterService.class);
238 ArrayNode instances = mapper.createArrayNode();
239
240 for (ControllerNode node : service.getNodes()) {
241 ObjectNode instance = mapper.createObjectNode()
242 .put("id", node.id().toString())
243 .put("ip", node.ip().toString())
Simon Hunt629b99e2015-07-27 17:38:33 -0700244 .put(TopoConstants.Glyphs.UI_ATTACHED,
245 node.equals(service.getLocalNode()));
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700246 instances.add(instance);
247 }
248
249 ObjectNode payload = mapper.createObjectNode();
Thomas Vachuska20084b72015-03-11 13:46:50 -0700250 payload.set("clusterNodes", instances);
251 sendMessage("bootstrap", 0, payload);
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700252 }
253
Thomas Vachuska3553b302015-03-07 14:49:43 -0800254}
255