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