blob: f1a4d52ec3595543be4d6d45e804237397e0fb84 [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
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -070033 url, // web socket URL
Simon Hunt8b6d2d42015-03-11 13:04:52 -070034 clusterNodes = [], // ONOS instances data for failover
35 clusterIndex = -1, // the instance to which we are connected
36 connectRetries = 0;
37
38 // =======================
39 // === Bootstrap Handler
Simon Huntacf410b2015-01-23 10:05:48 -080040
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -070041 var builtinHandlers = {
Simon Hunt8b6d2d42015-03-11 13:04:52 -070042 bootstrap: function (data) {
43 clusterNodes = data.instances;
44 clusterNodes.forEach(function (d, i) {
45 if (d.uiAttached) {
46 clusterIndex = i;
47 }
48 });
49 }
50 };
Simon Hunt20207df2015-03-10 18:30:14 -070051
52 // ==========================
53 // === Web socket callbacks
54
55 function handleOpen() {
56 $log.info('Web socket open');
Simon Hunt8b6d2d42015-03-11 13:04:52 -070057 vs.hide();
58
Simon Hunt20207df2015-03-10 18:30:14 -070059 $log.debug('Sending ' + pendingEvents.length + ' pending event(s)...');
60 pendingEvents.forEach(function (ev) {
61 _send(ev);
62 });
63 pendingEvents = [];
Simon Hunt8b6d2d42015-03-11 13:04:52 -070064
65 connectRetries = 0;
Simon Hunt20207df2015-03-10 18:30:14 -070066 wsUp = true;
67 }
68
69 // Handles the specified (incoming) message using handler bindings.
70 function handleMessage(msgEvent) {
71 var ev, h;
72
73 try {
74 ev = JSON.parse(msgEvent.data);
Simon Hunt20207df2015-03-10 18:30:14 -070075 } catch (e) {
Simon Hunt2d16fc82015-03-10 20:19:52 -070076 $log.error('Message.data is not valid JSON', msgEvent.data, e);
77 return;
Simon Hunt20207df2015-03-10 18:30:14 -070078 }
Simon Hunt2d16fc82015-03-10 20:19:52 -070079 $log.debug(' *Rx* >> ', ev.event, ev.payload);
80
81 if (h = handlers[ev.event]) {
82 try {
83 h(ev.payload);
84 } catch (e) {
85 $log.error('Problem handling event:', ev, e);
86 }
87 } else {
88 $log.warn('Unhandled event:', ev);
89 }
90
Simon Hunt20207df2015-03-10 18:30:14 -070091 }
92
93 function handleClose() {
Simon Hunt8b6d2d42015-03-11 13:04:52 -070094 var gsucc;
95
Simon Hunt20207df2015-03-10 18:30:14 -070096 $log.info('Web socket closed');
97 wsUp = false;
98
Simon Hunt8b6d2d42015-03-11 13:04:52 -070099 if (gsucc = findGuiSuccessor()) {
100 createWebSocket(webSockOpts, gsucc);
101 } else {
102 // If no controllers left to contact, show the Veil...
103 vs.show([
104 'Oops!',
105 'Web-socket connection to server closed...',
106 'Try refreshing the page.'
107 ]);
108 }
Simon Hunt20207df2015-03-10 18:30:14 -0700109 }
110
111
112 // ==============================
113 // === Private Helper Functions
114
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700115 function findGuiSuccessor() {
116 var ncn = clusterNodes.length,
117 ip = undefined,
118 node;
119
120 while (connectRetries < ncn && !ip) {
121 connectRetries++;
122 clusterIndex = (clusterIndex + 1) % ncn;
123 node = clusterNodes[clusterIndex];
124 ip = node && node.ip;
125 }
126
127 return ip;
128 }
129
Simon Hunt20207df2015-03-10 18:30:14 -0700130 function _send(ev) {
131 $log.debug(' *Tx* >> ', ev.event, ev.payload);
132 ws.send(JSON.stringify(ev));
133 }
134
135
136 // ===================
137 // === API Functions
138
139 // Required for unit tests to set to known state
Thomas Vachuska329af532015-03-10 02:08:33 -0700140 function resetSid() {
141 sid = 0;
Simon Hunt970e7fd2015-01-22 17:46:28 -0800142 }
143
Simon Hunt20207df2015-03-10 18:30:14 -0700144 // Currently supported opts:
145 // wsport: web socket port (other than default 8181)
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700146 // server: if defined, is the server address to use
147 function createWebSocket(opts, server) {
Simon Hunt20207df2015-03-10 18:30:14 -0700148 var wsport = (opts && opts.wsport) || null;
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700149 webSockOpts = opts; // preserved for future calls
Simon Hunt20207df2015-03-10 18:30:14 -0700150
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700151 url = ufs.wsUrl('core', wsport, server);
Simon Hunt20207df2015-03-10 18:30:14 -0700152
153 $log.debug('Attempting to open websocket to: ' + url);
154 ws = wsock.newWebSocket(url);
155 if (ws) {
156 ws.onopen = handleOpen;
157 ws.onmessage = handleMessage;
158 ws.onclose = handleClose;
159 }
160 // Note: Wsock logs an error if the new WebSocket call fails
Simon Hunt2d16fc82015-03-10 20:19:52 -0700161 return url;
Simon Hunt20207df2015-03-10 18:30:14 -0700162 }
163
Thomas Vachuska329af532015-03-10 02:08:33 -0700164 // Binds the specified message handlers.
Simon Hunt20207df2015-03-10 18:30:14 -0700165 // keys are the event IDs
166 // values are the API on which the handler function is a property
Thomas Vachuska329af532015-03-10 02:08:33 -0700167 function bindHandlers(handlerMap) {
168 var m = d3.map(handlerMap),
169 dups = [];
Simon Hunt970e7fd2015-01-22 17:46:28 -0800170
Simon Hunt20207df2015-03-10 18:30:14 -0700171 m.forEach(function (eventId, api) {
172 var fn = fs.isF(api[eventId]);
Thomas Vachuska329af532015-03-10 02:08:33 -0700173 if (!fn) {
Simon Hunt20207df2015-03-10 18:30:14 -0700174 $log.warn(eventId + ' handler not a function');
Thomas Vachuska329af532015-03-10 02:08:33 -0700175 return;
Simon Hunt970e7fd2015-01-22 17:46:28 -0800176 }
Thomas Vachuska329af532015-03-10 02:08:33 -0700177
Simon Hunt20207df2015-03-10 18:30:14 -0700178 if (handlers[eventId]) {
179 dups.push(eventId);
Thomas Vachuska329af532015-03-10 02:08:33 -0700180 } else {
Simon Hunt20207df2015-03-10 18:30:14 -0700181 handlers[eventId] = fn;
Thomas Vachuska329af532015-03-10 02:08:33 -0700182 }
183 });
184 if (dups.length) {
185 $log.warn('duplicate bindings ignored:', dups);
186 }
Simon Hunt970e7fd2015-01-22 17:46:28 -0800187 }
188
Thomas Vachuska329af532015-03-10 02:08:33 -0700189 // Unbinds the specified message handlers.
Simon Hunt20207df2015-03-10 18:30:14 -0700190 // Expected that the same map will be used, but we only care about keys
Thomas Vachuska329af532015-03-10 02:08:33 -0700191 function unbindHandlers(handlerMap) {
192 var m = d3.map(handlerMap);
Simon Hunt20207df2015-03-10 18:30:14 -0700193
194 m.forEach(function (eventId) {
195 delete handlers[eventId];
Thomas Vachuska329af532015-03-10 02:08:33 -0700196 });
197 }
Simon Huntacf410b2015-01-23 10:05:48 -0800198
Simon Hunt20207df2015-03-10 18:30:14 -0700199 // Formulates an event message and sends it via the web-socket.
200 // If the websocket is not up yet, we store it in a pending list.
Thomas Vachuska329af532015-03-10 02:08:33 -0700201 function sendEvent(evType, payload) {
Simon Hunt20207df2015-03-10 18:30:14 -0700202 var ev = {
Thomas Vachuska329af532015-03-10 02:08:33 -0700203 event: evType,
204 sid: ++sid,
Simon Hunt20207df2015-03-10 18:30:14 -0700205 payload: payload || {}
206 };
207
208 if (wsUp) {
209 _send(ev);
Thomas Vachuska329af532015-03-10 02:08:33 -0700210 } else {
Simon Hunt20207df2015-03-10 18:30:14 -0700211 pendingEvents.push(ev);
Thomas Vachuska329af532015-03-10 02:08:33 -0700212 }
213 }
214
215
Simon Hunt20207df2015-03-10 18:30:14 -0700216 // ============================
217 // ===== Definition of module
Simon Hunt584122a2015-01-21 15:32:40 -0800218 angular.module('onosRemote')
Simon Huntbb920fd2015-01-22 17:06:32 -0800219 .factory('WebSocketService',
Simon Hunt20207df2015-03-10 18:30:14 -0700220 ['$log', '$location', 'FnService', 'UrlFnService', 'WSock',
221 'VeilService',
Simon Huntbb920fd2015-01-22 17:06:32 -0800222
Simon Hunt20207df2015-03-10 18:30:14 -0700223 function (_$log_, _$loc_, _fs_, _ufs_, _wsock_, _vs_) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700224 $log = _$log_;
Simon Hunt20207df2015-03-10 18:30:14 -0700225 $loc = _$loc_;
226 fs = _fs_;
227 ufs = _ufs_;
228 wsock = _wsock_;
229 vs = _vs_;
Simon Hunt1e4a0012015-01-21 11:36:08 -0800230
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700231 bindHandlers(builtinHandlers);
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700232
Simon Hunt1e4a0012015-01-21 11:36:08 -0800233 return {
Thomas Vachuska329af532015-03-10 02:08:33 -0700234 resetSid: resetSid,
235 createWebSocket: createWebSocket,
236 bindHandlers: bindHandlers,
237 unbindHandlers: unbindHandlers,
238 sendEvent: sendEvent
Simon Hunt1e4a0012015-01-21 11:36:08 -0800239 };
Simon Hunt20207df2015-03-10 18:30:14 -0700240 }
241 ]);
Simon Hunt1e4a0012015-01-21 11:36:08 -0800242
243}());