GUI -- Continued work on supporting GUI failover. WIP
- Thomas to complete.

Change-Id: I4ed40a0d5b0b48cd1d9fac175a1f66e81df7dacf
diff --git a/web/gui/src/main/webapp/app/fw/remote/urlfn.js b/web/gui/src/main/webapp/app/fw/remote/urlfn.js
index c2addef..6ec01b4 100644
--- a/web/gui/src/main/webapp/app/fw/remote/urlfn.js
+++ b/web/gui/src/main/webapp/app/fw/remote/urlfn.js
@@ -33,25 +33,25 @@
                 return secure ? protocol + 's' : protocol;
             }
 
-            function urlBase(protocol, port) {
+            function urlBase(protocol, port, host) {
                 return matchSecure(protocol) + '://' +
-                    $loc.host() + ':' + (port || $loc.port());
+                    (host || $loc.host()) + ':' + (port || $loc.port());
             }
 
             function httpPrefix(suffix) {
                 return urlBase('http') + suffix;
             }
 
-            function wsPrefix(suffix, wsport) {
-                return urlBase('ws', wsport) + suffix;
+            function wsPrefix(suffix, wsport, host) {
+                return urlBase('ws', wsport, host) + suffix;
             }
 
             function rsUrl(path) {
                 return httpPrefix(rsSuffix) + path;
             }
 
-            function wsUrl(path, wsport) {
-                return wsPrefix(wsSuffix, wsport) + path;
+            function wsUrl(path, wsport, host) {
+                return wsPrefix(wsSuffix, wsport, host) + path;
             }
 
             return {
diff --git a/web/gui/src/main/webapp/app/fw/remote/websocket.js b/web/gui/src/main/webapp/app/fw/remote/websocket.js
index 861bb40..f1a4d52 100644
--- a/web/gui/src/main/webapp/app/fw/remote/websocket.js
+++ b/web/gui/src/main/webapp/app/fw/remote/websocket.js
@@ -24,30 +24,45 @@
     var $log, $loc, fs, ufs, wsock, vs;
 
     // internal state
-    var ws = null,              // web socket reference
+    var webSockOpts,            // web socket options
+        ws = null,              // web socket reference
         wsUp = false,           // web socket is good to go
         sid = 0,                // event sequence identifier
         handlers = {},          // event handler bindings
         pendingEvents = [],     // events TX'd while socket not up
         url,                    // web socket URL
-        instances = [];
+        clusterNodes = [],      // ONOS instances data for failover
+        clusterIndex = -1,      // the instance to which we are connected
+        connectRetries = 0;
+
+    // =======================
+    // === Bootstrap Handler
 
     var builtinHandlers = {
-            onosInstances: function (data) {
-                instances = data.instances;
-            }
-    }
+        bootstrap: function (data) {
+            clusterNodes = data.instances;
+            clusterNodes.forEach(function (d, i) {
+                if (d.uiAttached) {
+                    clusterIndex = i;
+                }
+            });
+        }
+    };
 
     // ==========================
     // === Web socket callbacks
 
     function handleOpen() {
         $log.info('Web socket open');
+        vs.hide();
+
         $log.debug('Sending ' + pendingEvents.length + ' pending event(s)...');
         pendingEvents.forEach(function (ev) {
             _send(ev);
         });
         pendingEvents = [];
+
+        connectRetries = 0;
         wsUp = true;
     }
 
@@ -76,23 +91,42 @@
     }
 
     function handleClose() {
+        var gsucc;
+
         $log.info('Web socket closed');
         wsUp = false;
 
-        // FIXME: implement controller failover logic
-
-        // If no controllers left to contact, show the Veil...
-        vs.show([
-            'Oops!',
-            'Web-socket connection to server closed...',
-            'Try refreshing the page.'
-        ]);
+        if (gsucc = findGuiSuccessor()) {
+            createWebSocket(webSockOpts, gsucc);
+        } else {
+            // If no controllers left to contact, show the Veil...
+            vs.show([
+                'Oops!',
+                'Web-socket connection to server closed...',
+                'Try refreshing the page.'
+            ]);
+        }
     }
 
 
     // ==============================
     // === Private Helper Functions
 
+    function findGuiSuccessor() {
+        var ncn = clusterNodes.length,
+            ip = undefined,
+            node;
+
+        while (connectRetries < ncn && !ip) {
+            connectRetries++;
+            clusterIndex = (clusterIndex + 1) % ncn;
+            node = clusterNodes[clusterIndex];
+            ip = node && node.ip;
+        }
+
+        return ip;
+    }
+
     function _send(ev) {
         $log.debug(' *Tx* >> ', ev.event, ev.payload);
         ws.send(JSON.stringify(ev));
@@ -109,10 +143,12 @@
 
     // Currently supported opts:
     //   wsport: web socket port (other than default 8181)
-    function createWebSocket(opts) {
+    // server: if defined, is the server address to use
+    function createWebSocket(opts, server) {
         var wsport = (opts && opts.wsport) || null;
+        webSockOpts = opts; // preserved for future calls
 
-        url = ufs.wsUrl('core', wsport);
+        url = ufs.wsUrl('core', wsport, server);
 
         $log.debug('Attempting to open websocket to: ' + url);
         ws = wsock.newWebSocket(url);
@@ -192,10 +228,7 @@
             wsock = _wsock_;
             vs = _vs_;
 
-            // Bind instance handlers
-            bindHandlers({
-                onosInstances: builtinHandlers
-            });
+            bindHandlers(builtinHandlers);
 
             return {
                 resetSid: resetSid,
diff --git a/web/gui/src/main/webapp/tests/app/fw/remote/urlfn-spec.js b/web/gui/src/main/webapp/tests/app/fw/remote/urlfn-spec.js
index ebcfba5..eddbf8b 100644
--- a/web/gui/src/main/webapp/tests/app/fw/remote/urlfn-spec.js
+++ b/web/gui/src/main/webapp/tests/app/fw/remote/urlfn-spec.js
@@ -81,4 +81,9 @@
         setLoc('http', 'foo', '123');
         expect(ufs.wsUrl('xyyzy', 456)).toEqual('ws://foo:456/onos/ui/websock/xyyzy');
     });
+
+    it('should allow us to define an alternate host', function () {
+        setLoc('http', 'foo', '123');
+        expect(ufs.wsUrl('core', 456, 'bar')).toEqual('ws://bar:456/onos/ui/websock/core');
+    });
 });