blob: cfebbe627d7dd6e64521ac89218f5ca8483d7f81 [file] [log] [blame]
Hyunsun Moondb72b8f2020-11-02 18:03:39 -08001#!/usr/bin/env python3
Hyunsun Moon53097ea2020-09-04 17:20:29 -07002
3# Copyright 2020-present Open Networking Foundation
4#
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -07005# SPDX-License-Identifier: LicenseRef-ONF-Member-Only-1.0
Hyunsun Moon53097ea2020-09-04 17:20:29 -07006
7import sys
8import os
9import json
10import logging
11import enum
Jeremy Ronquillo8d108652021-11-22 17:34:58 -080012import pycurl
Hyunsun Moon53097ea2020-09-04 17:20:29 -070013import time
14import serial
Jeremy Ronquillod996b512021-02-13 13:45:47 -080015import subprocess
Jeremy Ronquilloef17e362021-11-08 10:56:42 -080016import time
Hyunsun Moon53097ea2020-09-04 17:20:29 -070017from collections import namedtuple
Jeremy Ronquilloe0a8b422021-11-02 12:49:15 -070018from statistics import median
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -080019import xml.etree.ElementTree as ET
Hyunsun Moon53097ea2020-09-04 17:20:29 -070020
21'''
Jeremy Ronquilloa944fbc2021-03-30 10:57:45 -070022"Simple" script that checks Aether network operational status periodically
Hyunsun Moon53097ea2020-09-04 17:20:29 -070023by controlling the attached 4G/LTE modem with AT commands and
24report the result to the central monitoring server.
25'''
26
Jeremy Ronquillo6e352b72021-06-08 10:33:25 -070027# Parse config with backwards compatibility with config.json pre 0.6.6
28config_file_contents = open(os.getenv('CONFIG_FILE', "./config.json")).read()
29config_file_contents = config_file_contents.replace("user_plane_ping_test", "dns")
30config_file_contents = config_file_contents.replace("speedtest_iperf", "iperf_server")
Jeremy Ronquilloc03ba682021-10-06 10:27:09 -070031config_file_contents = config_file_contents.replace("\"speedtest_ping_dns\": \"1.1.1.1\",", "")
Hyunsun Moon53097ea2020-09-04 17:20:29 -070032CONF = json.loads(
Jeremy Ronquillo6e352b72021-06-08 10:33:25 -070033 config_file_contents, object_hook=lambda d: namedtuple('X', d.keys())(*d.values())
Hyunsun Moon53097ea2020-09-04 17:20:29 -070034)
35
36logging.basicConfig(
37 filename=CONF.log_file,
38 format='%(asctime)s [%(levelname)s] %(message)s',
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -070039 level=logging.getLevelName(CONF.log_level)
Hyunsun Moon53097ea2020-09-04 17:20:29 -070040)
41
Hyunsun Moon53097ea2020-09-04 17:20:29 -070042
43class State(enum.Enum):
44 error = "-1"
45 disconnected = "0"
46 connected = "1"
47
48 @classmethod
49 def has_value(cls, value):
50 return value in cls._value2member_map_
51
52
53class Modem():
54 log = logging.getLogger('aether_edge_monitoring.Modem')
55
56 read_timeout = 0.1
57
58 def __init__(self, port, baudrate):
59 self.port = port
60 self.baudrate = baudrate
61 self._response = None
62
Don Newtonbd91ae22021-05-11 14:58:18 -070063 def get_modem_port(self):
64 cmd = "ls " + CONF.modem.port
65 sp = subprocess.Popen(cmd,shell=True,stdout=subprocess.PIPE,
66 stderr=subprocess.PIPE, universal_newlines=True)
67 rc = sp.wait()
68 ret,err = sp.communicate()
69 if err != "" :
Shad Ansarib234ff82022-02-17 22:14:35 -080070 self.log.error("unable to find serial port " + err)
Don Newtonbd91ae22021-05-11 14:58:18 -070071
72 ret = ret.replace(CONF.modem.port,"").strip()
Shad Ansarib234ff82022-02-17 22:14:35 -080073 self.log.info("Modem.get_modem_port found " + ret)
Don Newtonbd91ae22021-05-11 14:58:18 -070074 return ret
75
Hyunsun Moon53097ea2020-09-04 17:20:29 -070076 def connect(self):
Don Newtonbd91ae22021-05-11 14:58:18 -070077 self.port=self.get_modem_port()
Shad Ansarib234ff82022-02-17 22:14:35 -080078 self.log.info("modem.connect Port: %s, BaudRate: %i",self.port,self.baudrate)
Hyunsun Moon53097ea2020-09-04 17:20:29 -070079 self.serial = serial.Serial(
80 port=self.port,
81 baudrate=self.baudrate,
82 timeout=1)
83
84 def _write(self, command):
85 if self.serial.inWaiting() > 0:
86 self.serial.flushInput()
87
88 self._response = b""
89
90 self.serial.write(bytearray(command + "\r", "ascii"))
91 read = self.serial.inWaiting()
92 while True:
Shad Ansarib234ff82022-02-17 22:14:35 -080093 self.log.debug("Waiting for write to complete...")
Hyunsun Moon53097ea2020-09-04 17:20:29 -070094 if read > 0:
95 self._response += self.serial.read(read)
96 else:
97 time.sleep(self.read_timeout)
98 read = self.serial.inWaiting()
99 if read == 0:
100 break
Shad Ansarib234ff82022-02-17 22:14:35 -0800101 self.log.debug("Write complete...")
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700102 return self._response.decode("ascii").replace('\r\n', ' ')
103
104 def write(self, command, wait_resp=True):
105 response = self._write(command)
106 self.log.debug("%s: %s", command, response)
107
108 if wait_resp and "ERROR" in response:
109 return False, None
110 return True, response
111
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700112 def get_state(self):
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700113 success, result = self.write('AT+CGATT?')
114 if not success or 'CGATT:' not in result:
115 return State.error
116 state = result.split('CGATT:')[1].split(' ')[0]
117 return State(state)
118
119 def close(self):
120 self.serial.close()
121
122
Shad Ansarib234ff82022-02-17 22:14:35 -0800123def get_control_plane_state(modem, dongle_stats=None):
124 if dongle_stats and dongle_stats['Connection'] == 'Connected':
125 return State.connected
126
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700127 # Disable radio fuction
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700128 # "echo" works more stable than serial for this action
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700129 try:
Don Newtonbd91ae22021-05-11 14:58:18 -0700130 logging.debug("echo 'AT+CFUN=0' > " + modem.port)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700131 subprocess.check_output(
Don Newtonbd91ae22021-05-11 14:58:18 -0700132 "echo 'AT+CFUN=0' > " + modem.port, shell=True)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700133 except subprocess.CalledProcessError as e:
134 logging.error("Write 'AT+CFUN=0' failed")
135 return State.error
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700136
137 # Wait until the modem is fully disconnected
138 retry = 0
139 state = None
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700140 while retry < CONF.detach_timeout:
141 state = modem.get_state()
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700142 if state is State.disconnected:
143 break
144 time.sleep(1)
145 retry += 1
146
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700147 if state is not State.disconnected:
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700148 logging.error("Failed to disconnect")
149 return State.error
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700150
151 time.sleep(2)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700152 # Enable radio function
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700153 # "echo" works more stable than serial for this action
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700154 try:
Don Newtonbd91ae22021-05-11 14:58:18 -0700155 logging.debug("echo 'AT+CFUN=1' > " + modem.port)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700156 subprocess.check_output(
Don Newtonbd91ae22021-05-11 14:58:18 -0700157 "echo 'AT+CFUN=1' > " + modem.port, shell=True)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700158 except subprocess.CalledProcessError as e:
159 logging.error("Write 'AT+CFUN=1' failed")
160 return State.error
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700161
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700162 # Wait attach_timeout sec for the modem to be fully connected
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700163 retry = 0
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700164 while retry < CONF.attach_timeout:
165 state = modem.get_state()
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700166 if state is State.connected:
167 break
168 time.sleep(1)
169 retry += 1
170 # CGATT sometimes returns None
171 if state is State.error:
172 state = State.disconnected
173
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700174 return state
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700175
176
177def get_user_plane_state(modem):
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800178 if "dry_run" in CONF.ips._fields and CONF.ips.dry_run: # run dry_run latency test as user plane test
179 dry_run_latency, dry_run_passed = run_ping_test(CONF.ips.dry_run, 3)
180 if dry_run_passed:
181 return State.connected, dry_run_latency
182 else:
183 logging.warning("User plane test failed")
184 return State.disconnected, dry_run_latency
185 else: # run default user plane test
186 try:
187 subprocess.check_output(
188 "ping -c 3 " + CONF.ips.dns + ">/dev/null 2>&1",
189 shell=True)
190 return State.connected, None
191 except subprocess.CalledProcessError as e:
192 logging.warning("User plane test failed")
193 return State.disconnected, None
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700194
195
Jeremy Ronquillod996b512021-02-13 13:45:47 -0800196def run_ping_test(ip, count):
197 '''
198 Runs the ping test
199 Input: IP to ping, # times to ping
Jeremy Ronquilloe0a8b422021-11-02 12:49:15 -0700200 Returns: Transmitted packets
201 Received packets
202 Median ping ms
203 Min ping ms
204 Avg ping ms
205 Max ping ms
206 Std Dev ping ms
Jeremy Ronquillod996b512021-02-13 13:45:47 -0800207 '''
Jeremy Ronquilloe0a8b422021-11-02 12:49:15 -0700208 result = {'transmitted': 0,
209 'received': 0,
210 'median': 0.0,
211 'min': 0.0,
Jeremy Ronquilloa944fbc2021-03-30 10:57:45 -0700212 'avg': 0.0,
213 'max': 0.0,
214 'stddev': 0.0}
Jeremy Ronquillo6e352b72021-06-08 10:33:25 -0700215 if not ip:
Jeremy Ronquillo115c5e32021-09-30 11:15:56 -0700216 return result, True
Jeremy Ronquillod996b512021-02-13 13:45:47 -0800217 try:
Jeremy Ronquilloe0a8b422021-11-02 12:49:15 -0700218 pingOutput = subprocess.check_output(
219 "ping -c " + str(count) + " " + \
220 ip, shell=True).decode("UTF-8").split()
221 result['transmitted'] = int(pingOutput[-15])
222 result['received'] = int(pingOutput[-12])
223 if result['received'] > 0:
224 pingValues = []
225
226 # Hack for getting all ping values for median
227 for word in pingOutput:
228 if "time=" in word:
229 pingValues.append(float(word.split("=")[1]))
230 result['median'] = round(median(pingValues), 3)
231
232 pingResult = pingOutput[-2].split('/')
233 result['min'] = float(pingResult[0])
234 result['avg'] = float(pingResult[1])
235 result['max'] = float(pingResult[2])
236 result['stddev'] = float(pingResult[3])
237 else:
238 logging.error("No packets received during ping " + ip)
239 return result, False
Jeremy Ronquillod996b512021-02-13 13:45:47 -0800240 except Exception as e:
241 logging.error("Ping test failed for " + ip + ": %s", e)
Jeremy Ronquillo115c5e32021-09-30 11:15:56 -0700242 return result, False
243 return result, True
Jeremy Ronquillod996b512021-02-13 13:45:47 -0800244
245
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800246def get_ping_test(modem, dry_run_latency=None):
Jeremy Ronquillod996b512021-02-13 13:45:47 -0800247 '''
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700248 Prepares the ping test.
Jeremy Ronquilloe0a8b422021-11-02 12:49:15 -0700249 Runs ping tests from 'ips' entry in config.json in order.
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800250 Note: 'dry_run' is not run here; it is run during the user plane test.
Jeremy Ronquillod996b512021-02-13 13:45:47 -0800251 '''
252 speedtest_ping = {}
Jeremy Ronquillo115c5e32021-09-30 11:15:56 -0700253 status = True
254 ping_test_passed = True
Jeremy Ronquillo6e352b72021-06-08 10:33:25 -0700255
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800256 if dry_run_latency:
257 speedtest_ping["dry_run"] = dry_run_latency
258
Jeremy Ronquillo6e352b72021-06-08 10:33:25 -0700259 for i in range(0, len(CONF.ips)):
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800260 if CONF.ips._fields[i] == "dry_run":
261 continue
262 count = 5
Jeremy Ronquilloe0a8b422021-11-02 12:49:15 -0700263 speedtest_ping[CONF.ips._fields[i]], status = run_ping_test(CONF.ips[i], count)
Jeremy Ronquillo115c5e32021-09-30 11:15:56 -0700264 if not status:
265 ping_test_passed = False
266 logging.error("Ping test failed. Not running further tests.")
267 return speedtest_ping, ping_test_passed
Jeremy Ronquillod996b512021-02-13 13:45:47 -0800268
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700269def run_iperf_test(ip, port, time_duration, is_downlink):
270 '''
271 Runs iperf test to specified IP in the config file.
272 - Runs for 10 seconds (10 iterations)
273 - Retrieves downlink and uplink test results from json output
274 '''
275 result = 0.0
Jeremy Ronquilloc03ba682021-10-06 10:27:09 -0700276 if not ip or port == 0:
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700277 return result
Jeremy Ronquillo115c5e32021-09-30 11:15:56 -0700278 maxRetries = 2
Jeremy Ronquillo79c3e672021-09-03 12:54:55 -0700279 err = None
Jeremy Ronquillo012ac662021-08-06 12:13:43 -0700280 for _ in range(0, maxRetries):
281 try:
282 iperfResult = json.loads(subprocess.check_output(
283 "iperf3 -c " + ip +
284 " -p " + str(port) +
285 " -t " + str(time_duration) +
286 (" -R " if is_downlink else "") +
287 " --json", shell=True).decode("UTF-8"))
288 received_mbps = iperfResult['end']['sum_received']['bits_per_second'] / 1000000
289 sent_mbps = iperfResult['end']['sum_sent']['bits_per_second'] / 1000000.0
290 result = received_mbps if is_downlink else sent_mbps
291 return result
292 except Exception as e:
Jeremy Ronquillo79c3e672021-09-03 12:54:55 -0700293 err = e
Jeremy Ronquillo012ac662021-08-06 12:13:43 -0700294 time.sleep(5)
295 pass
Jeremy Ronquillo79c3e672021-09-03 12:54:55 -0700296 logging.error("After " + str(maxRetries) + " retries, iperf test failed for " + ip + ": %s", err)
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700297 return result
298
299
300def get_iperf_test(modem):
301 '''
302 Prepares the iperf test.
303 '''
Jeremy Ronquilloef17e362021-11-08 10:56:42 -0800304 global hour_iperf_scheduled_time_last_ran
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700305 speedtest_iperf = {}
306 speedtest_iperf['cluster'] = {}
Jeremy Ronquilloef17e362021-11-08 10:56:42 -0800307
308 if "iperf_schedule" in CONF._fields and len(CONF.iperf_schedule) > 0:
309 if int(time.strftime("%H")) not in CONF.iperf_schedule: # not in the schedule
310 hour_iperf_scheduled_time_last_ran = -1
311 return None
312 elif int(time.strftime("%H")) == hour_iperf_scheduled_time_last_ran: # already ran this hour
313 return None
314 hour_iperf_scheduled_time_last_ran = int(time.strftime("%H"))
315
Jeremy Ronquillo6e352b72021-06-08 10:33:25 -0700316 speedtest_iperf['cluster']['downlink'] = run_iperf_test(CONF.ips.iperf_server, CONF.iperf_port, 10, True)
317 speedtest_iperf['cluster']['uplink'] = run_iperf_test(CONF.ips.iperf_server, CONF.iperf_port, 10, False)
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700318
319 return speedtest_iperf
320
Jeremy Ronquillod996b512021-02-13 13:45:47 -0800321
Shad Ansarib234ff82022-02-17 22:14:35 -0800322def get_signal_quality(modem, dongle_stats=None):
323 if dongle_stats and dongle_stats['RSRQ'] != '' and dongle_stats['RSRP'] != '':
324 rsrq = int((float(dongle_stats['RSRQ']) + 19.5) * 2)
325 rsrp = int(float(dongle_stats['RSRP']) + 140)
326 return {'rsrq': rsrq, 'rsrp': rsrp}
327
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700328 success, result = modem.write('AT+CESQ')
Don Newtonbd91ae22021-05-11 14:58:18 -0700329 logging.debug("get_signal_quality success %i result %s",success,result)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700330 if not success or 'CESQ: ' not in result:
331 logging.error("Failed to get signal quality")
Don Newtonbd91ae22021-05-11 14:58:18 -0700332 return {'rsrq':0, 'rsrp':0}
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700333
334 logging.debug("%s", result)
335 tmp_rsrq = result.split('CESQ:')[1].split(',')[4]
336 tmp_rsrp = result.split('CESQ:')[1].split(',')[5]
337
338 rsrq = int(tmp_rsrq.strip())
339 rsrp = int(tmp_rsrp.strip().split(' ')[0])
340 result = {
341 'rsrq': 0 if rsrq is 255 else rsrq,
342 'rsrp': 0 if rsrp is 255 else rsrp
343 }
344
345 return result
346
Shad Ansarib234ff82022-02-17 22:14:35 -0800347
348def get_dongle_stats():
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800349 result = {'SuccessfulFetch' : False}
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800350 if "report_in_band" in CONF._fields:
351 result['inBandReporting'] = CONF.report_in_band
352 else:
353 result['inBandReporting'] = False
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800354 XMLkeys = ["MAC",
355 "PLMNStatus",
356 "UICCStatus",
357 "IMEI",
358 "IMSI",
359 "PLMNSelected",
360 "MCC",
361 "MNC",
362 "PhyCellID",
363 "CellGlobalID",
364 "Band",
365 "EARFCN",
366 "BandWidth",
Shad Ansarib234ff82022-02-17 22:14:35 -0800367 "RSRP",
368 "RSRQ",
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800369 "ServCellState",
370 "Connection",
371 "IPv4Addr"]
372 dongleStatsXML = None
373 try:
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800374 dongleStatsXML = ET.fromstring(subprocess.check_output("curl -u admin:admin -s 'http://192.168.0.1:8080/cgi-bin/ltestatus.cgi?Command=Status'", shell=True).decode("UTF-8"))
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800375 except Exception as e:
376 logging.error("Failed to fetch dongle stats from URL: " + str(e))
377 return result
378 try:
379 for key in XMLkeys:
380 try:
381 result[key] = dongleStatsXML.find(key).text
382 except AttributeError as e:
383 logging.warn("Failed to find " + key + " in XML.")
384 result[key] = ""
385 result["SuccessfulFetch"] = True
386 except Exception as e:
387 logging.error("Failed to fetch dongle stats from XML: " + str(e))
388 return result
389 return result
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700390
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800391
392def report_status(signal_quality, dongle_stats, cp_state=None, up_state=None, speedtest_ping=None, speedtest_iperf=None):
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700393 report = {
394 'name': CONF.edge_name,
395 'status': {
396 'control_plane': "disconnected",
397 'user_plane': "disconnected"
398 },
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800399 'dongle_stats': {
400 'SuccessfulFetch' : False
401 },
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700402 'speedtest': {
403 'ping': {
404 'dns': {
Jeremy Ronquilloe0a8b422021-11-02 12:49:15 -0700405 'transmitted' : 0,
406 'received' : 0,
407 'median' : 0.0,
Jeremy Ronquillo6e352b72021-06-08 10:33:25 -0700408 'min': 0.0,
409 'avg': 0.0,
410 'max': 0.0,
411 'stddev': 0.0
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700412 }
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700413 },
414 'iperf': {
415 'cluster': {
416 'downlink': 0.0,
417 'uplink': 0.0
418 }
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700419 }
420 },
421 'signal_quality': {
422 'rsrq': 0,
423 'rsrp': 0
424 }
425 }
426
427 if cp_state is not None:
428 report['status']['control_plane'] = cp_state.name
429 if up_state is not None:
430 report['status']['user_plane'] = up_state.name
431 if speedtest_ping is not None:
432 report['speedtest']['ping'] = speedtest_ping
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700433 if speedtest_iperf is not None:
434 report['speedtest']['iperf'] = speedtest_iperf
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700435 report['signal_quality'] = signal_quality
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800436 report['dongle_stats'] = dongle_stats
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700437
438 logging.info("Sending report %s", report)
Don Newtonbd91ae22021-05-11 14:58:18 -0700439 global cycles
440 cycles += 1
441 logging.info("Number of cycles since modem restart %i",cycles)
Jeremy Ronquillo8d108652021-11-22 17:34:58 -0800442
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700443 try:
Jeremy Ronquilloeff2e6d2021-12-06 10:39:37 -0800444 interface = None
445 report_via_modem = "report_in_band" in CONF._fields and CONF.report_in_band and \
446 "iface" in CONF.modem._fields and CONF.modem.iface
447 report_via_given_iface = "report_iface" in CONF._fields and CONF.report_iface
448
Jeremy Ronquillo8d108652021-11-22 17:34:58 -0800449 c = pycurl.Curl()
450 c.setopt(pycurl.URL, CONF.report_url)
451 c.setopt(pycurl.POST, True)
452 c.setopt(pycurl.HTTPHEADER, ['Content-Type: application/json'])
453 c.setopt(pycurl.TIMEOUT, 10)
454 c.setopt(pycurl.POSTFIELDS, json.dumps(report))
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800455 c.setopt(pycurl.WRITEFUNCTION, lambda x: None) # don't output to console
Jeremy Ronquilloeff2e6d2021-12-06 10:39:37 -0800456
457 if report_via_modem: # report in-band
458 interface = CONF.modem.iface
459 c.setopt(pycurl.INTERFACE, interface)
460 elif report_via_given_iface: # report over given interface
461 interface = CONF.report_iface
462 c.setopt(pycurl.INTERFACE, interface)
463 # else, reports over default interface
464
465 try:
466 c.perform()
467 logging.info("Report sent via " + interface + "!")
468 except Exception as e:
469 if report_via_modem and report_via_given_iface:
470 logging.warning("Sending report via modem failed. Attempting to send report via " + str(CONF.report_iface) + ".")
471 interface = CONF.report_iface
472 c.setopt(pycurl.INTERFACE, interface)
473 c.perform()
474 logging.info("Report sent via " + interface + "!")
475 else:
476 logging.error("Failed to send report: " + str(e))
Jeremy Ronquillo8d108652021-11-22 17:34:58 -0800477 c.close()
478 except Exception as e:
479 logging.error("Failed to send report: " + str(e))
Jeremy Ronquilloeff2e6d2021-12-06 10:39:37 -0800480 c.close()
Jeremy Ronquillo8d108652021-11-22 17:34:58 -0800481
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700482 time.sleep(CONF.report_interval)
Jeremy Ronquillo82a14612021-10-08 12:08:20 -0700483
Don Newtonbd91ae22021-05-11 14:58:18 -0700484def reset_usb():
Jeremy Ronquillo82a14612021-10-08 12:08:20 -0700485 try:
486 # Attempt to run uhubctl
487 if (int(subprocess.call("which uhubctl",shell=True)) == 0):
488 cmd = "/usr/sbin/uhubctl -a 0 -l 2" # -a 0 = action is shutdown -l 2 location = bus 2 on pi controls power to all hubs
489 ret = subprocess.call(cmd,shell=True)
490 logging.info("Shutting down usb hub 2 results %s" , ret)
491 time.sleep(10)# let power down process settle out
492 cmd = "/usr/sbin/uhubctl -a 1 -l 2" # -a 1 = action is start -l 2 location = bus 2 on pi controls power to all hubs
493 ret = subprocess.call(cmd,shell=True)
494 logging.info("Starting up usb hub 2 results %s" , ret)
495 time.sleep(10) #allow dbus to finish
496 global cycles
497 cycles = 0
498 else:
499 reboot(120)
500 except Exception as e:
501 logging.error("Failed to run uhubctl: %s", e)
502 reboot(120)
503
504def reboot(delay):
505 logging.error("Failed to run uhubctl. Reboot system in " + str(delay) + " second(s).")
506 time.sleep(delay)
507 subprocess.check_output("sudo shutdown -r now", shell=True)
Don Newtonbd91ae22021-05-11 14:58:18 -0700508
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700509def main():
Don Newtonbd91ae22021-05-11 14:58:18 -0700510 global cycles
Jeremy Ronquilloef17e362021-11-08 10:56:42 -0800511 global hour_iperf_scheduled_time_last_ran
Don Newtonbd91ae22021-05-11 14:58:18 -0700512 cycles = 0
Jeremy Ronquilloef17e362021-11-08 10:56:42 -0800513 hour_iperf_scheduled_time_last_ran = -1
Jeremy Ronquillo8d108652021-11-22 17:34:58 -0800514
515 try:
516 if "report_in_band" in CONF._fields and \
517 "iface" in CONF.modem._fields and CONF.modem.iface:
518 if CONF.report_in_band: # need to add default gateway if reporting in-band
519 subprocess.check_output("sudo route add default gw " + CONF.modem.ip_addr + " " + CONF.modem.iface + " || true", shell=True)
520 else:
521 subprocess.check_output("sudo route del default gw " + CONF.modem.ip_addr + " " + CONF.modem.iface + " || true", shell=True)
522 except Exception as e:
523 logging.error("Failed to change default route for modem: " + str(e))
524
Don Newtonbd91ae22021-05-11 14:58:18 -0700525 for ip in CONF.ips:
526 if not ip:
527 continue
528 try:
529 subprocess.check_output("sudo ip route replace {}/32 via {}".format(
530 ip, CONF.modem.ip_addr), shell=True)
531 except subprocess.CalledProcessError as e:
Jeremy Ronquilloef17e362021-11-08 10:56:42 -0800532 logging.error("Failed to add routes: " + str(e.returncode) + str(e.output))
Jeremy Ronquillo82a14612021-10-08 12:08:20 -0700533 time.sleep(10) # Sleep for 10 seconds before retry
Don Newtonbd91ae22021-05-11 14:58:18 -0700534 sys.exit(1)
535
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700536 modem = Modem(CONF.modem.port, CONF.modem.baud)
537 try:
538 modem.connect()
539 except serial.serialutil.SerialException as e:
540 logging.error("Failed to connect the modem for %s", e)
Hyunsun Moon2b7d3e12021-01-04 13:30:19 -0800541 sys.exit(1)
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700542
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700543 while True:
Shad Ansarib234ff82022-02-17 22:14:35 -0800544 dongle_stats = get_dongle_stats()
545 cp_state = get_control_plane_state(modem, dongle_stats)
546 if cp_state != State.connected:
547 logging.error("Control plane not connected")
548 continue
549 signal_quality = get_signal_quality(modem, dongle_stats)
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700550 if cp_state is State.error:
551 logging.error("Modem is in error state.")
Don Newtonbd91ae22021-05-11 14:58:18 -0700552 reset_usb()
Hyunsun Moonf4242372020-10-04 23:32:38 -0700553 sys.exit(1)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700554 if cp_state is State.disconnected:
555 # Failed to attach, don't need to run other tests
Shad Ansarib234ff82022-02-17 22:14:35 -0800556 logging.error("Check control plane - fail")
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800557 report_status(signal_quality, dongle_stats)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700558 continue
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700559
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800560 up_state, dry_run_latency = get_user_plane_state(modem)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700561 if up_state is State.disconnected:
Shad Ansarib234ff82022-02-17 22:14:35 -0800562 logging.error("Check user plane - fail")
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700563 # Basic user plane test failed, don't need to run the rest of tests
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800564 report_status(signal_quality, dongle_stats, cp_state)
Hyunsun Moon5cd1eec2021-04-02 22:33:42 -0700565 continue
566
Jeremy Ronquillo56d23b12021-12-02 14:57:42 -0800567 speedtest_ping, speedtest_status = get_ping_test(modem, dry_run_latency)
Jeremy Ronquillo115c5e32021-09-30 11:15:56 -0700568 if speedtest_status:
569 speedtest_iperf = get_iperf_test(modem)
570 else:
Shad Ansarib234ff82022-02-17 22:14:35 -0800571 logging.error("Check iperf check - fail")
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800572 report_status(signal_quality, dongle_stats, cp_state, up_state, speedtest_ping)
Jeremy Ronquillo115c5e32021-09-30 11:15:56 -0700573 continue
Jeremy Ronquillo677c8832021-04-06 13:53:36 -0700574
Jeremy Ronquilloc45955a2021-11-09 12:04:57 -0800575 report_status(signal_quality, dongle_stats, cp_state, up_state, speedtest_ping, speedtest_iperf)
Hyunsun Moon53097ea2020-09-04 17:20:29 -0700576
577 modem.close()
578
579
580if __name__ == "__main__":
581 main()