osmo-gsm-tester/src/osmo_gsm_tester/bts_sysmo.py

209 lines
7.4 KiB
Python
Raw Normal View History

# osmo_gsm_tester: specifics for running a sysmoBTS
#
# Copyright (C) 2016-2017 by sysmocom - s.f.m.c. GmbH
#
# Author: Neels Hofmeyr <neels@hofmeyr.de>
#
# 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 os
import pprint
from . import log, config, util, template, process, pcu_sysmo
class SysmoBts(log.Origin):
suite_run = None
bsc = None
sgsn = None
run_dir = None
inst = None
remote_inst = None
remote_env = None
remote_dir = None
lac = None
rac = None
cellid = None
bvci = None
proc_bts = None
_pcu = None
REMOTE_DIR = '/osmo-gsm-tester-bts'
BTS_SYSMO_BIN = 'osmo-bts-sysmo'
BTS_SYSMO_CFG = 'osmo-bts-sysmo.cfg'
def __init__(self, suite_run, conf):
fix and refactor logging: drop 'with', simplify With the recent fix of the junit report related issues, another issue arose: the 'with log.Origin' was changed to disallow __enter__ing an object twice to fix problems, now still code would fail because it tries to do 'with' on the same object twice. The only reason is to ensure that logging is associated with a given object. Instead of complicating even more, implement differently. Refactor logging to simplify use: drop the 'with Origin' style completely, and instead use the python stack to determine which objects are created by which, and which object to associate a log statement with. The new way: we rely on the convention that each class instance has a local 'self' referencing the object instance. If we need to find an origin as a new object's parent, or to associate a log message with, we traverse each stack frame, fetching the first local 'self' object that is a log.Origin class instance. How to use: Simply call log.log() anywhere, and it finds an Origin object to log for, from the stack. Alternatively call self.log() for any Origin() object to skip the lookup. Create classes as child class of log.Origin and make sure to call super().__init__(category, name). This constructor will magically find a parent Origin on the stack. When an exception happens, we first escalate the exception up through call scopes to where ever it is handled by log.log_exn(). This then finds an Origin object in the traceback's stack frames, no need to nest in 'with' scopes. Hence the 'with log.Origin' now "happens implicitly", we can write pure natural python code, no more hassles with scope ordering. Furthermore, any frame can place additional logging information in a frame by calling log.ctx(). This is automatically inserted in the ancestry associated with a log statement / exception. Change-Id: I5f9b53150f2bb6fa9d63ce27f0806f0ca6a45e90
2017-06-09 23:18:27 +00:00
super().__init__(log.C_RUN, self.BTS_SYSMO_BIN)
self.suite_run = suite_run
self.conf = conf
self.remote_env = {}
self.remote_user = 'root'
def start(self):
fix and refactor logging: drop 'with', simplify With the recent fix of the junit report related issues, another issue arose: the 'with log.Origin' was changed to disallow __enter__ing an object twice to fix problems, now still code would fail because it tries to do 'with' on the same object twice. The only reason is to ensure that logging is associated with a given object. Instead of complicating even more, implement differently. Refactor logging to simplify use: drop the 'with Origin' style completely, and instead use the python stack to determine which objects are created by which, and which object to associate a log statement with. The new way: we rely on the convention that each class instance has a local 'self' referencing the object instance. If we need to find an origin as a new object's parent, or to associate a log message with, we traverse each stack frame, fetching the first local 'self' object that is a log.Origin class instance. How to use: Simply call log.log() anywhere, and it finds an Origin object to log for, from the stack. Alternatively call self.log() for any Origin() object to skip the lookup. Create classes as child class of log.Origin and make sure to call super().__init__(category, name). This constructor will magically find a parent Origin on the stack. When an exception happens, we first escalate the exception up through call scopes to where ever it is handled by log.log_exn(). This then finds an Origin object in the traceback's stack frames, no need to nest in 'with' scopes. Hence the 'with log.Origin' now "happens implicitly", we can write pure natural python code, no more hassles with scope ordering. Furthermore, any frame can place additional logging information in a frame by calling log.ctx(). This is automatically inserted in the ancestry associated with a log statement / exception. Change-Id: I5f9b53150f2bb6fa9d63ce27f0806f0ca6a45e90
2017-06-09 23:18:27 +00:00
if self.bsc is None:
raise RuntimeError('BTS needs to be added to a BSC or NITB before it can be started')
log.log('Starting sysmoBTS to connect to', self.bsc)
self.run_dir = util.Dir(self.suite_run.get_test_run_dir().new_dir(self.name()))
fix and refactor logging: drop 'with', simplify With the recent fix of the junit report related issues, another issue arose: the 'with log.Origin' was changed to disallow __enter__ing an object twice to fix problems, now still code would fail because it tries to do 'with' on the same object twice. The only reason is to ensure that logging is associated with a given object. Instead of complicating even more, implement differently. Refactor logging to simplify use: drop the 'with Origin' style completely, and instead use the python stack to determine which objects are created by which, and which object to associate a log statement with. The new way: we rely on the convention that each class instance has a local 'self' referencing the object instance. If we need to find an origin as a new object's parent, or to associate a log message with, we traverse each stack frame, fetching the first local 'self' object that is a log.Origin class instance. How to use: Simply call log.log() anywhere, and it finds an Origin object to log for, from the stack. Alternatively call self.log() for any Origin() object to skip the lookup. Create classes as child class of log.Origin and make sure to call super().__init__(category, name). This constructor will magically find a parent Origin on the stack. When an exception happens, we first escalate the exception up through call scopes to where ever it is handled by log.log_exn(). This then finds an Origin object in the traceback's stack frames, no need to nest in 'with' scopes. Hence the 'with log.Origin' now "happens implicitly", we can write pure natural python code, no more hassles with scope ordering. Furthermore, any frame can place additional logging information in a frame by calling log.ctx(). This is automatically inserted in the ancestry associated with a log statement / exception. Change-Id: I5f9b53150f2bb6fa9d63ce27f0806f0ca6a45e90
2017-06-09 23:18:27 +00:00
self.configure()
self.inst = util.Dir(os.path.abspath(self.suite_run.trial.get_inst(SysmoBts.BTS_SYSMO_BIN)))
lib = self.inst.child('lib')
if not os.path.isdir(lib):
raise log.Error('No lib/ in', self.inst)
if not self.inst.isfile('bin', SysmoBts.BTS_SYSMO_BIN):
raise log.Error('No osmo-bts-sysmo binary in', self.inst)
self.remote_dir = util.Dir(SysmoBts.REMOTE_DIR)
self.remote_inst = util.Dir(self.remote_dir.child(os.path.basename(str(self.inst))))
self.run_remote('rm-remote-dir', ('test', '!', '-d', SysmoBts.REMOTE_DIR, '||', 'rm', '-rf', SysmoBts.REMOTE_DIR))
self.run_remote('mk-remote-dir', ('mkdir', '-p', SysmoBts.REMOTE_DIR))
self.run_local('scp-inst-to-sysmobts',
('scp', '-r', str(self.inst), '%s@%s:%s' % (self.remote_user, self.remote_addr(), str(self.remote_inst))))
remote_run_dir = self.remote_dir.child(SysmoBts.BTS_SYSMO_BIN)
self.run_remote('mk-remote-run-dir', ('mkdir', '-p', remote_run_dir))
remote_config_file = self.remote_dir.child(SysmoBts.BTS_SYSMO_CFG)
self.run_local('scp-cfg-to-sysmobts',
('scp', '-r', self.config_file, '%s@%s:%s' % (self.remote_user, self.remote_addr(), remote_config_file)))
self.run_remote('reload-dsp-firmware', ('/bin/sh', '-c', '"cat /lib/firmware/sysmobts-v?.bit > /dev/fpgadl_par0 ; cat /lib/firmware/sysmobts-v?.out > /dev/dspdl_dm644x_0"'))
remote_lib = self.remote_inst.child('lib')
remote_binary = self.remote_inst.child('bin', 'osmo-bts-sysmo')
args = ('LD_LIBRARY_PATH=%s' % remote_lib,
remote_binary, '-c', remote_config_file, '-r', '1',
'-i', self.bsc.addr())
if self._direct_pcu_enabled():
args += ('-M',)
self.proc_bts = self.launch_remote('osmo-bts-sysmo', args, remote_cwd=remote_run_dir)
def cleanup(self):
pass
def _direct_pcu_enabled(self):
return util.str2bool(self.conf.get('direct_pcu'))
def pcu_socket_path(self):
return os.path.join(SysmoBts.REMOTE_DIR, 'pcu_bts')
def _process_remote(self, name, popen_args, remote_cwd=None):
run_dir = self.run_dir.new_dir(name)
return process.RemoteProcess(name, run_dir, self.remote_user, self.remote_addr(), remote_cwd,
popen_args)
def run_remote(self, name, popen_args, remote_cwd=None):
proc = self._process_remote(name, popen_args, remote_cwd)
proc.launch()
proc.wait()
if proc.result != 0:
fix and refactor logging: drop 'with', simplify With the recent fix of the junit report related issues, another issue arose: the 'with log.Origin' was changed to disallow __enter__ing an object twice to fix problems, now still code would fail because it tries to do 'with' on the same object twice. The only reason is to ensure that logging is associated with a given object. Instead of complicating even more, implement differently. Refactor logging to simplify use: drop the 'with Origin' style completely, and instead use the python stack to determine which objects are created by which, and which object to associate a log statement with. The new way: we rely on the convention that each class instance has a local 'self' referencing the object instance. If we need to find an origin as a new object's parent, or to associate a log message with, we traverse each stack frame, fetching the first local 'self' object that is a log.Origin class instance. How to use: Simply call log.log() anywhere, and it finds an Origin object to log for, from the stack. Alternatively call self.log() for any Origin() object to skip the lookup. Create classes as child class of log.Origin and make sure to call super().__init__(category, name). This constructor will magically find a parent Origin on the stack. When an exception happens, we first escalate the exception up through call scopes to where ever it is handled by log.log_exn(). This then finds an Origin object in the traceback's stack frames, no need to nest in 'with' scopes. Hence the 'with log.Origin' now "happens implicitly", we can write pure natural python code, no more hassles with scope ordering. Furthermore, any frame can place additional logging information in a frame by calling log.ctx(). This is automatically inserted in the ancestry associated with a log statement / exception. Change-Id: I5f9b53150f2bb6fa9d63ce27f0806f0ca6a45e90
2017-06-09 23:18:27 +00:00
log.ctx(proc)
raise log.Error('Exited in error')
def launch_remote(self, name, popen_args, remote_cwd=None):
proc = self._process_remote(name, popen_args, remote_cwd)
self.suite_run.remember_to_stop(proc)
proc.launch()
return proc
def run_local(self, name, popen_args):
run_dir = self.run_dir.new_dir(name)
proc = process.Process(name, run_dir, popen_args)
proc.launch()
proc.wait()
if proc.result != 0:
fix and refactor logging: drop 'with', simplify With the recent fix of the junit report related issues, another issue arose: the 'with log.Origin' was changed to disallow __enter__ing an object twice to fix problems, now still code would fail because it tries to do 'with' on the same object twice. The only reason is to ensure that logging is associated with a given object. Instead of complicating even more, implement differently. Refactor logging to simplify use: drop the 'with Origin' style completely, and instead use the python stack to determine which objects are created by which, and which object to associate a log statement with. The new way: we rely on the convention that each class instance has a local 'self' referencing the object instance. If we need to find an origin as a new object's parent, or to associate a log message with, we traverse each stack frame, fetching the first local 'self' object that is a log.Origin class instance. How to use: Simply call log.log() anywhere, and it finds an Origin object to log for, from the stack. Alternatively call self.log() for any Origin() object to skip the lookup. Create classes as child class of log.Origin and make sure to call super().__init__(category, name). This constructor will magically find a parent Origin on the stack. When an exception happens, we first escalate the exception up through call scopes to where ever it is handled by log.log_exn(). This then finds an Origin object in the traceback's stack frames, no need to nest in 'with' scopes. Hence the 'with log.Origin' now "happens implicitly", we can write pure natural python code, no more hassles with scope ordering. Furthermore, any frame can place additional logging information in a frame by calling log.ctx(). This is automatically inserted in the ancestry associated with a log statement / exception. Change-Id: I5f9b53150f2bb6fa9d63ce27f0806f0ca6a45e90
2017-06-09 23:18:27 +00:00
log.ctx(proc)
raise log.Error('Exited in error')
def pcu(self):
if self._pcu is None:
self._pcu = pcu_sysmo.OsmoPcuSysmo(self.suite_run, self, self.conf)
return self._pcu
def remote_addr(self):
return self.conf.get('addr')
def pcu_socket_path(self):
return os.path.join(SysmoBts.REMOTE_DIR, 'pcu_bts')
def configure(self):
if self.bsc is None:
raise RuntimeError('BTS needs to be added to a BSC or NITB before it can be configured')
self.config_file = self.run_dir.new_file(SysmoBts.BTS_SYSMO_CFG)
self.dbg(config_file=self.config_file)
values = { 'osmo_bts_sysmo': config.get_defaults('osmo_bts_sysmo') }
config.overlay(values, self.suite_run.config())
config.overlay(values, {
'osmo_bts_sysmo': {
'oml_remote_ip': self.bsc.addr(),
'pcu_socket_path': self.pcu_socket_path(),
}
})
config.overlay(values, { 'osmo_bts_sysmo': self.conf })
self.dbg('SYSMOBTS CONFIG:\n' + pprint.pformat(values))
with open(self.config_file, 'w') as f:
r = template.render(SysmoBts.BTS_SYSMO_CFG, values)
self.dbg(r)
f.write(r)
def conf_for_bsc(self):
values = config.get_defaults('bsc_bts')
config.overlay(values, config.get_defaults('osmo_bts_sysmo'))
if self.lac is not None:
config.overlay(values, { 'location_area_code': self.lac })
if self.rac is not None:
config.overlay(values, { 'routing_area_code': self.rac })
if self.cellid is not None:
config.overlay(values, { 'cell_identity': self.cellid })
if self.bvci is not None:
config.overlay(values, { 'bvci': self.bvci })
config.overlay(values, self.conf)
sgsn_conf = {} if self.sgsn is None else self.sgsn.conf_for_client()
config.overlay(values, sgsn_conf)
self.dbg(conf=values)
return values
def ready_for_pcu(self):
if not self.proc_bts or not self.proc_bts.is_running:
return False
return 'BTS is up' in (self.proc_bts.get_stderr() or '')
def set_bsc(self, bsc):
self.bsc = bsc
def set_sgsn(self, sgsn):
self.sgsn = sgsn
def set_lac(self, lac):
self.lac = lac
def set_rac(self, rac):
self.rac = rac
def set_cellid(self, cellid):
self.cellid = cellid
def set_bvci(self, bvci):
self.bvci = bvci
# vim: expandtab tabstop=4 shiftwidth=4