From 1b28a584eda2d2292ec0b73094bfaecb96b0c49f Mon Sep 17 00:00:00 2001 From: Pau Espin Pedrol Date: Thu, 8 Mar 2018 21:01:26 +0100 Subject: Introduce ip.access nanobts support Change-Id: Ibaea025b3a503dfe897d36701234445de6d49f82 --- example/defaults.conf | 4 + example/resources.conf.prod | 11 ++ example/resources.conf.rnd | 11 ++ example/scenarios/nanobts.conf | 3 + src/osmo_gsm_tester/bts_nanobts.py | 292 +++++++++++++++++++++++++++++++++++++ src/osmo_gsm_tester/resource.py | 7 +- src/osmo_gsm_tester/suite.py | 5 + 7 files changed, 332 insertions(+), 1 deletion(-) create mode 100644 example/scenarios/nanobts.conf create mode 100644 src/osmo_gsm_tester/bts_nanobts.py diff --git a/example/defaults.conf b/example/defaults.conf index c110dde..45c734a 100644 --- a/example/defaults.conf +++ b/example/defaults.conf @@ -41,3 +41,7 @@ bsc_bts: - phys_chan_config: TCH/F - phys_chan_config: PDCH - phys_chan_config: PDCH + +nanobts: + trx_list: + - arfcn: 531 diff --git a/example/resources.conf.prod b/example/resources.conf.prod index 1e8da53..9f815b8 100644 --- a/example/resources.conf.prod +++ b/example/resources.conf.prod @@ -44,6 +44,17 @@ bts: - hw_addr: 00:0c:90:2e:80:1e net_device: enp2s0 +- label: NanoBTS + type: nanobts + ipa_unit_id: 9 + addr: 10.42.42.53 + band: GSM-1900 + net_device: enp2s0 + power_supply: + type: 'sispm' + device: '01:01:4d:98:24' + port: '1' + arfcn: - arfcn: 512 band: GSM-1800 diff --git a/example/resources.conf.rnd b/example/resources.conf.rnd index 4697bdc..ae48081 100644 --- a/example/resources.conf.rnd +++ b/example/resources.conf.rnd @@ -27,6 +27,17 @@ bts: launch_trx: true ciphers: [a5_0, a5_1] +- label: NanoBTS + type: nanobts + ipa_unit_id: 9 + addr: 10.42.42.53 + band: GSM-1900 + net_device: enp2s0 + power_supply: + type: 'sispm' + device: '01:01:55:2e:b6' + port: '1' + arfcn: - arfcn: 512 band: GSM-1800 diff --git a/example/scenarios/nanobts.conf b/example/scenarios/nanobts.conf new file mode 100644 index 0000000..7322288 --- /dev/null +++ b/example/scenarios/nanobts.conf @@ -0,0 +1,3 @@ +resources: + bts: + - type: nanobts diff --git a/src/osmo_gsm_tester/bts_nanobts.py b/src/osmo_gsm_tester/bts_nanobts.py new file mode 100644 index 0000000..f666f7a --- /dev/null +++ b/src/osmo_gsm_tester/bts_nanobts.py @@ -0,0 +1,292 @@ +# osmo_gsm_tester: specifics for running an ip.access nanoBTS +# +# Copyright (C) 2018 by sysmocom - s.f.m.c. GmbH +# +# Author: Pau Espin Pedrol +# +# 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 . + +import os +import pprint +import tempfile +import re +from abc import ABCMeta, abstractmethod +from . import log, config, util, template, process, event_loop, pcap_recorder, bts, pcu +from . import powersupply + +class NanoBts(bts.Bts): + + pwsup = None + _pcu = None +############## +# PROTECTED +############## + def __init__(self, suite_run, conf): + if conf.get('addr') is None: + raise log.Error('No attribute addr provided in conf!') + super().__init__(suite_run, conf, 'nanobts_%s' % conf.get('addr')) + + def _configure(self): + if self.bsc is None: + raise log.Error('BTS needs to be added to a BSC or NITB before it can be configured') + + pwsup_opt = self.conf.get('power_supply', {}) + if not pwsup_opt: + raise log.Error('No power_supply attribute provided in conf!') + pwsup_type = pwsup_opt.get('type') + if not pwsup_type: + raise log.Error('No type attribute provided in power_supply conf!') + + self.pwsup = powersupply.get_instance_by_type(pwsup_type, pwsup_opt) + +######################## +# PUBLIC - INTERNAL API +######################## + + def conf_for_bsc(self): + values = config.get_defaults('bsc_bts') + config.overlay(values, config.get_defaults('nanobts')) + 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) + + config.overlay(values, { 'osmobsc_bts_type': 'nanobts' }) + + self.dbg(conf=values) + return values + + + def cleanup(self): + if self.pwsup: + self.dbg('Powering off NanoBTS') + self.pwsup.power_set(False) + +################### +# PUBLIC (test API included) +################### + + def start(self): + for attr in ['net_device', 'ipa_unit_id']: + if self.conf.get(attr) is None: + raise log.Error('No attribute %s provided in conf!' % attr) + self.run_dir = util.Dir(self.suite_run.get_test_run_dir().new_dir(self.name())) + self._configure() + + iface = self.conf.get('net_device') + unitid = int(self.conf.get('ipa_unit_id')) + bts_ip = self.remote_addr() + + # Make sure nanoBTS is powered and in a clean state: + self.pwsup.power_cycle(1.0) + + pcap_recorder.PcapRecorder(self.suite_run, self.run_dir.new_dir('pcap'), iface, + 'host %s and port not 22' % self.remote_addr()) + + self.log('Finding nanobts %s...' % bts_ip) + ipfind = AbisIpFind(self.suite_run, self.run_dir, iface, 'preconf') + ipfind.start() + ipfind.wait_bts_ready(bts_ip) + running_unitid = ipfind.get_unitid_by_ip(bts_ip) + self.log('Found nanobts %s with unit_id %d' % (bts_ip, running_unitid)) + ipfind.stop() + + ipconfig = IpAccessConfig(self.suite_run, self.run_dir, bts_ip) + if running_unitid != unitid: + if not ipconfig.set_unit_id(unitid, False): + raise log.Error('Failed configuring unit id %d' % unitid) + # Apply OML IP and restart nanoBTS as it is required to apply the changes. + if not ipconfig.set_oml_ip(self.bsc.addr(), True): + raise log.Error('Failed configuring OML IP %s' % bts_ip) + + # Let some time for BTS to restart. It takes much more than 20 secs, and + # this way we make sure we don't catch responses in abisip-find prior to + # BTS restarting. + event_loop.sleep(self, 20) + + self.log('Starting to connect to', self.bsc) + ipfind = AbisIpFind(self.suite_run, self.run_dir, iface, 'postconf') + ipfind.start() + ipfind.wait_bts_ready(bts_ip) + self.log('nanoBTS configured and running') + ipfind.stop() + + event_loop.wait(self, self.bsc.bts_is_connected, self, timeout=600) + self.log('nanoBTS connected to BSC') + + #According to roh, it can be configured to use a static IP in a permanent way: + # 1- use abisip-find to find the default address + # 2- use ./ipaccess-config --ip-address IP/MASK + # 3- use ./ipaccess-config --ip-gateway IP to set the IP of the main unit + # 4- use ./ipaccess-config --restart to restart and apply the changes + + #Start must do the following: + # 1- use abisip-find to find the default address + # 2- use ./ipaccess-config --unit-id UNIT_ID + # 3- use ./ipaccess-config --oml-ip --restart to set the IP of the BSC and apply+restart. + # According to roh, using the 3 of them together was not reliable to work properly. + + def ready_for_pcu(self): + """We don't really care as we use a Dummy PCU class.""" + return True + + def pcu(self): + if not self._pcu: + self._pcu = pcu.PcuDummy(self.suite_run, self, self.conf) + return self._pcu + + +class AbisIpFind(log.Origin): + suite_run = None + parent_run_dir = None + run_dir = None + inst = None + env = None + iface = None + proc = None + + BIN_ABISIP_FIND = 'abisip-find' + BTS_UNIT_ID_RE = re.compile("Unit_ID='(?P\d+)/\d+/\d+'") + + def __init__(self, suite_run, parent_run_dir, iface, name_suffix): + super().__init__(log.C_RUN, AbisIpFind.BIN_ABISIP_FIND + '-' + name_suffix) + self.suite_run = suite_run + self.parent_run_dir = parent_run_dir + self.iface = iface + self.env = {} + + def launch_process(self, binary_name, *args): + binary = os.path.abspath(self.inst.child('bin', binary_name)) + run_dir = self.run_dir.new_dir(binary_name) + if not os.path.isfile(binary): + raise RuntimeError('Binary missing: %r' % binary) + proc = process.Process(binary_name, run_dir, + (binary,) + args, + env=self.env) + self.suite_run.remember_to_stop(proc) + proc.launch() + return proc + + def start(self): + self.run_dir = util.Dir(self.parent_run_dir.new_dir(self.name())) + self.inst = util.Dir(os.path.abspath(self.suite_run.trial.get_inst('osmo-bsc'))) + lib = self.inst.child('lib') + if not os.path.isdir(lib): + raise log.Error('No lib/ in %r' % self.inst) + ipfind_path = self.inst.child('bin', AbisIpFind.BIN_ABISIP_FIND) + # setting capabilities will later disable use of LD_LIBRARY_PATH from ELF loader -> modify RPATH instead. + self.log('Setting RPATH for', AbisIpFind.BIN_ABISIP_FIND) + util.change_elf_rpath(ipfind_path, util.prepend_library_path(lib), self.run_dir.new_dir('patchelf')) + # osmo-bty-octphy requires CAP_NET_RAW to open AF_PACKET socket: + self.log('Applying CAP_NET_RAW capability to', AbisIpFind.BIN_ABISIP_FIND) + util.setcap_net_raw(ipfind_path, self.run_dir.new_dir('setcap_net_raw')) + self.proc = self.launch_process(AbisIpFind.BIN_ABISIP_FIND, self.iface) + + def stop(self): + self.suite_run.stop_process(self.proc) + + def get_line_by_ip(self, ipaddr): + """Get latest line (more up to date) from abisip-find based on ip address.""" + token = "IP_Address='%s'" % ipaddr + myline = None + for line in (self.proc.get_stdout() or '').splitlines(): + if token in line: + myline = line + return myline + + def get_unitid_by_ip(self, ipaddr): + line = self.get_line_by_ip(ipaddr) + if line is None: + return None + res = AbisIpFind.BTS_UNIT_ID_RE.search(line) + if res: + unit_id = int(res.group('unit_id')) + return unit_id + raise log.Error('abisip-find unit_id field for nanobts %s not found in %s' %(ipaddr, line)) + + def bts_ready(self, ipaddr): + return self.get_line_by_ip(ipaddr) is not None + + def wait_bts_ready(self, ipaddr): + event_loop.wait(self, self.bts_ready, ipaddr) + + +class IpAccessConfig(log.Origin): + suite_run = None + parent_run_dir = None + run_dir = None + inst = None + env = None + bts_ip = None + + BIN_IPACCESS_CONFIG = 'ipaccess-config' + + def __init__(self, suite_run, parent_run_dir, bts_ip): + super().__init__(log.C_RUN, IpAccessConfig.BIN_IPACCESS_CONFIG) + self.suite_run = suite_run + self.parent_run_dir = parent_run_dir + self.bts_ip = bts_ip + self.env = {} + + def launch_process(self, binary_name, *args): + binary = os.path.abspath(self.inst.child('bin', binary_name)) + run_dir = self.run_dir.new_dir(binary_name) + if not os.path.isfile(binary): + raise RuntimeError('Binary missing: %r' % binary) + proc = process.Process(binary_name, run_dir, + (binary,) + args, + env=self.env) + proc.launch() + return proc + + def run(self, name_suffix, *args): + self.run_dir = util.Dir(self.parent_run_dir.new_dir(self.name()+'-'+name_suffix)) + self.inst = util.Dir(os.path.abspath(self.suite_run.trial.get_inst('osmo-bsc'))) + lib = self.inst.child('lib') + self.env = { 'LD_LIBRARY_PATH': util.prepend_library_path(lib) } + self.proc = self.launch_process(IpAccessConfig.BIN_IPACCESS_CONFIG, *args) + try: + event_loop.wait(self, self.proc.terminated) + except Exception as e: + self.proc.terminate() + raise e + return self.proc.result + + def set_unit_id(self, unitid, restart=False): + if restart: + retcode = self.run('unitid', '--restart', '--unit-id', '%d/0/0' % unitid, self.bts_ip) + else: + retcode = self.run('unitid', '--unit-id', '%d/0/0' % unitid, self.bts_ip) + if retcode != 0: + log.err('ipaccess-config --unit-id %d/0/0 returned error code %d' % (unitid, retcode)) + return retcode == 0 + + def set_oml_ip(self, omlip, restart=False): + if restart: + retcode = self.run('oml', '--restart', '--oml-ip', omlip, self.bts_ip) + else: + retcode = self.run('oml', '--oml-ip', omlip, self.bts_ip) + if retcode != 0: + self.error('ipaccess-config --oml-ip %s returned error code %d' % (omlip, retcode)) + return retcode == 0 + +# vim: expandtab tabstop=4 shiftwidth=4 diff --git a/src/osmo_gsm_tester/resource.py b/src/osmo_gsm_tester/resource.py index 4acf0f6..29ede83 100644 --- a/src/osmo_gsm_tester/resource.py +++ b/src/osmo_gsm_tester/resource.py @@ -29,7 +29,7 @@ from . import util from . import schema from . import modem from . import osmo_nitb -from . import bts_sysmo, bts_osmotrx, bts_octphy +from . import bts_sysmo, bts_osmotrx, bts_octphy, bts_nanobts from .util import is_dict, is_list @@ -56,6 +56,10 @@ RESOURCES_SCHEMA = { 'bts[].trx_remote_ip': schema.IPV4, 'bts[].launch_trx': schema.BOOL_STR, 'bts[].direct_pcu': schema.BOOL_STR, + 'bts[].power_supply.type': schema.STR, + 'bts[].power_supply.device': schema.STR, + 'bts[].power_supply.port': schema.STR, + 'bts[].net_device': schema.STR, 'bts[].ciphers[]': schema.CIPHER, 'bts[].trx_list[].hw_addr': schema.HWADDR, 'bts[].trx_list[].net_device': schema.STR, @@ -84,6 +88,7 @@ KNOWN_BTS_TYPES = { 'osmo-bts-sysmo': bts_sysmo.SysmoBts, 'osmo-bts-trx': bts_osmotrx.OsmoBtsTrx, 'osmo-bts-octphy': bts_octphy.OsmoBtsOctphy, + 'nanobts': bts_nanobts.NanoBts, } def register_bts_type(name, clazz): diff --git a/src/osmo_gsm_tester/suite.py b/src/osmo_gsm_tester/suite.py index e7c68b7..106994e 100644 --- a/src/osmo_gsm_tester/suite.py +++ b/src/osmo_gsm_tester/suite.py @@ -212,6 +212,11 @@ class SuiteRun(log.Origin): while self._processes: self._processes.pop().terminate() + def stop_process(self, process): + 'Remove process from monitored list and stop it' + self._processes.remove(process) + process.terminate() + def free_resources(self): if self.reserved_resources is None: return -- cgit v1.2.3