blob: 38af57dbebbea4aadac578cf6376dd98b18b95ba [file] [log] [blame]
Thomas Vachuska3553b302015-03-07 14:49:43 -08001/*
Brian O'Connor5ab426f2016-04-09 01:19:45 -07002 * Copyright 2015-present Open Networking Laboratory
Thomas Vachuska3553b302015-03-07 14:49:43 -08003 *
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;
Simon Hunta0ddb022015-05-01 09:53:01 -070028import org.onosproject.ui.UiMessageHandler;
Simon Hunt7715e892016-04-12 19:55:32 -070029import org.onosproject.ui.UiMessageHandlerFactory;
Simon Hunte05cae42015-07-23 17:35:24 -070030import org.onosproject.ui.UiTopoOverlayFactory;
Simon Hunt629b99e2015-07-27 17:38:33 -070031import org.onosproject.ui.topo.TopoConstants;
Thomas Vachuska3553b302015-03-07 14:49:43 -080032import org.slf4j.Logger;
33import org.slf4j.LoggerFactory;
34
35import java.io.IOException;
36import java.util.HashMap;
37import java.util.Map;
38
39/**
Simon Hunt7092cc42016-04-06 18:40:17 -070040 * Web socket capable of interacting with the Web UI.
Thomas Vachuska3553b302015-03-07 14:49:43 -080041 */
42public class UiWebSocket
43 implements UiConnection, WebSocket.OnTextMessage, WebSocket.OnControl {
44
45 private static final Logger log = LoggerFactory.getLogger(UiWebSocket.class);
46
Simon Hunt7715e892016-04-12 19:55:32 -070047 private static final String EVENT = "event";
48 private static final String SID = "sid";
49 private static final String PAYLOAD = "payload";
50 private static final String UNKNOWN = "unknown";
51
52 private static final String ID = "id";
53 private static final String IP = "ip";
54 private static final String CLUSTER_NODES = "clusterNodes";
55 private static final String USER = "user";
56 private static final String BOOTSTRAP = "bootstrap";
57
Thomas Vachuska1a989c12015-06-09 18:29:22 -070058 private static final long MAX_AGE_MS = 30_000;
Thomas Vachuska3553b302015-03-07 14:49:43 -080059
60 private static final byte PING = 0x9;
61 private static final byte PONG = 0xA;
62 private static final byte[] PING_DATA = new byte[]{(byte) 0xde, (byte) 0xad};
63
Simon Hunt7092cc42016-04-06 18:40:17 -070064 private final ObjectMapper mapper = new ObjectMapper();
Thomas Vachuska3553b302015-03-07 14:49:43 -080065 private final ServiceDirectory directory;
Thomas Vachuska318cd582016-05-17 15:01:10 -070066// private final UiTopoSession topoSession;
Thomas Vachuska3553b302015-03-07 14:49:43 -080067
68 private Connection connection;
69 private FrameConnection control;
Thomas Vachuska0af26912016-03-21 21:37:30 -070070 private String userName;
Thomas Vachuska3553b302015-03-07 14:49:43 -080071
Thomas Vachuska3553b302015-03-07 14:49:43 -080072 private long lastActive = System.currentTimeMillis();
73
Simon Hunta0ddb022015-05-01 09:53:01 -070074 private Map<String, UiMessageHandler> handlers;
Simon Hunte05cae42015-07-23 17:35:24 -070075 private TopoOverlayCache overlayCache;
Thomas Vachuska3553b302015-03-07 14:49:43 -080076
77 /**
Simon Huntcda9c032016-04-11 10:32:54 -070078 * Creates a new web-socket for serving data to the Web UI.
Thomas Vachuska3553b302015-03-07 14:49:43 -080079 *
80 * @param directory service directory
Simon Hunt7715e892016-04-12 19:55:32 -070081 * @param userName user name of the logged-in user
Thomas Vachuska3553b302015-03-07 14:49:43 -080082 */
Thomas Vachuska0af26912016-03-21 21:37:30 -070083 public UiWebSocket(ServiceDirectory directory, String userName) {
Thomas Vachuska3553b302015-03-07 14:49:43 -080084 this.directory = directory;
Thomas Vachuska0af26912016-03-21 21:37:30 -070085 this.userName = userName;
Thomas Vachuska318cd582016-05-17 15:01:10 -070086// this.topoSession =
87// new UiTopoSession(this, directory.get(UiSharedTopologyModel.class));
Thomas Vachuska0af26912016-03-21 21:37:30 -070088 }
89
90 @Override
91 public String userName() {
92 return userName;
Thomas Vachuska3553b302015-03-07 14:49:43 -080093 }
94
95 /**
96 * Issues a close on the connection.
97 */
98 synchronized void close() {
Simon Hunte05cae42015-07-23 17:35:24 -070099 destroyHandlersAndOverlays();
Thomas Vachuska3553b302015-03-07 14:49:43 -0800100 if (connection.isOpen()) {
101 connection.close();
102 }
103 }
104
105 /**
106 * Indicates if this connection is idle.
107 *
108 * @return true if idle or closed
109 */
110 synchronized boolean isIdle() {
Simon Huntda580882015-05-12 20:58:18 -0700111 long quietFor = System.currentTimeMillis() - lastActive;
112 boolean idle = quietFor > MAX_AGE_MS;
Thomas Vachuska3553b302015-03-07 14:49:43 -0800113 if (idle || (connection != null && !connection.isOpen())) {
Simon Huntda580882015-05-12 20:58:18 -0700114 log.debug("IDLE (or closed) websocket [{} ms]", quietFor);
Thomas Vachuska3553b302015-03-07 14:49:43 -0800115 return true;
116 } else if (connection != null) {
117 try {
118 control.sendControl(PING, PING_DATA, 0, PING_DATA.length);
119 } catch (IOException e) {
120 log.warn("Unable to send ping message due to: ", e);
121 }
122 }
123 return false;
124 }
125
126 @Override
Satish K598c28d2015-11-24 17:20:40 +0530127 public synchronized void onOpen(Connection connection) {
Thomas Vachuska3553b302015-03-07 14:49:43 -0800128 this.connection = connection;
129 this.control = (FrameConnection) connection;
Thomas Vachuskafc52fec2015-05-18 19:13:56 -0700130 try {
Thomas Vachuska318cd582016-05-17 15:01:10 -0700131// topoSession.init();
Simon Hunte05cae42015-07-23 17:35:24 -0700132 createHandlersAndOverlays();
Simon Hunt7715e892016-04-12 19:55:32 -0700133 sendBootstrapData();
134 log.info("GUI client connected -- user <{}>", userName);
Thomas Vachuskafc52fec2015-05-18 19:13:56 -0700135
136 } catch (ServiceNotFoundException e) {
Brian O'Connor75deea62015-06-24 16:09:17 -0400137 log.warn("Unable to open GUI connection; services have been shut-down", e);
Thomas Vachuskafc52fec2015-05-18 19:13:56 -0700138 this.connection.close();
139 this.connection = null;
140 this.control = null;
141 }
Thomas Vachuska3553b302015-03-07 14:49:43 -0800142 }
143
144 @Override
145 public synchronized void onClose(int closeCode, String message) {
Thomas Vachuska318cd582016-05-17 15:01:10 -0700146// topoSession.destroy();
Simon Hunte05cae42015-07-23 17:35:24 -0700147 destroyHandlersAndOverlays();
Simon Huntda580882015-05-12 20:58:18 -0700148 log.info("GUI client disconnected [close-code={}, message={}]",
Simon Hunt7715e892016-04-12 19:55:32 -0700149 closeCode, message);
Thomas Vachuska3553b302015-03-07 14:49:43 -0800150 }
151
152 @Override
153 public boolean onControl(byte controlCode, byte[] data, int offset, int length) {
154 lastActive = System.currentTimeMillis();
155 return true;
156 }
157
158 @Override
159 public void onMessage(String data) {
Simon Hunte05cae42015-07-23 17:35:24 -0700160 log.debug("onMessage: {}", data);
Thomas Vachuska3553b302015-03-07 14:49:43 -0800161 lastActive = System.currentTimeMillis();
162 try {
163 ObjectNode message = (ObjectNode) mapper.reader().readTree(data);
Simon Hunt7715e892016-04-12 19:55:32 -0700164 String type = message.path(EVENT).asText(UNKNOWN);
Simon Hunta0ddb022015-05-01 09:53:01 -0700165 UiMessageHandler handler = handlers.get(type);
Thomas Vachuska3553b302015-03-07 14:49:43 -0800166 if (handler != null) {
167 handler.process(message);
168 } else {
169 log.warn("No GUI message handler for type {}", type);
170 }
171 } catch (Exception e) {
172 log.warn("Unable to parse GUI message {} due to {}", data, e);
173 log.debug("Boom!!!", e);
174 }
175 }
176
177 @Override
Thomas Vachuska35fa3d42015-04-30 10:11:47 -0700178 public synchronized void sendMessage(ObjectNode message) {
Thomas Vachuska3553b302015-03-07 14:49:43 -0800179 try {
180 if (connection.isOpen()) {
181 connection.sendMessage(message.toString());
182 }
183 } catch (IOException e) {
184 log.warn("Unable to send message {} to GUI due to {}", message, e);
185 log.debug("Boom!!!", e);
186 }
187 }
188
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700189 @Override
Thomas Vachuska35fa3d42015-04-30 10:11:47 -0700190 public synchronized void sendMessage(String type, long sid, ObjectNode payload) {
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700191 ObjectNode message = mapper.createObjectNode();
Simon Hunt7715e892016-04-12 19:55:32 -0700192 message.put(EVENT, type);
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700193 if (sid > 0) {
Simon Hunt7715e892016-04-12 19:55:32 -0700194 message.put(SID, sid);
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700195 }
Simon Hunt7715e892016-04-12 19:55:32 -0700196 message.set(PAYLOAD, payload != null ? payload : mapper.createObjectNode());
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700197 sendMessage(message);
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700198 }
199
Thomas Vachuska3553b302015-03-07 14:49:43 -0800200 // Creates new message handlers.
Simon Hunte05cae42015-07-23 17:35:24 -0700201 private synchronized void createHandlersAndOverlays() {
Simon Hunt7715e892016-04-12 19:55:32 -0700202 log.debug("Creating handlers and overlays...");
Thomas Vachuska3553b302015-03-07 14:49:43 -0800203 handlers = new HashMap<>();
Simon Hunte05cae42015-07-23 17:35:24 -0700204 overlayCache = new TopoOverlayCache();
205
Thomas Vachuska3553b302015-03-07 14:49:43 -0800206 UiExtensionService service = directory.get(UiExtensionService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700207 service.getExtensions().forEach(ext -> {
208 UiMessageHandlerFactory factory = ext.messageHandlerFactory();
209 if (factory != null) {
210 factory.newHandlers().forEach(handler -> {
Thomas Vachuskac4178cc2015-12-10 11:43:32 -0800211 try {
212 handler.init(this, directory);
213 handler.messageTypes().forEach(type -> handlers.put(type, handler));
Simon Hunte05cae42015-07-23 17:35:24 -0700214
Thomas Vachuskac4178cc2015-12-10 11:43:32 -0800215 // need to inject the overlay cache into topology message handler
216 if (handler instanceof TopologyViewMessageHandler) {
217 ((TopologyViewMessageHandler) handler).setOverlayCache(overlayCache);
218 }
219 } catch (Exception e) {
220 log.warn("Unable to setup handler {} due to", handler, e);
Simon Hunte05cae42015-07-23 17:35:24 -0700221 }
Thomas Vachuska329af532015-03-10 02:08:33 -0700222 });
223 }
Simon Hunte05cae42015-07-23 17:35:24 -0700224
225 UiTopoOverlayFactory overlayFactory = ext.topoOverlayFactory();
226 if (overlayFactory != null) {
227 overlayFactory.newOverlays().forEach(overlayCache::add);
228 }
Thomas Vachuska329af532015-03-10 02:08:33 -0700229 });
Simon Hunte05cae42015-07-23 17:35:24 -0700230 log.debug("#handlers = {}, #overlays = {}", handlers.size(),
Simon Hunt7715e892016-04-12 19:55:32 -0700231 overlayCache.size());
Thomas Vachuska3553b302015-03-07 14:49:43 -0800232 }
233
234 // Destroys message handlers.
Simon Hunte05cae42015-07-23 17:35:24 -0700235 private synchronized void destroyHandlersAndOverlays() {
Simon Hunt7715e892016-04-12 19:55:32 -0700236 log.debug("Destroying handlers and overlays...");
Thomas Vachuska3553b302015-03-07 14:49:43 -0800237 handlers.forEach((type, handler) -> handler.destroy());
238 handlers.clear();
Simon Hunte05cae42015-07-23 17:35:24 -0700239
240 if (overlayCache != null) {
241 overlayCache.destroy();
242 overlayCache = null;
243 }
Thomas Vachuska3553b302015-03-07 14:49:43 -0800244 }
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700245
Simon Hunt7715e892016-04-12 19:55:32 -0700246 // Sends initial information (username and cluster member information)
247 // to allow GUI to display logged-in user, and to be able to
248 // fail-over to an alternate cluster member if necessary.
249 private void sendBootstrapData() {
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700250 ClusterService service = directory.get(ClusterService.class);
251 ArrayNode instances = mapper.createArrayNode();
252
253 for (ControllerNode node : service.getNodes()) {
254 ObjectNode instance = mapper.createObjectNode()
Simon Hunt7715e892016-04-12 19:55:32 -0700255 .put(ID, node.id().toString())
256 .put(IP, node.ip().toString())
Simon Hunt629b99e2015-07-27 17:38:33 -0700257 .put(TopoConstants.Glyphs.UI_ATTACHED,
Simon Hunt7715e892016-04-12 19:55:32 -0700258 node.equals(service.getLocalNode()));
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700259 instances.add(instance);
260 }
261
262 ObjectNode payload = mapper.createObjectNode();
Simon Hunt7715e892016-04-12 19:55:32 -0700263 payload.set(CLUSTER_NODES, instances);
264 payload.put(USER, userName);
265 sendMessage(BOOTSTRAP, 0, payload);
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700266 }
267
Thomas Vachuska3553b302015-03-07 14:49:43 -0800268}
269