blob: cb68098dbd5612d29e2d2aa3152c4e8c0db2d9cd [file] [log] [blame]
Simon Hunt1e4a0012015-01-21 11:36:08 -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 */
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
Simon Hunt20207df2015-03-10 18:30:14 -070024 var $log, $loc, fs, ufs, wsock, vs;
Simon Hunt1e4a0012015-01-21 11:36:08 -080025
Thomas Vachuska329af532015-03-10 02:08:33 -070026 // internal state
Simon Hunt8b6d2d42015-03-11 13:04:52 -070027 var webSockOpts, // web socket options
28 ws = null, // web socket reference
Simon Hunt20207df2015-03-10 18:30:14 -070029 wsUp = false, // web socket is good to go
30 sid = 0, // event sequence identifier
31 handlers = {}, // event handler bindings
32 pendingEvents = [], // events TX'd while socket not up
Simon Hunt3b9ad04d2015-03-11 15:26:02 -070033 host, // web socket host
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -070034 url, // web socket URL
Simon Hunt8b6d2d42015-03-11 13:04:52 -070035 clusterNodes = [], // ONOS instances data for failover
36 clusterIndex = -1, // the instance to which we are connected
Simon Hunt3b9ad04d2015-03-11 15:26:02 -070037 connectRetries = 0, // limit our attempts at reconnecting
38 openListeners = {}, // registered listeners for websocket open()
39 nextListenerId = 1; // internal ID for open listeners
Simon Hunt8b6d2d42015-03-11 13:04:52 -070040
41 // =======================
42 // === Bootstrap Handler
Simon Huntacf410b2015-01-23 10:05:48 -080043
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -070044 var builtinHandlers = {
Simon Hunt8b6d2d42015-03-11 13:04:52 -070045 bootstrap: function (data) {
Thomas Vachuska20084b72015-03-11 13:46:50 -070046 clusterNodes = data.clusterNodes;
Simon Hunt8b6d2d42015-03-11 13:04:52 -070047 clusterNodes.forEach(function (d, i) {
48 if (d.uiAttached) {
49 clusterIndex = i;
Thomas Vachuska20084b72015-03-11 13:46:50 -070050 $log.info('Connected to cluster node ' + d.ip);
51 // TODO: add connect info to masthead somewhere
Simon Hunt8b6d2d42015-03-11 13:04:52 -070052 }
53 });
54 }
55 };
Simon Hunt20207df2015-03-10 18:30:14 -070056
57 // ==========================
58 // === Web socket callbacks
59
60 function handleOpen() {
Simon Hunt3b9ad04d2015-03-11 15:26:02 -070061 $log.info('Web socket open - ', url);
Simon Hunt8b6d2d42015-03-11 13:04:52 -070062 vs.hide();
63
Simon Hunt20207df2015-03-10 18:30:14 -070064 $log.debug('Sending ' + pendingEvents.length + ' pending event(s)...');
65 pendingEvents.forEach(function (ev) {
66 _send(ev);
67 });
68 pendingEvents = [];
Simon Hunt8b6d2d42015-03-11 13:04:52 -070069
70 connectRetries = 0;
Simon Hunt20207df2015-03-10 18:30:14 -070071 wsUp = true;
Simon Hunt3b9ad04d2015-03-11 15:26:02 -070072 informListeners(host, url);
Simon Hunt20207df2015-03-10 18:30:14 -070073 }
74
75 // Handles the specified (incoming) message using handler bindings.
76 function handleMessage(msgEvent) {
77 var ev, h;
78
79 try {
80 ev = JSON.parse(msgEvent.data);
Simon Hunt20207df2015-03-10 18:30:14 -070081 } catch (e) {
Simon Hunt2d16fc82015-03-10 20:19:52 -070082 $log.error('Message.data is not valid JSON', msgEvent.data, e);
83 return;
Simon Hunt20207df2015-03-10 18:30:14 -070084 }
Simon Hunt3b9ad04d2015-03-11 15:26:02 -070085 $log.debug(' << *Rx* ', ev.event, ev.payload);
Simon Hunt2d16fc82015-03-10 20:19:52 -070086
87 if (h = handlers[ev.event]) {
88 try {
89 h(ev.payload);
90 } catch (e) {
91 $log.error('Problem handling event:', ev, e);
92 }
93 } else {
94 $log.warn('Unhandled event:', ev);
95 }
96
Simon Hunt20207df2015-03-10 18:30:14 -070097 }
98
99 function handleClose() {
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700100 var gsucc;
101
Simon Hunt20207df2015-03-10 18:30:14 -0700102 $log.info('Web socket closed');
103 wsUp = false;
104
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700105 if (gsucc = findGuiSuccessor()) {
106 createWebSocket(webSockOpts, gsucc);
107 } else {
108 // If no controllers left to contact, show the Veil...
109 vs.show([
110 'Oops!',
111 'Web-socket connection to server closed...',
112 'Try refreshing the page.'
113 ]);
114 }
Simon Hunt20207df2015-03-10 18:30:14 -0700115 }
116
117
118 // ==============================
119 // === Private Helper Functions
120
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700121 function findGuiSuccessor() {
122 var ncn = clusterNodes.length,
123 ip = undefined,
124 node;
125
126 while (connectRetries < ncn && !ip) {
127 connectRetries++;
128 clusterIndex = (clusterIndex + 1) % ncn;
129 node = clusterNodes[clusterIndex];
130 ip = node && node.ip;
131 }
132
133 return ip;
134 }
135
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700136 function informListeners(host, url) {
137 angular.forEach(openListeners, function(lsnr) {
138 lsnr.cb(host, url);
139 });
140 }
141
Simon Hunt20207df2015-03-10 18:30:14 -0700142 function _send(ev) {
143 $log.debug(' *Tx* >> ', ev.event, ev.payload);
144 ws.send(JSON.stringify(ev));
145 }
146
Simon Hunt20207df2015-03-10 18:30:14 -0700147 // ===================
148 // === API Functions
149
150 // Required for unit tests to set to known state
Thomas Vachuska329af532015-03-10 02:08:33 -0700151 function resetSid() {
152 sid = 0;
Simon Hunt970e7fd2015-01-22 17:46:28 -0800153 }
154
Simon Hunt20207df2015-03-10 18:30:14 -0700155 // Currently supported opts:
156 // wsport: web socket port (other than default 8181)
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700157 // host: if defined, is the host address to use
158 function createWebSocket(opts, _host_) {
Simon Hunt20207df2015-03-10 18:30:14 -0700159 var wsport = (opts && opts.wsport) || null;
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700160
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700161 webSockOpts = opts; // preserved for future calls
Simon Hunt20207df2015-03-10 18:30:14 -0700162
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700163 host = _host_ || $loc.host();
164 url = ufs.wsUrl('core', wsport, _host_);
Simon Hunt20207df2015-03-10 18:30:14 -0700165
166 $log.debug('Attempting to open websocket to: ' + url);
167 ws = wsock.newWebSocket(url);
168 if (ws) {
169 ws.onopen = handleOpen;
170 ws.onmessage = handleMessage;
171 ws.onclose = handleClose;
172 }
173 // Note: Wsock logs an error if the new WebSocket call fails
Simon Hunt2d16fc82015-03-10 20:19:52 -0700174 return url;
Simon Hunt20207df2015-03-10 18:30:14 -0700175 }
176
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700177 // Binds the message handlers to their message type (event type) as
178 // specified in the given map. Note that keys are the event IDs; values
179 // are either:
180 // * the event handler function, or
181 // * an API object which has an event handler for the key
182 //
Thomas Vachuska329af532015-03-10 02:08:33 -0700183 function bindHandlers(handlerMap) {
184 var m = d3.map(handlerMap),
185 dups = [];
Simon Hunt970e7fd2015-01-22 17:46:28 -0800186
Simon Hunt20207df2015-03-10 18:30:14 -0700187 m.forEach(function (eventId, api) {
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700188 var fn = fs.isF(api) || fs.isF(api[eventId]);
Thomas Vachuska329af532015-03-10 02:08:33 -0700189 if (!fn) {
Simon Hunt20207df2015-03-10 18:30:14 -0700190 $log.warn(eventId + ' handler not a function');
Thomas Vachuska329af532015-03-10 02:08:33 -0700191 return;
Simon Hunt970e7fd2015-01-22 17:46:28 -0800192 }
Thomas Vachuska329af532015-03-10 02:08:33 -0700193
Simon Hunt20207df2015-03-10 18:30:14 -0700194 if (handlers[eventId]) {
195 dups.push(eventId);
Thomas Vachuska329af532015-03-10 02:08:33 -0700196 } else {
Simon Hunt20207df2015-03-10 18:30:14 -0700197 handlers[eventId] = fn;
Thomas Vachuska329af532015-03-10 02:08:33 -0700198 }
199 });
200 if (dups.length) {
201 $log.warn('duplicate bindings ignored:', dups);
202 }
Simon Hunt970e7fd2015-01-22 17:46:28 -0800203 }
204
Thomas Vachuska329af532015-03-10 02:08:33 -0700205 // Unbinds the specified message handlers.
Simon Hunt20207df2015-03-10 18:30:14 -0700206 // Expected that the same map will be used, but we only care about keys
Thomas Vachuska329af532015-03-10 02:08:33 -0700207 function unbindHandlers(handlerMap) {
208 var m = d3.map(handlerMap);
Simon Hunt20207df2015-03-10 18:30:14 -0700209
210 m.forEach(function (eventId) {
211 delete handlers[eventId];
Thomas Vachuska329af532015-03-10 02:08:33 -0700212 });
213 }
Simon Huntacf410b2015-01-23 10:05:48 -0800214
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700215 function addOpenListener(callback) {
216 var id = nextListenerId++,
217 cb = fs.isF(callback),
218 o = { id: id, cb: cb };
219
220 if (cb) {
221 openListeners[id] = o;
222 } else {
223 $log.error('WSS.addOpenListener(): callback not a function');
224 o.error = 'No callback defined';
225 }
226 return o;
227 }
228
229 function removeOpenListener(lsnr) {
230 var id = lsnr && lsnr.id,
231 o = openListeners[id];
232 if (o) {
233 delete openListeners[id];
234 }
235 }
236
Simon Hunt20207df2015-03-10 18:30:14 -0700237 // Formulates an event message and sends it via the web-socket.
238 // If the websocket is not up yet, we store it in a pending list.
Thomas Vachuska329af532015-03-10 02:08:33 -0700239 function sendEvent(evType, payload) {
Simon Hunt20207df2015-03-10 18:30:14 -0700240 var ev = {
Thomas Vachuska329af532015-03-10 02:08:33 -0700241 event: evType,
242 sid: ++sid,
Simon Hunt20207df2015-03-10 18:30:14 -0700243 payload: payload || {}
244 };
245
246 if (wsUp) {
247 _send(ev);
Thomas Vachuska329af532015-03-10 02:08:33 -0700248 } else {
Simon Hunt20207df2015-03-10 18:30:14 -0700249 pendingEvents.push(ev);
Thomas Vachuska329af532015-03-10 02:08:33 -0700250 }
251 }
252
253
Simon Hunt20207df2015-03-10 18:30:14 -0700254 // ============================
255 // ===== Definition of module
Simon Hunt584122a2015-01-21 15:32:40 -0800256 angular.module('onosRemote')
Simon Huntbb920fd2015-01-22 17:06:32 -0800257 .factory('WebSocketService',
Simon Hunt20207df2015-03-10 18:30:14 -0700258 ['$log', '$location', 'FnService', 'UrlFnService', 'WSock',
259 'VeilService',
Simon Huntbb920fd2015-01-22 17:06:32 -0800260
Simon Hunt20207df2015-03-10 18:30:14 -0700261 function (_$log_, _$loc_, _fs_, _ufs_, _wsock_, _vs_) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700262 $log = _$log_;
Simon Hunt20207df2015-03-10 18:30:14 -0700263 $loc = _$loc_;
264 fs = _fs_;
265 ufs = _ufs_;
266 wsock = _wsock_;
267 vs = _vs_;
Simon Hunt1e4a0012015-01-21 11:36:08 -0800268
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700269 bindHandlers(builtinHandlers);
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700270
Simon Hunt1e4a0012015-01-21 11:36:08 -0800271 return {
Thomas Vachuska329af532015-03-10 02:08:33 -0700272 resetSid: resetSid,
273 createWebSocket: createWebSocket,
274 bindHandlers: bindHandlers,
275 unbindHandlers: unbindHandlers,
Simon Hunt3b9ad04d2015-03-11 15:26:02 -0700276 addOpenListener: addOpenListener,
277 removeOpenListener: removeOpenListener,
Thomas Vachuska329af532015-03-10 02:08:33 -0700278 sendEvent: sendEvent
Simon Hunt1e4a0012015-01-21 11:36:08 -0800279 };
Simon Hunt20207df2015-03-10 18:30:14 -0700280 }
281 ]);
Simon Hunt1e4a0012015-01-21 11:36:08 -0800282
283}());