=== 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 ---- ==== 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]