blob: 6350c5633cd29b800756b5591ff0be8c27231a3d [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
31 pwsup = None
32 _pcu = None
33##############
34# PROTECTED
35##############
36 def __init__(self, suite_run, conf):
37 if conf.get('addr') is None:
38 raise log.Error('No attribute addr provided in conf!')
39 super().__init__(suite_run, conf, 'nanobts_%s' % conf.get('addr'))
40
41 def _configure(self):
42 if self.bsc is None:
43 raise log.Error('BTS needs to be added to a BSC or NITB before it can be configured')
44
45 pwsup_opt = self.conf.get('power_supply', {})
46 if not pwsup_opt:
47 raise log.Error('No power_supply attribute provided in conf!')
48 pwsup_type = pwsup_opt.get('type')
49 if not pwsup_type:
50 raise log.Error('No type attribute provided in power_supply conf!')
51
52 self.pwsup = powersupply.get_instance_by_type(pwsup_type, pwsup_opt)
53
54########################
55# PUBLIC - INTERNAL API
56########################
57
58 def conf_for_bsc(self):
59 values = config.get_defaults('bsc_bts')
60 config.overlay(values, config.get_defaults('nanobts'))
61 if self.lac is not None:
62 config.overlay(values, { 'location_area_code': self.lac })
63 if self.rac is not None:
64 config.overlay(values, { 'routing_area_code': self.rac })
65 if self.cellid is not None:
66 config.overlay(values, { 'cell_identity': self.cellid })
67 if self.bvci is not None:
68 config.overlay(values, { 'bvci': self.bvci })
69 config.overlay(values, self.conf)
70
71 sgsn_conf = {} if self.sgsn is None else self.sgsn.conf_for_client()
72 config.overlay(values, sgsn_conf)
73
Pau Espin Pedrolfef9c1c2018-03-27 19:20:47 +020074 # Hack until we have proper ARFCN resource allocation support (OS#2230)
75 band = values.get('band')
76 trx_list = values.get('trx_list')
77 if band == 'GSM-1900':
78 config.overlay(trx_list[0], { 'arfcn' : '531' })
79 elif band == 'GSM-900':
80 config.overlay(trx_list[0], { 'arfcn' : '50' })
81
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +010082 config.overlay(values, { 'osmobsc_bts_type': 'nanobts' })
83
84 self.dbg(conf=values)
85 return values
86
87
88 def cleanup(self):
89 if self.pwsup:
90 self.dbg('Powering off NanoBTS')
91 self.pwsup.power_set(False)
92
93###################
94# PUBLIC (test API included)
95###################
96
97 def start(self):
Pau Espin Pedrola238ed92018-03-26 13:06:12 +020098 if self.conf.get('ipa_unit_id') is None:
99 raise log.Error('No attribute %s provided in conf!' % attr)
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100100 self.run_dir = util.Dir(self.suite_run.get_test_run_dir().new_dir(self.name()))
101 self._configure()
102
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100103 unitid = int(self.conf.get('ipa_unit_id'))
104 bts_ip = self.remote_addr()
Pau Espin Pedrola238ed92018-03-26 13:06:12 +0200105 # This fine for now, however concurrent tests using Nanobts may run into "address already in use" since dst is broadcast.
106 # 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.
107 local_bind_ip =util.dst_ip_get_local_bind(bts_ip)
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100108
109 # Make sure nanoBTS is powered and in a clean state:
110 self.pwsup.power_cycle(1.0)
111
Pau Espin Pedrola238ed92018-03-26 13:06:12 +0200112 pcap_recorder.PcapRecorder(self.suite_run, self.run_dir.new_dir('pcap'), None,
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100113 'host %s and port not 22' % self.remote_addr())
114
Pau Espin Pedrola238ed92018-03-26 13:06:12 +0200115 self.log('Finding nanobts %s, binding on %s...' % (bts_ip, local_bind_ip))
116 ipfind = AbisIpFind(self.suite_run, self.run_dir, local_bind_ip, 'preconf')
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100117 ipfind.start()
118 ipfind.wait_bts_ready(bts_ip)
119 running_unitid = ipfind.get_unitid_by_ip(bts_ip)
120 self.log('Found nanobts %s with unit_id %d' % (bts_ip, running_unitid))
121 ipfind.stop()
122
123 ipconfig = IpAccessConfig(self.suite_run, self.run_dir, bts_ip)
124 if running_unitid != unitid:
125 if not ipconfig.set_unit_id(unitid, False):
126 raise log.Error('Failed configuring unit id %d' % unitid)
127 # Apply OML IP and restart nanoBTS as it is required to apply the changes.
128 if not ipconfig.set_oml_ip(self.bsc.addr(), True):
129 raise log.Error('Failed configuring OML IP %s' % bts_ip)
130
131 # Let some time for BTS to restart. It takes much more than 20 secs, and
132 # this way we make sure we don't catch responses in abisip-find prior to
133 # BTS restarting.
Pau Espin Pedrol9a4631c2018-03-28 19:17:34 +0200134 MainLoop.sleep(self, 20)
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100135
136 self.log('Starting to connect to', self.bsc)
Pau Espin Pedrola238ed92018-03-26 13:06:12 +0200137 ipfind = AbisIpFind(self.suite_run, self.run_dir, local_bind_ip, 'postconf')
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100138 ipfind.start()
139 ipfind.wait_bts_ready(bts_ip)
140 self.log('nanoBTS configured and running')
141 ipfind.stop()
142
Pau Espin Pedrol9a4631c2018-03-28 19:17:34 +0200143 MainLoop.wait(self, self.bsc.bts_is_connected, self, timeout=600)
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100144 self.log('nanoBTS connected to BSC')
145
146 #According to roh, it can be configured to use a static IP in a permanent way:
147 # 1- use abisip-find to find the default address
148 # 2- use ./ipaccess-config --ip-address IP/MASK
149 # 3- use ./ipaccess-config --ip-gateway IP to set the IP of the main unit
150 # 4- use ./ipaccess-config --restart to restart and apply the changes
151
152 #Start must do the following:
153 # 1- use abisip-find to find the default address
154 # 2- use ./ipaccess-config --unit-id UNIT_ID
155 # 3- use ./ipaccess-config --oml-ip --restart to set the IP of the BSC and apply+restart.
156 # According to roh, using the 3 of them together was not reliable to work properly.
157
158 def ready_for_pcu(self):
159 """We don't really care as we use a Dummy PCU class."""
160 return True
161
162 def pcu(self):
163 if not self._pcu:
164 self._pcu = pcu.PcuDummy(self.suite_run, self, self.conf)
165 return self._pcu
166
167
168class AbisIpFind(log.Origin):
169 suite_run = None
170 parent_run_dir = None
171 run_dir = None
172 inst = None
173 env = None
Pau Espin Pedrola238ed92018-03-26 13:06:12 +0200174 bind_ip = None
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100175 proc = None
176
177 BIN_ABISIP_FIND = 'abisip-find'
178 BTS_UNIT_ID_RE = re.compile("Unit_ID='(?P<unit_id>\d+)/\d+/\d+'")
179
Pau Espin Pedrola238ed92018-03-26 13:06:12 +0200180 def __init__(self, suite_run, parent_run_dir, bind_ip, name_suffix):
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100181 super().__init__(log.C_RUN, AbisIpFind.BIN_ABISIP_FIND + '-' + name_suffix)
182 self.suite_run = suite_run
183 self.parent_run_dir = parent_run_dir
Pau Espin Pedrola238ed92018-03-26 13:06:12 +0200184 self.bind_ip = bind_ip
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100185 self.env = {}
186
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100187 def start(self):
188 self.run_dir = util.Dir(self.parent_run_dir.new_dir(self.name()))
189 self.inst = util.Dir(os.path.abspath(self.suite_run.trial.get_inst('osmo-bsc')))
Pau Espin Pedrola238ed92018-03-26 13:06:12 +0200190
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100191 lib = self.inst.child('lib')
192 if not os.path.isdir(lib):
193 raise log.Error('No lib/ in %r' % self.inst)
194 ipfind_path = self.inst.child('bin', AbisIpFind.BIN_ABISIP_FIND)
Pau Espin Pedrola238ed92018-03-26 13:06:12 +0200195 if not os.path.isfile(ipfind_path):
196 raise RuntimeError('Binary missing: %r' % ipfind_path)
197
198 env = { 'LD_LIBRARY_PATH': util.prepend_library_path(lib) }
199 self.proc = process.Process(self.name(), self.run_dir,
Pau Espin Pedrol48fce862018-04-04 12:44:24 +0200200 (ipfind_path, '-i', '1', '-b', self.bind_ip),
Pau Espin Pedrola238ed92018-03-26 13:06:12 +0200201 env=env)
202 self.suite_run.remember_to_stop(self.proc)
203 self.proc.launch()
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100204
205 def stop(self):
206 self.suite_run.stop_process(self.proc)
207
208 def get_line_by_ip(self, ipaddr):
209 """Get latest line (more up to date) from abisip-find based on ip address."""
210 token = "IP_Address='%s'" % ipaddr
211 myline = None
212 for line in (self.proc.get_stdout() or '').splitlines():
213 if token in line:
214 myline = line
215 return myline
216
217 def get_unitid_by_ip(self, ipaddr):
218 line = self.get_line_by_ip(ipaddr)
219 if line is None:
220 return None
221 res = AbisIpFind.BTS_UNIT_ID_RE.search(line)
222 if res:
223 unit_id = int(res.group('unit_id'))
224 return unit_id
225 raise log.Error('abisip-find unit_id field for nanobts %s not found in %s' %(ipaddr, line))
226
227 def bts_ready(self, ipaddr):
228 return self.get_line_by_ip(ipaddr) is not None
229
230 def wait_bts_ready(self, ipaddr):
Pau Espin Pedrol9a4631c2018-03-28 19:17:34 +0200231 MainLoop.wait(self, self.bts_ready, ipaddr)
Pau Espin Pedrol48fce862018-04-04 12:44:24 +0200232 # There's a period of time after boot in which nanobts answers to
233 # abisip-find but tcp RSTs ipacces-config conns. Let's wait in here a
234 # bit more time to avoid failing after stating the BTS is ready.
235 MainLoop.sleep(self, 2)
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100236
237class IpAccessConfig(log.Origin):
238 suite_run = None
239 parent_run_dir = None
240 run_dir = None
241 inst = None
242 env = None
243 bts_ip = None
244
245 BIN_IPACCESS_CONFIG = 'ipaccess-config'
246
247 def __init__(self, suite_run, parent_run_dir, bts_ip):
248 super().__init__(log.C_RUN, IpAccessConfig.BIN_IPACCESS_CONFIG)
249 self.suite_run = suite_run
250 self.parent_run_dir = parent_run_dir
251 self.bts_ip = bts_ip
252 self.env = {}
253
254 def launch_process(self, binary_name, *args):
255 binary = os.path.abspath(self.inst.child('bin', binary_name))
256 run_dir = self.run_dir.new_dir(binary_name)
257 if not os.path.isfile(binary):
258 raise RuntimeError('Binary missing: %r' % binary)
259 proc = process.Process(binary_name, run_dir,
260 (binary,) + args,
261 env=self.env)
262 proc.launch()
263 return proc
264
265 def run(self, name_suffix, *args):
266 self.run_dir = util.Dir(self.parent_run_dir.new_dir(self.name()+'-'+name_suffix))
267 self.inst = util.Dir(os.path.abspath(self.suite_run.trial.get_inst('osmo-bsc')))
268 lib = self.inst.child('lib')
269 self.env = { 'LD_LIBRARY_PATH': util.prepend_library_path(lib) }
270 self.proc = self.launch_process(IpAccessConfig.BIN_IPACCESS_CONFIG, *args)
271 try:
Pau Espin Pedrol9a4631c2018-03-28 19:17:34 +0200272 MainLoop.wait(self, self.proc.terminated)
Pau Espin Pedrol1b28a582018-03-08 21:01:26 +0100273 except Exception as e:
274 self.proc.terminate()
275 raise e
276 return self.proc.result
277
278 def set_unit_id(self, unitid, restart=False):
279 if restart:
280 retcode = self.run('unitid', '--restart', '--unit-id', '%d/0/0' % unitid, self.bts_ip)
281 else:
282 retcode = self.run('unitid', '--unit-id', '%d/0/0' % unitid, self.bts_ip)
283 if retcode != 0:
284 log.err('ipaccess-config --unit-id %d/0/0 returned error code %d' % (unitid, retcode))
285 return retcode == 0
286
287 def set_oml_ip(self, omlip, restart=False):
288 if restart:
289 retcode = self.run('oml', '--restart', '--oml-ip', omlip, self.bts_ip)
290 else:
291 retcode = self.run('oml', '--oml-ip', omlip, self.bts_ip)
292 if retcode != 0:
293 self.error('ipaccess-config --oml-ip %s returned error code %d' % (omlip, retcode))
294 return retcode == 0
295
296# vim: expandtab tabstop=4 shiftwidth=4