| # osmo_gsm_tester: specifics for running an AndroidUE modem |
| # |
| # Copyright (C) 2020 by Software Radio Systems Limited |
| # |
| # Author: Nils Fürste <nils.fuerste@softwareradiosystems.com> |
| # Author: Bedran Karakoc <bedran.karakoc@softwareradiosystems.com> |
| # |
| # This program is free software: you can redistribute it and/or modify |
| # it under the terms of the GNU General Public License as |
| # published by the Free Software Foundation, either version 3 of the |
| # License, or (at your option) any later version. |
| # |
| # This program is distributed in the hope that it will be useful, |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| # GNU General Public License for more details. |
| # |
| # You should have received a copy of the GNU General Public License |
| # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| |
| import pprint |
| |
| from ..core import log, util, config, remote, schema, process |
| from .run_node import RunNode |
| from .ms import MS |
| from .srslte_common import srslte_common |
| from ..core.event_loop import MainLoop |
| from .ms_srs import srsUEMetrics |
| from .android_bitrate_monitor import BitRateMonitor |
| from . import qc_diag |
| from .android_apn import AndroidApn |
| from .android_host import AndroidHost |
| |
| |
| def on_register_schemas(): |
| resource_schema = { |
| 'additional_args[]': schema.STR, |
| 'enable_pcap': schema.BOOL_STR, |
| } |
| for key, val in RunNode.schema().items(): |
| resource_schema['run_node.%s' % key] = val |
| for key, val in AndroidApn.schema().items(): |
| resource_schema['apn.%s' % key] = val |
| schema.register_resource_schema('modem', resource_schema) |
| |
| config_schema = { |
| 'enable_pcap': schema.BOOL_STR, |
| 'log_all_level': schema.STR, |
| } |
| schema.register_config_schema('modem', config_schema) |
| |
| |
| class AndroidUE(MS, AndroidHost, srslte_common): |
| |
| REMOTEDIR = '/osmo-gsm-tester-androidue' |
| METRICSFILE = 'android_ue_metrics.csv' |
| PCAPFILE = 'android_ue.pcap' |
| |
| ############## |
| # PROTECTED |
| ############## |
| def __init__(self, testenv, conf): |
| self._run_node = RunNode.from_conf(conf.get('run_node', {})) |
| self.apn_worker = AndroidApn.from_conf(conf.get('apn', {})) if conf.get('apn', {}) != {} else None |
| self.qc_diag_mon = qc_diag.QcDiag(testenv, conf) |
| super().__init__('androidue_%s' % self.addr(), testenv, conf) |
| srslte_common.__init__(self) |
| self.rem_host = None |
| self.run_dir = None |
| self.remote_run_dir = None |
| self.emm_connected = False |
| self.rrc_connected = False |
| self.conn_reset_intvl = 20 # sec |
| self.connect_timeout = 300 # sec |
| self.enable_pcap = None |
| self.remote_pcap_file = None |
| self.pcap_file = None |
| self.data_interface = None |
| self.remote_metrics_file = None |
| self.metrics_file = None |
| self.brate_mon = None |
| self.num_carriers = 1 |
| |
| def configure(self): |
| values = dict(ue=config.get_defaults('androidue')) |
| config.overlay(values, dict(ue=self.testenv.suite().config().get('modem', {}))) |
| config.overlay(values, dict(ue=self._conf)) |
| self.dbg('AndroidUE CONFIG:\n' + pprint.pformat(values)) |
| |
| if 'qc_diag' in self.features(): |
| self.enable_pcap = util.str2bool(values['ue'].get('enable_pcap', 'false')) |
| |
| self.metrics_file = self.run_dir.child(AndroidUE.METRICSFILE) |
| self.pcap_file = self.run_dir.child(AndroidUE.PCAPFILE) |
| if not self._run_node.is_local(): |
| self.rem_host = remote.RemoteHost(self.run_dir, self._run_node.ssh_user(), self._run_node.ssh_addr(), None, |
| self._run_node.ssh_port()) |
| self.remote_run_dir = util.Dir(AndroidUE.REMOTEDIR) |
| self.remote_metrics_file = self.remote_run_dir.child(AndroidUE.METRICSFILE) |
| self.remote_pcap_file = self.remote_run_dir.child(AndroidUE.PCAPFILE) |
| |
| if self.apn_worker: |
| self.apn_worker.configure(self.testenv, self.run_dir, self._run_node, self.rem_host) |
| # some Android UEs only accept new APNs when airplane mode is turned off |
| self.set_airplane_mode(False) |
| self.apn_worker.set_apn() |
| MainLoop.sleep(1) |
| self.set_airplane_mode(True) |
| |
| # clear old diag files |
| self._clear_diag_logs() |
| |
| def _clear_diag_logs(self): |
| popen_args_clear_diag_logs = \ |
| ['su', '-c', '\"rm -r /data/local/tmp/diag_logs/ || true\"'] |
| clear_diag_logs_proc = self.run_androidue_cmd('clear-diag-logs', popen_args_clear_diag_logs) |
| clear_diag_logs_proc.launch_sync() |
| |
| def verify_metric(self, value, operation='avg', metric='dl_brate', criterion='gt', window=1): |
| self.brate_mon.save_metrics(self.metrics_file) |
| metrics = srsUEMetrics(self.metrics_file) |
| return metrics.verify(value, operation, metric, criterion, window) |
| |
| def set_airplane_mode(self, apm_state): |
| self.log("Setting airplane mode: " + str(apm_state)) |
| popen_args = ['settings', 'put', 'global', 'airplane_mode_on', str(int(apm_state)), ';', |
| 'wait $!;', |
| 'su', '-c', '\"am broadcast -a android.intent.action.AIRPLANE_MODE\";'] |
| proc = self.run_androidue_cmd('set-airplane-mode', popen_args) |
| proc.launch_sync() |
| |
| def get_assigned_addr(self, ipv6=False): |
| ip_prefix = '172.16.0' |
| proc = self.run_androidue_cmd('get-assigned-addr', ['ip', 'addr', 'show']) |
| proc.launch_sync() |
| out_l = proc.get_stdout().split('\n') |
| ip = '' |
| for line in out_l: |
| if ip_prefix in line: |
| ip = line.split(' ')[5][:-3] |
| self.data_interface = line.split(' ')[-1] |
| return ip |
| |
| ######################## |
| # PUBLIC - INTERNAL API |
| ######################## |
| def cleanup(self): |
| self.set_airplane_mode(True) |
| |
| def addr(self): |
| return self._run_node.run_addr() |
| |
| def run_node(self): |
| return self._run_node |
| |
| def features(self): |
| return self._conf.get('features', []) |
| |
| ################### |
| # PUBLIC (test API included) |
| ################### |
| def run_netns_wait(self, name, popen_args): |
| # This function guarantees the compatibility with the current ping test. Please |
| # note that this function cannot execute commands on the machine the Android UE |
| # is attached to. |
| proc = self.run_androidue_cmd(name, popen_args) |
| proc.launch_sync() |
| return proc |
| |
| def connect(self, enb): |
| self.log('Starting AndroidUE') |
| self.run_dir = util.Dir(self.testenv.test().get_run_dir().new_dir(self.name())) |
| self.configure() |
| CONN_CHK = 'osmo-gsm-tester_androidue_conn_chk.sh' |
| |
| if 'qc_diag' in self.features(): |
| self.qc_diag_mon.start() |
| |
| if self._run_node.is_local(): |
| popen_args_emm_conn_chk = [CONN_CHK, self._run_node.adb_serial_id(), '0', '0'] |
| else: |
| popen_args_emm_conn_chk = [CONN_CHK, '0', self.rem_host.host(), self.rem_host.get_remote_port()] |
| |
| # make sure osmo-gsm-tester_androidue_conn_chk.sh is available on the OGT master unit |
| name = 'emm-conn-chk' |
| run_dir = self.run_dir.new_dir(name) |
| emm_conn_chk_proc = process.Process(name, run_dir, popen_args_emm_conn_chk) |
| self.testenv.remember_to_stop(emm_conn_chk_proc) |
| emm_conn_chk_proc.launch() |
| |
| # check connection status |
| timer = self.connect_timeout |
| while timer > 0: |
| if timer % self.conn_reset_intvl == 0: |
| self.set_airplane_mode(True) |
| MainLoop.sleep(1) |
| timer -= 1 |
| self.set_airplane_mode(False) |
| |
| if 'LTE' in emm_conn_chk_proc.get_stdout(): |
| if not(self.get_assigned_addr() is ''): |
| self.emm_connected = True |
| self.rrc_connected = True |
| self.testenv.stop_process(emm_conn_chk_proc) |
| break |
| |
| MainLoop.sleep(2) |
| timer -= 2 |
| |
| if timer <= 0: |
| raise log.Error('Connection timer of Android UE %s expired' % self._run_node.adb_serial_id()) |
| |
| self.brate_mon = BitRateMonitor(self.testenv, self.run_dir, self._run_node, self.rem_host, self.data_interface) |
| self.brate_mon.start() |
| |
| def is_rrc_connected(self): |
| if not ('qc_diag' in self.features()): |
| raise log.Error('Monitoring RRC states not supported (missing qc_diag feature?)') |
| |
| # if not self.qc_diag_mon.running(): |
| # raise log.Error('Diag monitoring crashed or was not started') |
| |
| rrc_state = self.qc_diag_mon.get_rrc_state() |
| if 'RRC_IDLE_CAMPED' in rrc_state: |
| self.rrc_connected = False |
| elif 'RRC_CONNECTED' in rrc_state: |
| self.rrc_connected = True |
| return self.rrc_connected |
| |
| def is_registered(self, mcc_mnc=None): |
| if mcc_mnc: |
| raise log.Error('An AndroidUE cannot register to any predefined MCC/MNC') |
| return self.emm_connected |
| |
| def get_counter(self, counter_name): |
| if counter_name == 'prach_sent': |
| # not implemented so far, return 2 to pass tests |
| return 2 |
| elif counter_name == 'paging_received': |
| return self.qc_diag_mon.get_paging_counter() |
| else: |
| raise log.Error('Counter %s not implemented' % counter_name) |
| |
| def netns(self): |
| return None |