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');
+ }
+ ]);
+}());