Network toplogy GUI for VM-to-VM flow statistics configuration.

Change-Id: I86543c7bb30d79ec8b5d57f822756b5e8a8e5e40
diff --git a/apps/openstacknetworkingui/BUCK b/apps/openstacknetworkingui/BUCK
index ce5e71e..12f1264 100644
--- a/apps/openstacknetworkingui/BUCK
+++ b/apps/openstacknetworkingui/BUCK
@@ -9,6 +9,7 @@
   '//lib:org.apache.karaf.shell.console',
   '//lib:sshd-core',
   '//apps/openstacknode/api:onos-apps-openstacknode-api',
+  '//apps/openstacktelemetry/api:onos-apps-openstacktelemetry-api',
   '//apps/openstacknetworking/api:onos-apps-openstacknetworking-api',
 ]
 
diff --git a/apps/openstacknetworkingui/BUILD b/apps/openstacknetworkingui/BUILD
index de75e88..5058dd1 100644
--- a/apps/openstacknetworkingui/BUILD
+++ b/apps/openstacknetworkingui/BUILD
@@ -6,6 +6,7 @@
 COMPILE_DEPS = CORE_DEPS + JACKSON + CLI + REST + [
     "//core/store/serializers:onos-core-serializers",
     "//apps/openstacknode/api:onos-apps-openstacknode-api",
+    "//apps/openstacktelemetry/api:onos-apps-openstacktelemetry-api",
     "//apps/openstacknetworking/api:onos-apps-openstacknetworking-api",
     "@sshd_core//jar",
 ]
diff --git a/apps/openstacknetworkingui/src/main/java/org/onosproject/openstacknetworkingui/OpenstackNetworkingUiMessageHandler.java b/apps/openstacknetworkingui/src/main/java/org/onosproject/openstacknetworkingui/OpenstackNetworkingUiMessageHandler.java
index 1dfefb8..c49944d 100644
--- a/apps/openstacknetworkingui/src/main/java/org/onosproject/openstacknetworkingui/OpenstackNetworkingUiMessageHandler.java
+++ b/apps/openstacknetworkingui/src/main/java/org/onosproject/openstacknetworkingui/OpenstackNetworkingUiMessageHandler.java
@@ -47,6 +47,7 @@
 import org.onosproject.openstacknode.api.OpenstackNode;
 import org.onosproject.openstacknode.api.OpenstackNodeService;
 import org.onosproject.openstacknode.api.OpenstackSshAuth;
+import org.onosproject.openstacktelemetry.api.StatsFlowRuleAdminService;
 import org.onosproject.ui.JsonUtils;
 import org.onosproject.ui.RequestHandler;
 import org.onosproject.ui.UiConnection;
@@ -84,6 +85,8 @@
     private static final String OPENSTACK_NETWORKING_UI_UPDATE = "openstackNetworkingUiUpdate";
     private static final String OPENSTACK_NETWORKING_UI_STOP = "openstackNetworkingUiStop";
     private static final String ANNOTATION_NETWORK_ID = "networkId";
+    private static final String FLOW_STATS_ADD_REQUEST = "flowStatsAddRequest";
+    private static final String FLOW_STATS_REMOVE_REQUEST = "flowStatsRemoveRequest";
     private static final String FLOW_TRACE_REQUEST = "flowTraceRequest";
     private static final String SRC_IP = "srcIp";
     private static final String DST_IP = "dstIp";
@@ -95,6 +98,9 @@
     private static final String TRACE_RESULT = "traceResult";
     private static final String IS_SUCCESS = "isSuccess";
     private static final String TRACE_SUCCESS = "traceSuccess";
+    private static final String STATS_SUCCESS = "statsSuccess";
+    private static final String FLOW_STATS_ADD_RESULT = "flowStatsAddResult";
+    private static final String FLOW_STATS_REMOVE_RESULT = "flowStatsRemoveResult";
     private static final String FLOW_TRACE_RESULT = "flowTraceResult";
     private static final String SRC_DEVICE_ID = "srcDeviceId";
     private static final String DST_DEVICE_ID = "dstDeviceId";
@@ -124,6 +130,7 @@
     private OpenstackNodeService osNodeService;
     private InstancePortService instancePortService;
     private OpenstackNetworkService osNetService;
+    private StatsFlowRuleAdminService statsFlowRuleService;
     private Mode currentMode = Mode.IDLE;
     private Element elementOfNote;
 
@@ -139,6 +146,7 @@
         osNodeService = directory.get(OpenstackNodeService.class);
         instancePortService = directory.get(InstancePortService.class);
         osNetService = directory.get(OpenstackNetworkService.class);
+        statsFlowRuleService = directory.get(StatsFlowRuleAdminService.class);
     }
 
     @Override
@@ -147,6 +155,8 @@
                 new DisplayStartHandler(),
                 new DisplayUpdateHandler(),
                 new DisplayStopHandler(),
+                new FlowStatsAddRequestHandler(),
+                new FlowStatsRemoveRequestHandler(),
                 new FlowTraceRequestHandler()
         );
     }
@@ -179,6 +189,36 @@
         }
     }
 
+    private final class FlowStatsAddRequestHandler extends RequestHandler {
+        public FlowStatsAddRequestHandler() {
+            super(FLOW_STATS_ADD_REQUEST);
+        }
+
+        @Override
+        public void process(ObjectNode payload) {
+            String srcIp = string(payload, SRC_IP);
+            String dstIp = string(payload, DST_IP);
+            log.info("Flow statistics add request called with src IP: {}, dst IP: {}",
+                    srcIp, dstIp);
+            eventExecutor.execute(() -> processFlowStatsRequest(srcIp, dstIp, true));
+        }
+    }
+
+    private final class FlowStatsRemoveRequestHandler extends RequestHandler {
+        public FlowStatsRemoveRequestHandler() {
+            super(FLOW_STATS_REMOVE_REQUEST);
+        }
+
+        @Override
+        public void process(ObjectNode payload) {
+            String srcIp = string(payload, SRC_IP);
+            String dstIp = string(payload, DST_IP);
+            log.info("Flow statistics removal request called with src IP: {}, dst IP: {}",
+                    srcIp, dstIp);
+            eventExecutor.execute(() -> processFlowStatsRequest(srcIp, dstIp, false));
+        }
+    }
+
     private final class FlowTraceRequestHandler extends RequestHandler {
         public FlowTraceRequestHandler() {
             super(FLOW_TRACE_REQUEST);
@@ -393,6 +433,16 @@
         return NodeBadge.number(Status.INFO, n, "Openstack Node");
     }
 
+    private void processFlowStatsRequest(String srcIp, String dstIp, Boolean install) {
+        boolean statsSuccess = true;
+        ObjectMapper mapper = new ObjectMapper();
+        ObjectNode statsResult = mapper.createObjectNode();
+
+        statsFlowRuleService.setStatFlowL2Rule(srcIp, dstIp, install);
+        statsResult.put(STATS_SUCCESS, statsSuccess);
+        sendMessagetoUi(FLOW_STATS_ADD_RESULT, statsResult);
+    }
+
     private void processFlowTraceRequest(String srcIp, String dstIp, String srcDeviceId) {
         boolean traceSuccess = true;
 
@@ -443,8 +493,6 @@
             traceSuccess = false;
         }
 
-        //TODO implements trace result in backward
-
         traceResult.put(TRACE_SUCCESS, traceSuccess);
         log.debug("traceResult Json: {}", traceResult);
 
diff --git a/apps/openstacknetworkingui/src/main/java/org/onosproject/openstacknetworkingui/OpenstackNetworkingUiOverlay.java b/apps/openstacknetworkingui/src/main/java/org/onosproject/openstacknetworkingui/OpenstackNetworkingUiOverlay.java
index ec71db7..2788f1c 100644
--- a/apps/openstacknetworkingui/src/main/java/org/onosproject/openstacknetworkingui/OpenstackNetworkingUiOverlay.java
+++ b/apps/openstacknetworkingui/src/main/java/org/onosproject/openstacknetworkingui/OpenstackNetworkingUiOverlay.java
@@ -47,6 +47,7 @@
     private static final ButtonId RESET_BUTTON = new ButtonId("reset");
     private static final ButtonId TO_GATEWAY_BUTTON = new ButtonId("toGateway");
     private static final ButtonId TO_EXTERNAL_BUTTON = new ButtonId("toExternal");
+    private static final ButtonId VFLOW_STATS_BUTTON = new ButtonId("FlowStats");
 
     private final HostService hostService = DefaultServiceDirectory.getService(HostService.class);
 
@@ -78,6 +79,7 @@
                 .addButton(FLOW_TRACE_BUTTON)
                 .addButton(RESET_BUTTON)
                 .addButton(TO_GATEWAY_BUTTON)
+                .addButton(VFLOW_STATS_BUTTON)
                 .addButton(TO_EXTERNAL_BUTTON);
     }
 }
diff --git a/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopov.css b/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopov.css
index 11523e1..83f0ba3 100644
--- a/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopov.css
+++ b/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopov.css
@@ -18,7 +18,7 @@
 
 #traceInfoDialogId h2 {
     text-align: center;
-    width: 200px;
+    width: 300px;
     margin: 0;
     font-weight: lighter;
     word-wrap: break-word;
@@ -47,7 +47,7 @@
     cursor: pointer;
 
     color: #3c3a3a;
-    width: 100px;
+    width: 170px;
 }
 
 div.traceInfo td.label {
@@ -123,4 +123,5 @@
 
 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/sonaTopovOverlay.js b/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopovOverlay.js
index c9d6262..e588cb2 100644
--- a/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopovOverlay.js
+++ b/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopovOverlay.js
@@ -24,10 +24,12 @@
     var traceDst = null;
     var srcDeviceId = null;
     var dstDeviceId = null;
+    var flowStatsSrcIp = null;
+    var flowStatsDstIp = null;
 
     var traceInfoDialogId = 'traceInfoDialogId',
         traceInfoDialogOpt = {
-            width: 300,
+            width: 350,
             edge: 'left',
             margin: 20,
             hideMargin: -20
@@ -117,6 +119,22 @@
                         flash.flash('Trace to External')
                     }
                }
+            },
+            FlowStats: {
+                gid: 'meterTable',
+                tt: 'Flow Statistics',
+                cb: function (data) {
+                    if (flowStatsSrcIp == null && data.navPath == 'host') {
+                        flowStatsSrcIp = data.propValues.ip;
+                        flash.flash('Source ' + flowStatsSrcIp + ' is selected. Choose a destination IP address');
+                        $log.info('Source VM is selected for Flow Statistics: ', data);
+                    } else if (flowStatsDstIp == null && data.title != flowStatsSrcIp && data.navPath == 'host') {
+                        flowStatsDstIp = data.propValues.ip;
+                        flash.flash('Destination ' + flowStatsDstIp + ' is selected. Press [Request] button !');
+                        openFlowStatsInfoDialog();
+                        $log.info('Destination VM is selected for Flow Statistics: ', data);
+                    }
+                }
             }
         },
 
@@ -161,6 +179,60 @@
         }
     };
 
+    function openFlowStatsInfoDialog() {
+        ds.openDialog(traceInfoDialogId, traceInfoDialogOpt)
+            .setTitle('VM to VM Flow Statistics Rule Configuration')
+            .addContent(createFlowStatsInfoDiv(flowStatsSrcIp, flowStatsDstIp))
+            .addCancel(dStatsBoxClose, 'Close')
+            .addOk(flowStatsRemoveReqBtn, 'Remove Stats Rule')
+            .addOk(flowStatsAddReqBtn, 'Add Stats Rule')
+            .bindKeys();
+    }
+
+    function createFlowStatsInfoDiv(srcIp, dstIp) {
+        var texts = ds.createDiv('FlowStatsInfo');
+        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(srcIp).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");
+        tBodySelection.append('td').text(dstIp).attr("class", "value");
+
+        texts.append('hr');
+
+        return texts;
+    }
+
+    function dStatsBoxClose() {
+        $log.info('Dialog Close button clicked (or Esc pressed)');
+        flowStatsSrcIp = null;
+        flowStatsDstIp = null;
+    }
+
+    function flowStatsAddReqBtn() {
+        sts.sendFlowStatsAddRequest(flowStatsSrcIp, flowStatsDstIp);
+        ds.closeDialog();
+        flowStatsSrcIp = null;
+        flowStatsDstIp = null;
+        flash.flash('Send Flow Statistics Addition Request');
+    }
+
+    function flowStatsRemoveReqBtn() {
+        sts.sendFlowStatsRemoveRequest(flowStatsSrcIp, flowStatsDstIp);
+        ds.closeDialog();
+        flowStatsSrcIp = null;
+        flowStatsDstIp = null;
+        flash.flash('Send Flow Statistics Removal Request');
+    }
+
     function openTraceInfoDialog() {
         ds.openDialog(traceInfoDialogId, traceInfoDialogOpt)
             .setTitle('Flow Trace Information')
@@ -203,7 +275,6 @@
         texts.append('hr');
 
         return texts;
-
     }
 
     function flowTraceResultBtn() {
diff --git a/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopovService.js b/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopovService.js
index 719703a..0212ea2 100644
--- a/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopovService.js
+++ b/apps/openstacknetworkingui/src/main/resources/app/view/sonaTopov/sonaTopovService.js
@@ -32,6 +32,8 @@
     var displayStart = 'openstackNetworkingUiStart',
         displayUpdate = 'openstackNetworkingUiUpdate',
         displayStop = 'openstackNetworkingUiStop',
+        flowStatsAddRequest = 'flowStatsAddRequest',
+        flowStatsRemoveRequest = 'flowStatsRemoveRequest',
         flowTraceRequest = 'flowTraceRequest';
 
     // internal state
@@ -56,6 +58,24 @@
         wss.sendEvent(displayStop);
     }
 
+    function sendFlowStatsAddRequest(src, dst) {
+        wss.sendEvent(flowStatsAddRequest, {
+            srcIp: src,
+            dstIp: dst,
+        });
+        $log.info('FlowStatsAdd request message is sent.');
+        flash.flash('sendFlowStatsAddRequest called');
+    }
+
+     function sendFlowStatsRemoveRequest(src, dst) {
+        wss.sendEvent(flowStatsRemoveRequest, {
+            srcIp: src,
+            dstIp: dst,
+        });
+        $log.info('FlowStatsRemove request message is sent.');
+        flash.flash('sendFlowStatsRemoveRequest called');
+    }
+
     function sendFlowTraceRequest(src, dst, srcDeviceId, dstDeviceId) {
         wss.sendEvent(flowTraceRequest, {
             srcIp: src,
@@ -96,10 +116,56 @@
         return false;
     }
 
-
     function dOk() {
         ds.closeDialog();
     }
+
+    function openFlowStatsAddResultDialog(data) {
+        $log.info('Addition Result message: ', data);
+        var flowStatsAddResultDialogId = 'flowStatsAddResultDialogId',
+            flowStatsAddResultDialogOpt = {
+                width: 650,
+                edge: 'left',
+                margin: 20,
+                hideMargin: -20
+            }
+        var statsSuccess = data.statsSuccess == true ? "SUCCESS" : "FALSE";
+        ds.openDialog(flowStatsAddResultDialogId, flowStatsAddResultDialogOpt)
+                    .setTitle('Flow Stats Addition Result: ' + statsSuccess)
+                    .addContent(createStatsAddResultInfoDiv(data))
+                    .addOk(dOk, 'Close')
+                    .bindKeys();
+    }
+
+    function createStatsAddResultInfoDiv(data) {
+        var texts = ds.createDiv('flowStatsAddResult');
+        texts.append('p').text('Result of addition: ' + data.statsSuccess);
+        return texts;
+    }
+
+    function openFlowStatsRemoveResultDialog(data) {
+        $log.info('Removal Result message: ', data);
+        var flowStatsRemoveResultDialogId = 'flowStatsRemoveResultDialogId',
+            flowStatsRemoveResultDialogOpt = {
+                width: 650,
+                edge: 'left',
+                margin: 20,
+                hideMargin: -20
+            }
+        var statsSuccess = data.statsSuccess == true ? "SUCCESS" : "FALSE";
+        ds.openDialog(flowStatsRemoveResultDialogId, flowStatsRemoveResultDialogOpt)
+                    .setTitle('Flow Stats Removal Result: ' + statsSuccess)
+                    .addContent(createStatsRemoveResultInfoDiv(data))
+                    .addOk(dOk, 'Close')
+                    .bindKeys();
+    }
+
+    function createStatsRemoveResultInfoDiv(data) {
+        var texts = ds.createDiv('flowStatsRemoveResult');
+        texts.append('p').text('Result of removal: ' + data.statsSuccess);
+        return texts;
+    }
+
     function openFlowTraceResultDialog(data) {
         var flowTraceResultDialogId = 'flowTraceResultDialogId',
             flowTraceResultDialogOpt = {
@@ -168,6 +234,21 @@
 
     }
 
+    function flowStatsAddResult(data) {
+        flash.flash('flowStatsAddResult called');
+        $log.debug(data);
+
+        openFlowStatsAddResultDialog(data)
+    }
+
+    function flowStatsRemoveResult(data) {
+        flash.flash('flowStatsRemoveResult called');
+        $log.debug(data);
+
+        openFlowStatsRemoveResultDialog(data)
+    }
+
+
     function flowTraceResult(data) {
         flash.flash('flowTraceResult called');
         $log.debug(data);
@@ -193,6 +274,10 @@
                 startDisplay: startDisplay,
                 updateDisplay: updateDisplay,
                 stopDisplay: stopDisplay,
+                flowStatsAddResult: flowStatsAddResult,
+                flowStatsRemoveResult: flowStatsRemoveResult,
+                sendFlowStatsAddRequest: sendFlowStatsAddRequest,
+                sendFlowStatsRemoveRequest: sendFlowStatsRemoveRequest,
                 flowTraceResult: flowTraceResult,
                 sendFlowTraceRequest: sendFlowTraceRequest,
             };
diff --git a/apps/openstacktelemetry/api/src/main/java/org/onosproject/openstacktelemetry/api/StatsFlowRuleAdminService.java b/apps/openstacktelemetry/api/src/main/java/org/onosproject/openstacktelemetry/api/StatsFlowRuleAdminService.java
index e8a4a38..c2fb893 100644
--- a/apps/openstacktelemetry/api/src/main/java/org/onosproject/openstacktelemetry/api/StatsFlowRuleAdminService.java
+++ b/apps/openstacktelemetry/api/src/main/java/org/onosproject/openstacktelemetry/api/StatsFlowRuleAdminService.java
@@ -35,6 +35,15 @@
     void stop();
 
     /**
+     * Creates or delete a stat flow rule with network layer-2 information.
+     *
+     * @param srcIp source IP address
+     * @param dstIp destination IP address
+     * @param install installing flag
+     */
+    void setStatFlowL2Rule(String srcIp, String dstIp, Boolean install);
+
+    /**
      * Creates a stat flow rule.
      *
      * @param statFlowRule stat flow rule for a VM
diff --git a/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/impl/DefaultFlowInfo.java b/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/impl/DefaultFlowInfo.java
index 2f67050..8560170 100644
--- a/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/impl/DefaultFlowInfo.java
+++ b/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/impl/DefaultFlowInfo.java
@@ -196,8 +196,11 @@
                 return EGRESS_STATS + dstIp.toString();
             }
         }
-        return srcIp.toString() + ":" + srcPort.toString() + " -> " +
-                dstIp.toString() + ":" + dstPort.toString();
+        return srcIp.toString() + ":" +
+                ((srcPort == null) ? "any" : srcPort.toString()) +
+                " -> " +
+                dstIp.toString() + ":" +
+                ((dstPort == null) ? "any" : dstPort.toString());
     }
 
     @Override
diff --git a/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/impl/StatsFlowRuleManager.java b/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/impl/StatsFlowRuleManager.java
index 760fd86..f1318de 100644
--- a/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/impl/StatsFlowRuleManager.java
+++ b/apps/openstacktelemetry/app/src/main/java/org/onosproject/openstacktelemetry/impl/StatsFlowRuleManager.java
@@ -145,6 +145,7 @@
     private static final boolean DEFAULT_MONITOR_UNDERLAY = true;
 
     private static final String ARBITRARY_IP = "0.0.0.0/32";
+    private static final int ARBITRARY_PROTOCOL = 0x0;
     private static final int ARBITRARY_LENGTH = 32;
     private static final String ARBITRARY_MAC = "00:00:00:00:00:00";
     private static final IpAddress NO_HOST_IP = IpAddress.valueOf("255.255.255.255");
@@ -266,14 +267,22 @@
     }
 
     @Override
-    public void createStatFlowRule(StatsFlowRule statsFlowRule) {
+    public void setStatFlowL2Rule(String srcIp, String dstIp, Boolean install) {
+        StatsFlowRule statsFlowRule = DefaultStatsFlowRule.builder()
+                .srcIpPrefix(IpPrefix.valueOf(IpAddress.valueOf(srcIp), ARBITRARY_LENGTH))
+                .dstIpPrefix(IpPrefix.valueOf(IpAddress.valueOf(dstIp), ARBITRARY_LENGTH))
+                .ipProtocol((byte) ARBITRARY_PROTOCOL)
+                .build();
+        setStatFlowRule(statsFlowRule, install);
+    }
 
+    @Override
+    public void createStatFlowRule(StatsFlowRule statsFlowRule) {
         setStatFlowRule(statsFlowRule, true);
     }
 
     @Override
     public void deleteStatFlowRule(StatsFlowRule statsFlowRule) {
-
         setStatFlowRule(statsFlowRule, false);
     }