[AETHER-2442]: Add fetching dongle stats in monitoring agent

Change-Id: I0b43652be971c4a12ee228cefcf864d77751ed0c
diff --git a/VERSION b/VERSION
index 39e898a..7486fdb 100644
--- a/VERSION
+++ b/VERSION
@@ -1 +1 @@
-0.7.1
+0.7.2
diff --git a/edge-monitoring-server/edge_monitoring_server.py b/edge-monitoring-server/edge_monitoring_server.py
index 0110e34..3fd0212 100755
--- a/edge-monitoring-server/edge_monitoring_server.py
+++ b/edge-monitoring-server/edge_monitoring_server.py
@@ -55,6 +55,28 @@
             },
             "required": ["control_plane", "user_plane"]
         },
+        "dongle_stats": {
+            "type": "object",
+            "properties": {
+                "SuccessfulFetch": {"type": "boolean"},
+                "MAC": {"type": "string"},
+                "PLMNStatus": {"type": "string"},
+                "UICCStatus": {"type": "string"},
+                "IMEI": {"type": "string"},
+                "IMSI": {"type": "string"},
+                "PLMNSelected": {"type": "string"},
+                "MCC": {"type": "string"},
+                "MNC": {"type": "string"},
+                "PhyCellID": {"type": "string"},
+                "CellGlobalID": {"type": "string"},
+                "Band": {"type": "string"},
+                "EARFCN": {"type": "string"},
+                "BandWidth": {"type": "string"},
+                "ServCellState": {"type": "string"},
+                "Connection": {"type": "string"},
+                "IPv4Addr": {"type": "string"}
+            }
+        },
         "speedtest": {
             "type": "object",
             "properties": {
@@ -149,6 +171,9 @@
             'control_plane': 'connected',
             'user_plane': 'connected'
         },
+        'dongle_stats': {
+            'SuccessfulFetch' : False
+        },
         'speedtest': {
             'ping': {
                 'dry_run': {
@@ -226,6 +251,11 @@
 ping_test_ok = prom.Gauge("aetheredge_ping_test_ok", "Last ping test passed", ["name"])
 e2e_tests_down = prom.Gauge("aetheredge_e2e_tests_down", "E2E tests not reporting", ["name"])
 
+# Dongle Stats (strings can't be stored in Prometheus)
+dongle_stats_imsi = prom.Gauge("aetheredge_dongle_stats_imsi", "IMSI of the UE", ["name"])
+dongle_stats_cellglobalid = prom.Gauge("aetheredge_dongle_stats_cellglobalid", "CellGlobalID of the UE", ["name", "value"])
+dongle_stats_plmnstatus = prom.Gauge("aetheredge_dongle_stats_plmnstatus", "PLMNStatus of the UE", ["name", "value"])
+
 # Ping dry_run metrics
 ping_dry_run_transmitted = prom.Gauge("aetheredge_ping_dry_run_test_transmitted","Last ping test to dry_run number of transmitted packets",["name"])
 ping_dry_run_received = prom.Gauge("aetheredge_ping_dry_run_test_received","Last ping test to dry_run number of received packets",["name"])
@@ -329,6 +359,7 @@
         if time_elapsed > NO_RESULT_THRESHOLD:
             edge['status']['control_plane'] = "no result"
             edge['status']['user_plane'] = "no result"
+            edge['dongle_stats'] = {'SuccessfulFetch' : False}
             edge['speedtest']['ping']['dry_run'] = {'transmitted': 0,
                                                     'received': 0,
                                                     'median': 0.0,
@@ -377,6 +408,13 @@
         pass
 
     try:
+        dongle_stats_imsi.remove(name)
+        dongle_stats_cellglobalid.clear()
+        dongle_stats_plmnstatus.clear()
+    except:
+        pass
+
+    try:
         ping_dry_run_min.remove(name)
         ping_dry_run_avg.remove(name)
         ping_dry_run_max.remove(name)
@@ -448,6 +486,13 @@
         connect_status = edge['status']['control_plane']
         ping_status = edge['status']['user_plane']
 
+        try:
+            dongle_stats_imsi.labels(edge['name']).set(float(edge['dongle_stats']['IMSI']))
+            dongle_stats_cellglobalid.labels(name=edge['name'], value=edge['dongle_stats']['CellGlobalID']).set(0)
+            dongle_stats_plmnstatus.labels(name=edge['name'], value=edge['dongle_stats']['PLMNStatus']).set(0)
+        except KeyError:
+            pass
+
         # Add ping dry_run latency results if available
         try:
             if edge['speedtest']['ping']['dry_run']['avg']:
@@ -537,6 +582,10 @@
     res.append(prom.generate_latest(cp_status))
     res.append(prom.generate_latest(up_status))
 
+    res.append(prom.generate_latest(dongle_stats_imsi))
+    res.append(prom.generate_latest(dongle_stats_cellglobalid))
+    res.append(prom.generate_latest(dongle_stats_plmnstatus))
+
     res.append(prom.generate_latest(ping_dry_run_min))
     res.append(prom.generate_latest(ping_dry_run_avg))
     res.append(prom.generate_latest(ping_dry_run_max))
@@ -618,6 +667,9 @@
             'control_plane': request.json['status']['control_plane'],
             'user_plane': request.json['status']['user_plane']
         },
+        'dongle_stats': {
+            'SuccessfulFetch' : False
+        },
         'speedtest': {
             'ping': {
                 'dry_run': {
@@ -676,6 +728,9 @@
     if 'signal_quality' in request.json:
         req_edge['signal_quality'] = request.json['signal_quality']
 
+    if 'dongle_stats' in request.json:
+        req_edge['dongle_stats'] = request.json['dongle_stats']
+
     edge = [edge for edge in edges if edge['name'] == req_edge['name']]
     if len(edge) == 0:
         app.logger.info("new edge request " + req_edge['name'])
@@ -685,6 +740,7 @@
         edge[0]['status']['user_plane'] = req_edge['status']['user_plane']
         edge[0]['speedtest']['ping'] = req_edge['speedtest']['ping']
         edge[0]['speedtest']['iperf'] = req_edge['speedtest']['iperf']
+        edge[0]['dongle_stats'] = req_edge['dongle_stats']
         if 'signal_quality' in req_edge.keys():
             edge[0]['signal_quality'] = req_edge['signal_quality']
         edge[0]['last_update'] = req_edge['last_update']
diff --git a/edge-monitoring-server/test_edge_monitoring_server.py b/edge-monitoring-server/test_edge_monitoring_server.py
index 146bfca..15bcca5 100755
--- a/edge-monitoring-server/test_edge_monitoring_server.py
+++ b/edge-monitoring-server/test_edge_monitoring_server.py
@@ -19,6 +19,12 @@
         'control_plane': 'connected',
         'user_plane': 'connected'
     },
+    'dongle_stats': {
+        'SuccessfulFetch': True,
+        'IMSI': '315021000000000',
+        'CellGlobalID': '315010001F800',
+        'PLMNStatus': 'Success'
+    },
     'speedtest': {
         'ping': {
             'dry_run': {
@@ -295,6 +301,65 @@
     'last_update': time.time()
 }
 
+test_edge_no_dongle_stats = {
+    'name': 'ace-menlo-rasp-pi',
+    'status': {
+        'control_plane': 'connected',
+        'user_plane': 'connected'
+    },
+    'speedtest': {
+        'ping': {
+            'dry_run': {
+                'transmitted': 3,
+                'received': 2,
+                'median': 5.0,
+                'min': 1.0,
+                'avg': 5.0,
+                'max': 9.0,
+                'stddev': 1.0
+            },
+            'dns': {
+                'transmitted': 10,
+                'received': 9,
+                'median': 4.0,
+                'min': 2.0,
+                'avg': 4.0,
+                'max': 6.0,
+                'stddev': 1.0
+            },
+            'iperf_server': {
+                'transmitted': 10,
+                'received': 10,
+                'median': 3.0,
+                'min': 1.0,
+                'avg': 3.0,
+                'max': 5.0,
+                'stddev': 1.0
+            },
+            'management_server': {
+                'transmitted': 10,
+                'received': 8,
+                'median': 6.0,
+                'min': 2.0,
+                'avg': 6.0,
+                'max': 10.0,
+                'stddev': 1.0
+            }
+        },
+        'iperf': {
+            'cluster': {
+                'downlink': 100.0,
+                'uplink': 10.0
+            }
+        }
+    },
+    'signal_quality': {
+        'rsrq': 30,
+        'rsrp': 80
+    },
+    'last_update': time.time()
+}
+
 class MyEvent:
     def __init__ (self, location = "", description = "", summary = "", start = None, end = None, all_day = False):
         self.location = location
@@ -372,6 +437,11 @@
         self.assertTrue('aetheredge_signal_quality_rsrq{name="ace-menlo-rasp-pi"} 30' in data)
         self.assertTrue('aetheredge_signal_quality_rsrp{name="ace-menlo-rasp-pi"} 80' in data)
 
+    def _assert_dongle_stats_metrics_exist(self, data):
+        self.assertTrue('aetheredge_dongle_stats_imsi{name="ace-menlo-rasp-pi"}' in data)
+        self.assertTrue('aetheredge_dongle_stats_cellglobalid{name="ace-menlo-rasp-pi",value="315010001F800"}' in data)
+        self.assertTrue('aetheredge_dongle_stats_plmnstatus{name="ace-menlo-rasp-pi",value="Success"}' in data)
+
     def test_match_location(self):
         event = MyEvent(location = "ace-menlo-rasp-pi-production")
         self.assertTrue(ems.is_my_event(event, "ace-menlo-rasp-pi"))
@@ -489,6 +559,7 @@
         self._assert_status_metrics_exist(data)
         self._assert_speedtest_metrics_exist(data)
         self._assert_signal_quality_metrics_exist(data)
+        self._assert_dongle_stats_metrics_exist(data)
 
         response = self.app.delete('/testresults/ace-menlo-rasp-pi')
         data = json.loads(response.get_data(as_text=True))
@@ -689,6 +760,22 @@
         data = json.loads(response.get_data(as_text=True))
         self.assertEqual(data['result'], True)
 
+    def test_backwards_compatible_no_dongle_stats(self):
+        response = self.app.post('/testresults', json=test_edge_no_dongle_stats)
+        data = json.loads(response.get_data(as_text=True))
+        self.assertEqual(data['edge']['name'], 'ace-menlo-rasp-pi')
+
+        response = self.app.get('/edges/metrics')
+        data = response.get_data(as_text=True)
+
+        self.assertTrue('aetheredge_dongle_stats_imsi{name="ace-menlo-rasp-pi"} 0.0' in data)
+        self.assertFalse('aetheredge_dongle_stats_cellglobalid{name="ace-menlo-rasp-pi",value=' in data)
+        self.assertFalse('aetheredge_dongle_stats_plmnstatus{name="ace-menlo-rasp-pi",value=' in data)
+
+        response = self.app.delete('/testresults/ace-menlo-rasp-pi')
+        data = json.loads(response.get_data(as_text=True))
+        self.assertEqual(data['result'], True)
+
     def test_handle_invalid_schema(self):
         response = self.app.post('/testresults', json="")
         self.assertEqual(response.status_code, 400)
diff --git a/edge-monitoring/agent_modem/edge_monitoring_agent_modem.py b/edge-monitoring/agent_modem/edge_monitoring_agent_modem.py
index 157d41e..1a4f7bc 100755
--- a/edge-monitoring/agent_modem/edge_monitoring_agent_modem.py
+++ b/edge-monitoring/agent_modem/edge_monitoring_agent_modem.py
@@ -16,6 +16,7 @@
 import time
 from collections import namedtuple
 from statistics import median
+import xml.etree.ElementTree as ET
 
 '''
 "Simple" script that checks Aether network operational status periodically
@@ -321,14 +322,54 @@
 
     return result
 
+def get_dongle_stats(modem):
+    result = {'SuccessfulFetch' : False}
+    XMLkeys = ["MAC",
+               "PLMNStatus",
+               "UICCStatus",
+               "IMEI",
+               "IMSI",
+               "PLMNSelected",
+               "MCC",
+               "MNC",
+               "PhyCellID",
+               "CellGlobalID",
+               "Band",
+               "EARFCN",
+               "BandWidth",
+               "ServCellState",
+               "Connection",
+               "IPv4Addr"]
+    dongleStatsXML = None
+    try:
+        dongleStatsXML = ET.fromstring(subprocess.check_output("curl -u admin:admin 'http://192.168.0.1:8080/cgi-bin/ltestatus.cgi?Command=Status'", shell=True).decode("UTF-8"))
+    except Exception as e:
+        logging.error("Failed to fetch dongle stats from URL: " + str(e))
+        return result
+    try:
+        for key in XMLkeys:
+            try:
+                result[key] = dongleStatsXML.find(key).text
+            except AttributeError as e:
+                logging.warn("Failed to find " + key + " in XML.")
+                result[key] = ""
+        result["SuccessfulFetch"] = True
+    except Exception as e:
+        logging.error("Failed to fetch dongle stats from XML: " + str(e))
+        return result
+    return result
 
-def report_status(signal_quality, cp_state=None, up_state=None, speedtest_ping=None, speedtest_iperf=None):
+
+def report_status(signal_quality, dongle_stats, cp_state=None, up_state=None, speedtest_ping=None, speedtest_iperf=None):
     report = {
         'name': CONF.edge_name,
         'status': {
             'control_plane': "disconnected",
             'user_plane': "disconnected"
         },
+        'dongle_stats': {
+            'SuccessfulFetch' : False
+        },
         'speedtest': {
             'ping': {
                 'dns': {
@@ -363,6 +404,7 @@
     if speedtest_iperf is not None:
         report['speedtest']['iperf'] = speedtest_iperf
     report['signal_quality'] = signal_quality
+    report['dongle_stats'] = dongle_stats
 
     logging.info("Sending report %s", report)
     global cycles
@@ -431,6 +473,8 @@
         sys.exit(1)
 
     while True:
+        dongle_stats = get_dongle_stats(modem)
+
         signal_quality = get_signal_quality(modem)
 
         cp_state = get_control_plane_state(modem)
@@ -440,23 +484,23 @@
             sys.exit(1)
         if cp_state is State.disconnected:
             # Failed to attach, don't need to run other tests
-            report_status(signal_quality)
+            report_status(signal_quality, dongle_stats)
             continue
 
         up_state = get_user_plane_state(modem)
         if up_state is State.disconnected:
             # Basic user plane test failed, don't need to run the rest of tests
-            report_status(signal_quality, cp_state)
+            report_status(signal_quality, dongle_stats, cp_state)
             continue
 
         speedtest_ping, speedtest_status = get_ping_test(modem)
         if speedtest_status:
             speedtest_iperf = get_iperf_test(modem)
         else:
-            report_status(signal_quality, cp_state, up_state, speedtest_ping)
+            report_status(signal_quality, dongle_stats, cp_state, up_state, speedtest_ping)
             continue
 
-        report_status(signal_quality, cp_state, up_state, speedtest_ping, speedtest_iperf)
+        report_status(signal_quality, dongle_stats, cp_state, up_state, speedtest_ping, speedtest_iperf)
 
     modem.close()