[AETHER-1472]: Add iperf downlink+uplink test to monitoring agent

Change-Id: I9d37ddfa0d8e2f8c773f54a751a751c8b6a42c12
diff --git a/edge-monitoring/agent_modem/config.json b/edge-monitoring/agent_modem/config.json
index c8717b0..3a33d1d 100644
--- a/edge-monitoring/agent_modem/config.json
+++ b/edge-monitoring/agent_modem/config.json
@@ -7,12 +7,14 @@
     },
     "ips": {
       "user_plane_ping_test": "8.8.8.8",
-      "speedtest_ping_dns": "8.8.8.8"
+      "speedtest_ping_dns": "8.8.8.8",
+      "speedtest_iperf": ""
     },
     "attach_timeout": 30,
     "detach_timeout": 10,
     "report_url": "https://monitoring.aetherproject.org/edges",
     "report_interval": 180,
     "log_level": "WARN",
-    "log_file": "/var/log/edge_monitoring_agent.log"
+    "log_file": "/var/log/edge_monitoring_agent.log",
+    "iperf_port": 0
 }
diff --git a/edge-monitoring/agent_modem/edge_monitoring_agent_modem.py b/edge-monitoring/agent_modem/edge_monitoring_agent_modem.py
index cdfab63..180ea76 100755
--- a/edge-monitoring/agent_modem/edge_monitoring_agent_modem.py
+++ b/edge-monitoring/agent_modem/edge_monitoring_agent_modem.py
@@ -185,6 +185,7 @@
 
 def get_ping_test(modem):
     '''
+    Prepares the ping test.
     Each ping test result saves the min/avg/max/stddev to dict.
     1) Performs ping test to Google Public DNS for 10 iterations.
     2) # TODO: Performs ping to device on network.
@@ -193,6 +194,41 @@
     speedtest_ping['dns'] = run_ping_test(CONF.ips.speedtest_ping_dns, 10)
     return speedtest_ping
 
+def run_iperf_test(ip, port, time_duration, is_downlink):
+    '''
+    Runs iperf test to specified IP in the config file.
+    - Runs for 10 seconds (10 iterations)
+    - Retrieves downlink and uplink test results from json output
+    '''
+    result = 0.0
+    if not ip:
+        return result
+    try:
+        iperfResult = json.loads(subprocess.check_output(
+                "iperf3 -c " + ip +
+                " -p " + str(port) +
+                " -t " + str(time_duration) +
+                (" -R " if is_downlink else "") +
+                " --json", shell=True).decode("UTF-8"))
+        received_mbps = iperfResult['end']['sum_received']['bits_per_second'] / 1000000
+        sent_mbps = iperfResult['end']['sum_sent']['bits_per_second'] / 1000000.0
+        result = received_mbps if is_downlink else sent_mbps
+    except Exception as e:
+        logging.error("iperf test failed for " + ip + ": %s", e)
+    return result
+
+
+def get_iperf_test(modem):
+    '''
+    Prepares the iperf test.
+    '''
+    speedtest_iperf = {}
+    speedtest_iperf['cluster'] = {}
+    speedtest_iperf['cluster']['downlink'] = run_iperf_test(CONF.ips.speedtest_iperf, CONF.iperf_port, 10, True)
+    speedtest_iperf['cluster']['uplink'] = run_iperf_test(CONF.ips.speedtest_iperf, CONF.iperf_port, 10, False)
+
+    return speedtest_iperf
+
 
 def get_signal_quality(modem):
     success, result = modem.write('AT+CESQ')
@@ -214,7 +250,7 @@
     return result
 
 
-def report_status(signal_quality, cp_state=None, up_state=None, speedtest_ping=None):
+def report_status(signal_quality, cp_state=None, up_state=None, speedtest_ping=None, speedtest_iperf=None):
     report = {
         'name': CONF.edge_name,
         'status': {
@@ -229,6 +265,12 @@
                     'max': 0.0,
                     'stddev': 0.0
                 }
+            },
+            'iperf': {
+                'cluster': {
+                    'downlink': 0.0,
+                    'uplink': 0.0
+                }
             }
         },
         'signal_quality': {
@@ -243,6 +285,8 @@
         report['status']['user_plane'] = up_state.name
     if speedtest_ping is not None:
         report['speedtest']['ping'] = speedtest_ping
+    if speedtest_iperf is not None:
+        report['speedtest']['iperf'] = speedtest_iperf
     report['signal_quality'] = signal_quality
 
     logging.info("Sending report %s", report)
@@ -261,6 +305,8 @@
 
 def main():
     for ip in CONF.ips:
+        if not ip:
+            continue
         try:
             subprocess.check_output("sudo ip route replace {}/32 via {}".format(
                 ip, CONF.modem.ip_addr), shell=True)
@@ -289,12 +335,14 @@
 
         up_state = get_user_plane_state(modem)
         if up_state is State.disconnected:
-            # Basic user plan test failed, don't need to run the rest of tests
+            # Basic user plane test failed, don't need to run the rest of tests
             report_status(signal_quality, cp_state)
             continue
 
         speedtest_ping = get_ping_test(modem)
-        report_status(signal_quality, cp_state, up_state, speedtest_ping)
+        speedtest_iperf = get_iperf_test(modem)
+
+        report_status(signal_quality, cp_state, up_state, speedtest_ping, speedtest_iperf)
 
     modem.close()