osmo-gsm-tester/src/osmo_gsm_tester/obj/qc_diag.py

275 lines
13 KiB
Python

# osmo_gsm_tester: specifics for running Qualcomm diagnostics on an AndroidUE modem
#
# Copyright (C) 2020 by Software Radio Systems Limited
#
# Author: Nils Fürste <nils.fuerste@softwareradiosystems.com>
# Author: Bedran Karakoc <bedran.karakoc@softwareradiosystems.com>
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as
# published by the Free Software Foundation, either version 3 of the
# License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
import getpass
import os
from ..core import remote, util, process, schema, log
from ..core.event_loop import MainLoop
from . import ms_android
from .android_host import AndroidHost
from .run_node import RunNode
def on_register_schemas():
resource_schema = {}
for key, val in ScatParser.schema().items():
resource_schema['scat_parser.%s' % key] = val
schema.register_resource_schema('modem', resource_schema)
class QcDiag(AndroidHost):
DIAG_PARSER = 'osmo-gsm-tester_androidue_diag_parser.sh'
##############
# PROTECTED
##############
def __init__(self, testenv, conf):
self._run_node = RunNode.from_conf(conf.get('run_node', {}))
super().__init__('qcdiag_%s' % self._run_node.run_addr())
self.testenv = testenv
self.run_dir = util.Dir(self.testenv.test().get_run_dir().new_dir(self.name()))
if not self._run_node.is_local():
self.rem_host = remote.RemoteHost(self.run_dir, self._run_node.ssh_user(), self._run_node.ssh_addr(), None,
self._run_node.ssh_port())
self.remote_run_dir = util.Dir(ms_android.AndroidUE.REMOTEDIR)
self.scat_parser = ScatParser(testenv, conf)
testenv.register_for_cleanup(self.scat_parser)
self.diag_monitor_proc = None
self.enable_pcap = util.str2bool(conf.get('enable_pcap', 'false'))
########################
# PUBLIC - INTERNAL API
########################
def get_rrc_state(self):
scat_parser_stdout_l = self.scat_parser.get_stdout().split('\n')
# Find the first "Pulling new .qmdl file..." and check the state afterwards. This has to be done to
# ensure that no process is reading the ScatParser's stdout while the parser is still writing to it.
is_full_block = False
for line in reversed(scat_parser_stdout_l):
if 'Pulling new .qmdl file...' in line:
is_full_block = True
if is_full_block and 'LTE_RRC_STATE_CHANGE' in line:
rrc_state = line.split(' ')[-1].replace('rrc_state=', '')
rrc_state.replace('\'', '')
return rrc_state
return ''
def get_paging_counter(self):
diag_parser_stdout_l = self.scat_parser.get_stdout().split('\n')
return diag_parser_stdout_l.count('Paging received')
def running(self):
return self.diag_monitor_proc.is_running()
def write_pcap(self, restart=False):
self.scat_parser.write_pcap(restart)
def start(self):
popen_args_diag = ['/vendor/bin/diag_mdlog', '-s', '90000', '-f', '/data/local/tmp/ogt_diag.cfg',
'-o', '/data/local/tmp/diag_logs']
self.diag_monitor_proc = self.run_androidue_cmd('start-diag-monitor_%s' % self._run_node.adb_serial_id(), popen_args_diag)
self.testenv.remember_to_stop(self.diag_monitor_proc)
self.diag_monitor_proc.launch()
self.scat_parser.configure(self._run_node, self.enable_pcap)
self.scat_parser.start()
def scp_back_pcap(self):
self.scat_parser.scp_back_pcap()
class ScatParser(AndroidHost):
##############
# PROTECTED
##############
def __init__(self, testenv, conf):
self.testenv = testenv
self._run_node = RunNode.from_conf(conf.get('scat_parser', {}))
super().__init__('scat_parser_%s' % self._run_node.run_addr())
self.run_dir = util.Dir(self.testenv.test().get_run_dir().new_dir(self.name()))
self.remote_run_dir = None
self.rem_host = None
self.pcap_file = None
self.remote_pcap_file = None
self.parser_proc = None
self._parser_proc = None
self.popen_args_diag_parser = None
self._run_node_ue = None
self.enable_pcap = False
def _clear_diag_files(self):
name_chown = 'chown-diag-files'
diag_dir_local = str(self.run_dir) + '/diag_logs/'
diag_dir_remote = str(self.remote_run_dir) + '/diag_logs/'
popen_args_change_owner = ['sudo', 'chown', '-R', '', '']
run_dir_chown = self.run_dir.new_dir(name_chown)
if self._run_node.is_local():
if os.path.exists(diag_dir_local):
# Due to errors the diag_logs dir can be non-existing. To avoid errors the path
# is checked for existence first.
popen_args_change_owner[3] = getpass.getuser()
popen_args_change_owner[4] = diag_dir_local
change_owner_proc = process.Process(name_chown, run_dir_chown, popen_args_change_owner)
change_owner_proc.launch_sync()
else:
popen_args_change_owner = ['sudo', 'chown', '-R', self.rem_host.user(), diag_dir_remote]
change_owner_proc = self.rem_host.RemoteProcess(name_chown, popen_args_change_owner, remote_env={})
change_owner_proc.launch_sync()
name_clear = 'clear-diag-files'
run_dir_clear = self.run_dir.new_dir(name_clear)
popen_args_clear_diag_files = ['rm', '-r', '']
if self._run_node.is_local():
popen_args_clear_diag_files[2] = diag_dir_local
clear_run_dir_proc = process.Process(name_clear, run_dir_clear, popen_args_clear_diag_files)
else:
popen_args_clear_diag_files[2] = diag_dir_remote
clear_run_dir_proc = self.rem_host.RemoteProcess(name_clear, popen_args_clear_diag_files, remote_env={})
clear_run_dir_proc.launch_sync()
########################
# PUBLIC - INTERNAL API
########################
@classmethod
def schema(cls):
resource_schema = {
'run_type': schema.STR,
'run_addr': schema.IPV4,
'ssh_user': schema.STR,
'ssh_addr': schema.IPV4,
'run_label': schema.STR,
'ssh_port': schema.STR,
'adb_serial_id': schema.STR,
}
return resource_schema
def configure(self, run_node, enable_pcap):
self.enable_pcap = enable_pcap
self._run_node_ue = run_node
if not self._run_node.is_local():
self.rem_host = remote.RemoteHost(self.run_dir, self._run_node.ssh_user(), self._run_node.ssh_addr())
self.remote_run_dir = util.Dir(ms_android.AndroidUE.REMOTEDIR)
self.remote_pcap_file = self.remote_run_dir.child(ms_android.AndroidUE.PCAPFILE)
self.pcap_file = self.run_dir.child(ms_android.AndroidUE.PCAPFILE)
def start(self):
# format: osmo-gsm-tester_androidue_diag_parser.sh $serial $run_dir $pcap_path $remote_ip $remote_port
self.popen_args_diag_parser = [QcDiag.DIAG_PARSER, '', '', '', '', '']
if self._run_node_ue.is_local():
if not self._run_node.is_local():
# AndroidUE is attached to Master but ScatParser is running remote
raise log.Error('Running the network locally and the ScatParser remotely is currently not supported')
else:
# Master, ScatParser, and AndroidUE are attached to/running on the same host
self.popen_args_diag_parser[1] = str(self._run_node.adb_serial_id()) # adb serial
self.popen_args_diag_parser[2] = str(self.run_dir) # run dir path
self.popen_args_diag_parser[3] = str(self.pcap_file) # pcap file path
self.popen_args_diag_parser[4] = '0' # remote ip
self.popen_args_diag_parser[5] = '0' # remote port
else:
if self._run_node.is_local():
# Master and ScatParser running on the same machine, the AndroidUE runs remote
self.popen_args_diag_parser[1] = '0' # adb serial
self.popen_args_diag_parser[2] = str(self.run_dir) # run dir path
self.popen_args_diag_parser[3] = str(self.pcap_file) # pcap file path
self.popen_args_diag_parser[4] = str(self._run_node_ue.ssh_addr()) # remote ip AndroidUE
self.popen_args_diag_parser[5] = str(self._run_node_ue.ssh_port()) # remote port AndroidUE
elif self._run_node.ssh_addr() == self._run_node_ue.ssh_addr():
# ScatParser and AndroidUE are remote but on the same machine
self.popen_args_diag_parser[1] = str(self._run_node.adb_serial_id()) # adb serial
self.popen_args_diag_parser[2] = str(self.remote_run_dir) # run dir path
self.popen_args_diag_parser[3] = str(self.remote_pcap_file) # pcap file path
self.popen_args_diag_parser[4] = '0' # remote ip
self.popen_args_diag_parser[5] = '0' # remote port
else:
# Master, ScatParser and AndroidUE are running on/attached to different machines
self.popen_args_diag_parser[1] = '0' # adb serial
self.popen_args_diag_parser[2] = str(self.remote_run_dir) # run dir path
self.popen_args_diag_parser[3] = str(self.remote_pcap_file) # pcap file path
self.popen_args_diag_parser[4] = str(self._run_node_ue.ssh_addr()) # remote ip AndroidUE
self.popen_args_diag_parser[5] = str(self._run_node_ue.ssh_port()) # remote port AndroidUE
if not self._run_node.is_local():
# The diag_logs directory only exists here if the ScatParser entity is running remote
self._clear_diag_files()
name = 'scat_parser_%s' % self._run_node.run_addr()
if self._run_node.is_local():
run_dir = self.run_dir.new_dir(name)
self.parser_proc = process.Process(name, run_dir, self.popen_args_diag_parser)
else:
self.parser_proc = self.rem_host.RemoteProcess(name, self.popen_args_diag_parser, remote_env={})
self.testenv.remember_to_stop(self.parser_proc)
self.parser_proc.launch()
def stop(self):
self.testenv.stop_process(self.parser_proc)
def write_pcap(self, restart=False):
# We need to stop the diag_parser to avoid pulling a new .qmdl during
# the parsing process. The process can be restarted afterwards but keep in
# mind that this will overwrite the pcap after some time. The diag_monitor
# process can continue, as it does not hinder this process.
if self.parser_proc and self.parser_proc.is_running():
self.testenv.stop_process(self.parser_proc)
self._clear_diag_files()
name = 'write-pcap_%s' % self._run_node.run_addr()
if self._run_node.is_local():
run_dir = self.run_dir.new_dir(name)
self._parser_proc = process.Process(name, run_dir, self.popen_args_diag_parser)
else:
self._parser_proc = self.rem_host.RemoteProcess(name, self.popen_args_diag_parser, remote_env={})
self.testenv.remember_to_stop(self._parser_proc)
self._parser_proc.launch()
MainLoop.wait(self.finished_parsing, timestep=0.1, timeout=300)
if restart:
self.parser_proc = self._parser_proc
else:
self.testenv.stop_process(self._parser_proc)
def finished_parsing(self):
scat_parser_stdout = self._parser_proc.get_stdout()
# If the parsers pulls the .qmdl file for the second time we know that
# the parsing of the first one is done
return scat_parser_stdout.count('Pulling new .qmdl file...') > 1
def get_stdout(self):
return self.parser_proc.get_stdout()
def is_running(self):
return self.parser_proc.is_running()
def scp_back_pcap(self):
try:
self.rem_host.scpfrom('scp-back-pcap', self.remote_pcap_file, self.pcap_file)
except Exception as e:
self.log(repr(e))
def cleanup(self):
if self.enable_pcap:
self.write_pcap(restart=False)
if not self._run_node.is_local():
self.scp_back_pcap()