From: Jan Hajnar Date: Tue, 16 Jun 2015 09:18:24 +0000 (+0200) Subject: Bug 2572: Docs and tutorial for WebSockets notifications X-Git-Tag: release/lithium-sr1~9 X-Git-Url: https://git.opendaylight.org/gerrit/gitweb?a=commitdiff_plain;h=c7c57e95136146cf06a95c4b6552ef46a4f3b925;p=docs.git Bug 2572: Docs and tutorial for WebSockets notifications * added tutorial for websocket notifications subscription Change-Id: I76313ee220e7aea7a71a4353c521a7966371c67c Signed-off-by: Jan Hajnar (cherry picked from commit 50f1db5232c05866fc0f8397933edf9d0794d41a) --- diff --git a/manuals/developer-guide/src/main/asciidoc/controller/controller.adoc b/manuals/developer-guide/src/main/asciidoc/controller/controller.adoc index 30155e94d..903c4c5a7 100644 --- a/manuals/developer-guide/src/main/asciidoc/controller/controller.adoc +++ b/manuals/developer-guide/src/main/asciidoc/controller/controller.adoc @@ -49,4 +49,6 @@ include::md-sal-rpc-routing.adoc[MD-SAL Rpc Routing] include::restconf.adoc[RESTCONF] +include::websocket-notifications.adoc[Websocket Notifications] + include::config.adoc[Config Subsystem] diff --git a/manuals/developer-guide/src/main/asciidoc/controller/websocket-notifications.adoc b/manuals/developer-guide/src/main/asciidoc/controller/websocket-notifications.adoc new file mode 100644 index 000000000..fb1a8ca72 --- /dev/null +++ b/manuals/developer-guide/src/main/asciidoc/controller/websocket-notifications.adoc @@ -0,0 +1,273 @@ +=== Websocket change event notification subscription tutorial + +Subscribing to data change notifications makes it possible to obtain +notifications about data manipulation (insert, change, delete) which are +done on any specified *path* of any specified *datastore* with specific +*scope*. In following examples _\{odlAddress}_ is address of server +where ODL is running and _\{odlPort}_ is port on which OpenDaylight is +running. + +==== Websocket notifications subscription process + +In this section we will learn what steps need to be taken in order to +successfully subscribe to data change event notifications. + +===== Create stream + +In order to use event notifications you first need to call RPC that +creates notification stream that you can later listen to. You need to +provide three parameters to this RPC: + +* *path*: data store path that you plan to listen to. You can register + listener on containers, lists and leaves. +* *datastore*: data store type. _OPERATIONAL_ or _CONFIGURATION_. +* *scope*: Represents scope of data change. Possible options are: +** BASE: only changes directly to the data tree node specified in the + path will be reported +** ONE: changes to the node and to direct child nodes will be reported +** SUBTREE: changes anywhere in the subtree starting at the node will + be reported + +The RPC to create the stream can be invoked via RESCONF like this: + +* URI: +\http://\{odlAddress}:\{odlPort}/restconf/operations/sal-remote:create-data-change-event-subscription +* HEADER: Content-Type=application/json +* OPERATION: POST +* DATA: ++ +[source,json] +---- +{ + "input": { + "path": "/toaster:toaster/toaster:toasterStatus", + "sal-remote-augment:datastore": "OPERATIONAL", + "sal-remote-augment:scope": "ONE" + } +} +---- + +The response should look something like this: + +[source,json] +---- +{ + "output": { + "stream-name": "toaster:toaster/toaster:toasterStatus/datastore=CONFIGURATION/scope=SUBTREE" + } +} +---- + +*stream-name* is important because you will need to use it when you +subscribe to the stream in the next step. + +NOTE: Internally, this will create a new listener for _stream-name_ + if it did not already exist. + +===== Subscribe to stream + +In order to subscribe to stream and obtain WebSocket location you need +to call _GET_ on your stream path. The URI should generally be +\http://\{odlAddress}:\{odlPort}/restconf/streams/stream/\{streamName}, +where _\{streamName}_ is the _stream-name_ parameter contained in +response from _create-data-change-event-subscription_ RPC from the +previous step. + +* URI: +\http://\{odlAddress}:\{odlPort}/restconf/streams/stream/toaster:toaster/datastore=CONFIGURATION/scope=SUBTREE +* OPERATION: GET + +The expected response status is 200 OK and response body should be empty. +You will get your WebSocket location from *Location* header of response. +For example in our particular toaster example location header would have +this value: +_ws://\{odlAddress}:8185/toaster:toaster/datastore=CONFIGURATION/scope=SUBTREE_ + +NOTE: During this phase there is an internal check for to see if a + listener for the _stream-name_ from the URI exists. If not, new + a new listener is registered with the DOM data broker. + +===== Receive notifications + +You should now have a data change notification stream created and have +location of a WebSocket. You can use this WebSocket to listen to data +change notifications. To listen to notifications you can use a +JavaScript client or if you are using chrome browser you can use the +https://chrome.google.com/webstore/detail/simple-websocket-client/pfdhoblngboilpfeibdedpjgfnlcodoo[Simple +WebSocket Client]. + +Also, for testing purposes, there is simple Java application named +WebSocketClient. The application is placed in the +_-sal-rest-connector-classes.class_ project. It accepts a WebSocket URI +as and input parameter. After starting the utility (WebSocketClient +class directly in Eclipse/InteliJ Idea) received notifications should be +displayed in console. + +Notifications are always in XML format and look like this: + +[source,xml] +---- + + 2014-09-11T09:58:23+02:00 + + + /meae:toaster + updated + + + +---- + +NOTE: Notifications never contain data. They only contain information + about the path that was changed and what kind of operation + executed on that path. If you need data you should execute a + RESTCONF GET request on the path that was changed. + +==== Example use case + +The typical use case is listening to data change events to update web +page data in real-time. In this tutorial we will be using toaster as +the base. +// TODO: link to toaster tutorial? + +When you call _make-toast_ RPC, it sets _toasterStatus_ to "down" to +reflect that the toaster is busy making toast. When it finishes, +_toasterStatus_ is set to "up" again. We will listen to this toaster +status changes in data store and will reflect it on our web page in +real-time thanks to WebSocket data change notification. + +==== Simple javascript client implementation + +We will create simple JavaScript web application that will listen +updates on _toasterStatus_ leaf and update some element of our web page +according to new toaster status state. + +===== Create stream + +First you need to create stream that you are planing to subscribe to. +This can be achieved by invoking "create-data-change-event-subscription" +RPC on RESTCONF via AJAX request. You need to provide data store *path* +that you plan to listen on, *data store type* and *scope*. If the +request is successful you can extract the *stream-name* from the +response and use that to subscribe to the newly created stream. The +_\{username}_ and _\{password}_ fields represent your credentials that +you use to connect to OpenDaylight via RESTCONF: + +NOTE: The default user name and password are "admin". + +[source,javascript] +---- +function createStream() { + $.ajax( + { + url: 'http://{odlAddress}:{odlPort}/restconf/operations/sal-remote:create-data-change-event-subscription', + type: 'POST', + headers: { + 'Authorization': 'Basic ' + btoa('{username}:{password}'), + 'Content-Type': 'application/json' + }, + data: JSON.stringify( + { + 'input': { + 'path': '/toaster:toaster/toaster:toasterStatus', + 'sal-remote-augment:datastore': 'OPERATIONAL', + 'sal-remote-augment:scope': 'ONE' + } + } + ) + }).done(function (data) { + // this function will be called when ajax call is executed successfully + subscribeToStream(data.output['stream-name']); + }).fail(function (data) { + // this function will be called when ajax call fails + console.log("Create stream call unsuccessful"); + }) +} +---- + +===== Subscribe to stream + +The Next step is to subscribe to the stream. To subscribe to the stream +you need to call _GET_ on +_\http://\{odlAddress}:\{odlPort}/restconf/streams/stream/\{stream-name}_. +If the call is successful, you get WebSocket address for this stream in +*Location* parameter inside response header. You can get response header +by calling _getResponseHeader('Location')_ on HttpRequest object inside +_done()_ function call: + +[source,javascript] +---- +function subscribeToStream(streamName) { + $.ajax( + { + url: 'http://{odlAddress}:{odlPort}/restconf/streams/stream/' + streamName; + type: 'GET', + headers: { + 'Authorization': 'Basic ' + btoa('{username}:{password}'), + } + } + ).done(function (data, textStatus, httpReq) { + // we need function that has http request object parameter in order to access response headers. + listenToNotifications(httpReq.getResponseHeader('Location')); + }).fail(function (data) { + console.log("Subscribe to stream call unsuccessful"); + }); +} +---- + +===== Receive notifications + +Once you got WebSocket server location you can now connect to it and +start receiving data change events. You need to define functions that +will handle events on WebSocket. In order to process incoming events +from OpenDaylight you need to provide a function that will handle +_onmessage_ events. The function must have one parameter that represents +the received event object. The event data will be stored in _event.data_. +The data will be in an XML format that you can then easily parse using +jQuery. + +[source,javascript] +---- +function listenToNotifications(socketLocation) { + try { + var notificatinSocket = new WebSocket(socketLocation); + + notificatinSocket.onmessage = function (event) { + // we process our received event here + console.log('Received toaster data change event.'); + $($.parseXML(event.data)).find('data-change-event').each( + function (index) { + var operation = $(this).find('operation').text(); + if (operation == 'updated') { + // toaster status was updated so we call function that gets the value of toasterStatus leaf + updateToasterStatus(); + return false; + } + } + ); + } + notificatinSocket.onerror = function (error) { + console.log("Socket error: " + error); + } + notificatinSocket.onopen = function (event) { + console.log("Socket connection opened."); + } + notificatinSocket.onclose = function (event) { + console.log("Socket connection closed."); + } + // if there is a problem on socket creation we get exception (i.e. when socket address is incorrect) + } catch(e) { + alert("Error when creating WebSocket" + e ); + } +} +---- + +The _updateToasterStatus()_ function represents function that calls +_GET_ on the path that was modified and sets toaster status in some web +page element according to received data. After the WebSocket connection +has been established you can test events by calling make-toast RPC via +RESTCONF. + +NOTE: for more information about WebSockets in JavaScript visit +https://developer.mozilla.org/en-US/docs/WebSockets/Writing_WebSocket_client_applications[Writing +WebSocket client applications]