<p>pespin <strong>submitted</strong> this change.</p><p><a href="https://gerrit.osmocom.org/c/osmo-gsm-tester/+/21302">View Change</a></p><div style="white-space:pre-wrap">Approvals:
  Jenkins Builder: Verified
  pespin: Looks good to me, approved

</div><pre style="font-family: monospace,monospace; white-space: pre-wrap;">Introduce Android UEs as new modems<br><br>To expand the test capacities we would like to introduce<br>Android UEs as new modems. Currently the following tests<br>are supported:<br>- Ping<br>- iPerf3 DL/UL<br>- RRC Mobile MT Ping<br><br>In the following is a small description.<br><br>Prerequisites:<br>    - Android UE<br>        - Rooted (Ping, iPerf, RRC Idle MT Ping)<br>        - Qualcomm baseband with working diag_mdlog (RRC Idle MT Ping)<br>        - iPerf3<br>        - Dropbear<br>    - OGT Slave Unit<br>        - Android SDK Platform-Tools<br>   (https://developer.android.com/studio/releases/platform-tools#downloads)<br>        - Pycrate (https://github.com/P1sec/pycrate)<br>        - SCAT<br>            clone https://github.com/bedrankara/scat/ & install dependencies<br>            checkout branch ogt<br>            symlink scat (ln -s ~/scat/scat.py /usr/local/bin/scat)<br><br>Infrastructure explaination:<br>The Android UEs are connected to the OGT Units via USB. We<br>activate tethering and set up a SSH server (with Dropbear).<br>We chose tethering over WiFi to have a more stable route<br>for the ssh connection. We forward incoming connections to<br>the OGT unit hosting the Android UE(s) on specific ports<br>to the UEs via iptables. This enables OGT to issue commands<br>directly to the UEs. In case of local execution we use ADB<br>to issue commands to the AndroidUE. The set up was tested<br>with 5 Android UEs connected in parallel but it should be<br>scalable to the number of available IPs in the respective<br>subnet. Furthermore, we need to cross compile Dropbear<br>and iPerf3 to use them on the UEs. These tools have to be<br>added to the $PATH variable of the UEs.<br><br>Examplary set up:<br>In this example we have two separate OGT units (master<br>and slave) and two Android UEs that are connected to the<br>slave unit. An illustration may be found here: https://ibb.co/6BXSP2C<br><br>On UE 1:<br>ip address add 192.168.42.130/24 dev rndis0<br>ip route add 192.168.42.0/24 dev rndis0 table local_network<br>dropbearmulti dropbear -F -E -p 130 -R -T /data/local/tmp/authorized_keys  -U 0 -G 0 -N root -A<br><br>On UE 2:<br>ip address add 192.168.42.131/24 dev rndis0<br>ip route add 192.168.42.0/24 dev rndis0 table local_network<br>dropbearmulti dropbear -F -E -p 131 -R -T /data/local/tmp/authorized_keys  -U 0 -G 0 -N root -A<br><br>On OGT slave unit:<br>sudo ip link add name ogt type bridge<br>sudo ip l set eth0 master ogt<br>sudo ip l set enp0s20f0u1 master ogt<br>sudo ip l set enp0s20f0u2 master ogt<br>sudo ip a a 192.168.42.1/24 dev ogt<br>sudo ip link set ogt up<br><br>Now we have to manually connect to every UE from OGT Master<br>to set up SSH keys and verify that the setup works.<br>Therefore, use:<br>ssh -p [UE-PORT] root@[OGT SLAVE UNIT's IP]<br><br>Finally, to finish the setup procedure create the<br>remote_run_dir for Android UEs on the slave unit like<br>following:<br>mkdir /osmo-gsm-tester-androidue<br>chown jenkins /osmo-gsm-tester-androidue<br><br>Example for modem in resource.conf:<br>- label: mi5g<br>  type: androidue<br>  imsi: '901700000034757'<br>  ki: '85E9E9A947B9ACBB966ED7113C7E1B8A'<br>  opc: '3E1C73A29B9C293DC5A763E42C061F15'<br>  apn:<br>    apn: 'srsapn'<br>    mcc: '901'<br>    mnc: '70'<br>    select: 'True'<br>    auth_algo: 'milenage'<br>  features: ['4g', 'dl_qam256', 'qc_diag']<br>  run_node:<br>    run_type: ssh<br>    run_addr: 100.113.1.170<br>    ssh_user: jenkins<br>    ssh_addr: 100.113.1.170<br>    ue_ssh_port: 130<br>    adb_serial_id: '8d3c79a7'<br>  scat_parser:<br>    run_type: local<br>    run_addr: 127.0.0.1<br>    adb_serial_id: '8d3c79a7'<br><br>Example for default-suites.conf:<br>- 4g:ms-label@mi5g+srsenb-rftype@uhd+mod-enb-nprb@25+mod-enb-txmode@1<br><br>Change-Id: I79a5d803e869a868d4dac5e0d4c2feb38038dc5c<br>---<br>M src/osmo_gsm_tester/core/process.py<br>M src/osmo_gsm_tester/core/schema.py<br>A src/osmo_gsm_tester/obj/android_apn.py<br>A src/osmo_gsm_tester/obj/android_bitrate_monitor.py<br>A src/osmo_gsm_tester/obj/android_host.py<br>M src/osmo_gsm_tester/obj/iperf3.py<br>M src/osmo_gsm_tester/obj/ms.py<br>A src/osmo_gsm_tester/obj/ms_android.py<br>A src/osmo_gsm_tester/obj/qc_diag.py<br>M src/osmo_gsm_tester/obj/run_node.py<br>A sysmocom/scenarios/ms-label.conf<br>A utils/bin/osmo-gsm-tester_androidue_conn_chk.sh<br>A utils/bin/osmo-gsm-tester_androidue_diag_parser.sh<br>13 files changed, 963 insertions(+), 5 deletions(-)<br><br></pre><pre style="font-family: monospace,monospace; white-space: pre-wrap;"><span>diff --git a/src/osmo_gsm_tester/core/process.py b/src/osmo_gsm_tester/core/process.py</span><br><span>index 09dce55..bac258a 100644</span><br><span>--- a/src/osmo_gsm_tester/core/process.py</span><br><span>+++ b/src/osmo_gsm_tester/core/process.py</span><br><span>@@ -493,6 +493,14 @@</span><br><span>         args = ['sudo', self.NETNS_EXEC_BIN, self.netns] + list(popen_args)</span><br><span>         super().__init__(name, run_dir, remote_user, remote_host, remote_cwd, args, **popen_kwargs)</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+class AdbProcess(Process):</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, name, run_dir, adb_serial, popen_args, **popen_kwargs):</span><br><span style="color: hsl(120, 100%, 40%);">+        super().__init__(name, run_dir, popen_args, **popen_kwargs)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.adb_serial = adb_serial</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        self.popen_args = ['adb', '-s', self.adb_serial, 'exec-out', 'su', '-c'] + list(popen_args)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.dbg(self.popen_args, dir=self.run_dir, conf=self.popen_kwargs)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> def run_local_sync(run_dir, name, popen_args):</span><br><span>     run_dir =run_dir.new_dir(name)</span><br><span>     proc = Process(name, run_dir, popen_args)</span><br><span>diff --git a/src/osmo_gsm_tester/core/schema.py b/src/osmo_gsm_tester/core/schema.py</span><br><span>index 89c4494..d4a460d 100644</span><br><span>--- a/src/osmo_gsm_tester/core/schema.py</span><br><span>+++ b/src/osmo_gsm_tester/core/schema.py</span><br><span>@@ -111,7 +111,7 @@</span><br><span>     raise ValueError('Unknown Cipher value: %r' % val)</span><br><span> </span><br><span> def modem_feature(val):</span><br><span style="color: hsl(0, 100%, 40%);">-    if val in ('sms', 'gprs', 'voice', 'ussd', 'sim', '2g', '3g', '4g', 'dl_qam256', 'ul_qam64'):</span><br><span style="color: hsl(120, 100%, 40%);">+    if val in ('sms', 'gprs', 'voice', 'ussd', 'sim', '2g', '3g', '4g', 'dl_qam256', 'ul_qam64', 'qc_diag'):</span><br><span>         return True</span><br><span>     raise ValueError('Unknown Modem Feature: %r' % val)</span><br><span> </span><br><span>diff --git a/src/osmo_gsm_tester/obj/android_apn.py b/src/osmo_gsm_tester/obj/android_apn.py</span><br><span>new file mode 100644</span><br><span>index 0000000..3c8bfcf</span><br><span>--- /dev/null</span><br><span>+++ b/src/osmo_gsm_tester/obj/android_apn.py</span><br><span>@@ -0,0 +1,210 @@</span><br><span style="color: hsl(120, 100%, 40%);">+# osmo_gsm_tester: specifics for setting an APN on an AndroidUE modem</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# Copyright (C) 2020 by Software Radio Systems Limited</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# Author: Nils Fürste <nils.fuerste@softwareradiosystems.com></span><br><span style="color: hsl(120, 100%, 40%);">+# Author: Bedran Karakoc <bedran.karakoc@softwareradiosystems.com></span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# This program is free software: you can redistribute it and/or modify</span><br><span style="color: hsl(120, 100%, 40%);">+# it under the terms of the GNU General Public License as</span><br><span style="color: hsl(120, 100%, 40%);">+# published by the Free Software Foundation, either version 3 of the</span><br><span style="color: hsl(120, 100%, 40%);">+# License, or (at your option) any later version.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# This program is distributed in the hope that it will be useful,</span><br><span style="color: hsl(120, 100%, 40%);">+# but WITHOUT ANY WARRANTY; without even the implied warranty of</span><br><span style="color: hsl(120, 100%, 40%);">+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the</span><br><span style="color: hsl(120, 100%, 40%);">+# GNU General Public License for more details.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# You should have received a copy of the GNU General Public License</span><br><span style="color: hsl(120, 100%, 40%);">+# along with this program.  If not, see <http://www.gnu.org/licenses/>.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+import re</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import log</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import schema</span><br><span style="color: hsl(120, 100%, 40%);">+from .android_host import AndroidHost</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class AndroidApn(AndroidHost):</span><br><span style="color: hsl(120, 100%, 40%);">+##############</span><br><span style="color: hsl(120, 100%, 40%);">+# PROTECTED</span><br><span style="color: hsl(120, 100%, 40%);">+##############</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, apn, mcc, mnc, select=None):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.logger_name = 'apn_worker_'</span><br><span style="color: hsl(120, 100%, 40%);">+        super().__init__(self.logger_name)</span><br><span style="color: hsl(120, 100%, 40%);">+        self._apn_name = apn</span><br><span style="color: hsl(120, 100%, 40%);">+        self._apn = apn</span><br><span style="color: hsl(120, 100%, 40%);">+        self._mcc = mcc</span><br><span style="color: hsl(120, 100%, 40%);">+        self._mnc = mnc</span><br><span style="color: hsl(120, 100%, 40%);">+        self._select = select</span><br><span style="color: hsl(120, 100%, 40%);">+        if not self._apn:</span><br><span style="color: hsl(120, 100%, 40%);">+            raise log.Error('APN name not set')</span><br><span style="color: hsl(120, 100%, 40%);">+        if not self._mcc:</span><br><span style="color: hsl(120, 100%, 40%);">+            raise log.Error('MCC not set')</span><br><span style="color: hsl(120, 100%, 40%);">+        if not self._mnc:</span><br><span style="color: hsl(120, 100%, 40%);">+            raise log.Error('MNC not set')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # optional parameters, set with set_additional_params()</span><br><span style="color: hsl(120, 100%, 40%);">+        self.proxy = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.port = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.user = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.password = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.server = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.mmsc = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.mmsport = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.mmsproxy = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.auth = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.type = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.protocol = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.mvnoval = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.mvnotype = None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def get_carrier_id(self, carrier_name):</span><br><span style="color: hsl(120, 100%, 40%);">+        qry_carrier_cmd = "content query --uri \"content://telephony/carriers\""</span><br><span style="color: hsl(120, 100%, 40%);">+        proc = self.run_androidue_cmd('get-carrier-id', [qry_carrier_cmd])</span><br><span style="color: hsl(120, 100%, 40%);">+        proc.launch_sync()</span><br><span style="color: hsl(120, 100%, 40%);">+        available_carriers = proc.get_stdout().split('\n')</span><br><span style="color: hsl(120, 100%, 40%);">+        carr_id = -1</span><br><span style="color: hsl(120, 100%, 40%);">+        for carr in available_carriers:</span><br><span style="color: hsl(120, 100%, 40%);">+            if 'name=' + carrier_name in carr:  # found carrier</span><br><span style="color: hsl(120, 100%, 40%);">+                carr_id = re.findall(r'_id=(\S+),', carr)[0]</span><br><span style="color: hsl(120, 100%, 40%);">+                break</span><br><span style="color: hsl(120, 100%, 40%);">+        return carr_id</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def set_new_carrier(self, apn_parameter, carr_id):</span><br><span style="color: hsl(120, 100%, 40%);">+        # check if carrier was found, delete it if exists</span><br><span style="color: hsl(120, 100%, 40%);">+        if carr_id != -1:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.delete_apn(apn_parameter['carrier'])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        set_carrier_cmd = "content insert --uri content://telephony/carriers" \</span><br><span style="color: hsl(120, 100%, 40%);">+                          + " --bind name:s:\"" + apn_parameter["carrier"] + "\"" \</span><br><span style="color: hsl(120, 100%, 40%);">+                          + " --bind numeric:s:\"" + apn_parameter["mcc"] + apn_parameter["mnc"] + "\"" \</span><br><span style="color: hsl(120, 100%, 40%);">+                          + " --bind mcc:s:\"" + apn_parameter["mcc"] + "\"" \</span><br><span style="color: hsl(120, 100%, 40%);">+                          + " --bind mnc:s:\"" + apn_parameter["mnc"] + "\""\</span><br><span style="color: hsl(120, 100%, 40%);">+                          + " --bind apn:s:\"" + apn_parameter["apn"] + "\"" \</span><br><span style="color: hsl(120, 100%, 40%);">+                          + " --bind user:s:\"" + apn_parameter["user"] + "\"" \</span><br><span style="color: hsl(120, 100%, 40%);">+                          + " --bind password:s:\"" + apn_parameter["password"] + "\"" \</span><br><span style="color: hsl(120, 100%, 40%);">+                          + " --bind mmsc:s:\"" + apn_parameter["mmsc"] + "\"" \</span><br><span style="color: hsl(120, 100%, 40%);">+                          + " --bind mmsport:s:\"" + apn_parameter["mmsport"] + "\"" \</span><br><span style="color: hsl(120, 100%, 40%);">+                          + " --bind mmsproxy:s:\"" + apn_parameter["mmsproxy"] + "\"" \</span><br><span style="color: hsl(120, 100%, 40%);">+                          + " --bind authtype:s:\"" + apn_parameter["auth"] + "\"" \</span><br><span style="color: hsl(120, 100%, 40%);">+                          + " --bind type:s:\"" + apn_parameter["type"] + "\"" \</span><br><span style="color: hsl(120, 100%, 40%);">+                          + " --bind protocol:s:\"" + apn_parameter["protocol"] + "\"" \</span><br><span style="color: hsl(120, 100%, 40%);">+                          + " --bind mvno_type:s:\"" + apn_parameter["mvnotype"] + "\"" \</span><br><span style="color: hsl(120, 100%, 40%);">+                          + " --bind mvno_match_data:s:\"" + apn_parameter["mvnoval"] + "\""</span><br><span style="color: hsl(120, 100%, 40%);">+        proc = self.run_androidue_cmd("set-new-carrier", [set_carrier_cmd])</span><br><span style="color: hsl(120, 100%, 40%);">+        proc.launch_sync()</span><br><span style="color: hsl(120, 100%, 40%);">+        return self.get_carrier_id(apn_parameter['carrier'])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def set_preferred_apn(self, carr_id):</span><br><span style="color: hsl(120, 100%, 40%);">+        if carr_id != -1:</span><br><span style="color: hsl(120, 100%, 40%);">+            set_apn_cmd = "content insert --uri content://telephony/carriers/preferapn --bind apn_id:s:\"" + str(carr_id) + "\""</span><br><span style="color: hsl(120, 100%, 40%);">+            proc = self.run_androidue_cmd('set-preferred-apn', [set_apn_cmd])</span><br><span style="color: hsl(120, 100%, 40%);">+            proc.launch_sync()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def select_apn(self, carr_name):</span><br><span style="color: hsl(120, 100%, 40%);">+        carr_id = self.get_carrier_id(carr_name)</span><br><span style="color: hsl(120, 100%, 40%);">+        if carr_id == 0:</span><br><span style="color: hsl(120, 100%, 40%);">+            return False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # select carrier by ID</span><br><span style="color: hsl(120, 100%, 40%);">+        sel_apn_cmd = "content update --uri content://telephony/carriers/preferapn --bind apn_id:s:\"" + str(carr_id) + "\""</span><br><span style="color: hsl(120, 100%, 40%);">+        proc = self.run_androidue_cmd('select-apn', [sel_apn_cmd])</span><br><span style="color: hsl(120, 100%, 40%);">+        proc.launch_sync()</span><br><span style="color: hsl(120, 100%, 40%);">+        return True</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def delete_apn(self, carr_name):</span><br><span style="color: hsl(120, 100%, 40%);">+        set_apn_cmd = "content delete --uri content://telephony/carriers --where \'name=\"" + str(carr_name) + "\" \'"</span><br><span style="color: hsl(120, 100%, 40%);">+        proc = self.run_androidue_cmd('delete-apn', [set_apn_cmd])</span><br><span style="color: hsl(120, 100%, 40%);">+        proc.launch_sync()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+########################</span><br><span style="color: hsl(120, 100%, 40%);">+# PUBLIC - INTERNAL API</span><br><span style="color: hsl(120, 100%, 40%);">+########################</span><br><span style="color: hsl(120, 100%, 40%);">+    @classmethod</span><br><span style="color: hsl(120, 100%, 40%);">+    def from_conf(cls, conf):</span><br><span style="color: hsl(120, 100%, 40%);">+        return cls(conf.get('apn', None), conf.get('mcc', None),</span><br><span style="color: hsl(120, 100%, 40%);">+                   conf.get('mnc', None), conf.get('select', None))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    @classmethod</span><br><span style="color: hsl(120, 100%, 40%);">+    def schema(cls):</span><br><span style="color: hsl(120, 100%, 40%);">+        resource_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+            'apn': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+            'mcc': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+            'mnc': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+            'select': schema.BOOL_STR,</span><br><span style="color: hsl(120, 100%, 40%);">+            }</span><br><span style="color: hsl(120, 100%, 40%);">+        return resource_schema</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def configure(self, testenv, run_dir, run_node, rem_host):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.testenv = testenv</span><br><span style="color: hsl(120, 100%, 40%);">+        self.rem_host = rem_host</span><br><span style="color: hsl(120, 100%, 40%);">+        self._run_node = run_node</span><br><span style="color: hsl(120, 100%, 40%);">+        self.run_dir = run_dir</span><br><span style="color: hsl(120, 100%, 40%);">+        self.logger_name += self._run_node.run_addr()</span><br><span style="color: hsl(120, 100%, 40%);">+        self.set_name(self.logger_name)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def set_additional_params(self, proxy=None, port=None, user=None, password=None, server=None, auth=None, apn_type=None,</span><br><span style="color: hsl(120, 100%, 40%);">+                              mmsc=None, mmsport=None, mmsproxy=None,  protocol=None, mvnoval=None, mvnotype=None):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.proxy = proxy</span><br><span style="color: hsl(120, 100%, 40%);">+        self.port = port</span><br><span style="color: hsl(120, 100%, 40%);">+        self.user = user</span><br><span style="color: hsl(120, 100%, 40%);">+        self.password = password</span><br><span style="color: hsl(120, 100%, 40%);">+        self.server = server</span><br><span style="color: hsl(120, 100%, 40%);">+        self.auth = auth</span><br><span style="color: hsl(120, 100%, 40%);">+        self.type = apn_type</span><br><span style="color: hsl(120, 100%, 40%);">+        self.mmsc = mmsc</span><br><span style="color: hsl(120, 100%, 40%);">+        self.mmsport = mmsport</span><br><span style="color: hsl(120, 100%, 40%);">+        self.mmsproxy = mmsproxy</span><br><span style="color: hsl(120, 100%, 40%);">+        self.protocol = protocol</span><br><span style="color: hsl(120, 100%, 40%);">+        self.mvnoval = mvnoval</span><br><span style="color: hsl(120, 100%, 40%);">+        self.mvnotype = mvnotype</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def set_apn(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        apn_params = {</span><br><span style="color: hsl(120, 100%, 40%);">+            'carrier': self._apn_name,</span><br><span style="color: hsl(120, 100%, 40%);">+            'apn': self._apn,</span><br><span style="color: hsl(120, 100%, 40%);">+            'proxy': self.proxy or '',</span><br><span style="color: hsl(120, 100%, 40%);">+            'port': self.port or '',</span><br><span style="color: hsl(120, 100%, 40%);">+            'user': self.user or '',</span><br><span style="color: hsl(120, 100%, 40%);">+            'password': self.password or '',</span><br><span style="color: hsl(120, 100%, 40%);">+            'server': self.server or '',</span><br><span style="color: hsl(120, 100%, 40%);">+            'mmsc': self.mmsc or '',</span><br><span style="color: hsl(120, 100%, 40%);">+            'mmsport': self.mmsport or '',</span><br><span style="color: hsl(120, 100%, 40%);">+            'mmsproxy': self.mmsproxy or '',</span><br><span style="color: hsl(120, 100%, 40%);">+            'mcc': self._mcc,</span><br><span style="color: hsl(120, 100%, 40%);">+            'mnc': self._mnc,</span><br><span style="color: hsl(120, 100%, 40%);">+            'auth': self.auth or '-1',</span><br><span style="color: hsl(120, 100%, 40%);">+            'type': self.type or 'default',</span><br><span style="color: hsl(120, 100%, 40%);">+            'protocol': self.protocol or '',</span><br><span style="color: hsl(120, 100%, 40%);">+            'mvnotype': self.mvnotype or '',</span><br><span style="color: hsl(120, 100%, 40%);">+            'mvnoval': self.mvnoval or '',</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+        self.dbg('APN parameters: ' + str(apn_params))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # search for carrier in database</span><br><span style="color: hsl(120, 100%, 40%);">+        carrier_id = self.get_carrier_id(apn_params['carrier'])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # add/update carrier</span><br><span style="color: hsl(120, 100%, 40%);">+        carrier_id = self.set_new_carrier(apn_params, carrier_id)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # select as preferred APN</span><br><span style="color: hsl(120, 100%, 40%);">+        if self.select:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.set_preferred_apn(carrier_id)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def __str__(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        return self.name()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def apn(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        return self._apn</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def mcc(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        return self._mcc</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def mnc(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        return self._mnc</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def select(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        return self._select</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+# vim: expandtab tabstop=4 shiftwidth=4</span><br><span>diff --git a/src/osmo_gsm_tester/obj/android_bitrate_monitor.py b/src/osmo_gsm_tester/obj/android_bitrate_monitor.py</span><br><span>new file mode 100644</span><br><span>index 0000000..43d5524</span><br><span>--- /dev/null</span><br><span>+++ b/src/osmo_gsm_tester/obj/android_bitrate_monitor.py</span><br><span>@@ -0,0 +1,118 @@</span><br><span style="color: hsl(120, 100%, 40%);">+# osmo_gsm_tester: specifics for monitoring the bit rate of an AndroidUE modem</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# Copyright (C) 2020 by Software Radio Systems Limited</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# Author: Nils Fürste <nils.fuerste@softwareradiosystems.com></span><br><span style="color: hsl(120, 100%, 40%);">+# Author: Bedran Karakoc <bedran.karakoc@softwareradiosystems.com></span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# This program is free software: you can redistribute it and/or modify</span><br><span style="color: hsl(120, 100%, 40%);">+# it under the terms of the GNU General Public License as</span><br><span style="color: hsl(120, 100%, 40%);">+# published by the Free Software Foundation, either version 3 of the</span><br><span style="color: hsl(120, 100%, 40%);">+# License, or (at your option) any later version.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# This program is distributed in the hope that it will be useful,</span><br><span style="color: hsl(120, 100%, 40%);">+# but WITHOUT ANY WARRANTY; without even the implied warranty of</span><br><span style="color: hsl(120, 100%, 40%);">+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the</span><br><span style="color: hsl(120, 100%, 40%);">+# GNU General Public License for more details.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# You should have received a copy of the GNU General Public License</span><br><span style="color: hsl(120, 100%, 40%);">+# along with this program.  If not, see <http://www.gnu.org/licenses/>.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import log</span><br><span style="color: hsl(120, 100%, 40%);">+from .android_host import AndroidHost</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class BitRateMonitor(AndroidHost):</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+##############</span><br><span style="color: hsl(120, 100%, 40%);">+# PROTECTED</span><br><span style="color: hsl(120, 100%, 40%);">+##############</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, testenv, run_dir, run_node, rem_host, data_interface):</span><br><span style="color: hsl(120, 100%, 40%);">+        super().__init__('brate_monitor_%s' % run_node.run_addr())</span><br><span style="color: hsl(120, 100%, 40%);">+        self.testenv = testenv</span><br><span style="color: hsl(120, 100%, 40%);">+        self.rem_host = rem_host</span><br><span style="color: hsl(120, 100%, 40%);">+        self._run_node = run_node</span><br><span style="color: hsl(120, 100%, 40%);">+        self.run_dir = run_dir</span><br><span style="color: hsl(120, 100%, 40%);">+        self.data_interface = data_interface</span><br><span style="color: hsl(120, 100%, 40%);">+        self.rx_monitor_proc = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.tx_monitor_proc = None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+########################</span><br><span style="color: hsl(120, 100%, 40%);">+# PUBLIC - INTERNAL API</span><br><span style="color: hsl(120, 100%, 40%);">+########################</span><br><span style="color: hsl(120, 100%, 40%);">+    def start(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        # start bit rate monitoring on Android UE</span><br><span style="color: hsl(120, 100%, 40%);">+        popen_args_rx_mon = ['while true; do cat /sys/class/net/' + self.data_interface + '/statistics/rx_bytes;',</span><br><span style="color: hsl(120, 100%, 40%);">+                             'sleep 1;', 'done']</span><br><span style="color: hsl(120, 100%, 40%);">+        popen_args_tx_mon = ['while true; do cat /sys/class/net/' + self.data_interface + '/statistics/tx_bytes;',</span><br><span style="color: hsl(120, 100%, 40%);">+                             'sleep 1;', 'done']</span><br><span style="color: hsl(120, 100%, 40%);">+        self.rx_monitor_proc = self.run_androidue_cmd('start-rx-monitor', popen_args_rx_mon)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.testenv.remember_to_stop(self.rx_monitor_proc)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.rx_monitor_proc.launch()</span><br><span style="color: hsl(120, 100%, 40%);">+        self.tx_monitor_proc = self.run_androidue_cmd('start-tx-monitor', popen_args_tx_mon)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.testenv.remember_to_stop(self.tx_monitor_proc)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.tx_monitor_proc.launch()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def stop(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.testenv.stop_process(self.rx_monitor_proc)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.testenv.stop_process(self.tx_monitor_proc)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def save_metrics(self, metrics_file):</span><br><span style="color: hsl(120, 100%, 40%);">+        brate_rx_raw = self.rx_monitor_proc.get_stdout().split('\n')</span><br><span style="color: hsl(120, 100%, 40%);">+        brate_tx_raw = self.tx_monitor_proc.get_stdout().split('\n')</span><br><span style="color: hsl(120, 100%, 40%);">+        brate_rx_raw.remove('')</span><br><span style="color: hsl(120, 100%, 40%);">+        brate_tx_raw.remove('')</span><br><span style="color: hsl(120, 100%, 40%);">+        brate_rx_l = brate_rx_raw[1:]</span><br><span style="color: hsl(120, 100%, 40%);">+        brate_tx_l = brate_tx_raw[1:]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if len(brate_rx_l) < 2 or len(brate_tx_l) < 2:</span><br><span style="color: hsl(120, 100%, 40%);">+            raise log.Error('Insufficient data available to write metrics file')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # cut of elements if lists don't have the same length</span><br><span style="color: hsl(120, 100%, 40%);">+        if len(brate_rx_l) > len(brate_tx_l):</span><br><span style="color: hsl(120, 100%, 40%);">+            brate_rx_l = brate_rx_l[:len(brate_tx_l) - len(brate_rx_l)]</span><br><span style="color: hsl(120, 100%, 40%);">+        if len(brate_rx_l) < len(brate_tx_l):</span><br><span style="color: hsl(120, 100%, 40%);">+            brate_tx_l = brate_tx_l[:len(brate_rx_l) - len(brate_tx_l)]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # get start value</span><br><span style="color: hsl(120, 100%, 40%);">+        brate_rx_last = int(brate_rx_l[0])</span><br><span style="color: hsl(120, 100%, 40%);">+        brate_tx_last = int(brate_tx_l[0])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        with open(metrics_file, 'w') as ue_metrics_fh:</span><br><span style="color: hsl(120, 100%, 40%);">+            ue_metrics_fh.write('time;cc;earfcn;pci;rsrp;pl;cfo;pci_neigh;rsrp_neigh;cfo_neigh;'</span><br><span style="color: hsl(120, 100%, 40%);">+                                + 'dl_mcs;dl_snr;dl_turbo;dl_brate;dl_bler;'</span><br><span style="color: hsl(120, 100%, 40%);">+                                + 'ul_ta;ul_mcs;ul_buff;ul_brate;ul_bler;rf_o;rf_u;rf_l;'</span><br><span style="color: hsl(120, 100%, 40%);">+                                + 'is_attached\n')</span><br><span style="color: hsl(120, 100%, 40%);">+            for i in range(1, len(brate_rx_l)):</span><br><span style="color: hsl(120, 100%, 40%);">+                time = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                cc = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                earfcn = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                pci = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                rsrp = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                pl = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                cfo = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                pci_neigh = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                rsrp_neigh = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                cfo_neigh = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                dl_mcs = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                dl_snr = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                dl_turbo = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                dl_brate = str((int(brate_rx_l[i]) - brate_rx_last) * 8)</span><br><span style="color: hsl(120, 100%, 40%);">+                brate_rx_last = int(brate_rx_l[i])</span><br><span style="color: hsl(120, 100%, 40%);">+                dl_bler = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                ul_ta = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                ul_mcs = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                ul_buff = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                ul_brate = str((int(brate_tx_l[i]) - brate_tx_last) * 8)</span><br><span style="color: hsl(120, 100%, 40%);">+                brate_tx_last = int(brate_tx_l[i])</span><br><span style="color: hsl(120, 100%, 40%);">+                ul_bler = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                rf_o = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                rf_u = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                rf_l = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+                is_attached = '0'</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+                line = time + ';' + cc + ';' + earfcn + ';' + pci + ';' + rsrp + ';' + pl + ';' + cfo + ';' \</span><br><span style="color: hsl(120, 100%, 40%);">+                       + pci_neigh + ';' + rsrp_neigh + ';' + cfo_neigh + ';' + dl_mcs + ';' + dl_snr + ';' \</span><br><span style="color: hsl(120, 100%, 40%);">+                       + dl_turbo + ';' + dl_brate + ';' + dl_bler + ';' + ul_ta + ';' + ul_mcs + ';' + ul_buff + ';' \</span><br><span style="color: hsl(120, 100%, 40%);">+                       + ul_brate + ';' + ul_bler + ';' + rf_o + ';' + rf_u + ';' + rf_l + ';' + is_attached</span><br><span style="color: hsl(120, 100%, 40%);">+                ue_metrics_fh.write(line + '\n')</span><br><span>diff --git a/src/osmo_gsm_tester/obj/android_host.py b/src/osmo_gsm_tester/obj/android_host.py</span><br><span>new file mode 100644</span><br><span>index 0000000..38e8fb9</span><br><span>--- /dev/null</span><br><span>+++ b/src/osmo_gsm_tester/obj/android_host.py</span><br><span>@@ -0,0 +1,47 @@</span><br><span style="color: hsl(120, 100%, 40%);">+# osmo_gsm_tester: Base class for AndroidUE modems</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# Copyright (C) 2020 by Software Radio Systems Limited</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# Author: Nils Fürste <nils.fuerste@softwareradiosystems.com></span><br><span style="color: hsl(120, 100%, 40%);">+# Author: Bedran Karakoc <bedran.karakoc@softwareradiosystems.com></span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# This program is free software: you can redistribute it and/or modify</span><br><span style="color: hsl(120, 100%, 40%);">+# it under the terms of the GNU General Public License as</span><br><span style="color: hsl(120, 100%, 40%);">+# published by the Free Software Foundation, either version 3 of the</span><br><span style="color: hsl(120, 100%, 40%);">+# License, or (at your option) any later version.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# This program is distributed in the hope that it will be useful,</span><br><span style="color: hsl(120, 100%, 40%);">+# but WITHOUT ANY WARRANTY; without even the implied warranty of</span><br><span style="color: hsl(120, 100%, 40%);">+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the</span><br><span style="color: hsl(120, 100%, 40%);">+# GNU General Public License for more details.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# You should have received a copy of the GNU General Public License</span><br><span style="color: hsl(120, 100%, 40%);">+# along with this program.  If not, see <http://www.gnu.org/licenses/>.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import log, process</span><br><span style="color: hsl(120, 100%, 40%);">+from abc import ABCMeta</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class AndroidHost(log.Origin, metaclass=ABCMeta):</span><br><span style="color: hsl(120, 100%, 40%);">+    """Base for everything AndroidUE related."""</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+##############</span><br><span style="color: hsl(120, 100%, 40%);">+# PROTECTED</span><br><span style="color: hsl(120, 100%, 40%);">+##############</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, name):</span><br><span style="color: hsl(120, 100%, 40%);">+        log.Origin.__init__(self, log.C_TST, name)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+########################</span><br><span style="color: hsl(120, 100%, 40%);">+# PUBLIC - INTERNAL API</span><br><span style="color: hsl(120, 100%, 40%);">+########################</span><br><span style="color: hsl(120, 100%, 40%);">+    def run_androidue_cmd(self, name, popen_args):</span><br><span style="color: hsl(120, 100%, 40%);">+        # This function executes the given command directly on the Android UE. Therefore,</span><br><span style="color: hsl(120, 100%, 40%);">+        # ADB is used to execute commands locally and ssh for remote execution. Make sure</span><br><span style="color: hsl(120, 100%, 40%);">+        # Android SDK Platform-Tools >= 23 is installed</span><br><span style="color: hsl(120, 100%, 40%);">+        if self._run_node.is_local():</span><br><span style="color: hsl(120, 100%, 40%);">+            # use adb instead of ssh</span><br><span style="color: hsl(120, 100%, 40%);">+            run_dir = self.run_dir.new_dir(name)</span><br><span style="color: hsl(120, 100%, 40%);">+            proc = process.AdbProcess(name, run_dir, self._run_node.adb_serial_id(), popen_args, env={})</span><br><span style="color: hsl(120, 100%, 40%);">+        else:</span><br><span style="color: hsl(120, 100%, 40%);">+            proc = self.rem_host.RemoteProcess(name, popen_args, remote_env={})</span><br><span style="color: hsl(120, 100%, 40%);">+        return proc</span><br><span>diff --git a/src/osmo_gsm_tester/obj/iperf3.py b/src/osmo_gsm_tester/obj/iperf3.py</span><br><span>index 2039a9b..4b88df0 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/iperf3.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/iperf3.py</span><br><span>@@ -227,6 +227,7 @@</span><br><span>         self.remote_log_file = None</span><br><span>         self.log_copied = False</span><br><span>         self.logfile_supported = False # some older versions of iperf doesn't support --logfile arg</span><br><span style="color: hsl(120, 100%, 40%);">+        self.is_android_ue = False</span><br><span> </span><br><span>     def runs_locally(self):</span><br><span>         locally = not self._run_node or self._run_node.is_local()</span><br><span>@@ -281,7 +282,8 @@</span><br><span>         return proc</span><br><span> </span><br><span>     def prepare_test_proc_remotely(self, netns, popen_args):</span><br><span style="color: hsl(0, 100%, 40%);">-        self.rem_host = remote.RemoteHost(self.run_dir, self._run_node.ssh_user(), self._run_node.ssh_addr())</span><br><span style="color: hsl(120, 100%, 40%);">+        self.rem_host = remote.RemoteHost(self.run_dir, self._run_node.ssh_user(), self._run_node.ssh_addr(), None,</span><br><span style="color: hsl(120, 100%, 40%);">+                                          self._run_node.ssh_port())</span><br><span> </span><br><span>         remote_prefix_dir = util.Dir(IPerf3Client.REMOTE_DIR)</span><br><span>         remote_run_dir = util.Dir(remote_prefix_dir.child('cli-' + str(self)))</span><br><span>@@ -307,6 +309,8 @@</span><br><span> </span><br><span>         if netns:</span><br><span>             self.process = process.NetNSProcess(self.name(), self.run_dir, netns, popen_args, env={})</span><br><span style="color: hsl(120, 100%, 40%);">+        elif self._run_node.adb_serial_id():</span><br><span style="color: hsl(120, 100%, 40%);">+            self.process = process.AdbProcess(self.name(), self.run_dir, self._run_node.adb_serial_id(), popen_args, env={})</span><br><span>         else:</span><br><span>             self.process = process.Process(self.name(), self.run_dir, popen_args, env={})</span><br><span>         return self.process</span><br><span>diff --git a/src/osmo_gsm_tester/obj/ms.py b/src/osmo_gsm_tester/obj/ms.py</span><br><span>index 7257769..552de71 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/ms.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/ms.py</span><br><span>@@ -48,7 +48,7 @@</span><br><span> # PROTECTED</span><br><span> ##############</span><br><span>     def __init__(self, name, testenv, conf):</span><br><span style="color: hsl(0, 100%, 40%);">-        super().__init__(log.C_TST, name)</span><br><span style="color: hsl(120, 100%, 40%);">+        log.Origin.__init__(self, log.C_TST, name)</span><br><span>         self.testenv = testenv</span><br><span>         self._conf = conf</span><br><span>         self._msisdn = None</span><br><span>@@ -77,6 +77,9 @@</span><br><span>         elif ms_type == 'srsue':</span><br><span>             from .ms_srs import srsUE</span><br><span>             ms_class = srsUE</span><br><span style="color: hsl(120, 100%, 40%);">+        elif ms_type == 'androidue':</span><br><span style="color: hsl(120, 100%, 40%);">+            from .ms_android import AndroidUE</span><br><span style="color: hsl(120, 100%, 40%);">+            ms_class = AndroidUE</span><br><span>         elif ms_type == 'amarisoftue':</span><br><span>             from .ms_amarisoft import AmarisoftUE</span><br><span>             ms_class = AmarisoftUE</span><br><span>diff --git a/src/osmo_gsm_tester/obj/ms_android.py b/src/osmo_gsm_tester/obj/ms_android.py</span><br><span>new file mode 100644</span><br><span>index 0000000..9fddff3</span><br><span>--- /dev/null</span><br><span>+++ b/src/osmo_gsm_tester/obj/ms_android.py</span><br><span>@@ -0,0 +1,244 @@</span><br><span style="color: hsl(120, 100%, 40%);">+# osmo_gsm_tester: specifics for running an AndroidUE modem</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# Copyright (C) 2020 by Software Radio Systems Limited</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# Author: Nils Fürste <nils.fuerste@softwareradiosystems.com></span><br><span style="color: hsl(120, 100%, 40%);">+# Author: Bedran Karakoc <bedran.karakoc@softwareradiosystems.com></span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# This program is free software: you can redistribute it and/or modify</span><br><span style="color: hsl(120, 100%, 40%);">+# it under the terms of the GNU General Public License as</span><br><span style="color: hsl(120, 100%, 40%);">+# published by the Free Software Foundation, either version 3 of the</span><br><span style="color: hsl(120, 100%, 40%);">+# License, or (at your option) any later version.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# This program is distributed in the hope that it will be useful,</span><br><span style="color: hsl(120, 100%, 40%);">+# but WITHOUT ANY WARRANTY; without even the implied warranty of</span><br><span style="color: hsl(120, 100%, 40%);">+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the</span><br><span style="color: hsl(120, 100%, 40%);">+# GNU General Public License for more details.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# You should have received a copy of the GNU General Public License</span><br><span style="color: hsl(120, 100%, 40%);">+# along with this program.  If not, see <http://www.gnu.org/licenses/>.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+import pprint</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import log, util, config, remote, schema, process</span><br><span style="color: hsl(120, 100%, 40%);">+from .run_node import RunNode</span><br><span style="color: hsl(120, 100%, 40%);">+from .ms import MS</span><br><span style="color: hsl(120, 100%, 40%);">+from .srslte_common import srslte_common</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core.event_loop import MainLoop</span><br><span style="color: hsl(120, 100%, 40%);">+from .ms_srs import srsUEMetrics</span><br><span style="color: hsl(120, 100%, 40%);">+from .android_bitrate_monitor import BitRateMonitor</span><br><span style="color: hsl(120, 100%, 40%);">+from . import qc_diag</span><br><span style="color: hsl(120, 100%, 40%);">+from .android_apn import AndroidApn</span><br><span style="color: hsl(120, 100%, 40%);">+from .android_host import AndroidHost</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    resource_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'additional_args[]': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'enable_pcap': schema.BOOL_STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    for key, val in RunNode.schema().items():</span><br><span style="color: hsl(120, 100%, 40%);">+        resource_schema['run_node.%s' % key] = val</span><br><span style="color: hsl(120, 100%, 40%);">+    for key, val in AndroidApn.schema().items():</span><br><span style="color: hsl(120, 100%, 40%);">+        resource_schema['apn.%s' % key] = val</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_resource_schema('modem', resource_schema)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    config_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+        'enable_pcap': schema.BOOL_STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        'log_all_level': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+        }</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_config_schema('modem', config_schema)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class AndroidUE(MS, AndroidHost, srslte_common):</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    REMOTEDIR = '/osmo-gsm-tester-androidue'</span><br><span style="color: hsl(120, 100%, 40%);">+    METRICSFILE = 'android_ue_metrics.csv'</span><br><span style="color: hsl(120, 100%, 40%);">+    PCAPFILE = 'android_ue.pcap'</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+##############</span><br><span style="color: hsl(120, 100%, 40%);">+# PROTECTED</span><br><span style="color: hsl(120, 100%, 40%);">+##############</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, testenv, conf):</span><br><span style="color: hsl(120, 100%, 40%);">+        self._run_node = RunNode.from_conf(conf.get('run_node', {}))</span><br><span style="color: hsl(120, 100%, 40%);">+        self.apn_worker = AndroidApn.from_conf(conf.get('apn', {})) if conf.get('apn', {}) != {} else None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.qc_diag_mon = qc_diag.QcDiag(testenv, conf)</span><br><span style="color: hsl(120, 100%, 40%);">+        super().__init__('androidue_%s' % self.addr(), testenv, conf)</span><br><span style="color: hsl(120, 100%, 40%);">+        srslte_common.__init__(self)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.rem_host = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.run_dir = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.remote_run_dir = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.emm_connected = False</span><br><span style="color: hsl(120, 100%, 40%);">+        self.rrc_connected = False</span><br><span style="color: hsl(120, 100%, 40%);">+        self.conn_reset_intvl = 20  # sec</span><br><span style="color: hsl(120, 100%, 40%);">+        self.connect_timeout = 300  # sec</span><br><span style="color: hsl(120, 100%, 40%);">+        self.enable_pcap = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.remote_pcap_file = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.pcap_file = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.data_interface = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.remote_metrics_file = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.metrics_file = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.brate_mon = None</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def configure(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        values = dict(ue=config.get_defaults('androidue'))</span><br><span style="color: hsl(120, 100%, 40%);">+        config.overlay(values, dict(ue=self.testenv.suite().config().get('modem', {})))</span><br><span style="color: hsl(120, 100%, 40%);">+        config.overlay(values, dict(ue=self._conf))</span><br><span style="color: hsl(120, 100%, 40%);">+        self.dbg('AndroidUE CONFIG:\n' + pprint.pformat(values))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if 'qc_diag' in self.features():</span><br><span style="color: hsl(120, 100%, 40%);">+            self.enable_pcap = util.str2bool(values['ue'].get('enable_pcap', 'false'))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        self.metrics_file = self.run_dir.child(AndroidUE.METRICSFILE)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.pcap_file = self.run_dir.child(AndroidUE.PCAPFILE)</span><br><span style="color: hsl(120, 100%, 40%);">+        if not self._run_node.is_local():</span><br><span style="color: hsl(120, 100%, 40%);">+            self.rem_host = remote.RemoteHost(self.run_dir, self._run_node.ssh_user(), self._run_node.ssh_addr(), None,</span><br><span style="color: hsl(120, 100%, 40%);">+                                              self._run_node.ssh_port())</span><br><span style="color: hsl(120, 100%, 40%);">+            self.remote_run_dir = util.Dir(AndroidUE.REMOTEDIR)</span><br><span style="color: hsl(120, 100%, 40%);">+            self.remote_metrics_file = self.remote_run_dir.child(AndroidUE.METRICSFILE)</span><br><span style="color: hsl(120, 100%, 40%);">+            self.remote_pcap_file = self.remote_run_dir.child(AndroidUE.PCAPFILE)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if self.apn_worker:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.apn_worker.configure(self.testenv, self.run_dir, self._run_node, self.rem_host)</span><br><span style="color: hsl(120, 100%, 40%);">+            # some Android UEs only accept new APNs when airplane mode is turned off</span><br><span style="color: hsl(120, 100%, 40%);">+            self.set_airplane_mode(False)</span><br><span style="color: hsl(120, 100%, 40%);">+            self.apn_worker.set_apn()</span><br><span style="color: hsl(120, 100%, 40%);">+            MainLoop.sleep(1)</span><br><span style="color: hsl(120, 100%, 40%);">+            self.set_airplane_mode(True)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # clear old diag files</span><br><span style="color: hsl(120, 100%, 40%);">+        self._clear_diag_logs()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def _clear_diag_logs(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        popen_args_clear_diag_logs = \</span><br><span style="color: hsl(120, 100%, 40%);">+            ['su', '-c', '\"rm -r /data/local/tmp/diag_logs/ || true\"']</span><br><span style="color: hsl(120, 100%, 40%);">+        clear_diag_logs_proc = self.run_androidue_cmd('clear-diag-logs', popen_args_clear_diag_logs)</span><br><span style="color: hsl(120, 100%, 40%);">+        clear_diag_logs_proc.launch_sync()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def verify_metric(self, value, operation='avg', metric='dl_brate', criterion='gt', window=1):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.brate_mon.save_metrics(self.metrics_file)</span><br><span style="color: hsl(120, 100%, 40%);">+        metrics = srsUEMetrics(self.metrics_file)</span><br><span style="color: hsl(120, 100%, 40%);">+        return metrics.verify(value, operation, metric, criterion, window)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def set_airplane_mode(self, apm_state):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.log("Setting airplane mode: " + str(apm_state))</span><br><span style="color: hsl(120, 100%, 40%);">+        popen_args = ['settings', 'put', 'global', 'airplane_mode_on', str(int(apm_state)), ';',</span><br><span style="color: hsl(120, 100%, 40%);">+                      'wait $!;',</span><br><span style="color: hsl(120, 100%, 40%);">+                      'su', '-c', '\"am broadcast -a android.intent.action.AIRPLANE_MODE\";']</span><br><span style="color: hsl(120, 100%, 40%);">+        proc = self.run_androidue_cmd('set-airplane-mode', popen_args)</span><br><span style="color: hsl(120, 100%, 40%);">+        proc.launch_sync()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def get_assigned_addr(self, ipv6=False):</span><br><span style="color: hsl(120, 100%, 40%);">+        ip_prefix = '172.16.0'</span><br><span style="color: hsl(120, 100%, 40%);">+        proc = self.run_androidue_cmd('get-assigned-addr', ['ip', 'addr', 'show'])</span><br><span style="color: hsl(120, 100%, 40%);">+        proc.launch_sync()</span><br><span style="color: hsl(120, 100%, 40%);">+        out_l = proc.get_stdout().split('\n')</span><br><span style="color: hsl(120, 100%, 40%);">+        ip = ''</span><br><span style="color: hsl(120, 100%, 40%);">+        for line in out_l:</span><br><span style="color: hsl(120, 100%, 40%);">+            if ip_prefix in line:</span><br><span style="color: hsl(120, 100%, 40%);">+                ip = line.split(' ')[5][:-3]</span><br><span style="color: hsl(120, 100%, 40%);">+                self.data_interface = line.split(' ')[-1]</span><br><span style="color: hsl(120, 100%, 40%);">+        return ip</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+########################</span><br><span style="color: hsl(120, 100%, 40%);">+# PUBLIC - INTERNAL API</span><br><span style="color: hsl(120, 100%, 40%);">+########################</span><br><span style="color: hsl(120, 100%, 40%);">+    def cleanup(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.set_airplane_mode(True)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def addr(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        return self._run_node.run_addr()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def run_node(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        return self._run_node</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def features(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        return self._conf.get('features', [])</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+###################</span><br><span style="color: hsl(120, 100%, 40%);">+# PUBLIC (test API included)</span><br><span style="color: hsl(120, 100%, 40%);">+###################</span><br><span style="color: hsl(120, 100%, 40%);">+    def run_netns_wait(self, name, popen_args):</span><br><span style="color: hsl(120, 100%, 40%);">+        # This function guarantees the compatibility with the current ping test. Please</span><br><span style="color: hsl(120, 100%, 40%);">+        # note that this function cannot execute commands on the machine the Android UE</span><br><span style="color: hsl(120, 100%, 40%);">+        # is attached to.</span><br><span style="color: hsl(120, 100%, 40%);">+        proc = self.run_androidue_cmd(name, popen_args)</span><br><span style="color: hsl(120, 100%, 40%);">+        proc.launch_sync()</span><br><span style="color: hsl(120, 100%, 40%);">+        return proc</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def connect(self, enb):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.log('Starting AndroidUE')</span><br><span style="color: hsl(120, 100%, 40%);">+        self.run_dir = util.Dir(self.testenv.test().get_run_dir().new_dir(self.name()))</span><br><span style="color: hsl(120, 100%, 40%);">+        self.configure()</span><br><span style="color: hsl(120, 100%, 40%);">+        CONN_CHK = 'osmo-gsm-tester_androidue_conn_chk.sh'</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if 'qc_diag' in self.features():</span><br><span style="color: hsl(120, 100%, 40%);">+            self.qc_diag_mon.start()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if self._run_node.is_local():</span><br><span style="color: hsl(120, 100%, 40%);">+            popen_args_emm_conn_chk = [CONN_CHK, self._run_node.adb_serial_id(), '0', '0']</span><br><span style="color: hsl(120, 100%, 40%);">+        else:</span><br><span style="color: hsl(120, 100%, 40%);">+            popen_args_emm_conn_chk = [CONN_CHK, '0', self.rem_host.host(), self.rem_host.get_remote_port()]</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # make sure osmo-gsm-tester_androidue_conn_chk.sh is available on the OGT master unit</span><br><span style="color: hsl(120, 100%, 40%);">+        name = 'emm-conn-chk'</span><br><span style="color: hsl(120, 100%, 40%);">+        run_dir = self.run_dir.new_dir(name)</span><br><span style="color: hsl(120, 100%, 40%);">+        emm_conn_chk_proc = process.Process(name, run_dir, popen_args_emm_conn_chk)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.testenv.remember_to_stop(emm_conn_chk_proc)</span><br><span style="color: hsl(120, 100%, 40%);">+        emm_conn_chk_proc.launch()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # check connection status</span><br><span style="color: hsl(120, 100%, 40%);">+        timer = self.connect_timeout</span><br><span style="color: hsl(120, 100%, 40%);">+        while timer > 0:</span><br><span style="color: hsl(120, 100%, 40%);">+            if timer % self.conn_reset_intvl == 0:</span><br><span style="color: hsl(120, 100%, 40%);">+                self.set_airplane_mode(True)</span><br><span style="color: hsl(120, 100%, 40%);">+                MainLoop.sleep(1)</span><br><span style="color: hsl(120, 100%, 40%);">+                timer -= 1</span><br><span style="color: hsl(120, 100%, 40%);">+                self.set_airplane_mode(False)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+            if 'LTE' in emm_conn_chk_proc.get_stdout():</span><br><span style="color: hsl(120, 100%, 40%);">+                if not(self.get_assigned_addr() is ''):</span><br><span style="color: hsl(120, 100%, 40%);">+                    self.emm_connected = True</span><br><span style="color: hsl(120, 100%, 40%);">+                    self.rrc_connected = True</span><br><span style="color: hsl(120, 100%, 40%);">+                    self.testenv.stop_process(emm_conn_chk_proc)</span><br><span style="color: hsl(120, 100%, 40%);">+                    break</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+            MainLoop.sleep(2)</span><br><span style="color: hsl(120, 100%, 40%);">+            timer -= 2</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if timer == 0:</span><br><span style="color: hsl(120, 100%, 40%);">+            raise log.Error('Connection timer of Android UE %s expired' % self._run_node.adb_serial_id())</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        self.brate_mon = BitRateMonitor(self.testenv, self.run_dir, self._run_node, self.rem_host, self.data_interface)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.brate_mon.start()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def is_rrc_connected(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        if not ('qc_diag' in self.features()):</span><br><span style="color: hsl(120, 100%, 40%);">+            raise log.Error('Monitoring RRC states not supported (missing qc_diag feature?)')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        # if not self.qc_diag_mon.running():</span><br><span style="color: hsl(120, 100%, 40%);">+        #     raise log.Error('Diag monitoring crashed or was not started')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        rrc_state = self.qc_diag_mon.get_rrc_state()</span><br><span style="color: hsl(120, 100%, 40%);">+        if 'RRC_IDLE_CAMPED' in rrc_state:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.rrc_connected = False</span><br><span style="color: hsl(120, 100%, 40%);">+        elif 'RRC_CONNECTED' in rrc_state:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.rrc_connected = True</span><br><span style="color: hsl(120, 100%, 40%);">+        return self.rrc_connected</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def is_registered(self, mcc_mnc=None):</span><br><span style="color: hsl(120, 100%, 40%);">+        if mcc_mnc:</span><br><span style="color: hsl(120, 100%, 40%);">+            raise log.Error('An AndroidUE cannot register to any predefined MCC/MNC')</span><br><span style="color: hsl(120, 100%, 40%);">+        return self.emm_connected</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def get_counter(self, counter_name):</span><br><span style="color: hsl(120, 100%, 40%);">+        if counter_name == 'prach_sent':</span><br><span style="color: hsl(120, 100%, 40%);">+            # not implemented so far, return 2 to pass tests</span><br><span style="color: hsl(120, 100%, 40%);">+            return 2</span><br><span style="color: hsl(120, 100%, 40%);">+        elif counter_name == 'paging_received':</span><br><span style="color: hsl(120, 100%, 40%);">+            return self.qc_diag_mon.get_paging_counter()</span><br><span style="color: hsl(120, 100%, 40%);">+        else:</span><br><span style="color: hsl(120, 100%, 40%);">+            raise log.Error('Counter %s not implemented' % counter_name)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def netns(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        return None</span><br><span>diff --git a/src/osmo_gsm_tester/obj/qc_diag.py b/src/osmo_gsm_tester/obj/qc_diag.py</span><br><span>new file mode 100644</span><br><span>index 0000000..a2029b4</span><br><span>--- /dev/null</span><br><span>+++ b/src/osmo_gsm_tester/obj/qc_diag.py</span><br><span>@@ -0,0 +1,274 @@</span><br><span style="color: hsl(120, 100%, 40%);">+# osmo_gsm_tester: specifics for running Qualcomm diagnostics on an AndroidUE modem</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# Copyright (C) 2020 by Software Radio Systems Limited</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# Author: Nils Fürste <nils.fuerste@softwareradiosystems.com></span><br><span style="color: hsl(120, 100%, 40%);">+# Author: Bedran Karakoc <bedran.karakoc@softwareradiosystems.com></span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# This program is free software: you can redistribute it and/or modify</span><br><span style="color: hsl(120, 100%, 40%);">+# it under the terms of the GNU General Public License as</span><br><span style="color: hsl(120, 100%, 40%);">+# published by the Free Software Foundation, either version 3 of the</span><br><span style="color: hsl(120, 100%, 40%);">+# License, or (at your option) any later version.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# This program is distributed in the hope that it will be useful,</span><br><span style="color: hsl(120, 100%, 40%);">+# but WITHOUT ANY WARRANTY; without even the implied warranty of</span><br><span style="color: hsl(120, 100%, 40%);">+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the</span><br><span style="color: hsl(120, 100%, 40%);">+# GNU General Public License for more details.</span><br><span style="color: hsl(120, 100%, 40%);">+#</span><br><span style="color: hsl(120, 100%, 40%);">+# You should have received a copy of the GNU General Public License</span><br><span style="color: hsl(120, 100%, 40%);">+# along with this program.  If not, see <http://www.gnu.org/licenses/>.</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+import getpass</span><br><span style="color: hsl(120, 100%, 40%);">+import os</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core import remote, util, process, schema, log</span><br><span style="color: hsl(120, 100%, 40%);">+from ..core.event_loop import MainLoop</span><br><span style="color: hsl(120, 100%, 40%);">+from . import ms_android</span><br><span style="color: hsl(120, 100%, 40%);">+from .android_host import AndroidHost</span><br><span style="color: hsl(120, 100%, 40%);">+from .run_node import RunNode</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+def on_register_schemas():</span><br><span style="color: hsl(120, 100%, 40%);">+    resource_schema = {}</span><br><span style="color: hsl(120, 100%, 40%);">+    for key, val in ScatParser.schema().items():</span><br><span style="color: hsl(120, 100%, 40%);">+        resource_schema['scat_parser.%s' % key] = val</span><br><span style="color: hsl(120, 100%, 40%);">+    schema.register_resource_schema('modem', resource_schema)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class QcDiag(AndroidHost):</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    DIAG_PARSER = 'osmo-gsm-tester_androidue_diag_parser.sh'</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+##############</span><br><span style="color: hsl(120, 100%, 40%);">+# PROTECTED</span><br><span style="color: hsl(120, 100%, 40%);">+##############</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, testenv, conf):</span><br><span style="color: hsl(120, 100%, 40%);">+        self._run_node = RunNode.from_conf(conf.get('run_node', {}))</span><br><span style="color: hsl(120, 100%, 40%);">+        super().__init__('qcdiag_%s' % self._run_node.run_addr())</span><br><span style="color: hsl(120, 100%, 40%);">+        self.testenv = testenv</span><br><span style="color: hsl(120, 100%, 40%);">+        self.run_dir = util.Dir(self.testenv.test().get_run_dir().new_dir(self.name()))</span><br><span style="color: hsl(120, 100%, 40%);">+        if not self._run_node.is_local():</span><br><span style="color: hsl(120, 100%, 40%);">+            self.rem_host = remote.RemoteHost(self.run_dir, self._run_node.ssh_user(), self._run_node.ssh_addr(), None,</span><br><span style="color: hsl(120, 100%, 40%);">+                                              self._run_node.ssh_port())</span><br><span style="color: hsl(120, 100%, 40%);">+            self.remote_run_dir = util.Dir(ms_android.AndroidUE.REMOTEDIR)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.scat_parser = ScatParser(testenv, conf)</span><br><span style="color: hsl(120, 100%, 40%);">+        testenv.register_for_cleanup(self.scat_parser)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.diag_monitor_proc = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.enable_pcap = util.str2bool(conf.get('enable_pcap', 'false'))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+########################</span><br><span style="color: hsl(120, 100%, 40%);">+# PUBLIC - INTERNAL API</span><br><span style="color: hsl(120, 100%, 40%);">+########################</span><br><span style="color: hsl(120, 100%, 40%);">+    def get_rrc_state(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        scat_parser_stdout_l = self.scat_parser.get_stdout().split('\n')</span><br><span style="color: hsl(120, 100%, 40%);">+        # Find the first "Pulling new .qmdl file..." and check the state afterwards. This has to be done to</span><br><span style="color: hsl(120, 100%, 40%);">+        # ensure that no process is reading the ScatParser's stdout while the parser is still writing to it.</span><br><span style="color: hsl(120, 100%, 40%);">+        is_full_block = False</span><br><span style="color: hsl(120, 100%, 40%);">+        for line in reversed(scat_parser_stdout_l):</span><br><span style="color: hsl(120, 100%, 40%);">+            if 'Pulling new .qmdl file...' in line:</span><br><span style="color: hsl(120, 100%, 40%);">+                is_full_block = True</span><br><span style="color: hsl(120, 100%, 40%);">+            if is_full_block and 'LTE_RRC_STATE_CHANGE' in line:</span><br><span style="color: hsl(120, 100%, 40%);">+                rrc_state = line.split(' ')[-1].replace('rrc_state=', '')</span><br><span style="color: hsl(120, 100%, 40%);">+                rrc_state.replace('\'', '')</span><br><span style="color: hsl(120, 100%, 40%);">+                return rrc_state</span><br><span style="color: hsl(120, 100%, 40%);">+        return ''</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def get_paging_counter(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        diag_parser_stdout_l = self.scat_parser.get_stdout().split('\n')</span><br><span style="color: hsl(120, 100%, 40%);">+        return diag_parser_stdout_l.count('Paging received')</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def running(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        return self.diag_monitor_proc.is_running()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def write_pcap(self, restart=False):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.scat_parser.write_pcap(restart)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def start(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        popen_args_diag = ['/vendor/bin/diag_mdlog', '-s', '90000', '-f', '/data/local/tmp/ogt_diag.cfg',</span><br><span style="color: hsl(120, 100%, 40%);">+                           '-o', '/data/local/tmp/diag_logs']</span><br><span style="color: hsl(120, 100%, 40%);">+        self.diag_monitor_proc = self.run_androidue_cmd('start-diag-monitor_%s' % self._run_node.adb_serial_id(), popen_args_diag)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.testenv.remember_to_stop(self.diag_monitor_proc)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.diag_monitor_proc.launch()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        self.scat_parser.configure(self._run_node, self.enable_pcap)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.scat_parser.start()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def scp_back_pcap(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.scat_parser.scp_back_pcap()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+class ScatParser(AndroidHost):</span><br><span style="color: hsl(120, 100%, 40%);">+##############</span><br><span style="color: hsl(120, 100%, 40%);">+# PROTECTED</span><br><span style="color: hsl(120, 100%, 40%);">+##############</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, testenv, conf):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.testenv = testenv</span><br><span style="color: hsl(120, 100%, 40%);">+        self._run_node = RunNode.from_conf(conf.get('scat_parser', {}))</span><br><span style="color: hsl(120, 100%, 40%);">+        super().__init__('scat_parser_%s' % self._run_node.run_addr())</span><br><span style="color: hsl(120, 100%, 40%);">+        self.run_dir = util.Dir(self.testenv.test().get_run_dir().new_dir(self.name()))</span><br><span style="color: hsl(120, 100%, 40%);">+        self.remote_run_dir = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.rem_host = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.pcap_file = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.remote_pcap_file = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.parser_proc = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self._parser_proc = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.popen_args_diag_parser = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self._run_node_ue = None</span><br><span style="color: hsl(120, 100%, 40%);">+        self.enable_pcap = False</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def _clear_diag_files(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        name_chown = 'chown-diag-files'</span><br><span style="color: hsl(120, 100%, 40%);">+        diag_dir_local = str(self.run_dir) + '/diag_logs/'</span><br><span style="color: hsl(120, 100%, 40%);">+        diag_dir_remote = str(self.remote_run_dir) + '/diag_logs/'</span><br><span style="color: hsl(120, 100%, 40%);">+        popen_args_change_owner = ['sudo', 'chown', '-R', '', '']</span><br><span style="color: hsl(120, 100%, 40%);">+        run_dir_chown = self.run_dir.new_dir(name_chown)</span><br><span style="color: hsl(120, 100%, 40%);">+        if self._run_node.is_local():</span><br><span style="color: hsl(120, 100%, 40%);">+            if os.path.exists(diag_dir_local):</span><br><span style="color: hsl(120, 100%, 40%);">+                # Due to errors the diag_logs dir can be non-existing. To avoid errors the path</span><br><span style="color: hsl(120, 100%, 40%);">+                # is checked for existence first.</span><br><span style="color: hsl(120, 100%, 40%);">+                popen_args_change_owner[3] = getpass.getuser()</span><br><span style="color: hsl(120, 100%, 40%);">+                popen_args_change_owner[4] = diag_dir_local</span><br><span style="color: hsl(120, 100%, 40%);">+                change_owner_proc = process.Process(name_chown, run_dir_chown, popen_args_change_owner)</span><br><span style="color: hsl(120, 100%, 40%);">+                change_owner_proc.launch_sync()</span><br><span style="color: hsl(120, 100%, 40%);">+        else:</span><br><span style="color: hsl(120, 100%, 40%);">+            popen_args_change_owner = ['sudo', 'chown', '-R', self.rem_host.user(), diag_dir_remote]</span><br><span style="color: hsl(120, 100%, 40%);">+            change_owner_proc = self.rem_host.RemoteProcess(name_chown, popen_args_change_owner, remote_env={})</span><br><span style="color: hsl(120, 100%, 40%);">+            change_owner_proc.launch_sync()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        name_clear = 'clear-diag-files'</span><br><span style="color: hsl(120, 100%, 40%);">+        run_dir_clear = self.run_dir.new_dir(name_clear)</span><br><span style="color: hsl(120, 100%, 40%);">+        popen_args_clear_diag_files = ['rm', '-r', '']</span><br><span style="color: hsl(120, 100%, 40%);">+        if self._run_node.is_local():</span><br><span style="color: hsl(120, 100%, 40%);">+            popen_args_clear_diag_files[2] = diag_dir_local</span><br><span style="color: hsl(120, 100%, 40%);">+            clear_run_dir_proc = process.Process(name_clear, run_dir_clear, popen_args_clear_diag_files)</span><br><span style="color: hsl(120, 100%, 40%);">+        else:</span><br><span style="color: hsl(120, 100%, 40%);">+            popen_args_clear_diag_files[2] = diag_dir_remote</span><br><span style="color: hsl(120, 100%, 40%);">+            clear_run_dir_proc = self.rem_host.RemoteProcess(name_clear, popen_args_clear_diag_files, remote_env={})</span><br><span style="color: hsl(120, 100%, 40%);">+        clear_run_dir_proc.launch_sync()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+########################</span><br><span style="color: hsl(120, 100%, 40%);">+# PUBLIC - INTERNAL API</span><br><span style="color: hsl(120, 100%, 40%);">+########################</span><br><span style="color: hsl(120, 100%, 40%);">+    @classmethod</span><br><span style="color: hsl(120, 100%, 40%);">+    def schema(cls):</span><br><span style="color: hsl(120, 100%, 40%);">+        resource_schema = {</span><br><span style="color: hsl(120, 100%, 40%);">+            'run_type': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+            'run_addr': schema.IPV4,</span><br><span style="color: hsl(120, 100%, 40%);">+            'ssh_user': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+            'ssh_addr': schema.IPV4,</span><br><span style="color: hsl(120, 100%, 40%);">+            'run_label': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+            'ssh_port': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+            'adb_serial_id': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+            }</span><br><span style="color: hsl(120, 100%, 40%);">+        return resource_schema</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def configure(self, run_node, enable_pcap):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.enable_pcap = enable_pcap</span><br><span style="color: hsl(120, 100%, 40%);">+        self._run_node_ue = run_node</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if not self._run_node.is_local():</span><br><span style="color: hsl(120, 100%, 40%);">+            self.rem_host = remote.RemoteHost(self.run_dir, self._run_node.ssh_user(), self._run_node.ssh_addr())</span><br><span style="color: hsl(120, 100%, 40%);">+            self.remote_run_dir = util.Dir(ms_android.AndroidUE.REMOTEDIR)</span><br><span style="color: hsl(120, 100%, 40%);">+            self.remote_pcap_file = self.remote_run_dir.child(ms_android.AndroidUE.PCAPFILE)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.pcap_file = self.run_dir.child(ms_android.AndroidUE.PCAPFILE)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def start(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        # format: osmo-gsm-tester_androidue_diag_parser.sh $serial $run_dir $pcap_path $remote_ip $remote_port</span><br><span style="color: hsl(120, 100%, 40%);">+        self.popen_args_diag_parser = [QcDiag.DIAG_PARSER, '', '', '', '', '']</span><br><span style="color: hsl(120, 100%, 40%);">+        if self._run_node_ue.is_local():</span><br><span style="color: hsl(120, 100%, 40%);">+            if not self._run_node.is_local():</span><br><span style="color: hsl(120, 100%, 40%);">+                # AndroidUE is attached to Master but ScatParser is running remote</span><br><span style="color: hsl(120, 100%, 40%);">+                raise log.Error('Running the network locally and the ScatParser remotely is currently not supported')</span><br><span style="color: hsl(120, 100%, 40%);">+            else:</span><br><span style="color: hsl(120, 100%, 40%);">+                # Master, ScatParser, and AndroidUE are attached to/running on the same host</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[1] = str(self._run_node.adb_serial_id())    # adb serial</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[2] = str(self.run_dir)                      # run dir path</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[3] = str(self.pcap_file)                    # pcap file path</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[4] = '0'                                    # remote ip</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[5] = '0'                                    # remote port</span><br><span style="color: hsl(120, 100%, 40%);">+        else:</span><br><span style="color: hsl(120, 100%, 40%);">+            if self._run_node.is_local():</span><br><span style="color: hsl(120, 100%, 40%);">+                # Master and ScatParser running on the same machine, the AndroidUE runs remote</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[1] = '0'                                    # adb serial</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[2] = str(self.run_dir)                      # run dir path</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[3] = str(self.pcap_file)                    # pcap file path</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[4] = str(self._run_node_ue.ssh_addr())      # remote ip AndroidUE</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[5] = str(self._run_node_ue.ssh_port())      # remote port AndroidUE</span><br><span style="color: hsl(120, 100%, 40%);">+            elif self._run_node.ssh_addr() == self._run_node_ue.ssh_addr():</span><br><span style="color: hsl(120, 100%, 40%);">+                # ScatParser and AndroidUE are remote but on the same machine</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[1] = str(self._run_node.adb_serial_id())    # adb serial</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[2] = str(self.remote_run_dir)               # run dir path</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[3] = str(self.remote_pcap_file)             # pcap file path</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[4] = '0'                                    # remote ip</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[5] = '0'                                    # remote port</span><br><span style="color: hsl(120, 100%, 40%);">+            else:</span><br><span style="color: hsl(120, 100%, 40%);">+                # Master, ScatParser and AndroidUE are running on/attached to different machines</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[1] = '0'                                    # adb serial</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[2] = str(self.remote_run_dir)               # run dir path</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[3] = str(self.remote_pcap_file)             # pcap file path</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[4] = str(self._run_node_ue.ssh_addr())      # remote ip AndroidUE</span><br><span style="color: hsl(120, 100%, 40%);">+                self.popen_args_diag_parser[5] = str(self._run_node_ue.ssh_port())      # remote port AndroidUE</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if not self._run_node.is_local():</span><br><span style="color: hsl(120, 100%, 40%);">+            # The diag_logs directory only exists here if the ScatParser entity is running remote</span><br><span style="color: hsl(120, 100%, 40%);">+            self._clear_diag_files()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        name = 'scat_parser_%s' % self._run_node.run_addr()</span><br><span style="color: hsl(120, 100%, 40%);">+        if self._run_node.is_local():</span><br><span style="color: hsl(120, 100%, 40%);">+            run_dir = self.run_dir.new_dir(name)</span><br><span style="color: hsl(120, 100%, 40%);">+            self.parser_proc = process.Process(name, run_dir, self.popen_args_diag_parser)</span><br><span style="color: hsl(120, 100%, 40%);">+        else:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.parser_proc = self.rem_host.RemoteProcess(name, self.popen_args_diag_parser, remote_env={})</span><br><span style="color: hsl(120, 100%, 40%);">+        self.testenv.remember_to_stop(self.parser_proc)</span><br><span style="color: hsl(120, 100%, 40%);">+        self.parser_proc.launch()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def stop(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        self.testenv.stop_process(self.parser_proc)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def write_pcap(self, restart=False):</span><br><span style="color: hsl(120, 100%, 40%);">+        # We need to stop the diag_parser to avoid pulling a new .qmdl during</span><br><span style="color: hsl(120, 100%, 40%);">+        # the parsing process. The process can be restarted afterwards but keep in</span><br><span style="color: hsl(120, 100%, 40%);">+        # mind that this will overwrite the pcap after some time. The diag_monitor</span><br><span style="color: hsl(120, 100%, 40%);">+        # process can continue, as it does not hinder this process.</span><br><span style="color: hsl(120, 100%, 40%);">+        if self.parser_proc and self.parser_proc.is_running():</span><br><span style="color: hsl(120, 100%, 40%);">+            self.testenv.stop_process(self.parser_proc)</span><br><span style="color: hsl(120, 100%, 40%);">+        self._clear_diag_files()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        name = 'write-pcap_%s' % self._run_node.run_addr()</span><br><span style="color: hsl(120, 100%, 40%);">+        if self._run_node.is_local():</span><br><span style="color: hsl(120, 100%, 40%);">+            run_dir = self.run_dir.new_dir(name)</span><br><span style="color: hsl(120, 100%, 40%);">+            self._parser_proc = process.Process(name, run_dir, self.popen_args_diag_parser)</span><br><span style="color: hsl(120, 100%, 40%);">+        else:</span><br><span style="color: hsl(120, 100%, 40%);">+            self._parser_proc = self.rem_host.RemoteProcess(name, self.popen_args_diag_parser, remote_env={})</span><br><span style="color: hsl(120, 100%, 40%);">+        self.testenv.remember_to_stop(self._parser_proc)</span><br><span style="color: hsl(120, 100%, 40%);">+        self._parser_proc.launch()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        MainLoop.wait(self.finished_parsing, timestep=0.1, timeout=300)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+        if restart:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.parser_proc = self._parser_proc</span><br><span style="color: hsl(120, 100%, 40%);">+        else:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.testenv.stop_process(self._parser_proc)</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def finished_parsing(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        scat_parser_stdout = self._parser_proc.get_stdout()</span><br><span style="color: hsl(120, 100%, 40%);">+        # If the parsers pulls the .qmdl file for the second time we know that</span><br><span style="color: hsl(120, 100%, 40%);">+        # the parsing of the first one is done</span><br><span style="color: hsl(120, 100%, 40%);">+        return scat_parser_stdout.count('Pulling new .qmdl file...') > 1</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def get_stdout(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        return self.parser_proc.get_stdout()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def is_running(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        return self.parser_proc.is_running()</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def scp_back_pcap(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        try:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.rem_host.scpfrom('scp-back-pcap', self.remote_pcap_file, self.pcap_file)</span><br><span style="color: hsl(120, 100%, 40%);">+        except Exception as e:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.log(repr(e))</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span style="color: hsl(120, 100%, 40%);">+    def cleanup(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        if self.enable_pcap:</span><br><span style="color: hsl(120, 100%, 40%);">+            self.write_pcap(restart=False)</span><br><span style="color: hsl(120, 100%, 40%);">+            if not self._run_node.is_local():</span><br><span style="color: hsl(120, 100%, 40%);">+                self.scp_back_pcap()</span><br><span>diff --git a/src/osmo_gsm_tester/obj/run_node.py b/src/osmo_gsm_tester/obj/run_node.py</span><br><span>index 7c41705..72879a7 100644</span><br><span>--- a/src/osmo_gsm_tester/obj/run_node.py</span><br><span>+++ b/src/osmo_gsm_tester/obj/run_node.py</span><br><span>@@ -30,7 +30,7 @@</span><br><span>     T_LOCAL = 'local'</span><br><span>     T_REM_SSH = 'ssh'</span><br><span> </span><br><span style="color: hsl(0, 100%, 40%);">-    def __init__(self, type=None, run_addr=None, ssh_user=None, ssh_addr=None, run_label=None, ssh_port=None):</span><br><span style="color: hsl(120, 100%, 40%);">+    def __init__(self, type=None, run_addr=None, ssh_user=None, ssh_addr=None, run_label=None, ssh_port=None, adb_serial_id=None):</span><br><span>         super().__init__(log.C_RUN, 'runnode')</span><br><span>         self._type = type</span><br><span>         self._run_addr = run_addr</span><br><span>@@ -38,6 +38,7 @@</span><br><span>         self._ssh_addr = ssh_addr</span><br><span>         self._run_label = run_label</span><br><span>         self._ssh_port = ssh_port</span><br><span style="color: hsl(120, 100%, 40%);">+        self._adb_serial_id = adb_serial_id</span><br><span>         if not self._type:</span><br><span>             raise log.Error('run_type not set')</span><br><span>         if not self._run_addr:</span><br><span>@@ -56,7 +57,8 @@</span><br><span>     def from_conf(cls, conf):</span><br><span>         return cls(conf.get('run_type', None), conf.get('run_addr', None),</span><br><span>                    conf.get('ssh_user', None), conf.get('ssh_addr', None),</span><br><span style="color: hsl(0, 100%, 40%);">-                   conf.get('run_label', None), conf.get('ssh_port', None))</span><br><span style="color: hsl(120, 100%, 40%);">+                   conf.get('run_label', None), conf.get('ssh_port', None),</span><br><span style="color: hsl(120, 100%, 40%);">+                   conf.get('adb_serial_id', None))</span><br><span> </span><br><span>     @classmethod</span><br><span>     def schema(cls):</span><br><span>@@ -67,6 +69,7 @@</span><br><span>             'ssh_addr': schema.IPV4,</span><br><span>             'run_label': schema.STR,</span><br><span>             'ssh_port': schema.STR,</span><br><span style="color: hsl(120, 100%, 40%);">+            'adb_serial_id': schema.STR,</span><br><span>             }</span><br><span>         return resource_schema</span><br><span> </span><br><span>@@ -94,4 +97,7 @@</span><br><span>     def ssh_port(self):</span><br><span>         return self._ssh_port</span><br><span> </span><br><span style="color: hsl(120, 100%, 40%);">+    def adb_serial_id(self):</span><br><span style="color: hsl(120, 100%, 40%);">+        return self._adb_serial_id</span><br><span style="color: hsl(120, 100%, 40%);">+</span><br><span> # vim: expandtab tabstop=4 shiftwidth=4</span><br><span>diff --git a/sysmocom/scenarios/ms-label.conf b/sysmocom/scenarios/ms-label.conf</span><br><span>new file mode 100644</span><br><span>index 0000000..a129c0e</span><br><span>--- /dev/null</span><br><span>+++ b/sysmocom/scenarios/ms-label.conf</span><br><span>@@ -0,0 +1,3 @@</span><br><span style="color: hsl(120, 100%, 40%);">+resources:</span><br><span style="color: hsl(120, 100%, 40%);">+  modem:</span><br><span style="color: hsl(120, 100%, 40%);">+  - label: ${param1}</span><br><span>diff --git a/utils/bin/osmo-gsm-tester_androidue_conn_chk.sh b/utils/bin/osmo-gsm-tester_androidue_conn_chk.sh</span><br><span>new file mode 100644</span><br><span>index 0000000..d8ff049</span><br><span>--- /dev/null</span><br><span>+++ b/utils/bin/osmo-gsm-tester_androidue_conn_chk.sh</span><br><span>@@ -0,0 +1,16 @@</span><br><span style="color: hsl(120, 100%, 40%);">+#!/bin/bash</span><br><span style="color: hsl(120, 100%, 40%);">+# This script reads the network type of an Android phone via ADB</span><br><span style="color: hsl(120, 100%, 40%);">+# usage: osmo-gsm-tester_androidue_conn_chk.sh $serial $remote_ip $remote_port</span><br><span style="color: hsl(120, 100%, 40%);">+serial=$1</span><br><span style="color: hsl(120, 100%, 40%);">+remote_ip=$2</span><br><span style="color: hsl(120, 100%, 40%);">+remote_port=$3</span><br><span style="color: hsl(120, 100%, 40%);">+while true; do</span><br><span style="color: hsl(120, 100%, 40%);">+  if [ "${serial}" == "0" ]; then</span><br><span style="color: hsl(120, 100%, 40%);">+    # run_type == ssh</span><br><span style="color: hsl(120, 100%, 40%);">+    ssh -p "${remote_port}" root@"${remote_ip}" getprop "gsm.network.type"</span><br><span style="color: hsl(120, 100%, 40%);">+  else</span><br><span style="color: hsl(120, 100%, 40%);">+    # run_type = local</span><br><span style="color: hsl(120, 100%, 40%);">+    adb -s "${serial}" shell getprop "gsm.network.type"</span><br><span style="color: hsl(120, 100%, 40%);">+  fi</span><br><span style="color: hsl(120, 100%, 40%);">+  sleep 1</span><br><span style="color: hsl(120, 100%, 40%);">+done</span><br><span>diff --git a/utils/bin/osmo-gsm-tester_androidue_diag_parser.sh b/utils/bin/osmo-gsm-tester_androidue_diag_parser.sh</span><br><span>new file mode 100644</span><br><span>index 0000000..db53261</span><br><span>--- /dev/null</span><br><span>+++ b/utils/bin/osmo-gsm-tester_androidue_diag_parser.sh</span><br><span>@@ -0,0 +1,25 @@</span><br><span style="color: hsl(120, 100%, 40%);">+#!/bin/bash</span><br><span style="color: hsl(120, 100%, 40%);">+# This script pulls the diag folder created by diag_mdlog and parses the</span><br><span style="color: hsl(120, 100%, 40%);">+# .qmdl file. Further, it writes all packets to a pcap file.</span><br><span style="color: hsl(120, 100%, 40%);">+# usage: osmo-gsm-tester_androidue_diag_parser.sh $serial $run_dir $pcap_path $remote_ip $remote_port</span><br><span style="color: hsl(120, 100%, 40%);">+serial=$1</span><br><span style="color: hsl(120, 100%, 40%);">+run_dir=$2</span><br><span style="color: hsl(120, 100%, 40%);">+pcap_path=$3</span><br><span style="color: hsl(120, 100%, 40%);">+remote_ip=$4</span><br><span style="color: hsl(120, 100%, 40%);">+remote_port=$5</span><br><span style="color: hsl(120, 100%, 40%);">+while true; do</span><br><span style="color: hsl(120, 100%, 40%);">+  echo "Pulling new .qmdl file..."</span><br><span style="color: hsl(120, 100%, 40%);">+  if [ "${remote_ip}" == "0" ]; then</span><br><span style="color: hsl(120, 100%, 40%);">+    # ScatParser and AndroidUe are attached to/running on the same machine</span><br><span style="color: hsl(120, 100%, 40%);">+    sudo adb -s "${serial}" pull /data/local/tmp/diag_logs "${run_dir}" >/dev/null</span><br><span style="color: hsl(120, 100%, 40%);">+    wait $!</span><br><span style="color: hsl(120, 100%, 40%);">+  else</span><br><span style="color: hsl(120, 100%, 40%);">+    # ScatParser and AndroidUe are attached to/running on different machines</span><br><span style="color: hsl(120, 100%, 40%);">+    scp -r -P "${remote_port}" root@"${remote_ip}":/data/local/tmp/diag_logs/ "${run_dir}"</span><br><span style="color: hsl(120, 100%, 40%);">+    wait $!</span><br><span style="color: hsl(120, 100%, 40%);">+  fi</span><br><span style="color: hsl(120, 100%, 40%);">+  qmdl_fn=$(find "${run_dir}" -maxdepth 2 -type f -name "*.qmdl")</span><br><span style="color: hsl(120, 100%, 40%);">+  wait $!</span><br><span style="color: hsl(120, 100%, 40%);">+  sudo scat -t qc --event -d "${qmdl_fn}" -F "${pcap_path}"</span><br><span style="color: hsl(120, 100%, 40%);">+  wait $!</span><br><span style="color: hsl(120, 100%, 40%);">+done</span><br><span></span><br></pre><p>To view, visit <a href="https://gerrit.osmocom.org/c/osmo-gsm-tester/+/21302">change 21302</a>. To unsubscribe, or for help writing mail filters, visit <a href="https://gerrit.osmocom.org/settings">settings</a>.</p><div itemscope itemtype="http://schema.org/EmailMessage"><div itemscope itemprop="action" itemtype="http://schema.org/ViewAction"><link itemprop="url" href="https://gerrit.osmocom.org/c/osmo-gsm-tester/+/21302"/><meta itemprop="name" content="View Change"/></div></div>

<div style="display:none"> Gerrit-Project: osmo-gsm-tester </div>
<div style="display:none"> Gerrit-Branch: master </div>
<div style="display:none"> Gerrit-Change-Id: I79a5d803e869a868d4dac5e0d4c2feb38038dc5c </div>
<div style="display:none"> Gerrit-Change-Number: 21302 </div>
<div style="display:none"> Gerrit-PatchSet: 7 </div>
<div style="display:none"> Gerrit-Owner: ninjab3s <nils.fuerste@softwareradiosystems.com> </div>
<div style="display:none"> Gerrit-Reviewer: Jenkins Builder </div>
<div style="display:none"> Gerrit-Reviewer: pespin <pespin@sysmocom.de> </div>
<div style="display:none"> Gerrit-MessageType: merged </div>