blob: 7f0151f848a2601fc235ed4904132f57aa8d2843 [file] [log] [blame]
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +01001# 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
20import os
21import pprint
22import tempfile
23import re
24from abc import ABCMeta, abstractmethod
Pau Espin Pedrol9a4631c2018-03-28 19:17:34 +020025from . import log, config, util, template, process, pcap_recorder, bts, pcu
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +010026from . import powersupply
Pau Espin Pedrol9a4631c2018-03-28 19:17:34 +020027from .event_loop import MainLoop
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +010028
29class NanoBts(bts.Bts):
30
Pau Espin Pedrolf6a07122018-07-27 16:24:29 +020031 pwsup_list = []
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +010032 _pcu = None
33##############
34# PROTECTED
35##############
36 def __init__(self, suite_run, conf):
Pau Espin Pedrolf6a07122018-07-27 16:24:29 +020037 super().__init__(suite_run, conf, 'nanobts_%s' % conf.get('label', 'nolabel'), 'nanobts')
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +010038
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 Pedrolf6a07122018-07-27 16:24:29 +020043 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 Pedrol1b28a582018-03-08 21:01:26 +010051
Pau Espin Pedrolf6a07122018-07-27 16:24:29 +020052
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 Pedrol1b28a582018-03-08 21:01:26 +010062
63########################
64# PUBLIC - INTERNAL API
65########################
66
67 def conf_for_bsc(self):
Pau Espin Pedrole5194622018-05-07 13:36:58 +020068 values = self.conf_for_bsc_prepare()
Pau Espin Pedrolfef9c1c2018-03-27 19:20:47 +020069 # 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 Pedrolf6a07122018-07-27 16:24:29 +020073 for trx_i in range(len(trx_list)):
74 config.overlay(trx_list[trx_i], { 'arfcn' : str(531 + trx_i * 2) })
Pau Espin Pedrolfef9c1c2018-03-27 19:20:47 +020075 elif band == 'GSM-900':
Pau Espin Pedrolf6a07122018-07-27 16:24:29 +020076 for trx_i in range(len(trx_list)):
77 config.overlay(trx_list[trx_i], { 'arfcn' : str(50 + trx_i * 2) })
Pau Espin Pedrolfef9c1c2018-03-27 19:20:47 +020078
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +010079 config.overlay(values, { 'osmobsc_bts_type': 'nanobts' })
80
81 self.dbg(conf=values)
82 return values
83
84
85 def cleanup(self):
Pau Espin Pedrolf6a07122018-07-27 16:24:29 +020086 for pwsup in self.pwsup_list:
87 self.dbg('Powering off NanoBTS TRX')
88 pwsup.power_set(False)
89 self.pwsup_list = []
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +010090
91###################
92# PUBLIC (test API included)
93###################
94
Pau Espin Pedrolb1526b92018-05-22 20:32:30 +020095 def start(self, keepalive=False):
Pau Espin Pedrola238ed92018-03-26 13:06:12 +020096 if self.conf.get('ipa_unit_id') is None:
97 raise log.Error('No attribute %s provided in conf!' % attr)
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +010098 self.run_dir = util.Dir(self.suite_run.get_test_run_dir().new_dir(self.name()))
99 self._configure()
100
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100101 unitid = int(self.conf.get('ipa_unit_id'))
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100102
Pau Espin Pedrolf6a07122018-07-27 16:24:29 +0200103 # 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 Pedrol1b28a582018-03-08 21:01:26 +0100107
Pau Espin Pedrola238ed92018-03-26 13:06:12 +0200108 pcap_recorder.PcapRecorder(self.suite_run, self.run_dir.new_dir('pcap'), None,
Pau Espin Pedrolf6a07122018-07-27 16:24:29 +0200109 '%s and port not 22' % self.get_pcap_filter_all_trx_ip())
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100110
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100111
Pau Espin Pedrolf6a07122018-07-27 16:24:29 +0200112 # 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 Pedrol1b28a582018-03-08 21:01:26 +0100118
Pau Espin Pedrolf6a07122018-07-27 16:24:29 +0200119 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 Pedrol1b28a582018-03-08 21:01:26 +0100126
Pau Espin Pedrolf6a07122018-07-27 16:24:29 +0200127 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 Pedrol1b28a582018-03-08 21:01:26 +0100146
Pau Espin Pedrol9a4631c2018-03-28 19:17:34 +0200147 MainLoop.wait(self, self.bsc.bts_is_connected, self, timeout=600)
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100148 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
172class AbisIpFind(log.Origin):
173 suite_run = None
174 parent_run_dir = None
175 run_dir = None
176 inst = None
177 env = None
Pau Espin Pedrola238ed92018-03-26 13:06:12 +0200178 bind_ip = None
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100179 proc = None
180
181 BIN_ABISIP_FIND = 'abisip-find'
Pau Espin Pedrolf6a07122018-07-27 16:24:29 +0200182 BTS_UNIT_ID_RE = re.compile("Unit_ID='(?P<unit_id>\d+)/\d+/(?P<trx_id>\d+)'")
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100183
Pau Espin Pedrola238ed92018-03-26 13:06:12 +0200184 def __init__(self, suite_run, parent_run_dir, bind_ip, name_suffix):
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100185 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 Pedrola238ed92018-03-26 13:06:12 +0200188 self.bind_ip = bind_ip
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100189 self.env = {}
190
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100191 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 Pedrola238ed92018-03-26 13:06:12 +0200194
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100195 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 Pedrola238ed92018-03-26 13:06:12 +0200199 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 Pedrol48fce862018-04-04 12:44:24 +0200204 (ipfind_path, '-i', '1', '-b', self.bind_ip),
Pau Espin Pedrola238ed92018-03-26 13:06:12 +0200205 env=env)
206 self.suite_run.remember_to_stop(self.proc)
207 self.proc.launch()
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100208
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 Pedrolf6a07122018-07-27 16:24:29 +0200228 trx_id = int(res.group('trx_id'))
229 return (unit_id, trx_id)
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100230 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 Pedrol9a4631c2018-03-28 19:17:34 +0200236 MainLoop.wait(self, self.bts_ready, ipaddr)
Pau Espin Pedrol48fce862018-04-04 12:44:24 +0200237 # 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 Pedrol1b28a582018-03-08 21:01:26 +0100241
242class 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 Pedrol9a4631c2018-03-28 19:17:34 +0200277 MainLoop.wait(self, self.proc.terminated)
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100278 except Exception as e:
279 self.proc.terminate()
280 raise e
281 return self.proc.result
282
Pau Espin Pedrolf6a07122018-07-27 16:24:29 +0200283 def set_unit_id(self, unitid, trx_num, restart=False):
284 uid_str = '%d/0/%d' % (unitid, trx_num)
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100285 if restart:
Pau Espin Pedrolf6a07122018-07-27 16:24:29 +0200286 retcode = self.run('unitid', '--restart', '--unit-id', '%s' % uid_str, self.bts_ip)
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100287 else:
Pau Espin Pedrolf6a07122018-07-27 16:24:29 +0200288 retcode = self.run('unitid', '--unit-id', '%s' % uid_str, self.bts_ip)
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100289 if retcode != 0:
Pau Espin Pedrolf6a07122018-07-27 16:24:29 +0200290 log.err('ipaccess-config --unit-id %s returned error code %d' % (uid_str, retcode))
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100291 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