blob: bd3e9e16fb104f64179f3aee36975184bbc75e4b [file] [log] [blame]
Simon Hunt1e4a0012015-01-21 11:36:08 -08001/*
Brian O'Connora09fe5b2017-08-03 21:12:30 -07002 * Copyright 2015-present Open Networking Foundation
Simon Hunt1e4a0012015-01-21 11:36:08 -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 */
16
17/*
18 ONOS GUI -- Remote -- Web Socket Service
19 */
20(function () {
21 'use strict';
22
Thomas Vachuska329af532015-03-10 02:08:33 -070023 // injected refs
Laszlo Papp759f0d32018-03-05 13:24:30 +000024 var $log, $loc, fs, gs, ufs, wsock, vs, ls;
Simon Hunt1e4a0012015-01-21 11:36:08 -080025
Thomas Vachuska329af532015-03-10 02:08:33 -070026 // internal state
Steven Burrows1c2a9682017-07-14 16:52:46 +010027 var webSockOpts, // web socket options
28 ws = null, // web socket reference
29 wsUp = false, // web socket is good to go
30 handlers = {}, // event handler bindings
31 pendingEvents = [], // events TX'd while socket not up
32 host, // web socket host
33 url, // web socket URL
34 clusterNodes = [], // ONOS instances data for failover
35 clusterIndex = -1, // the instance to which we are connected
Laszlo Papp759f0d32018-03-05 13:24:30 +000036 glyphs = [],
Steven Burrows1c2a9682017-07-14 16:52:46 +010037 connectRetries = 0, // limit our attempts at reconnecting
38 openListeners = {}, // registered listeners for websocket open()
39 nextListenerId = 1, // internal ID for open listeners
40 loggedInUser = null; // name of logged-in user
Simon Hunt8b6d2d42015-03-11 13:04:52 -070041
Simon Hunt1169c952017-06-05 11:20:11 -070042 // built-in handlers
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -070043 var builtinHandlers = {
Simon Hunt1169c952017-06-05 11:20:11 -070044
Simon Hunt8b6d2d42015-03-11 13:04:52 -070045 bootstrap: function (data) {
Simon Hunt7715e892016-04-12 19:55:32 -070046 $log.debug('bootstrap data', data);
47 loggedInUser = data.user;
Thomas Vachuska20084b72015-03-11 13:46:50 -070048 clusterNodes = data.clusterNodes;
Simon Hunt8b6d2d42015-03-11 13:04:52 -070049 clusterNodes.forEach(function (d, i) {
50 if (d.uiAttached) {
51 clusterIndex = i;
Thomas Vachuska20084b72015-03-11 13:46:50 -070052 $log.info('Connected to cluster node ' + d.ip);
53 // TODO: add connect info to masthead somewhere
Simon Hunt8b6d2d42015-03-11 13:04:52 -070054 }
55 });
Laszlo Papp759f0d32018-03-05 13:24:30 +000056 glyphs = data.glyphs;
57 glyphs.forEach(function (d, i) {
58 var gdata = {};
59 gdata['_' + d.id] = d.viewbox;
60 gdata[d.id] = d.path;
61 gs.registerGlyphs(gdata);
62 });
Simon Hunt1169c952017-06-05 11:20:11 -070063 },
64
65 error: function (data) {
66 var m = data.message || 'error from server';
67 $log.error(m, data);
68
69 // Unrecoverable error - throw up the veil...
70 vs && vs.show([
71 'Oops!',
72 'Server reports error...',
Steven Burrows1c2a9682017-07-14 16:52:46 +010073 m,
Simon Hunt1169c952017-06-05 11:20:11 -070074 ]);
Steven Burrows1c2a9682017-07-14 16:52:46 +010075 },
Simon Hunt8b6d2d42015-03-11 13:04:52 -070076 };
Simon Hunt20207df2015-03-10 18:30:14 -070077
Steven Burrows530cf462016-04-12 16:04:37 +010078
Simon Hunt20207df2015-03-10 18:30:14 -070079 // ==========================
80 // === Web socket callbacks
81
82 function handleOpen() {
Simon Hunt3b9ad04d2015-03-11 15:26:02 -070083 $log.info('Web socket open - ', url);
Thomas Vachuska0af26912016-03-21 21:37:30 -070084 vs && vs.hide();
Simon Hunt8b6d2d42015-03-11 13:04:52 -070085
Simon Hunt4deb0e82015-06-10 16:18:25 -070086 if (fs.debugOn('txrx')) {
87 $log.debug('Sending ' + pendingEvents.length + ' pending event(s)...');
88 }
Simon Hunt20207df2015-03-10 18:30:14 -070089 pendingEvents.forEach(function (ev) {
90 _send(ev);
91 });
92 pendingEvents = [];
Simon Hunt8b6d2d42015-03-11 13:04:52 -070093
94 connectRetries = 0;
Simon Hunt20207df2015-03-10 18:30:14 -070095 wsUp = true;
Simon Hunt3b9ad04d2015-03-11 15:26:02 -070096 informListeners(host, url);
Simon Hunt20207df2015-03-10 18:30:14 -070097 }
98
99 // Handles the specified (incoming) message using handler bindings.
100 function handleMessage(msgEvent) {
101 var ev, h;
102
103 try {
104 ev = JSON.parse(msgEvent.data);
Simon Hunt20207df2015-03-10 18:30:14 -0700105 } catch (e) {
Simon Hunt2d16fc82015-03-10 20:19:52 -0700106 $log.error('Message.data is not valid JSON', msgEvent.data, e);
Bri Prebilic Cole6ed04eb2015-04-27 16:26:03 -0700107 return null;
Simon Hunt20207df2015-03-10 18:30:14 -0700108 }
Simon Hunt4deb0e82015-06-10 16:18:25 -0700109 if (fs.debugOn('txrx')) {
110 $log.debug(' << *Rx* ', ev.event, ev.payload);
111 }
Simon Hunt2d16fc82015-03-10 20:19:52 -0700112
113 if (h = handlers[ev.event]) {
114 try {
115 h(ev.payload);
116 } catch (e) {
117 $log.error('Problem handling event:', ev, e);
Bri Prebilic Cole6ed04eb2015-04-27 16:26:03 -0700118 return null;
Simon Hunt2d16fc82015-03-10 20:19:52 -0700119 }
120 } else {
121 $log.warn('Unhandled event:', ev);
122 }
123
Simon Hunt20207df2015-03-10 18:30:14 -0700124 }
125
126 function handleClose() {
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700127 var gsucc;
128
Simon Hunt8a0429a2017-01-06 16:52:47 -0800129 $log.warn('Web socket closed');
Thomas Vachuska0af26912016-03-21 21:37:30 -0700130 ls && ls.stop();
Simon Hunt20207df2015-03-10 18:30:14 -0700131 wsUp = false;
132
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700133 if (gsucc = findGuiSuccessor()) {
134 createWebSocket(webSockOpts, gsucc);
135 } else {
136 // If no controllers left to contact, show the Veil...
Thomas Vachuska0af26912016-03-21 21:37:30 -0700137 vs && vs.show([
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700138 'Oops!',
139 'Web-socket connection to server closed...',
Steven Burrows1c2a9682017-07-14 16:52:46 +0100140 'Try refreshing the page.',
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700141 ]);
142 }
Simon Hunt20207df2015-03-10 18:30:14 -0700143 }
144
145
146 // ==============================
147 // === Private Helper Functions
148
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700149 function findGuiSuccessor() {
150 var ncn = clusterNodes.length,
Bri Prebilic Cole6ed04eb2015-04-27 16:26:03 -0700151 ip, node;
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700152
153 while (connectRetries < ncn && !ip) {
154 connectRetries++;
155 clusterIndex = (clusterIndex + 1) % ncn;
156 node = clusterNodes[clusterIndex];
157 ip = node && node.ip;
158 }
159
160 return ip;
161 }
162
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700163 function informListeners(host, url) {
Bri Prebilic Cole6ed04eb2015-04-27 16:26:03 -0700164 angular.forEach(openListeners, function (lsnr) {
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700165 lsnr.cb(host, url);
166 });
167 }
168
Simon Hunt20207df2015-03-10 18:30:14 -0700169 function _send(ev) {
Simon Hunt4deb0e82015-06-10 16:18:25 -0700170 if (fs.debugOn('txrx')) {
171 $log.debug(' *Tx* >> ', ev.event, ev.payload);
172 }
Simon Hunt20207df2015-03-10 18:30:14 -0700173 ws.send(JSON.stringify(ev));
174 }
175
Bri Prebilic Cole6ed04eb2015-04-27 16:26:03 -0700176 function noHandlersWarn(handlers, caller) {
177 if (!handlers || fs.isEmptyObject(handlers)) {
178 $log.warn('WSS.' + caller + '(): no event handlers');
179 return true;
180 }
181 return false;
182 }
183
Simon Hunt20207df2015-03-10 18:30:14 -0700184 // ===================
185 // === API Functions
186
187 // Required for unit tests to set to known state
Bri Prebilic Cole6ed04eb2015-04-27 16:26:03 -0700188 function resetState() {
189 webSockOpts = undefined;
190 ws = null;
191 wsUp = false;
192 host = undefined;
193 url = undefined;
194 pendingEvents = [];
195 handlers = {};
Bri Prebilic Cole6ed04eb2015-04-27 16:26:03 -0700196 clusterNodes = [];
197 clusterIndex = -1;
Laszlo Papp759f0d32018-03-05 13:24:30 +0000198 glyphs = [];
Bri Prebilic Cole6ed04eb2015-04-27 16:26:03 -0700199 connectRetries = 0;
200 openListeners = {};
201 nextListenerId = 1;
202 }
Simon Hunt970e7fd2015-01-22 17:46:28 -0800203
Simon Hunt20207df2015-03-10 18:30:14 -0700204 // Currently supported opts:
205 // wsport: web socket port (other than default 8181)
Simon Hunt1169c952017-06-05 11:20:11 -0700206 // host: if defined, is the host address to use
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700207 function createWebSocket(opts, _host_) {
Simon Hunt20207df2015-03-10 18:30:14 -0700208 var wsport = (opts && opts.wsport) || null;
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700209
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700210 webSockOpts = opts; // preserved for future calls
Simon Hunt20207df2015-03-10 18:30:14 -0700211
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700212 host = _host_ || $loc.host();
213 url = ufs.wsUrl('core', wsport, _host_);
Simon Hunt20207df2015-03-10 18:30:14 -0700214
215 $log.debug('Attempting to open websocket to: ' + url);
216 ws = wsock.newWebSocket(url);
217 if (ws) {
218 ws.onopen = handleOpen;
219 ws.onmessage = handleMessage;
220 ws.onclose = handleClose;
Simon Hunt1169c952017-06-05 11:20:11 -0700221
Steven Burrows1c2a9682017-07-14 16:52:46 +0100222 sendEvent('authentication', { token: onosAuth });
Simon Hunt20207df2015-03-10 18:30:14 -0700223 }
224 // Note: Wsock logs an error if the new WebSocket call fails
Simon Hunt2d16fc82015-03-10 20:19:52 -0700225 return url;
Simon Hunt20207df2015-03-10 18:30:14 -0700226 }
227
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700228 // Binds the message handlers to their message type (event type) as
229 // specified in the given map. Note that keys are the event IDs; values
230 // are either:
231 // * the event handler function, or
232 // * an API object which has an event handler for the key
233 //
Thomas Vachuska329af532015-03-10 02:08:33 -0700234 function bindHandlers(handlerMap) {
Bri Prebilic Cole6ed04eb2015-04-27 16:26:03 -0700235 var m,
Thomas Vachuska329af532015-03-10 02:08:33 -0700236 dups = [];
Simon Hunt970e7fd2015-01-22 17:46:28 -0800237
Bri Prebilic Cole6ed04eb2015-04-27 16:26:03 -0700238 if (noHandlersWarn(handlerMap, 'bindHandlers')) {
239 return null;
240 }
241 m = d3.map(handlerMap);
242
Simon Hunt20207df2015-03-10 18:30:14 -0700243 m.forEach(function (eventId, api) {
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700244 var fn = fs.isF(api) || fs.isF(api[eventId]);
Thomas Vachuska329af532015-03-10 02:08:33 -0700245 if (!fn) {
Simon Hunt20207df2015-03-10 18:30:14 -0700246 $log.warn(eventId + ' handler not a function');
Thomas Vachuska329af532015-03-10 02:08:33 -0700247 return;
Simon Hunt970e7fd2015-01-22 17:46:28 -0800248 }
Thomas Vachuska329af532015-03-10 02:08:33 -0700249
Simon Hunt20207df2015-03-10 18:30:14 -0700250 if (handlers[eventId]) {
251 dups.push(eventId);
Thomas Vachuska329af532015-03-10 02:08:33 -0700252 } else {
Simon Hunt20207df2015-03-10 18:30:14 -0700253 handlers[eventId] = fn;
Thomas Vachuska329af532015-03-10 02:08:33 -0700254 }
255 });
256 if (dups.length) {
257 $log.warn('duplicate bindings ignored:', dups);
258 }
Simon Hunt970e7fd2015-01-22 17:46:28 -0800259 }
260
Thomas Vachuska329af532015-03-10 02:08:33 -0700261 // Unbinds the specified message handlers.
Simon Hunt20207df2015-03-10 18:30:14 -0700262 // Expected that the same map will be used, but we only care about keys
Thomas Vachuska329af532015-03-10 02:08:33 -0700263 function unbindHandlers(handlerMap) {
Bri Prebilic Cole6ed04eb2015-04-27 16:26:03 -0700264 var m;
265
266 if (noHandlersWarn(handlerMap, 'unbindHandlers')) {
267 return null;
268 }
269 m = d3.map(handlerMap);
Simon Hunt20207df2015-03-10 18:30:14 -0700270
271 m.forEach(function (eventId) {
272 delete handlers[eventId];
Thomas Vachuska329af532015-03-10 02:08:33 -0700273 });
274 }
Simon Huntacf410b2015-01-23 10:05:48 -0800275
Simon Huntd5b96732016-07-08 13:22:27 -0700276 // TODO: simplify listener handling (see theme.js for sample code)
277
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700278 function addOpenListener(callback) {
279 var id = nextListenerId++,
280 cb = fs.isF(callback),
281 o = { id: id, cb: cb };
282
283 if (cb) {
284 openListeners[id] = o;
285 } else {
286 $log.error('WSS.addOpenListener(): callback not a function');
287 o.error = 'No callback defined';
288 }
289 return o;
290 }
291
292 function removeOpenListener(lsnr) {
Bri Prebilic Cole6ed04eb2015-04-27 16:26:03 -0700293 var id = fs.isO(lsnr) && lsnr.id,
294 o;
295 if (!id) {
296 $log.warn('WSS.removeOpenListener(): invalid listener', lsnr);
297 return null;
298 }
299 o = openListeners[id];
300
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700301 if (o) {
302 delete openListeners[id];
303 }
304 }
305
Simon Hunt20207df2015-03-10 18:30:14 -0700306 // Formulates an event message and sends it via the web-socket.
307 // If the websocket is not up yet, we store it in a pending list.
Thomas Vachuska329af532015-03-10 02:08:33 -0700308 function sendEvent(evType, payload) {
Simon Hunt20207df2015-03-10 18:30:14 -0700309 var ev = {
Thomas Vachuska329af532015-03-10 02:08:33 -0700310 event: evType,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100311 payload: payload || {},
Simon Hunt20207df2015-03-10 18:30:14 -0700312 };
313
314 if (wsUp) {
315 _send(ev);
Thomas Vachuska329af532015-03-10 02:08:33 -0700316 } else {
Simon Hunt20207df2015-03-10 18:30:14 -0700317 pendingEvents.push(ev);
Thomas Vachuska329af532015-03-10 02:08:33 -0700318 }
319 }
320
Thomas Vachuska0af26912016-03-21 21:37:30 -0700321 // Binds the veil service as a delegate
322 function setVeilDelegate(vd) {
323 vs = vd;
324 }
325
326 // Binds the loading service as a delegate
327 function setLoadingDelegate(ld) {
328 ls = ld;
329 }
330
Thomas Vachuska329af532015-03-10 02:08:33 -0700331
Simon Hunt20207df2015-03-10 18:30:14 -0700332 // ============================
333 // ===== Definition of module
Simon Hunt584122a2015-01-21 15:32:40 -0800334 angular.module('onosRemote')
Simon Huntbb920fd2015-01-22 17:06:32 -0800335 .factory('WebSocketService',
Laszlo Papp759f0d32018-03-05 13:24:30 +0000336 ['$log', '$location', 'FnService', 'GlyphService', 'UrlFnService', 'WSock',
Simon Huntbb920fd2015-01-22 17:06:32 -0800337
Laszlo Papp759f0d32018-03-05 13:24:30 +0000338 function (_$log_, _$loc_, _fs_, _gs_, _ufs_, _wsock_) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700339 $log = _$log_;
Simon Hunt20207df2015-03-10 18:30:14 -0700340 $loc = _$loc_;
341 fs = _fs_;
Laszlo Papp759f0d32018-03-05 13:24:30 +0000342 gs = _gs_;
Simon Hunt20207df2015-03-10 18:30:14 -0700343 ufs = _ufs_;
344 wsock = _wsock_;
Simon Hunt1e4a0012015-01-21 11:36:08 -0800345
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700346 bindHandlers(builtinHandlers);
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700347
Simon Hunt1e4a0012015-01-21 11:36:08 -0800348 return {
Bri Prebilic Cole6ed04eb2015-04-27 16:26:03 -0700349 resetState: resetState,
Thomas Vachuska329af532015-03-10 02:08:33 -0700350 createWebSocket: createWebSocket,
351 bindHandlers: bindHandlers,
352 unbindHandlers: unbindHandlers,
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700353 addOpenListener: addOpenListener,
354 removeOpenListener: removeOpenListener,
Simon Hunt412adc82015-12-11 15:56:20 -0800355 sendEvent: sendEvent,
Thomas Vachuska0af26912016-03-21 21:37:30 -0700356 isConnected: function () { return wsUp; },
Simon Hunt7715e892016-04-12 19:55:32 -0700357 loggedInUser: function () { return loggedInUser || '(no-one)'; },
Thomas Vachuska0af26912016-03-21 21:37:30 -0700358
359 _setVeilDelegate: setVeilDelegate,
Steven Burrows1c2a9682017-07-14 16:52:46 +0100360 _setLoadingDelegate: setLoadingDelegate,
Simon Hunt1e4a0012015-01-21 11:36:08 -0800361 };
Steven Burrows1c2a9682017-07-14 16:52:46 +0100362 },
Simon Hunt20207df2015-03-10 18:30:14 -0700363 ]);
Simon Hunt1e4a0012015-01-21 11:36:08 -0800364
365}());