blob: ca2840d2cdedde8358f80e2d123c234dc3ff7b39 [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) {
Thomas Vachuska20084b72015-03-11 13:46:50 -070043 clusterNodes = data.clusterNodes;
Simon Hunt8b6d2d42015-03-11 13:04:52 -070044 clusterNodes.forEach(function (d, i) {
45 if (d.uiAttached) {
46 clusterIndex = i;
Thomas Vachuska20084b72015-03-11 13:46:50 -070047 $log.info('Connected to cluster node ' + d.ip);
48 // TODO: add connect info to masthead somewhere
Simon Hunt8b6d2d42015-03-11 13:04:52 -070049 }
50 });
51 }
52 };
Simon Hunt20207df2015-03-10 18:30:14 -070053
54 // ==========================
55 // === Web socket callbacks
56
57 function handleOpen() {
58 $log.info('Web socket open');
Simon Hunt8b6d2d42015-03-11 13:04:52 -070059 vs.hide();
60
Simon Hunt20207df2015-03-10 18:30:14 -070061 $log.debug('Sending ' + pendingEvents.length + ' pending event(s)...');
62 pendingEvents.forEach(function (ev) {
63 _send(ev);
64 });
65 pendingEvents = [];
Simon Hunt8b6d2d42015-03-11 13:04:52 -070066
67 connectRetries = 0;
Simon Hunt20207df2015-03-10 18:30:14 -070068 wsUp = true;
69 }
70
71 // Handles the specified (incoming) message using handler bindings.
72 function handleMessage(msgEvent) {
73 var ev, h;
74
75 try {
76 ev = JSON.parse(msgEvent.data);
Simon Hunt20207df2015-03-10 18:30:14 -070077 } catch (e) {
Simon Hunt2d16fc82015-03-10 20:19:52 -070078 $log.error('Message.data is not valid JSON', msgEvent.data, e);
79 return;
Simon Hunt20207df2015-03-10 18:30:14 -070080 }
Simon Hunt2d16fc82015-03-10 20:19:52 -070081 $log.debug(' *Rx* >> ', ev.event, ev.payload);
82
83 if (h = handlers[ev.event]) {
84 try {
85 h(ev.payload);
86 } catch (e) {
87 $log.error('Problem handling event:', ev, e);
88 }
89 } else {
90 $log.warn('Unhandled event:', ev);
91 }
92
Simon Hunt20207df2015-03-10 18:30:14 -070093 }
94
95 function handleClose() {
Simon Hunt8b6d2d42015-03-11 13:04:52 -070096 var gsucc;
97
Simon Hunt20207df2015-03-10 18:30:14 -070098 $log.info('Web socket closed');
99 wsUp = false;
100
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700101 if (gsucc = findGuiSuccessor()) {
102 createWebSocket(webSockOpts, gsucc);
103 } else {
104 // If no controllers left to contact, show the Veil...
105 vs.show([
106 'Oops!',
107 'Web-socket connection to server closed...',
108 'Try refreshing the page.'
109 ]);
110 }
Simon Hunt20207df2015-03-10 18:30:14 -0700111 }
112
113
114 // ==============================
115 // === Private Helper Functions
116
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700117 function findGuiSuccessor() {
118 var ncn = clusterNodes.length,
119 ip = undefined,
120 node;
121
122 while (connectRetries < ncn && !ip) {
123 connectRetries++;
124 clusterIndex = (clusterIndex + 1) % ncn;
125 node = clusterNodes[clusterIndex];
126 ip = node && node.ip;
127 }
128
129 return ip;
130 }
131
Simon Hunt20207df2015-03-10 18:30:14 -0700132 function _send(ev) {
133 $log.debug(' *Tx* >> ', ev.event, ev.payload);
134 ws.send(JSON.stringify(ev));
135 }
136
137
138 // ===================
139 // === API Functions
140
141 // Required for unit tests to set to known state
Thomas Vachuska329af532015-03-10 02:08:33 -0700142 function resetSid() {
143 sid = 0;
Simon Hunt970e7fd2015-01-22 17:46:28 -0800144 }
145
Simon Hunt20207df2015-03-10 18:30:14 -0700146 // Currently supported opts:
147 // wsport: web socket port (other than default 8181)
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700148 // server: if defined, is the server address to use
149 function createWebSocket(opts, server) {
Simon Hunt20207df2015-03-10 18:30:14 -0700150 var wsport = (opts && opts.wsport) || null;
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700151 webSockOpts = opts; // preserved for future calls
Simon Hunt20207df2015-03-10 18:30:14 -0700152
Simon Hunt8b6d2d42015-03-11 13:04:52 -0700153 url = ufs.wsUrl('core', wsport, server);
Simon Hunt20207df2015-03-10 18:30:14 -0700154
155 $log.debug('Attempting to open websocket to: ' + url);
156 ws = wsock.newWebSocket(url);
157 if (ws) {
158 ws.onopen = handleOpen;
159 ws.onmessage = handleMessage;
160 ws.onclose = handleClose;
161 }
162 // Note: Wsock logs an error if the new WebSocket call fails
Simon Hunt2d16fc82015-03-10 20:19:52 -0700163 return url;
Simon Hunt20207df2015-03-10 18:30:14 -0700164 }
165
Thomas Vachuska329af532015-03-10 02:08:33 -0700166 // Binds the specified message handlers.
Simon Hunt20207df2015-03-10 18:30:14 -0700167 // keys are the event IDs
168 // values are the API on which the handler function is a property
Thomas Vachuska329af532015-03-10 02:08:33 -0700169 function bindHandlers(handlerMap) {
170 var m = d3.map(handlerMap),
171 dups = [];
Simon Hunt970e7fd2015-01-22 17:46:28 -0800172
Simon Hunt20207df2015-03-10 18:30:14 -0700173 m.forEach(function (eventId, api) {
174 var fn = fs.isF(api[eventId]);
Thomas Vachuska329af532015-03-10 02:08:33 -0700175 if (!fn) {
Simon Hunt20207df2015-03-10 18:30:14 -0700176 $log.warn(eventId + ' handler not a function');
Thomas Vachuska329af532015-03-10 02:08:33 -0700177 return;
Simon Hunt970e7fd2015-01-22 17:46:28 -0800178 }
Thomas Vachuska329af532015-03-10 02:08:33 -0700179
Simon Hunt20207df2015-03-10 18:30:14 -0700180 if (handlers[eventId]) {
181 dups.push(eventId);
Thomas Vachuska329af532015-03-10 02:08:33 -0700182 } else {
Simon Hunt20207df2015-03-10 18:30:14 -0700183 handlers[eventId] = fn;
Thomas Vachuska329af532015-03-10 02:08:33 -0700184 }
185 });
186 if (dups.length) {
187 $log.warn('duplicate bindings ignored:', dups);
188 }
Simon Hunt970e7fd2015-01-22 17:46:28 -0800189 }
190
Thomas Vachuska329af532015-03-10 02:08:33 -0700191 // Unbinds the specified message handlers.
Simon Hunt20207df2015-03-10 18:30:14 -0700192 // Expected that the same map will be used, but we only care about keys
Thomas Vachuska329af532015-03-10 02:08:33 -0700193 function unbindHandlers(handlerMap) {
194 var m = d3.map(handlerMap);
Simon Hunt20207df2015-03-10 18:30:14 -0700195
196 m.forEach(function (eventId) {
197 delete handlers[eventId];
Thomas Vachuska329af532015-03-10 02:08:33 -0700198 });
199 }
Simon Huntacf410b2015-01-23 10:05:48 -0800200
Simon Hunt20207df2015-03-10 18:30:14 -0700201 // Formulates an event message and sends it via the web-socket.
202 // If the websocket is not up yet, we store it in a pending list.
Thomas Vachuska329af532015-03-10 02:08:33 -0700203 function sendEvent(evType, payload) {
Simon Hunt20207df2015-03-10 18:30:14 -0700204 var ev = {
Thomas Vachuska329af532015-03-10 02:08:33 -0700205 event: evType,
206 sid: ++sid,
Simon Hunt20207df2015-03-10 18:30:14 -0700207 payload: payload || {}
208 };
209
210 if (wsUp) {
211 _send(ev);
Thomas Vachuska329af532015-03-10 02:08:33 -0700212 } else {
Simon Hunt20207df2015-03-10 18:30:14 -0700213 pendingEvents.push(ev);
Thomas Vachuska329af532015-03-10 02:08:33 -0700214 }
215 }
216
217
Simon Hunt20207df2015-03-10 18:30:14 -0700218 // ============================
219 // ===== Definition of module
Simon Hunt584122a2015-01-21 15:32:40 -0800220 angular.module('onosRemote')
Simon Huntbb920fd2015-01-22 17:06:32 -0800221 .factory('WebSocketService',
Simon Hunt20207df2015-03-10 18:30:14 -0700222 ['$log', '$location', 'FnService', 'UrlFnService', 'WSock',
223 'VeilService',
Simon Huntbb920fd2015-01-22 17:06:32 -0800224
Simon Hunt20207df2015-03-10 18:30:14 -0700225 function (_$log_, _$loc_, _fs_, _ufs_, _wsock_, _vs_) {
Thomas Vachuska329af532015-03-10 02:08:33 -0700226 $log = _$log_;
Simon Hunt20207df2015-03-10 18:30:14 -0700227 $loc = _$loc_;
228 fs = _fs_;
229 ufs = _ufs_;
230 wsock = _wsock_;
231 vs = _vs_;
Simon Hunt1e4a0012015-01-21 11:36:08 -0800232
Thomas Vachuska20084b72015-03-11 13:46:50 -0700233 // TODO: Consider how to simplify handler structure
234 // Now it is an object of key -> object that has a method named 'key'.
235 bindHandlers({
236 bootstrap: builtinHandlers
237 });
Thomas Vachuskab6acc7b2015-03-11 09:11:21 -0700238
Simon Hunt1e4a0012015-01-21 11:36:08 -0800239 return {
Thomas Vachuska329af532015-03-10 02:08:33 -0700240 resetSid: resetSid,
241 createWebSocket: createWebSocket,
242 bindHandlers: bindHandlers,
243 unbindHandlers: unbindHandlers,
244 sendEvent: sendEvent
Simon Hunt1e4a0012015-01-21 11:36:08 -0800245 };
Simon Hunt20207df2015-03-10 18:30:14 -0700246 }
247 ]);
Simon Hunt1e4a0012015-01-21 11:36:08 -0800248
249}());