Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 1 | # osmo_gsm_tester: specifics for running an ip.access nanoBTS |
| 2 | # |
| 3 | # Copyright (C) 2018 by sysmocom - s.f.m.c. GmbH |
| 4 | # |
| 5 | # Author: Pau Espin Pedrol <pespin@sysmocom.de> |
| 6 | # |
| 7 | # This program is free software: you can redistribute it and/or modify |
| 8 | # it under the terms of the GNU General Public License as |
| 9 | # published by the Free Software Foundation, either version 3 of the |
| 10 | # License, or (at your option) any later version. |
| 11 | # |
| 12 | # This program is distributed in the hope that it will be useful, |
| 13 | # but WITHOUT ANY WARRANTY; without even the implied warranty of |
| 14 | # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
| 15 | # GNU General Public License for more details. |
| 16 | # |
| 17 | # You should have received a copy of the GNU General Public License |
| 18 | # along with this program. If not, see <http://www.gnu.org/licenses/>. |
| 19 | |
| 20 | import os |
| 21 | import pprint |
| 22 | import tempfile |
| 23 | import re |
| 24 | from abc import ABCMeta, abstractmethod |
Pau Espin Pedrol | 9a4631c | 2018-03-28 19:17:34 +0200 | [diff] [blame] | 25 | from . import log, config, util, template, process, pcap_recorder, bts, pcu |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 26 | from . import powersupply |
Pau Espin Pedrol | 9a4631c | 2018-03-28 19:17:34 +0200 | [diff] [blame] | 27 | from .event_loop import MainLoop |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 28 | |
| 29 | class NanoBts(bts.Bts): |
| 30 | |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 31 | ############## |
| 32 | # PROTECTED |
| 33 | ############## |
| 34 | def __init__(self, suite_run, conf): |
Pau Espin Pedrol | f6a0712 | 2018-07-27 16:24:29 +0200 | [diff] [blame] | 35 | super().__init__(suite_run, conf, 'nanobts_%s' % conf.get('label', 'nolabel'), 'nanobts') |
Pau Espin Pedrol | 926a4b8 | 2018-08-09 12:57:03 +0200 | [diff] [blame] | 36 | self.pwsup_list = [] |
| 37 | self._pcu = None |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 38 | |
| 39 | def _configure(self): |
| 40 | if self.bsc is None: |
| 41 | raise log.Error('BTS needs to be added to a BSC or NITB before it can be configured') |
| 42 | |
Pau Espin Pedrol | f6a0712 | 2018-07-27 16:24:29 +0200 | [diff] [blame] | 43 | for trx_i in range(self.num_trx()): |
| 44 | pwsup_opt = self.conf.get('trx_list')[trx_i].get('power_supply', {}) |
| 45 | if not pwsup_opt: |
| 46 | raise log.Error('No power_supply attribute provided in conf for TRX %d!' % trx_i) |
| 47 | pwsup_type = pwsup_opt.get('type') |
| 48 | if not pwsup_type: |
| 49 | raise log.Error('No type attribute provided in power_supply conf for TRX %d!' % trx_i) |
| 50 | self.pwsup_list.append(powersupply.get_instance_by_type(pwsup_type, pwsup_opt)) |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 51 | |
Pau Espin Pedrol | f6a0712 | 2018-07-27 16:24:29 +0200 | [diff] [blame] | 52 | |
| 53 | def get_pcap_filter_all_trx_ip(self): |
| 54 | ret = "(" |
| 55 | for trx_i in range(self.num_trx()): |
| 56 | if trx_i != 0: |
| 57 | ret = ret + " or " |
| 58 | bts_trx_ip = self.conf.get('trx_list')[trx_i].get('addr') |
| 59 | ret = ret + "host " + bts_trx_ip |
| 60 | ret = ret + ")" |
| 61 | return ret |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 62 | |
| 63 | ######################## |
| 64 | # PUBLIC - INTERNAL API |
| 65 | ######################## |
| 66 | |
| 67 | def conf_for_bsc(self): |
Pau Espin Pedrol | e519462 | 2018-05-07 13:36:58 +0200 | [diff] [blame] | 68 | values = self.conf_for_bsc_prepare() |
Pau Espin Pedrol | fef9c1c | 2018-03-27 19:20:47 +0200 | [diff] [blame] | 69 | # Hack until we have proper ARFCN resource allocation support (OS#2230) |
| 70 | band = values.get('band') |
| 71 | trx_list = values.get('trx_list') |
| 72 | if band == 'GSM-1900': |
Pau Espin Pedrol | f6a0712 | 2018-07-27 16:24:29 +0200 | [diff] [blame] | 73 | for trx_i in range(len(trx_list)): |
| 74 | config.overlay(trx_list[trx_i], { 'arfcn' : str(531 + trx_i * 2) }) |
Pau Espin Pedrol | fef9c1c | 2018-03-27 19:20:47 +0200 | [diff] [blame] | 75 | elif band == 'GSM-900': |
Pau Espin Pedrol | f6a0712 | 2018-07-27 16:24:29 +0200 | [diff] [blame] | 76 | for trx_i in range(len(trx_list)): |
| 77 | config.overlay(trx_list[trx_i], { 'arfcn' : str(50 + trx_i * 2) }) |
Pau Espin Pedrol | fef9c1c | 2018-03-27 19:20:47 +0200 | [diff] [blame] | 78 | |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 79 | config.overlay(values, { 'osmobsc_bts_type': 'nanobts' }) |
| 80 | |
| 81 | self.dbg(conf=values) |
| 82 | return values |
| 83 | |
| 84 | |
| 85 | def cleanup(self): |
Pau Espin Pedrol | f6a0712 | 2018-07-27 16:24:29 +0200 | [diff] [blame] | 86 | for pwsup in self.pwsup_list: |
| 87 | self.dbg('Powering off NanoBTS TRX') |
| 88 | pwsup.power_set(False) |
| 89 | self.pwsup_list = [] |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 90 | |
| 91 | ################### |
| 92 | # PUBLIC (test API included) |
| 93 | ################### |
| 94 | |
Pau Espin Pedrol | b1526b9 | 2018-05-22 20:32:30 +0200 | [diff] [blame] | 95 | def start(self, keepalive=False): |
Pau Espin Pedrol | a238ed9 | 2018-03-26 13:06:12 +0200 | [diff] [blame] | 96 | if self.conf.get('ipa_unit_id') is None: |
| 97 | raise log.Error('No attribute %s provided in conf!' % attr) |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 98 | self.run_dir = util.Dir(self.suite_run.get_test_run_dir().new_dir(self.name())) |
| 99 | self._configure() |
| 100 | |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 101 | unitid = int(self.conf.get('ipa_unit_id')) |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 102 | |
Pau Espin Pedrol | f6a0712 | 2018-07-27 16:24:29 +0200 | [diff] [blame] | 103 | # Make sure all nanoBTS TRX are powered and in a clean state: |
| 104 | for pwsup in self.pwsup_list: |
| 105 | self.dbg('Powering cycling NanoBTS TRX') |
| 106 | pwsup.power_cycle(1.0) |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 107 | |
Pau Espin Pedrol | a238ed9 | 2018-03-26 13:06:12 +0200 | [diff] [blame] | 108 | pcap_recorder.PcapRecorder(self.suite_run, self.run_dir.new_dir('pcap'), None, |
Pau Espin Pedrol | f6a0712 | 2018-07-27 16:24:29 +0200 | [diff] [blame] | 109 | '%s and port not 22' % self.get_pcap_filter_all_trx_ip()) |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 110 | |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 111 | |
Pau Espin Pedrol | f6a0712 | 2018-07-27 16:24:29 +0200 | [diff] [blame] | 112 | # TODO: If setting N TRX, we should set up them in parallel instead of waiting for each one. |
| 113 | for trx_i in range(self.num_trx()): |
| 114 | bts_trx_ip = self.conf.get('trx_list')[trx_i].get('addr') |
| 115 | # This fine for now, however concurrent tests using Nanobts may run into "address already in use" since dst is broadcast. |
| 116 | # Once concurrency is needed, a new config attr should be added to have an extra static IP assigned on the main-unit to each Nanobts resource. |
| 117 | local_bind_ip = util.dst_ip_get_local_bind(bts_trx_ip) |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 118 | |
Pau Espin Pedrol | f6a0712 | 2018-07-27 16:24:29 +0200 | [diff] [blame] | 119 | self.log('Finding nanobts %s, binding on %s...' % (bts_trx_ip, local_bind_ip)) |
| 120 | ipfind = AbisIpFind(self.suite_run, self.run_dir, local_bind_ip, 'preconf') |
| 121 | ipfind.start() |
| 122 | ipfind.wait_bts_ready(bts_trx_ip) |
| 123 | running_unitid, running_trx = ipfind.get_unitid_by_ip(bts_trx_ip) |
| 124 | self.log('Found nanobts %s with unit_id %d trx %d' % (bts_trx_ip, running_unitid, running_trx)) |
| 125 | ipfind.stop() |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 126 | |
Pau Espin Pedrol | f6a0712 | 2018-07-27 16:24:29 +0200 | [diff] [blame] | 127 | ipconfig = IpAccessConfig(self.suite_run, self.run_dir, bts_trx_ip) |
| 128 | if running_unitid != unitid or running_trx != trx_i: |
| 129 | if not ipconfig.set_unit_id(unitid, trx_i, False): |
| 130 | raise log.Error('Failed configuring unit id %d trx %d' % (unitid, trx_i)) |
| 131 | # Apply OML IP and restart nanoBTS as it is required to apply the changes. |
| 132 | if not ipconfig.set_oml_ip(self.bsc.addr(), True): |
| 133 | raise log.Error('Failed configuring OML IP %s' % bts_trx_ip) |
| 134 | |
| 135 | # Let some time for BTS to restart. It takes much more than 20 secs, and |
| 136 | # this way we make sure we don't catch responses in abisip-find prior to |
| 137 | # BTS restarting. |
| 138 | MainLoop.sleep(self, 20) |
| 139 | |
| 140 | self.log('Starting to connect id %d trx %d to' % (unitid, trx_i), self.bsc) |
| 141 | ipfind = AbisIpFind(self.suite_run, self.run_dir, local_bind_ip, 'postconf') |
| 142 | ipfind.start() |
| 143 | ipfind.wait_bts_ready(bts_trx_ip) |
| 144 | self.log('nanoBTS id %d trx %d configured and running' % (unitid, trx_i)) |
| 145 | ipfind.stop() |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 146 | |
Pau Espin Pedrol | 9a4631c | 2018-03-28 19:17:34 +0200 | [diff] [blame] | 147 | MainLoop.wait(self, self.bsc.bts_is_connected, self, timeout=600) |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 148 | self.log('nanoBTS connected to BSC') |
| 149 | |
| 150 | #According to roh, it can be configured to use a static IP in a permanent way: |
| 151 | # 1- use abisip-find to find the default address |
| 152 | # 2- use ./ipaccess-config --ip-address IP/MASK |
| 153 | # 3- use ./ipaccess-config --ip-gateway IP to set the IP of the main unit |
| 154 | # 4- use ./ipaccess-config --restart to restart and apply the changes |
| 155 | |
| 156 | #Start must do the following: |
| 157 | # 1- use abisip-find to find the default address |
| 158 | # 2- use ./ipaccess-config --unit-id UNIT_ID |
| 159 | # 3- use ./ipaccess-config --oml-ip --restart to set the IP of the BSC and apply+restart. |
| 160 | # According to roh, using the 3 of them together was not reliable to work properly. |
| 161 | |
| 162 | def ready_for_pcu(self): |
| 163 | """We don't really care as we use a Dummy PCU class.""" |
| 164 | return True |
| 165 | |
| 166 | def pcu(self): |
| 167 | if not self._pcu: |
| 168 | self._pcu = pcu.PcuDummy(self.suite_run, self, self.conf) |
| 169 | return self._pcu |
| 170 | |
| 171 | |
| 172 | class AbisIpFind(log.Origin): |
| 173 | suite_run = None |
| 174 | parent_run_dir = None |
| 175 | run_dir = None |
| 176 | inst = None |
| 177 | env = None |
Pau Espin Pedrol | a238ed9 | 2018-03-26 13:06:12 +0200 | [diff] [blame] | 178 | bind_ip = None |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 179 | proc = None |
| 180 | |
| 181 | BIN_ABISIP_FIND = 'abisip-find' |
Pau Espin Pedrol | f6a0712 | 2018-07-27 16:24:29 +0200 | [diff] [blame] | 182 | BTS_UNIT_ID_RE = re.compile("Unit_ID='(?P<unit_id>\d+)/\d+/(?P<trx_id>\d+)'") |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 183 | |
Pau Espin Pedrol | a238ed9 | 2018-03-26 13:06:12 +0200 | [diff] [blame] | 184 | def __init__(self, suite_run, parent_run_dir, bind_ip, name_suffix): |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 185 | super().__init__(log.C_RUN, AbisIpFind.BIN_ABISIP_FIND + '-' + name_suffix) |
| 186 | self.suite_run = suite_run |
| 187 | self.parent_run_dir = parent_run_dir |
Pau Espin Pedrol | a238ed9 | 2018-03-26 13:06:12 +0200 | [diff] [blame] | 188 | self.bind_ip = bind_ip |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 189 | self.env = {} |
| 190 | |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 191 | def start(self): |
| 192 | self.run_dir = util.Dir(self.parent_run_dir.new_dir(self.name())) |
| 193 | self.inst = util.Dir(os.path.abspath(self.suite_run.trial.get_inst('osmo-bsc'))) |
Pau Espin Pedrol | a238ed9 | 2018-03-26 13:06:12 +0200 | [diff] [blame] | 194 | |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 195 | lib = self.inst.child('lib') |
| 196 | if not os.path.isdir(lib): |
| 197 | raise log.Error('No lib/ in %r' % self.inst) |
| 198 | ipfind_path = self.inst.child('bin', AbisIpFind.BIN_ABISIP_FIND) |
Pau Espin Pedrol | a238ed9 | 2018-03-26 13:06:12 +0200 | [diff] [blame] | 199 | if not os.path.isfile(ipfind_path): |
| 200 | raise RuntimeError('Binary missing: %r' % ipfind_path) |
| 201 | |
| 202 | env = { 'LD_LIBRARY_PATH': util.prepend_library_path(lib) } |
| 203 | self.proc = process.Process(self.name(), self.run_dir, |
Pau Espin Pedrol | 48fce86 | 2018-04-04 12:44:24 +0200 | [diff] [blame] | 204 | (ipfind_path, '-i', '1', '-b', self.bind_ip), |
Pau Espin Pedrol | a238ed9 | 2018-03-26 13:06:12 +0200 | [diff] [blame] | 205 | env=env) |
| 206 | self.suite_run.remember_to_stop(self.proc) |
| 207 | self.proc.launch() |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 208 | |
| 209 | def stop(self): |
| 210 | self.suite_run.stop_process(self.proc) |
| 211 | |
| 212 | def get_line_by_ip(self, ipaddr): |
| 213 | """Get latest line (more up to date) from abisip-find based on ip address.""" |
| 214 | token = "IP_Address='%s'" % ipaddr |
| 215 | myline = None |
| 216 | for line in (self.proc.get_stdout() or '').splitlines(): |
| 217 | if token in line: |
| 218 | myline = line |
| 219 | return myline |
| 220 | |
| 221 | def get_unitid_by_ip(self, ipaddr): |
| 222 | line = self.get_line_by_ip(ipaddr) |
| 223 | if line is None: |
| 224 | return None |
| 225 | res = AbisIpFind.BTS_UNIT_ID_RE.search(line) |
| 226 | if res: |
| 227 | unit_id = int(res.group('unit_id')) |
Pau Espin Pedrol | f6a0712 | 2018-07-27 16:24:29 +0200 | [diff] [blame] | 228 | trx_id = int(res.group('trx_id')) |
| 229 | return (unit_id, trx_id) |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 230 | raise log.Error('abisip-find unit_id field for nanobts %s not found in %s' %(ipaddr, line)) |
| 231 | |
| 232 | def bts_ready(self, ipaddr): |
| 233 | return self.get_line_by_ip(ipaddr) is not None |
| 234 | |
| 235 | def wait_bts_ready(self, ipaddr): |
Pau Espin Pedrol | 9a4631c | 2018-03-28 19:17:34 +0200 | [diff] [blame] | 236 | MainLoop.wait(self, self.bts_ready, ipaddr) |
Pau Espin Pedrol | 48fce86 | 2018-04-04 12:44:24 +0200 | [diff] [blame] | 237 | # There's a period of time after boot in which nanobts answers to |
| 238 | # abisip-find but tcp RSTs ipacces-config conns. Let's wait in here a |
| 239 | # bit more time to avoid failing after stating the BTS is ready. |
| 240 | MainLoop.sleep(self, 2) |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 241 | |
| 242 | class IpAccessConfig(log.Origin): |
| 243 | suite_run = None |
| 244 | parent_run_dir = None |
| 245 | run_dir = None |
| 246 | inst = None |
| 247 | env = None |
| 248 | bts_ip = None |
| 249 | |
| 250 | BIN_IPACCESS_CONFIG = 'ipaccess-config' |
| 251 | |
| 252 | def __init__(self, suite_run, parent_run_dir, bts_ip): |
| 253 | super().__init__(log.C_RUN, IpAccessConfig.BIN_IPACCESS_CONFIG) |
| 254 | self.suite_run = suite_run |
| 255 | self.parent_run_dir = parent_run_dir |
| 256 | self.bts_ip = bts_ip |
| 257 | self.env = {} |
| 258 | |
| 259 | def launch_process(self, binary_name, *args): |
| 260 | binary = os.path.abspath(self.inst.child('bin', binary_name)) |
| 261 | run_dir = self.run_dir.new_dir(binary_name) |
| 262 | if not os.path.isfile(binary): |
| 263 | raise RuntimeError('Binary missing: %r' % binary) |
| 264 | proc = process.Process(binary_name, run_dir, |
| 265 | (binary,) + args, |
| 266 | env=self.env) |
| 267 | proc.launch() |
| 268 | return proc |
| 269 | |
| 270 | def run(self, name_suffix, *args): |
| 271 | self.run_dir = util.Dir(self.parent_run_dir.new_dir(self.name()+'-'+name_suffix)) |
| 272 | self.inst = util.Dir(os.path.abspath(self.suite_run.trial.get_inst('osmo-bsc'))) |
| 273 | lib = self.inst.child('lib') |
| 274 | self.env = { 'LD_LIBRARY_PATH': util.prepend_library_path(lib) } |
| 275 | self.proc = self.launch_process(IpAccessConfig.BIN_IPACCESS_CONFIG, *args) |
| 276 | try: |
Pau Espin Pedrol | 9a4631c | 2018-03-28 19:17:34 +0200 | [diff] [blame] | 277 | MainLoop.wait(self, self.proc.terminated) |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 278 | except Exception as e: |
| 279 | self.proc.terminate() |
| 280 | raise e |
| 281 | return self.proc.result |
| 282 | |
Pau Espin Pedrol | f6a0712 | 2018-07-27 16:24:29 +0200 | [diff] [blame] | 283 | def set_unit_id(self, unitid, trx_num, restart=False): |
| 284 | uid_str = '%d/0/%d' % (unitid, trx_num) |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 285 | if restart: |
Pau Espin Pedrol | f6a0712 | 2018-07-27 16:24:29 +0200 | [diff] [blame] | 286 | retcode = self.run('unitid', '--restart', '--unit-id', '%s' % uid_str, self.bts_ip) |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 287 | else: |
Pau Espin Pedrol | f6a0712 | 2018-07-27 16:24:29 +0200 | [diff] [blame] | 288 | retcode = self.run('unitid', '--unit-id', '%s' % uid_str, self.bts_ip) |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 289 | if retcode != 0: |
Pau Espin Pedrol | f6a0712 | 2018-07-27 16:24:29 +0200 | [diff] [blame] | 290 | log.err('ipaccess-config --unit-id %s returned error code %d' % (uid_str, retcode)) |
Pau Espin Pedrol | 1b28a58 | 2018-03-08 21:01:26 +0100 | [diff] [blame] | 291 | return retcode == 0 |
| 292 | |
| 293 | def set_oml_ip(self, omlip, restart=False): |
| 294 | if restart: |
| 295 | retcode = self.run('oml', '--restart', '--oml-ip', omlip, self.bts_ip) |
| 296 | else: |
| 297 | retcode = self.run('oml', '--oml-ip', omlip, self.bts_ip) |
| 298 | if retcode != 0: |
| 299 | self.error('ipaccess-config --oml-ip %s returned error code %d' % (omlip, retcode)) |
| 300 | return retcode == 0 |
| 301 | |
| 302 | # vim: expandtab tabstop=4 shiftwidth=4 |