[Kimchi-devel] [PATCH] [WoK] Asynchronous UI notification implementation

Aline Manera alinefm at linux.vnet.ibm.com
Fri Mar 10 12:36:19 UTC 2017



On 03/10/2017 09:32 AM, Aline Manera wrote:
>
>
> On 03/10/2017 09:30 AM, Daniel Henrique Barboza wrote:
>>
>>
>> On 03/10/2017 08:40 AM, Aline Manera wrote:
>>> Hi Daniel,
>>>
>>> On 03/07/2017 03:52 PM, dhbarboza82 at gmail.com wrote:
>>>> From: Daniel Henrique Barboza <danielhb at linux.vnet.ibm.com>
>>>>
>>>> This patch makes backend and UI changes to implement the asynchronous
>>>> UI notification in WoK.
>>>>
>>>> - Backend:
>>>>
>>>> A push server was implemented from scratch to manage the opened 
>>>> websocket
>>>> connections. The push server connects to the
>>>> /run/user/<user_id>/woknotifications UNIX socket and broadcasts all 
>>>> messages
>>>> to all connections.
>>>>
>>>> The websocket module is the same module that exists in the Kimchi
>>>> plug-in. The idea is to remove the module from Kimchi and make it
>>>> use the module from WoK. ws_proxy initialization was also added
>>>> in src/wok/server.py.
>>>>
>>>> A change were made in Wok base control classes to allow every
>>>> call of log_request to also send a websocket notification in
>>>> the following format:
>>>>
>>>> <METHOD>:/<plugin>/<entity>/<action>
>>>>
>>>> For example, creating a new user in Ginger would trigger the
>>>> following websocket notification:
>>>>
>>>> 'POST:/ginger/users'
>>>>
>>>> - Frontend:
>>>>
>>>> In ui/js/wok.main.js two new functions were added to help the
>>>> usage of asynchronous notifications in the frontend. The idea:
>>>> a single websocket is opened per session. This opened websocket
>>>> will broadcast all incoming messages to all listeners registered.
>>>> Listeners can be added by the new wok.addNotificationListener()
>>>> method. This method will clean up any registered listener by
>>>> itself when the user changes tabs/URL.
>>>>
>>>> The single websocket sends heartbeats to the backend side each
>>>> 30 seconds. No reply from the backend is issued or expected. This
>>>> heartbeat is just a way to ensure that the browser does not
>>>> close the connection due to inactivity. This behavior varies from
>>>> browser to browser but this 30 second heartbeat is more than enough
>>>> to ensure that the websocket is kept alive.
>>>>
>>>> - Working example in User Log:
>>>>
>>>> A simple usage is provided in this patch. Changes were made in the
>>>> UI of the User Log feature to refresh the listing each time a new
>>>> log entry websocket notification is received. The idea is to allow
>>>> this code to be a working example of how other tabs can consume
>>>> the asynchronous notifications.
>>>>
>>>> Signed-off-by: Daniel Henrique Barboza <danielhb at linux.vnet.ibm.com>
>>>> ---
>>>>   contrib/DEBIAN/control.in      |   1 +
>>>>   contrib/wok.spec.fedora.in     |   1 +
>>>>   contrib/wok.spec.suse.in       |   1 +
>>>>   docs/fedora-deps.md            |   2 +-
>>>>   docs/opensuse-deps.md          |   3 +-
>>>>   docs/ubuntu-deps.md            |   3 +-
>>>>   src/wok/config.py.in           |   8 ++
>>>>   src/wok/control/base.py        |  16 ++--
>>>>   src/wok/model/notifications.py |   2 +-
>>>>   src/wok/pushserver.py          | 163 
>>>> +++++++++++++++++++++++++++++++++++++++++
>>>>   src/wok/reqlogger.py           |  11 ++-
>>>>   src/wok/root.py                |   3 +
>>>>   src/wok/server.py              |  13 +++-
>>>>   src/wok/websocket.py           | 123 +++++++++++++++++++++++++++++++
>>>>   ui/js/src/wok.main.js          |  43 +++++++++++
>>>>   ui/js/wok.user-log.js          |  12 +++
>>>>   16 files changed, 391 insertions(+), 14 deletions(-)
>>>>   create mode 100644 src/wok/pushserver.py
>>>>   create mode 100644 src/wok/websocket.py
>>>
>>>> diff --git a/ui/js/src/wok.main.js b/ui/js/src/wok.main.js
>>>> index c8c3889..b58706d 100644
>>>> --- a/ui/js/src/wok.main.js
>>>> +++ b/ui/js/src/wok.main.js
>>>> @@ -29,6 +29,46 @@ wok.getConfig(function(result) {
>>>>       wok.config = {};
>>>>   });
>>>
>>>> +wok.notificationListeners = [];
>>>> +wok.addNotificationListener = function(func) {
>>>> +    wok.notificationListeners.push(func);
>>>> +    $(window).one("hashchange", function() {
>>>> +        var del_index = wok.notificationListeners.indexOf(func);
>>>> +        wok.notificationListeners.splice(del_index, 1);
>>>> +    });
>>>> +};
>>>> +
>>>> +wok.notificationsWebSocket = undefined;
>>>> +wok.startNotificationWebSocket = function () {
>>>> +    var addr = window.location.hostname + ':' + window.location.port;
>>>> +    var token = 
>>>> wok.urlSafeB64Encode('woknotifications').replace(/=*$/g, "");
>>>> +    var url = 'wss://' + addr + '/websockify?token=' + token;
>>>> +    wok.notificationsWebSocket = new WebSocket(url, ['base64']);
>>>> +
>>>> +    wok.notificationsWebSocket.onmessage = function(event) {
>>>> +        var buffer_rcv = window.atob(event.data);
>>>> +        var messages = buffer_rcv.split("//EOM//");
>>>> +        for (var i = 0; i < messages.length; i++) {
>>>> +            if (messages[i] === "") {
>>>> +                continue;
>>>> +        }
>>>> +            for (var j = 0; j < wok.notificationListeners.length; 
>>>> j++) {
>>>> +                wok.notificationListeners[j](messages[i]);
>>>> +            }
>>>> +        }
>>>> +    };
>>>> +
>>>
>>> I am not sure I understood this code correctly.
>>> From what I had in mind wok.notificationListeners should be an dict 
>>> instead of an array. In which the key is the notification message 
>>> and the value the functions to be executed on message received.
>>>
>>> wok.notificationListeners = {}
>>>
>>> When adding a new listener, we would do:
>>>
>>> wok.notificationListeners[<msg>] = <func-to-handle-msg>
>>>
>>> That way, we avoid calling each wok.notificationListeners sending 
>>> the same message to listeners not related to that message.
>>>
>>> To call the listener, we do:
>>>
>>> func = wok.notificationListeners.get(<message>)
>>> if func != undefined:
>>>     #call func
>>>     func()
>>
>> The idea was to make it generic. Every message would be forwarded to 
>> all functions
>> that is currently listening to websocket events. The job of filtering 
>> what you want
>> would be done inside each listener itself.
>>
>> This allows for multiple listeners being able to receive the same 
>> message. Otherwise,
>> suppose that in the same tab we have multiple independent features 
>> (like Ginger with
>> sidebar), common interest messages like "WoK is going to be reloaded" 
>> or "A task of
>> id=N has been finished" would be captured by only one of them.
>>

Also thinking about the Ginger scenario, the $(window).one("hashchange") 
will not work
So it is better to check the function is defined or not.

func_list = wok.listenerNotification[<msg>]
if func_list:
     for func in func_list:
         if func != undefined:
             #call func

>
> So do a list of functions to be called.
>
> wok.listenerNotifications[<msg>] = [<list of listener>]
>
> Doesn't make sense forward all the messgaes to all the listener. The 
> listener should be trciked by a message.
>
>>
>>>
>>>> + sessionStorage.setItem('wokNotificationWebSocket', 'true');
>>>> +    var heartbeat = setInterval(function() {
>>>> + wok.notificationsWebSocket.send(window.btoa('heartbeat'));
>>>> +    }, 30000);
>>>> +
>>>> +    wok.notificationsWebSocket.onclose = function() {
>>>> +        clearInterval(heartbeat);
>>>> +    };
>>>> +};
>>>> +
>>>> +
>>>>   wok.main = function() {
>>>>       wok.isLoggingOut = false;
>>>>       wok.popable();
>>>> @@ -395,6 +435,9 @@ wok.main = function() {
>>>>
>>>>           // Set handler for help button
>>>>           $('#btn-help').on('click', wok.openHelp);
>>>> +
>>>> +        // start WebSocket
>>>> +        wok.startNotificationWebSocket();
>>>>       };
>>>>
>>>>       var initUI = function() {
>>>> diff --git a/ui/js/wok.user-log.js b/ui/js/wok.user-log.js
>>>> index 0e8fb09..765b75a 100644
>>>> --- a/ui/js/wok.user-log.js
>>>> +++ b/ui/js/wok.user-log.js
>>>> @@ -153,6 +153,18 @@ wok.initUserLogContent = function() {
>>>>           $("#user-log-grid").bootgrid("search");
>>>>           wok.initUserLogConfigGridData();
>>>>       });
>>>> +
>>>> +    wok.addNotificationListener(function(message) {
>>>
>>>> +        var msg_attr = message.split(":");
>>>> +        if (msg_attr.length != 2) {
>>>> +            return;
>>>> +    }
>>>> +        var method = msg_attr[0];
>>>> +        var target = msg_attr[1];
>>>> +        if (method === "POST" && target === '/wok/logs') {
>>>
>>> All the above block is not needed when you follow what I described 
>>> above, as this function would only be called when POST:/wok/logs
>>
>> Yes, if we restrict the messages to just be received by one listener 
>> this filtering is
>> unnecessary.
>>>
>>>> + $("#refresh-button").click();
>>>> +        }
>>>> +    });
>>>>   };
>>>>
>>>>   wok.initUserLogWindow = function() {
>>>
>>
>
> _______________________________________________
> Kimchi-devel mailing list
> Kimchi-devel at ovirt.org
> http://lists.ovirt.org/mailman/listinfo/kimchi-devel
>



More information about the Kimchi-devel mailing list