def _start_test(self): logger.info("Starting ndt5custom test...") # Check that a configuration file has been specified if "config" not in self._config: raise RunnerError( 'ndt5custom', 'No configuration file specified for the custom runner, \ skipping.') # Check that the ndt5-client executable is available. if shutil.which('ndt5-client') is None: raise RunnerError( 'ndt5custom', "Executable ndt5-client does not exist, please install ndt5-client-go.", ) custom_config = {} try: with open(self._config['config']) as config_file: custom_config = json.load(config_file) except IOError as err: raise RunnerError( 'ndt5custom', 'Cannot open the custom configuration file: ' + str(err)) # Get all the servers to run measurements against from the config, # applying the corresponding selection algorithm. servers = set() for group in custom_config.get('serverGroups', []): # the default selection algorithm is 'random' selection = group.get('selection', 'random') if selection not in self._server_selection: raise RunnerError( 'ndt5custom', 'Invalid server selection algorithm specified:' + selection) servers = servers | self._server_selection[selection].get_servers( group.get('servers', [])) # Run a measurement against each of the selected servers. results = [] for server in servers: cmdargs = [ "ndt5-client", "-protocol=ndt5", "-format=json", "-quiet", "-server=" + server ] logger.info("Running ndt5custom measurement (server): " + server) results.append(self._run_client(cmdargs)) return results
def _start_test(self): logger.info("Starting Speedtest multi-stream test...") if shutil.which("speedtest-cli") is not None: starttime = datetime.datetime.utcnow() output = subprocess.run(["speedtest-cli", "--json"], text=True, capture_output=True) endtime = datetime.datetime.utcnow() murakami_output = { 'TestName': "speedtest-cli-multi-stream", 'TestStartTime': starttime.strftime('%Y-%m-%dT%H:%M:%S.%f'), 'TestEndTime': endtime.strftime('%Y-%m-%dT%H:%M:%S.%f'), 'MurakamiLocation': self._location, 'MurakamiConnectionType': self._connection_type, 'MurakamiNetworkType': self._network_type, 'MurakamiDeviceID': self._device_id, } murakami_output.update(self._parse_summary(output)) return json.dumps(murakami_output) else: raise RunnerError( "speedtest", "Executable does not exist, please install speedtest-cli.")
def _start_test(): logger.info("Starting DASH test...") if shutil.which("dash-client") is not None: output = subprocess.run(["dash-client"], check=True, text=True, capture_output=True) logger.info("Dash test complete.") # TODO: write parser. Only print the last line for now. return output.stdout.splitlines()[-1] else: raise RunnerError( "dash", "Executable dash-client does not exist, please install DASH.")
def _run_client(self, args): starttime = datetime.datetime.utcnow() output = subprocess.run( args, text=True, capture_output=True, ) endtime = datetime.datetime.utcnow() murakami_output = { 'TestName': "ndt5", 'TestStartTime': starttime.strftime('%Y-%m-%dT%H:%M:%S.%f'), 'TestEndTime': endtime.strftime('%Y-%m-%dT%H:%M:%S.%f'), 'MurakamiLocation': self._location, 'MurakamiConnectionType': self._connection_type, 'MurakamiNetworkType': self._network_type, 'MurakamiDeviceID': self._device_id, } if output.returncode == 0: # Parse ndt5 summary. summary = {} try: summary = json.loads(output.stdout) except json.JSONDecodeError: raise RunnerError( 'ndt5-client', 'ndt5-client did not return a valid JSON summary.') logger.info("ndt5 test completed successfully.") # Parse ndt7-client-go's summary JSON and generate Murakami's # output format. download = summary.get('Download') upload = summary.get('Upload') retrans = summary.get('DownloadRetrans') min_rtt = summary.get('MinRTT') murakami_output['ServerName'] = summary.get('ServerFQDN') murakami_output['ServerIP'] = summary.get('ServerIP') murakami_output['ClientIP'] = summary.get('ClientIP') murakami_output['DownloadUUID'] = summary.get('DownloadUUID') if download is not None: murakami_output['DownloadValue'] = download.get('Value') murakami_output['DownloadUnit'] = download.get('Unit') if upload is not None: murakami_output['UploadValue'] = upload.get('Value') murakami_output['UploadUnit'] = upload.get('Unit') if retrans is not None: murakami_output['DownloadRetransValue'] = retrans.get('Value') murakami_output['DownloadRetransUnit'] = retrans.get('Unit') if min_rtt is not None: murakami_output['MinRTTValue'] = min_rtt.get('Value') murakami_output['MinRTTUnit'] = min_rtt.get('Unit') else: logger.warn("ndt5 test completed with errors.") # Consider any output as 'TestError'. murakami_output['TestError'] = output.stdout # All the other fields are set to None (which will become null # in the JSON.) murakami_output['ServerName'] = None murakami_output['ServerIP'] = None murakami_output['ClientIP'] = None murakami_output['DownloadUUID'] = None murakami_output['DownloadValue'] = None murakami_output['DownloadUnit'] = None murakami_output['UploadValue'] = None murakami_output['UploadUnit'] = None murakami_output['DownloadRetransValue'] = None murakami_output['DownloadRetransUnit'] = None murakami_output['MinRTTValue'] = None murakami_output['MinRTTUnit'] = None return json.dumps(murakami_output)
def _start_test(self): logger.info("Starting ndt7 test...") # Check that a configuration file has been specified if "config" not in self._config: raise RunnerError( 'ndt7custom', 'No configuration file specified for the custom runner, \ skipping.') # Check that the ndt7-client executable is available. if shutil.which('ndt7-client') is None: raise RunnerError( 'ndt7custom', "Executable ndt7-client does not exist, please install ndt7-client-go.", ) custom_config = {} try: with open(self._config['config']) as config_file: custom_config = json.load(config_file) except IOError as err: raise RunnerError( 'ndt7custom', 'Cannot open the custom configuration file: ' + str(err)) # Get all the servers to run measurements against from the config, # applying the corresponding selection algorithm. servers = set() for group in custom_config.get('serverGroups', []): # the default selection algorithm is 'random' selection = group.get('selection', 'random') if selection not in self._server_selection: raise RunnerError( 'ndt7custom', 'Invalid server selection algorithm specified:' + selection) servers = servers | self._server_selection[selection].get_servers( group.get('servers', [])) # Run a measurement against each of the selected servers. results = [] for server in servers: cmdargs = [ "ndt7-client", "-format=json", "-quiet", "-scheme=ws", "-server=" + server ] insecure = self._config.get('insecure', True) if insecure: cmdargs.append('-no-verify') logger.info("Running ndt7custom measurement (server): " + server) results.append(self._run_client(cmdargs)) # Check for additional countries/regions specified and run the client # using the locate service for each of them. countries = custom_config.get('countries', []) for country in countries: cmdargs = [ "ndt7-client", "-format=json", "-quiet", "-scheme=ws", "-locate.url=https://locate.measurementlab.net/v2/nearest/?country=" + country ] logger.info("Running ndt7custom measurement (country): " + country) results.append(self._run_client(cmdargs)) regions = custom_config.get('regions', []) for region in regions: cmdargs = [ "ndt7-client", "-format=json", "-quiet", "-scheme=ws", "-locate.url=https://locate.measurementlab.net/v2/nearest/?region=" + region ] logger.info("Running ndt7custom measurement (region): " + region) results.append(self._run_client(cmdargs)) return results
def _start_test(self): logger.info("Starting ooniprobe test...") if shutil.which("ooniprobe") is not None: output = None # Empty the ooniprobe database. # We do that to avoid wasting disk space with ooniprobe's database (which we don't use). logger.info("Emptying ooniprobe database...") cmdargs = [ "ooniprobe", "reset", "--force", ] try: output = subprocess.run( cmdargs, check=True, ) except subprocess.CalledProcessError as e: raise RunnerError( "ooniprobe reset --force returned a non-zero exit code.") # Programmatically perform ooniprobe's onboarding process. logger.info("Programmatically perform the onboarding process...") cmdargs = [ "ooniprobe", "onboard", "--yes", ] try: output = subprocess.run(cmdargs, check=True, stderr=subprocess.PIPE) except subprocess.CalledProcessError as e: raise RunnerError( "ooniprobe onboard --yes returned a non-zero exit code. (err: " + output.stderr + ")") # Run "ooniprobe run unattended" to start the tests. logger.info("Running ooniprobe tests...") starttime = datetime.datetime.utcnow() cmdargs = [ "ooniprobe", "--software-name=murakami-ooniprobe", "run", "unattended" ] try: output = subprocess.run(cmdargs, check=True, stderr=subprocess.PIPE) except subprocess.CalledProcessError as e: raise RunnerError( "ooniprobe run unattended returned a non-zero exit code. (err: " + output.stderr + ")") endtime = datetime.datetime.utcnow() # If the previous commands succeeded, then we can parse the output # of "ooniprobe list --batch", which only contains the results of # the current run (since the database was emptied beforehand). logging.info("Reading ooniprobe results...") cmdargs = ["ooniprobe", "list", "--batch"] try: output = subprocess.run(cmdargs, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except subprocess.CalledProcessError as e: raise RunnerError( "ooniprobe list --batch returned a non-zero exit code. (err: " + output.stderr + ")") # Parse the output of "ooniprobe list --batch". The output is in # JSONL format, and we need to only parse lines with type result_item. results = [] try: results = [ json.loads(line) for line in output.stdout.splitlines() ] except json.decoder.JSONDecodeError as e: raise RunnerError( "ooniprobe list --batch returned invalid JSON. (err: " + str(e) + ")") test_results = [] for js in results: # If the JSON type is result_item, then it is a nettest result. # Get the corresponding nettest summary data with: # "ooniprobe list <id> --batch". if js["fields"].get( "type") == "result_item" and "id" in js["fields"]: # Get the test's name. test_name = js["fields"]["name"] cmdargs = [ "ooniprobe", "list", str(js["fields"]["id"]), "--batch" ] try: output = subprocess.run(cmdargs, check=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE) except subprocess.CalledProcessError as e: raise RunnerError( "ooni probe list <id> --batch returned a non-zero exit code. (err: " + output.stderr + ")") try: # Wrap the test results in a JSON that's compatible with Murakami. murakami_output = { 'TestName': "ooniprobe-" + test_name, 'TestStartTime': starttime.strftime('%Y-%m-%dT%H:%M:%S.%f'), 'TestEndTime': endtime.strftime('%Y-%m-%dT%H:%M:%S.%f'), 'MurakamiLocation': self._location, 'MurakamiConnectionType': self._connection_type, 'MurakamiNetworkType': self._network_type, 'MurakamiDeviceID': self._device_id, } test_measurements = [] for line in output.stdout.splitlines(): js = json.loads(line) if js["fields"].get("type") == "measurement_item": test_measurements.append(js) murakami_output["TestResults"] = test_measurements test_results.append(json.dumps(murakami_output)) except json.decoder.JSONDecodeError as e: raise RunnerError( "ooni probe list <id> --batch returned invalid JSON. (err: " + str(e) + ")") return test_results
def _start_test(self): raise RunnerError(self.title, "No _start_test() function implemented.")