Initial implementation of IntService and sample application

This implementation has been used for P4Workshop demo (ONOS INT Support).

Change-Id: I2ff94f8a79f6d5a328b94f7ed178e575b81c3fe9
diff --git a/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.css b/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.css
new file mode 100644
index 0000000..1cfd447
--- /dev/null
+++ b/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.css
@@ -0,0 +1,108 @@
+/* css for sample app custom view */
+
+#ov-int-app-main {
+    padding: 20px;
+}
+.light #ov-int-app-main {
+    color: navy;
+}
+.dark #ov-int-app-main {
+    color: #88f;
+}
+
+#ov-int-app-main .button-panel {
+    margin: 10px;
+    width: 200px;
+}
+
+.light #ov-int-app-main .button-panel {
+    background-color: #ccf;
+}
+
+.dark #ov-int-app-main .button-panel {
+    background-color: #444;
+}
+
+#ov-int-app-main .int-app-button {
+    cursor: pointer;
+    padding: 4px;
+    text-align: center;
+}
+
+
+.light #ov-int-app-main .int-app-button {
+    color: white;
+    background-color: #99d;
+}
+.dark #ov-int-app-main .int-app-button {
+    color: black;
+    background-color: #aaa;
+}
+
+#ov-int-app-main .config-button-panel {
+    margin: 10px;
+    width: 200px;
+}
+
+.light #ov-int-app-main .config-button-panel {
+    background-color: #ccf;
+}
+
+.dark #ov-int-app-main .config-button-panel {
+    background-color: #444;
+}
+
+#ov-int-app-main .int-app-config-button {
+    cursor: pointer;
+    padding: 4px;
+    text-align: center;
+}
+
+
+.light #ov-int-app-main .int-app-config-button {
+    color: white;
+    background-color: #99d;
+}
+.dark #ov-int-app-main .int-app-config-button {
+    color: black;
+    background-color: #aaa;
+}
+/*---------------------------------------------------------------------------*/
+#ov-int-app-main h2 {
+    display: inline-block;
+}
+/* #ov-int-app-main .table-body{
+    display: inline-block;
+    overflow-y: scroll;
+    max-height:100px;
+} */
+
+/* Panel Styling */
+#ov-int-app-main-item-details-panel.floatpanel {
+    position: absolute;
+    top: 115px;
+}
+
+.light #ov-int-app-main-item-details-panel.floatpanel {
+    background-color: rgb(229, 234, 237);
+}
+.dark #ov-int-app-main-item-details-panel.floatpanel {
+    background-color: #3A4042;
+}
+
+#ov-int-app-main-item-details-panel h3 {
+    margin: 0;
+    font-size: large;
+}
+
+#ov-int-app-main-item-details-panel h4 {
+    margin: 0;
+}
+
+#ov-int-app-main-item-details-panel td {
+    padding: 5px;
+}
+#ov-int-app-main-item-details-panel td.label {
+    font-style: italic;
+    opacity: 0.8;
+}
diff --git a/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.html b/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.html
new file mode 100644
index 0000000..42717e0
--- /dev/null
+++ b/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.html
@@ -0,0 +1,110 @@
+<!-- partial HTML -->
+<div id="ov-int-app-main">
+    <div class="config-panel">
+        <div>
+            Collector IP
+            <input type="text" required pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}$" ng-model="collectorIp">
+
+            Collector Port
+            <input type="text" required pattern="^[0-9]{0,5}$" ng-model="collectorPort">
+        </div>
+    </div>
+    <div class="config-button-panel">
+        <div class = "int-app-config-button" ng-click="sendIntConfigString()">
+            Deploy
+        </div>
+    </div>
+    <hr>
+    <div class="input-panel">
+        <div>
+            Src Address
+            <input type="text" required pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?$" ng-model="ip4SrcPrefix">
+
+            Dst Address
+            <input type="text" required pattern="^([0-9]{1,3}\.){3}[0-9]{1,3}(/[0-9]{1,2})?$" ng-model="ip4DstPrefix">
+
+            Src Port
+            <input type="text" required pattern="^[0-9]{0,5}$" ng-model="l4SrcPort">
+
+            Dst Port
+            <input type="text" required pattern="^[0-9]{0,5}$" ng-model="l4DstPort">
+
+            Protocol
+            <select name="protocol" ng-model="protocol">
+                <option selected disabled hidden style="display: none" value=''></option>
+                <option value="TCP">TCP</option>
+                <option value="UDP">UDP</option>
+            </select>
+        </div>
+        <div>
+            <input type="checkbox" ng-model= "metaSwId">Switch Id
+            <input type="checkbox" ng-model= "metaPortId">Port Id
+            <input type="checkbox" ng-model= "metaHopLatency">Hop Latency
+            <input type="checkbox" ng-model= "metaQOccupancy">Queue Occupancy
+            <input type="checkbox" ng-model= "metaIngressTstamp">Ingress Timestamp
+            <input type="checkbox" ng-model= "metaEgressTstamp">Egress Timestamp
+            <!--<input type="checkbox" ng-model= "metaQCongestion">Queue Congestion Status-->
+            <input type="checkbox" ng-model= "metaEgressTx">Egress Port Tx Utilization
+        </div>
+    </div>
+
+    <div class="button-panel">
+        <div class = "int-app-button" ng-click="sendIntIntentString()">
+            Deploy
+        </div>
+    </div>
+    <!-- ++++++++++++++++++++++++++++++++++++++++++++++++++++++++ -->
+    <div class='int-app-main-intents'>
+        <div class="tabular-header">
+            <h2>Installed INT Intents ({{tableData.length}} total)</h2>
+            <div class="ctrl-btns">
+                <div class="refresh" ng-class="{active: autoRefresh}"
+                     icon icon-id="refresh" icon-size="36"
+                     tooltip tt-msg="autoRefreshTip"
+                     ng-click="toggleRefresh()">
+                </div>
+
+                <!-- tooltip tt-msg="uninstallTip" -->
+                <div icon icon-size="42" icon-id="garbage"
+                     ng-click="delIntIntent()"
+                     ng-class="{active: ctrlBtnState.selection}">
+                </div>
+            </div>
+        </div>
+
+        <div class="summary-list" onos-table-resize>
+
+            <div class="table-header" onos-sortable-header>
+                <table>
+                    <tr>
+                        <td colId="id" sortable>ID </td>
+                        <td colId="srcAddr" sortable>Src Address </td>
+                        <td colId="dstAddr" sortable>Dst Address </td>
+                        <td colId="srcPort" sortable>Src Port </td>
+                        <td colId="dstPort" sortable>Dst Port </td>
+                        <td colId="protocol" sortable>Protocol </td>
+                        <td colId="metadata" sortable>Metadata </td>
+                    </tr>
+                </table>
+            </div>
+
+            <div class="table-body">
+                <table>
+                    <tr ng-repeat="item in tableData track by $index"
+                        ng-click="selectCallback($event, item)"
+                        ng-class="{selected: item.id === selId}">
+                        <td>{{item.id}}</td>
+                        <td>{{item.srcAddr}}</td>
+                        <td>{{item.dstAddr}}</td>
+                        <td>{{item.srcPort}}</td>
+                        <td>{{item.dstPort}}</td>
+                        <td>{{item.protocol}}</td>
+                        <td>{{item.metadata}}</td>
+                    </tr>
+                </table>
+            </div>
+
+        </div>
+    </div>
+</div>
+<!-- +++++++++++++++++++++ -->
diff --git a/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.js b/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.js
new file mode 100644
index 0000000..6b2b6cc
--- /dev/null
+++ b/apps/inbandtelemetry/app/src/main/resources/app/view/intApp/intApp.js
@@ -0,0 +1,194 @@
+(function() {
+    'use strict';
+
+    // injected refs
+    var $log, $scope, $interval, $timeout, fs, wss, ks, ls;
+
+    // constants
+    var intIntentAddReq = 'intIntentAddRequest';
+    var intIntentDelReq = 'intIntentDelRequest';
+    var intConfigAddReq = 'intConfigAddRequest';
+
+    var refreshInterval = 1000;
+
+    var propOrder = ['id', 'srcAddr', 'dstAddr', 'srcPort', 'dstPort', 'insMask'];
+    var friendlyProps = ['IntIntent ID', 'Src Address', 'Dst Address', 'Src Port', 'Dst Port', 'Ins Mask'];
+
+    function sendIntConfigString() {
+        var configObjectNode = {
+            "collectorIp": $scope.collectorIp,
+            "collectorPort": $scope.collectorPort
+        };
+        wss.sendEvent(intConfigAddReq, configObjectNode);
+    }
+
+    function sendIntIntentString() {
+        var inst = [];
+        if ($scope.metaSwId) inst.push("SWITCH_ID");
+        if ($scope.metaPortId) inst.push("PORT_ID");
+        if ($scope.metaHopLatency) inst.push("HOP_LATENCY");
+        if ($scope.metaQOccupancy) inst.push("QUEUE_OCCUPANCY");
+        if ($scope.metaIngressTstamp) inst.push("INGRESS_TIMESTAMP");
+        if ($scope.metaEgressTstamp) inst.push("EGRESS_TIMESTAMP");
+        if ($scope.metaQCongestion) inst.push("QUEUE_CONGESTION");
+        if ($scope.metaEgressTx) inst.push("EGRESS_TX_UTIL");
+
+        var intentObjectNode = {
+            "ip4SrcPrefix": $scope.ip4SrcPrefix,
+            "ip4DstPrefix": $scope.ip4DstPrefix,
+            "l4SrcPort": $scope.l4SrcPort,
+            "l4DstPort": $scope.l4DstPort,
+            "protocol": $scope.protocol,
+            "metadata": inst
+        };
+        wss.sendEvent(intIntentAddReq, intentObjectNode);
+    }
+
+    function delIntIntent() {
+        if ($scope.selId) {
+            wss.sendEvent(intIntentDelReq, {
+                "intentId": $scope.selId
+            });
+        }
+    }
+
+    function intIntentBuildTable(o) {
+        var handlers = {},
+            root = o.tag,
+            req = o.tag + 'DataRequest',
+            resp = o.tag + 'DataResponse',
+            onSel = fs.isF(o.selCb),
+            onResp = fs.isF(o.respCb),
+            idKey = o.idKey || 'id',
+            oldTableData = [],
+            refreshPromise;
+
+        o.scope.tableData = [];
+        o.scope.changedData = [];
+        o.scope.sortParams = o.sortParams || {};
+        o.scope.autoRefresh = true;
+        o.scope.autoRefreshTip = 'Toggle auto refresh';
+
+        // === websocket functions --------------------
+        // response
+        function respCb(data) {
+            ls.stop();
+            o.scope.tableData = data[root];
+            o.scope.annots = data.annots;
+            onResp && onResp();
+
+            // checks if data changed for row flashing
+            if (!angular.equals(o.scope.tableData, oldTableData)) {
+                o.scope.changedData = [];
+                // only flash the row if the data already exists
+                if (oldTableData.length) {
+                    angular.forEach(o.scope.tableData, function (item) {
+                        if (!fs.containsObj(oldTableData, item)) {
+                            o.scope.changedData.push(item);
+                        }
+                    });
+                }
+                angular.copy(o.scope.tableData, oldTableData);
+            }
+        }
+        handlers[resp] = respCb;
+        wss.bindHandlers(handlers);
+
+        // request
+        function sortCb(params) {
+            var p = angular.extend({}, params, o.query);
+            if (wss.isConnected()) {
+                wss.sendEvent(req, p);
+                ls.start();
+            }
+        }
+        o.scope.sortCallback = sortCb;
+
+        // === selecting a row functions ----------------
+        function selCb($event, selRow) {
+            var selId = selRow[idKey];
+            o.scope.selId = (o.scope.selId === selId) ? null : selId;
+            onSel && onSel($event, selRow);
+        }
+        o.scope.selectCallback = selCb;
+
+        // === autoRefresh functions ------------------
+        function fetchDataIfNotWaiting() {
+            if (!ls.waiting()) {
+                if (fs.debugOn('widget')) {
+                    $log.debug('Refreshing ' + root + ' page');
+                }
+                sortCb(o.scope.sortParams);
+            }
+        }
+
+        function startRefresh() {
+            refreshPromise = $interval(fetchDataIfNotWaiting, refreshInterval);
+        }
+
+        function stopRefresh() {
+            if (refreshPromise) {
+                $interval.cancel(refreshPromise);
+                refreshPromise = null;
+            }
+        }
+
+        function toggleRefresh() {
+            o.scope.autoRefresh = !o.scope.autoRefresh;
+            o.scope.autoRefresh ? startRefresh() : stopRefresh();
+        }
+        o.scope.toggleRefresh = toggleRefresh;
+
+        // === Cleanup on destroyed scope -----------------
+        o.scope.$on('$destroy', function () {
+            wss.unbindHandlers(handlers);
+            stopRefresh();
+            ls.stop();
+        });
+
+        sortCb(o.scope.sortParams);
+        startRefresh();
+    }
+
+    var app1 = angular.module('ovIntApp', []);
+    app1.controller('OvIntAppCtrl',
+        ['$log', '$scope', '$interval', '$timeout', 'TableBuilderService',
+            'FnService', 'WebSocketService', 'KeyService', 'LoadingService',
+
+            function(_$log_, _$scope_, _$interval_, _$timeout_, tbs, _fs_, _wss_, _ks_, _ls_) {
+                $log = _$log_;
+                $scope = _$scope_;
+                $interval = _$interval_;
+                $timeout = _$timeout_;
+                fs = _fs_;
+                wss = _wss_;
+                ks = _ks_;
+                ls = _ls_;
+
+                // custom selection callback
+                function selCb($event, row) {
+                }
+                intIntentBuildTable({
+                    scope: $scope,
+                    tag: 'intAppIntIntent'
+                    // selCb: selCb
+                });
+
+                $scope.sendIntIntentString = sendIntIntentString;
+                $scope.delIntIntent = delIntIntent;
+                $scope.sendIntConfigString = sendIntConfigString;
+
+                // get data the first time...
+                // getData();
+
+                // cleanup
+                $scope.$on('$destroy', function() {
+                    // wss.unbindHandlers(handlers);
+                    /*ks.unbindKeys();*/
+                    $log.log('OvIntAppCtrl has been destroyed');
+                });
+
+                $log.log('OvIntAppCtrl has been created');
+            }
+        ]);
+}());