Nils Fürste | a8263f4 | 2020-11-23 14:45:15 +0100 | [diff] [blame] | 1 | # osmo_gsm_tester: specifics for running Qualcomm diagnostics on an AndroidUE modem |
| 2 | # |
| 3 | # Copyright (C) 2020 by Software Radio Systems Limited |
| 4 | # |
| 5 | # Author: Nils Fürste <nils.fuerste@softwareradiosystems.com> |
| 6 | # Author: Bedran Karakoc <bedran.karakoc@softwareradiosystems.com> |
| 7 | # |
| 8 | # This program is free software: you can redistribute it and/or modify |
| 9 | # it under the terms of the GNU General Public License as |
| 10 | # published by the Free Software Foundation, either version 3 of the |
| 11 | # License, or (at your option) any later version. |
| 12 | # |
| 13 | # This program is distributed in the hope that it will be useful, |
| 14 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 15 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 16 | # GNU General Public License for more details. |
| 17 | # |
| 18 | # You should have received a copy of the GNU General Public License |
| 19 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 20 | |
| 21 | import getpass |
| 22 | import os |
| 23 | from ..core import remote, util, process, schema, log |
| 24 | from ..core.event_loop import MainLoop |
| 25 | from . import ms_android |
| 26 | from .android_host import AndroidHost |
| 27 | from .run_node import RunNode |
| 28 | |
| 29 | |
| 30 | def on_register_schemas(): |
| 31 | resource_schema = {} |
| 32 | for key, val in ScatParser.schema().items(): |
| 33 | resource_schema['scat_parser.%s' % key] = val |
| 34 | schema.register_resource_schema('modem', resource_schema) |
| 35 | |
| 36 | |
| 37 | class QcDiag(AndroidHost): |
| 38 | |
| 39 | DIAG_PARSER = 'osmo-gsm-tester_androidue_diag_parser.sh' |
| 40 | |
| 41 | ############## |
| 42 | # PROTECTED |
| 43 | ############## |
| 44 | def __init__(self, testenv, conf): |
| 45 | self._run_node = RunNode.from_conf(conf.get('run_node', {})) |
| 46 | super().__init__('qcdiag_%s' % self._run_node.run_addr()) |
| 47 | self.testenv = testenv |
| 48 | self.run_dir = util.Dir(self.testenv.test().get_run_dir().new_dir(self.name())) |
| 49 | if not self._run_node.is_local(): |
| 50 | self.rem_host = remote.RemoteHost(self.run_dir, self._run_node.ssh_user(), self._run_node.ssh_addr(), None, |
| 51 | self._run_node.ssh_port()) |
| 52 | self.remote_run_dir = util.Dir(ms_android.AndroidUE.REMOTEDIR) |
| 53 | self.scat_parser = ScatParser(testenv, conf) |
| 54 | testenv.register_for_cleanup(self.scat_parser) |
| 55 | self.diag_monitor_proc = None |
| 56 | self.enable_pcap = util.str2bool(conf.get('enable_pcap', 'false')) |
| 57 | |
| 58 | ######################## |
| 59 | # PUBLIC - INTERNAL API |
| 60 | ######################## |
| 61 | def get_rrc_state(self): |
| 62 | scat_parser_stdout_l = self.scat_parser.get_stdout().split('\n') |
| 63 | # Find the first "Pulling new .qmdl file..." and check the state afterwards. This has to be done to |
| 64 | # ensure that no process is reading the ScatParser's stdout while the parser is still writing to it. |
| 65 | is_full_block = False |
| 66 | for line in reversed(scat_parser_stdout_l): |
| 67 | if 'Pulling new .qmdl file...' in line: |
| 68 | is_full_block = True |
| 69 | if is_full_block and 'LTE_RRC_STATE_CHANGE' in line: |
| 70 | rrc_state = line.split(' ')[-1].replace('rrc_state=', '') |
| 71 | rrc_state.replace('\'', '') |
| 72 | return rrc_state |
| 73 | return '' |
| 74 | |
| 75 | def get_paging_counter(self): |
| 76 | diag_parser_stdout_l = self.scat_parser.get_stdout().split('\n') |
| 77 | return diag_parser_stdout_l.count('Paging received') |
| 78 | |
| 79 | def running(self): |
| 80 | return self.diag_monitor_proc.is_running() |
| 81 | |
| 82 | def write_pcap(self, restart=False): |
| 83 | self.scat_parser.write_pcap(restart) |
| 84 | |
| 85 | def start(self): |
| 86 | popen_args_diag = ['/vendor/bin/diag_mdlog', '-s', '90000', '-f', '/data/local/tmp/ogt_diag.cfg', |
| 87 | '-o', '/data/local/tmp/diag_logs'] |
| 88 | self.diag_monitor_proc = self.run_androidue_cmd('start-diag-monitor_%s' % self._run_node.adb_serial_id(), popen_args_diag) |
| 89 | self.testenv.remember_to_stop(self.diag_monitor_proc) |
| 90 | self.diag_monitor_proc.launch() |
| 91 | |
| 92 | self.scat_parser.configure(self._run_node, self.enable_pcap) |
| 93 | self.scat_parser.start() |
| 94 | |
| 95 | def scp_back_pcap(self): |
| 96 | self.scat_parser.scp_back_pcap() |
| 97 | |
| 98 | |
| 99 | class ScatParser(AndroidHost): |
| 100 | ############## |
| 101 | # PROTECTED |
| 102 | ############## |
| 103 | def __init__(self, testenv, conf): |
| 104 | self.testenv = testenv |
| 105 | self._run_node = RunNode.from_conf(conf.get('scat_parser', {})) |
| 106 | super().__init__('scat_parser_%s' % self._run_node.run_addr()) |
| 107 | self.run_dir = util.Dir(self.testenv.test().get_run_dir().new_dir(self.name())) |
| 108 | self.remote_run_dir = None |
| 109 | self.rem_host = None |
| 110 | self.pcap_file = None |
| 111 | self.remote_pcap_file = None |
| 112 | self.parser_proc = None |
| 113 | self._parser_proc = None |
| 114 | self.popen_args_diag_parser = None |
| 115 | self._run_node_ue = None |
| 116 | self.enable_pcap = False |
| 117 | |
| 118 | def _clear_diag_files(self): |
| 119 | name_chown = 'chown-diag-files' |
| 120 | diag_dir_local = str(self.run_dir) + '/diag_logs/' |
| 121 | diag_dir_remote = str(self.remote_run_dir) + '/diag_logs/' |
| 122 | popen_args_change_owner = ['sudo', 'chown', '-R', '', ''] |
| 123 | run_dir_chown = self.run_dir.new_dir(name_chown) |
| 124 | if self._run_node.is_local(): |
| 125 | if os.path.exists(diag_dir_local): |
| 126 | # Due to errors the diag_logs dir can be non-existing. To avoid errors the path |
| 127 | # is checked for existence first. |
| 128 | popen_args_change_owner[3] = getpass.getuser() |
| 129 | popen_args_change_owner[4] = diag_dir_local |
| 130 | change_owner_proc = process.Process(name_chown, run_dir_chown, popen_args_change_owner) |
| 131 | change_owner_proc.launch_sync() |
| 132 | else: |
| 133 | popen_args_change_owner = ['sudo', 'chown', '-R', self.rem_host.user(), diag_dir_remote] |
| 134 | change_owner_proc = self.rem_host.RemoteProcess(name_chown, popen_args_change_owner, remote_env={}) |
| 135 | change_owner_proc.launch_sync() |
| 136 | |
| 137 | name_clear = 'clear-diag-files' |
| 138 | run_dir_clear = self.run_dir.new_dir(name_clear) |
| 139 | popen_args_clear_diag_files = ['rm', '-r', ''] |
| 140 | if self._run_node.is_local(): |
| 141 | popen_args_clear_diag_files[2] = diag_dir_local |
| 142 | clear_run_dir_proc = process.Process(name_clear, run_dir_clear, popen_args_clear_diag_files) |
| 143 | else: |
| 144 | popen_args_clear_diag_files[2] = diag_dir_remote |
| 145 | clear_run_dir_proc = self.rem_host.RemoteProcess(name_clear, popen_args_clear_diag_files, remote_env={}) |
| 146 | clear_run_dir_proc.launch_sync() |
| 147 | |
| 148 | ######################## |
| 149 | # PUBLIC - INTERNAL API |
| 150 | ######################## |
| 151 | @classmethod |
| 152 | def schema(cls): |
| 153 | resource_schema = { |
| 154 | 'run_type': schema.STR, |
| 155 | 'run_addr': schema.IPV4, |
| 156 | 'ssh_user': schema.STR, |
| 157 | 'ssh_addr': schema.IPV4, |
| 158 | 'run_label': schema.STR, |
| 159 | 'ssh_port': schema.STR, |
| 160 | 'adb_serial_id': schema.STR, |
| 161 | } |
| 162 | return resource_schema |
| 163 | |
| 164 | def configure(self, run_node, enable_pcap): |
| 165 | self.enable_pcap = enable_pcap |
| 166 | self._run_node_ue = run_node |
| 167 | |
| 168 | if not self._run_node.is_local(): |
| 169 | self.rem_host = remote.RemoteHost(self.run_dir, self._run_node.ssh_user(), self._run_node.ssh_addr()) |
| 170 | self.remote_run_dir = util.Dir(ms_android.AndroidUE.REMOTEDIR) |
| 171 | self.remote_pcap_file = self.remote_run_dir.child(ms_android.AndroidUE.PCAPFILE) |
| 172 | self.pcap_file = self.run_dir.child(ms_android.AndroidUE.PCAPFILE) |
| 173 | |
| 174 | def start(self): |
| 175 | # format: osmo-gsm-tester_androidue_diag_parser.sh $serial $run_dir $pcap_path $remote_ip $remote_port |
| 176 | self.popen_args_diag_parser = [QcDiag.DIAG_PARSER, '', '', '', '', ''] |
| 177 | if self._run_node_ue.is_local(): |
| 178 | if not self._run_node.is_local(): |
| 179 | # AndroidUE is attached to Master but ScatParser is running remote |
| 180 | raise log.Error('Running the network locally and the ScatParser remotely is currently not supported') |
| 181 | else: |
| 182 | # Master, ScatParser, and AndroidUE are attached to/running on the same host |
| 183 | self.popen_args_diag_parser[1] = str(self._run_node.adb_serial_id()) # adb serial |
| 184 | self.popen_args_diag_parser[2] = str(self.run_dir) # run dir path |
| 185 | self.popen_args_diag_parser[3] = str(self.pcap_file) # pcap file path |
| 186 | self.popen_args_diag_parser[4] = '0' # remote ip |
| 187 | self.popen_args_diag_parser[5] = '0' # remote port |
| 188 | else: |
| 189 | if self._run_node.is_local(): |
| 190 | # Master and ScatParser running on the same machine, the AndroidUE runs remote |
| 191 | self.popen_args_diag_parser[1] = '0' # adb serial |
| 192 | self.popen_args_diag_parser[2] = str(self.run_dir) # run dir path |
| 193 | self.popen_args_diag_parser[3] = str(self.pcap_file) # pcap file path |
| 194 | self.popen_args_diag_parser[4] = str(self._run_node_ue.ssh_addr()) # remote ip AndroidUE |
| 195 | self.popen_args_diag_parser[5] = str(self._run_node_ue.ssh_port()) # remote port AndroidUE |
| 196 | elif self._run_node.ssh_addr() == self._run_node_ue.ssh_addr(): |
| 197 | # ScatParser and AndroidUE are remote but on the same machine |
| 198 | self.popen_args_diag_parser[1] = str(self._run_node.adb_serial_id()) # adb serial |
| 199 | self.popen_args_diag_parser[2] = str(self.remote_run_dir) # run dir path |
| 200 | self.popen_args_diag_parser[3] = str(self.remote_pcap_file) # pcap file path |
| 201 | self.popen_args_diag_parser[4] = '0' # remote ip |
| 202 | self.popen_args_diag_parser[5] = '0' # remote port |
| 203 | else: |
| 204 | # Master, ScatParser and AndroidUE are running on/attached to different machines |
| 205 | self.popen_args_diag_parser[1] = '0' # adb serial |
| 206 | self.popen_args_diag_parser[2] = str(self.remote_run_dir) # run dir path |
| 207 | self.popen_args_diag_parser[3] = str(self.remote_pcap_file) # pcap file path |
| 208 | self.popen_args_diag_parser[4] = str(self._run_node_ue.ssh_addr()) # remote ip AndroidUE |
| 209 | self.popen_args_diag_parser[5] = str(self._run_node_ue.ssh_port()) # remote port AndroidUE |
| 210 | |
| 211 | if not self._run_node.is_local(): |
| 212 | # The diag_logs directory only exists here if the ScatParser entity is running remote |
| 213 | self._clear_diag_files() |
| 214 | |
| 215 | name = 'scat_parser_%s' % self._run_node.run_addr() |
| 216 | if self._run_node.is_local(): |
| 217 | run_dir = self.run_dir.new_dir(name) |
| 218 | self.parser_proc = process.Process(name, run_dir, self.popen_args_diag_parser) |
| 219 | else: |
| 220 | self.parser_proc = self.rem_host.RemoteProcess(name, self.popen_args_diag_parser, remote_env={}) |
| 221 | self.testenv.remember_to_stop(self.parser_proc) |
| 222 | self.parser_proc.launch() |
| 223 | |
| 224 | def stop(self): |
| 225 | self.testenv.stop_process(self.parser_proc) |
| 226 | |
| 227 | def write_pcap(self, restart=False): |
| 228 | # We need to stop the diag_parser to avoid pulling a new .qmdl during |
| 229 | # the parsing process. The process can be restarted afterwards but keep in |
| 230 | # mind that this will overwrite the pcap after some time. The diag_monitor |
| 231 | # process can continue, as it does not hinder this process. |
| 232 | if self.parser_proc and self.parser_proc.is_running(): |
| 233 | self.testenv.stop_process(self.parser_proc) |
| 234 | self._clear_diag_files() |
| 235 | |
| 236 | name = 'write-pcap_%s' % self._run_node.run_addr() |
| 237 | if self._run_node.is_local(): |
| 238 | run_dir = self.run_dir.new_dir(name) |
| 239 | self._parser_proc = process.Process(name, run_dir, self.popen_args_diag_parser) |
| 240 | else: |
| 241 | self._parser_proc = self.rem_host.RemoteProcess(name, self.popen_args_diag_parser, remote_env={}) |
| 242 | self.testenv.remember_to_stop(self._parser_proc) |
| 243 | self._parser_proc.launch() |
| 244 | |
| 245 | MainLoop.wait(self.finished_parsing, timestep=0.1, timeout=300) |
| 246 | |
| 247 | if restart: |
| 248 | self.parser_proc = self._parser_proc |
| 249 | else: |
| 250 | self.testenv.stop_process(self._parser_proc) |
| 251 | |
| 252 | def finished_parsing(self): |
| 253 | scat_parser_stdout = self._parser_proc.get_stdout() |
| 254 | # If the parsers pulls the .qmdl file for the second time we know that |
| 255 | # the parsing of the first one is done |
| 256 | return scat_parser_stdout.count('Pulling new .qmdl file...') > 1 |
| 257 | |
| 258 | def get_stdout(self): |
| 259 | return self.parser_proc.get_stdout() |
| 260 | |
| 261 | def is_running(self): |
| 262 | return self.parser_proc.is_running() |
| 263 | |
| 264 | def scp_back_pcap(self): |
| 265 | try: |
| 266 | self.rem_host.scpfrom('scp-back-pcap', self.remote_pcap_file, self.pcap_file) |
| 267 | except Exception as e: |
| 268 | self.log(repr(e)) |
| 269 | |
| 270 | def cleanup(self): |
| 271 | if self.enable_pcap: |
| 272 | self.write_pcap(restart=False) |
| 273 | if not self._run_node.is_local(): |
| 274 | self.scp_back_pcap() |