[ONOS-6982] Implement OpenStackNetworking UI Service
- This implements the UI service for OpenStack Netwrorking App
- When mouse is over host or device, based on VNI,the UI highlights related hosts, links and devices
- The UI also supports flow trace functionality
Change-Id: I1944f3237cc112ed5c5e0d19351759cc66145881
diff --git a/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopov.css b/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopov.css
new file mode 100644
index 0000000..11523e1
--- /dev/null
+++ b/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopov.css
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* css for OpenStack Networking UI. */
+
+#traceInfoDialogId h2 {
+ text-align: center;
+ width: 200px;
+ margin: 0;
+ font-weight: lighter;
+ word-wrap: break-word;
+ display: inline-block;
+ vertical-align: middle;
+ font-size: 14pt;
+}
+
+div.traceInfo table {
+ border-collapse: collapse;
+ table-layout: fixed;
+ empty-cells: show;
+ margin: 0;
+ width: 200px;
+}
+
+div.traceInfo td {
+ cursor: pointer;
+ font-variant: small-caps;
+
+ font-size: 10pt;
+ padding-top: 14px;
+ padding-bottom: 14px;
+
+ letter-spacing: 0.02em;
+ cursor: pointer;
+
+ color: #3c3a3a;
+ width: 100px;
+}
+
+div.traceInfo td.label {
+ font-weight: bold;
+}
+
+#flowTraceResultDialogId h2 {
+ text-align: center;
+ width: 600px;
+
+ padding: 0 0 0 10px;
+ margin: 0;
+ font-weight: lighter;
+ word-wrap: break-word;
+ display: inline-block;
+ vertical-align: middle;
+}
+
+div.flowTraceResult table {
+ border-collapse: collapse;
+ table-layout: fixed;
+ empty-cells: show;
+ margin: 0;
+ width: 600px;
+}
+
+div.flowTraceResult td {
+ padding: 4px;
+ text-align: center;
+ word-wrap: break-word;
+ font-size: 12pt;
+}
+
+div.flowTraceResult .table-header td {
+ font-weight: bold;
+ font-variant: small-caps;
+ text-transform: uppercase;
+ font-size: 11pt;
+ padding-top: 14px;
+ padding-bottom: 14px;
+
+ letter-spacing: 0.02em;
+ cursor: pointer;
+
+ background-color: #e5e5e6;
+ color: #3c3a3a;
+ width: 120px;
+
+}
+
+div.flowTraceResult .table-body tr:nth-child(even) {
+ background-color: #f4f4f4;
+}
+
+div.flowTraceResult .table-body tr:nth-child(odd) {
+ background-color: #fbfbfb;
+}
+
+div.flowTraceResult .table-body td {
+ cursor: pointer;
+ font-variant: small-caps;
+
+ font-size: 10pt;
+ padding-top: 14px;
+ padding-bottom: 14px;
+
+ letter-spacing: 0.02em;
+ cursor: pointer;
+
+ color: #3c3a3a;
+ width: 120px;
+}
+
+div.flowTraceResult .table-body tr.drop {
+ background-color: #e28d8d;
+}
\ No newline at end of file
diff --git a/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopov.html b/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopov.html
new file mode 100644
index 0000000..8d31977
--- /dev/null
+++ b/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopov.html
@@ -0,0 +1,4 @@
+<!-- partial HTML -->
+<div id="ov-sona-topov">
+ <p>This is a hidden view .. just a placeholder to house the javascript</p>
+</div>
diff --git a/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopovOverlay.js b/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopovOverlay.js
new file mode 100644
index 0000000..39517b6
--- /dev/null
+++ b/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopovOverlay.js
@@ -0,0 +1,220 @@
+/*
+ * Copyright 2017-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/* OpenStack Networking UI Overlay description */
+(function () {
+ 'use strict';
+
+ // injected refs
+ var $log, tov, sts, flash, ds, wss;
+ var traceSrc = null;
+ var traceDst = null;
+
+ var traceInfoDialogId = 'traceInfoDialogId',
+ traceInfoDialogOpt = {
+ width: 200,
+ edge: 'left',
+ margin: 20,
+ hideMargin: -20
+ }
+
+ var overlay = {
+ // NOTE: this must match the ID defined in AppUiTopovOverlay
+ overlayId: 'sona-overlay',
+ glyphId: '*star4',
+ tooltip: 'OpenstackNetworking UI',
+
+ glyphs: {
+ star4: {
+ vb: '0 0 8 8',
+ d: 'M1,4l2,-1l1,-2l1,2l2,1l-2,1l-1,2l-1,-2z'
+ },
+ banner: {
+ vb: '0 0 6 6',
+ d: 'M1,1v4l2,-2l2,2v-4z'
+ }
+ },
+
+ activate: function () {
+ $log.debug("OpenstackNetworking UI ACTIVATED");
+ },
+ deactivate: function () {
+ sts.stopDisplay();
+ $log.debug("OpenstackNetworking UI DEACTIVATED");
+ },
+
+ // detail panel button definitions
+ buttons: {
+ flowtrace: {
+ gid: 'checkMark',
+ tt: 'Flow Trace',
+ cb: function (data) {
+
+ if (traceSrc == null && data.navPath == 'host') {
+ traceSrc = data.title;
+
+ flash.flash('Src ' + traceSrc + ' selected. Please select the dst');
+ } else if (traceDst == null && data.title != traceSrc && data.navPath == 'host') {
+ traceDst = data.title;
+ openTraceInfoDialog();
+ flash.flash('Dst ' + traceDst + ' selected. Press Request button');
+ }
+
+ $log.debug('Perform flow trace test between VMs:', data);
+ }
+ },
+
+ reset: {
+ gid: 'xMark',
+ tt: 'Reset',
+ cb: function (data) {
+ flash.flash('Reset flow trace');
+ traceSrc = null;
+ traceDst = null;
+ ds.closeDialog();
+ $log.debug('BAR action invoked with data:', data);
+ }
+ },
+ toGateway: {
+ gid: 'm_switch',
+ tt: 'Trace to Gateway',
+ cb: function (data) {
+ if (traceSrc != null && data.title == traceSrc && data.navPath == 'host') {
+ //Set traceSrc to traceDst in case trace to gateway
+ traceDst = traceSrc;
+ openTraceInfoDialog();
+ flash.flash('Trace to Gateway');
+ }
+ }
+ },
+ toExternal: {
+ gid: 'm_cloud',
+ tt: 'Trace to External',
+ cb: function (data) {
+ if (traceSrc != null && data.title == traceSrc && data.navPath == 'host') {
+ //Set traceDst to 8.8.8.8 to check external connection
+ traceDst = '8.8.8.8';
+ openTraceInfoDialog();
+ flash.flash('Trace to External')
+ }
+ }
+ }
+ },
+
+ keyBindings: {
+ 0: {
+ cb: function () { sts.stopDisplay(); },
+ tt: 'Cancel OpenstackNetworking UI Overlay Mode',
+ gid: 'xMark'
+ },
+ V: {
+ cb: function () {
+ wss.bindHandlers({
+ flowTraceResult: sts,
+ });
+ sts.startDisplay('mouse');
+ },
+ tt: 'Start OpenstackNetworking UI Overlay Mode',
+ gid: 'crown'
+ },
+
+ _keyOrder: [
+ '0', 'V'
+ ]
+ },
+
+ hooks: {
+ // hook for handling escape key
+ // Must return true to consume ESC, false otherwise.
+ escape: function () {
+ // Must return true to consume ESC, false otherwise.
+ return sts.stopDisplay();
+ },
+ mouseover: function (m) {
+ // m has id, class, and type properties
+ $log.debug('mouseover:', m);
+ sts.updateDisplay(m);
+ },
+ mouseout: function () {
+ $log.debug('mouseout');
+ sts.updateDisplay();
+ }
+ }
+ };
+
+ function openTraceInfoDialog() {
+ ds.openDialog(traceInfoDialogId, traceInfoDialogOpt)
+ .setTitle('Flow Trace Information')
+ .addContent(createTraceInfoDiv(traceSrc, traceDst))
+ .addOk(flowTraceResultBtn, 'Request')
+ .bindKeys();
+ }
+
+ function createTraceInfoDiv(src, dst) {
+ var texts = ds.createDiv('traceInfo');
+ texts.append('hr');
+ texts.append('table').append('tbody').append('tr');
+
+ var tBodySelection = texts.select('table').select('tbody').select('tr');
+
+ tBodySelection.append('td').text('Source IP:').attr("class", "label");
+ tBodySelection.append('td').text(src).attr("class", "value");
+
+ texts.select('table').select('tbody').append('tr');
+
+ tBodySelection = texts.select('table').select('tbody').select('tr:nth-child(2)');
+
+ tBodySelection.append('td').text('Destination IP:').attr("class", "label");
+ if (dst == src) {
+ tBodySelection.append('td').text('toGateway').attr("class", "value");
+ } else {
+ tBodySelection.append('td').text(dst).attr("class", "value");
+ }
+
+ texts.append('hr');
+
+ return texts;
+
+ }
+
+ function flowTraceResultBtn() {
+ sts.sendFlowTraceRequest(traceSrc, traceDst);
+ ds.closeDialog();
+ traceSrc = null;
+ traceDst = null;
+ flash.flash('Send Flow Trace Request');
+ }
+
+ function buttonCallback(x) {
+ $log.debug('Toolbar-button callback', x);
+ }
+
+ // invoke code to register with the overlay service
+ angular.module('ovSonaTopov')
+ .run(['$log', 'TopoOverlayService', 'SonaTopovService',
+ 'FlashService', 'DialogService', 'WebSocketService',
+
+ function (_$log_, _tov_, _sts_, _flash_, _ds_, _wss_) {
+ $log = _$log_;
+ tov = _tov_;
+ sts = _sts_;
+ flash = _flash_;
+ ds = _ds_;
+ wss = _wss_;
+ tov.register(overlay);
+ }]);
+
+}());
diff --git a/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopovService.js b/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopovService.js
new file mode 100644
index 0000000..a9bbf69
--- /dev/null
+++ b/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopovService.js
@@ -0,0 +1,198 @@
+/*
+ * Copyright 2017-present Open Networking Laboratory
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+/*
+ OpenStack Networking UI Service
+
+ Provides a mechanism to highlight hosts, devices and links according to
+ a virtual network. Also provides trace functionality to prove that
+ flow rules for the specific vm are installed appropriately.
+ */
+
+(function () {
+ 'use strict';
+
+ // injected refs
+ var $log, fs, flash, wss, ds;
+
+ // constants
+ var displayStart = 'openstackNetworkingUiStart',
+ displayUpdate = 'openstackNetworkingUiUpdate',
+ displayStop = 'openstackNetworkingUiStop',
+ flowTraceRequest = 'flowTraceRequest';
+
+ // internal state
+ var currentMode = null;
+
+ // === ---------------------------
+ // === Helper functions
+
+ function sendDisplayStart(mode) {
+ wss.sendEvent(displayStart, {
+ mode: mode
+ });
+ }
+
+ function sendDisplayUpdate(what) {
+ wss.sendEvent(displayUpdate, {
+ id: what ? what.id : ''
+ });
+ }
+
+ function sendDisplayStop() {
+ wss.sendEvent(displayStop);
+ }
+
+ function sendFlowTraceRequest(src, dst) {
+ wss.sendEvent(flowTraceRequest, {
+ srcIp: src,
+ dstIp: dst
+ });
+ flash.flash('sendFlowTraceRequest called');
+ }
+
+ // === ---------------------------
+ // === Main API functions
+
+ function startDisplay(mode) {
+ if (currentMode === mode) {
+ $log.debug('(in mode', mode, 'already)');
+ } else {
+ currentMode = mode;
+ sendDisplayStart(mode);
+
+ flash.flash('Starting Openstack Networking UI mode');
+ }
+ }
+
+ function updateDisplay(m) {
+ if (currentMode) {
+ sendDisplayUpdate(m);
+ }
+ }
+
+ function stopDisplay() {
+ if (currentMode) {
+ currentMode = null;
+ sendDisplayStop();
+ flash.flash('Canceling Openstack Networking UI Overlay mode');
+ return true;
+ }
+ return false;
+ }
+
+
+ function dOk() {
+ ds.closeDialog();
+ }
+ function openFlowTraceResultDialog(data) {
+ var flowTraceResultDialogId = 'flowTraceResultDialogId',
+ flowTraceResultDialogOpt = {
+ width: 650,
+ edge: 'left',
+ margin: 20,
+ hideMargin: -20
+ }
+ var traceSuccess = data.trace_success == true ? "SUCCESS" : "FALSE";
+ ds.openDialog(flowTraceResultDialogId, flowTraceResultDialogOpt)
+ .setTitle('Flow Trace Result: ' + traceSuccess)
+ .addContent(createTraceResultInfoDiv(data))
+ .addOk(dOk, 'Close')
+ .bindKeys();
+ }
+
+ function createTraceResultInfoDiv(data) {
+ var texts = ds.createDiv('flowTraceResult');
+
+ texts.append('div').attr("class", "table-header");
+ texts.append('div').attr("class", "table-body");
+
+ texts.select('.table-header').append('table').append('tbody').append('tr');
+ texts.select('.table-body').append('table').append('tbody');
+
+
+ var theaderSelection = texts.select('.table-header')
+ .select('table').select('tbody').select('tr');
+
+ theaderSelection.append('td').text('Node');
+ theaderSelection.append('td').text('Table Id');
+ theaderSelection.append('td').text('Priority');
+ theaderSelection.append('td').text('Selector');
+ theaderSelection.append('td').text('Action');
+
+ var tbodySelection = texts.select('.table-body').select('table').select('tbody');
+ var rowNum = 1;
+
+ data.trace_result.forEach(function(result) {
+ result.flow_rules.forEach(function(flowRule) {
+ tbodySelection.append('tr');
+ var tbodyTrSelection = tbodySelection.select('tr:nth-child(' + rowNum + ')');
+ tbodyTrSelection.append('td').text(result.trace_node_name);
+ tbodyTrSelection.append('td').text(flowRule.table);
+ tbodyTrSelection.append('td').text(flowRule.priority);
+ tbodyTrSelection.append('td').text(jsonToSring(flowRule.selector));
+ tbodyTrSelection.append('td').text(jsonToSring(flowRule.actions));
+ if (jsonToSring(flowRule.actions).includes("drop")) {
+ tbodyTrSelection.attr("class", "drop");
+ }
+ rowNum++;
+ });
+
+ });
+
+ return texts;
+ }
+
+ function jsonToSring(jsonData) {
+ var result = [];
+ for (var key in jsonData) {
+ result.push(key + ':' + jsonData[key]);
+ }
+
+ return result.join('/');
+
+ }
+
+ function flowTraceResult(data) {
+ flash.flash('flowTraceResult called');
+ $log.debug(data);
+
+ openFlowTraceResultDialog(data)
+ }
+
+ // === ---------------------------
+ // === Module Factory Definition
+
+ angular.module('ovSonaTopov', [])
+ .factory('SonaTopovService',
+ ['$log', 'FnService', 'FlashService', 'WebSocketService', 'DialogService',
+
+ function (_$log_, _fs_, _flash_, _wss_, _ds_) {
+ $log = _$log_;
+ fs = _fs_;
+ flash = _flash_;
+ wss = _wss_;
+ ds = _ds_;
+
+ return {
+ startDisplay: startDisplay,
+ updateDisplay: updateDisplay,
+ stopDisplay: stopDisplay,
+ flowTraceResult: flowTraceResult,
+ sendFlowTraceRequest: sendFlowTraceRequest,
+ };
+ }]);
+}());