blob: 958ad323590d38108e67b08b13ef722a7e4ef167 [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;
Thomas Vachuska3553b302015-03-07 14:49:43 -080030import org.slf4j.Logger;
31import org.slf4j.LoggerFactory;
32
33import java.io.IOException;
34import java.util.HashMap;
35import java.util.Map;
36
37/**
38 * Web socket capable of interacting with the GUI.
39 */
40public class UiWebSocket
41 implements UiConnection, WebSocket.OnTextMessage, WebSocket.OnControl {
42
43 private static final Logger log = LoggerFactory.getLogger(UiWebSocket.class);
44
Simon Huntda580882015-05-12 20:58:18 -070045 private static final long MAX_AGE_MS = 15_000;
Thomas Vachuska3553b302015-03-07 14:49:43 -080046
47 private static final byte PING = 0x9;
48 private static final byte PONG = 0xA;
49 private static final byte[] PING_DATA = new byte[]{(byte) 0xde, (byte) 0xad};
50
51 private final ServiceDirectory directory;
52
53 private Connection connection;
54 private FrameConnection control;
55
56 private final ObjectMapper mapper = new ObjectMapper();
57
58 private long lastActive = System.currentTimeMillis();
59
Simon Hunta0ddb022015-05-01 09:53:01 -070060 private Map<String, UiMessageHandler> handlers;
Thomas Vachuska3553b302015-03-07 14:49:43 -080061
62 /**
63 * Creates a new web-socket for serving data to GUI.
64 *
65 * @param directory service directory
66 */
67 public UiWebSocket(ServiceDirectory directory) {
68 this.directory = directory;
69 }
70
71 /**
72 * Issues a close on the connection.
73 */
74 synchronized void close() {
75 destroyHandlers();
76 if (connection.isOpen()) {
77 connection.close();
78 }
79 }
80
81 /**
82 * Indicates if this connection is idle.
83 *
84 * @return true if idle or closed
85 */
86 synchronized boolean isIdle() {
Simon Huntda580882015-05-12 20:58:18 -070087 long quietFor = System.currentTimeMillis() - lastActive;
88 boolean idle = quietFor > MAX_AGE_MS;
Thomas Vachuska3553b302015-03-07 14:49:43 -080089 if (idle || (connection != null && !connection.isOpen())) {
Simon Huntda580882015-05-12 20:58:18 -070090 log.debug("IDLE (or closed) websocket [{} ms]", quietFor);
Thomas Vachuska3553b302015-03-07 14:49:43 -080091 return true;
92 } else if (connection != null) {
93 try {
94 control.sendControl(PING, PING_DATA, 0, PING_DATA.length);
95 } catch (IOException e) {
96 log.warn("Unable to send ping message due to: ", e);
97 }
98 }
99 return false;
100 }
101
102 @Override
103 public void onOpen(Connection connection) {
Thomas Vachuska3553b302015-03-07 14:49:43 -0800104 this.connection = connection;
105 this.control = (FrameConnection) connection;
Thomas Vachuskafc52fec2015-05-18 19:13:56 -0700106 try {
107 createHandlers();
108 sendInstanceData();
109 log.info("GUI client connected");
110
111 } catch (ServiceNotFoundException e) {
112 log.warn("Unable to open GUI connection; services have been shut-down");
113 this.connection.close();
114 this.connection = null;
115 this.control = null;
116 }
Thomas Vachuska3553b302015-03-07 14:49:43 -0800117 }
118
119 @Override
120 public synchronized void onClose(int closeCode, String message) {
121 destroyHandlers();
Simon Huntda580882015-05-12 20:58:18 -0700122 log.info("GUI client disconnected [close-code={}, message={}]",
123 closeCode, message);
Thomas Vachuska3553b302015-03-07 14:49:43 -0800124 }
125
126 @Override
127 public boolean onControl(byte controlCode, byte[] data, int offset, int length) {
128 lastActive = System.currentTimeMillis();
129 return true;
130 }
131
132 @Override
133 public void onMessage(String data) {
134 lastActive = System.currentTimeMillis();
135 try {
136 ObjectNode message = (ObjectNode) mapper.reader().readTree(data);
Thomas Vachuska329af532015-03-10 02:08:33 -0700137 String type = message.path("event").asText("unknown");
Simon Hunta0ddb022015-05-01 09:53:01 -0700138 UiMessageHandler handler = handlers.get(type);
Thomas Vachuska3553b302015-03-07 14:49:43 -0800139 if (handler != null) {
140 handler.process(message);
141 } else {
142 log.warn("No GUI message handler for type {}", type);
143 }
144 } catch (Exception e) {
145 log.warn("Unable to parse GUI message {} due to {}", data, e);
146 log.debug("Boom!!!", e);
147 }
148 }
149
150 @Override
Thomas Vachuska35fa3d42015-04-30 10:11:47 -0700151 public synchronized void sendMessage(ObjectNode message) {
Thomas Vachuska3553b302015-03-07 14:49:43 -0800152 try {
153 if (connection.isOpen()) {
154 connection.sendMessage(message.toString());
155 }
156 } catch (IOException e) {
157 log.warn("Unable to send message {} to GUI due to {}", message, e);
158 log.debug("Boom!!!", e);
159 }
160 }
161
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700162 @Override
Thomas Vachuska35fa3d42015-04-30 10:11:47 -0700163 public synchronized void sendMessage(String type, long sid, ObjectNode payload) {
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700164 ObjectNode message = mapper.createObjectNode();
165 message.put("event", type);
166 if (sid > 0) {
167 message.put("sid", sid);
168 }
169 message.set("payload", payload);
170 sendMessage(message);
171
172 }
173
Thomas Vachuska3553b302015-03-07 14:49:43 -0800174 // Creates new message handlers.
Thomas Vachuska35fa3d42015-04-30 10:11:47 -0700175 private synchronized void createHandlers() {
Thomas Vachuska3553b302015-03-07 14:49:43 -0800176 handlers = new HashMap<>();
177 UiExtensionService service = directory.get(UiExtensionService.class);
Thomas Vachuska329af532015-03-10 02:08:33 -0700178 service.getExtensions().forEach(ext -> {
179 UiMessageHandlerFactory factory = ext.messageHandlerFactory();
180 if (factory != null) {
181 factory.newHandlers().forEach(handler -> {
182 handler.init(this, directory);
183 handler.messageTypes().forEach(type -> handlers.put(type, handler));
184 });
185 }
186 });
Thomas Vachuska3553b302015-03-07 14:49:43 -0800187 }
188
189 // Destroys message handlers.
190 private synchronized void destroyHandlers() {
191 handlers.forEach((type, handler) -> handler.destroy());
192 handlers.clear();
193 }
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700194
195 // Sends cluster node/instance information to allow GUI to fail-over.
196 private void sendInstanceData() {
197 ClusterService service = directory.get(ClusterService.class);
198 ArrayNode instances = mapper.createArrayNode();
199
200 for (ControllerNode node : service.getNodes()) {
201 ObjectNode instance = mapper.createObjectNode()
202 .put("id", node.id().toString())
203 .put("ip", node.ip().toString())
204 .put("uiAttached", node.equals(service.getLocalNode()));
205 instances.add(instance);
206 }
207
208 ObjectNode payload = mapper.createObjectNode();
Thomas Vachuska20084b72015-03-11 13:46:50 -0700209 payload.set("clusterNodes", instances);
210 sendMessage("bootstrap", 0, payload);
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700211 }
212
Thomas Vachuska3553b302015-03-07 14:49:43 -0800213}
214