blob: a2029b4c58ce90f070e79ba0f571e0259fe77896 [file] [log] [blame]
Nils Fürstea8263f42020-11-23 14:45:15 +01001# 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
21import getpass
22import os
23from ..core import remote, util, process, schema, log
24from ..core.event_loop import MainLoop
25from . import ms_android
26from .android_host import AndroidHost
27from .run_node import RunNode
28
29
30def 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
37class 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
99class 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()