blob: f666f7a778a95de4bdb7e85d5c185c7558044205 [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
25from . import log, config, util, template, process, event_loop, pcap_recorder, bts, pcu
26from . import powersupply
27
28class NanoBts(bts.Bts):
29
30 pwsup = None
31 _pcu = None
32##############
33# PROTECTED
34##############
35 def __init__(self, suite_run, conf):
36 if conf.get('addr') is None:
37 raise log.Error('No attribute addr provided in conf!')
38 super().__init__(suite_run, conf, 'nanobts_%s' % conf.get('addr'))
39
40 def _configure(self):
41 if self.bsc is None:
42 raise log.Error('BTS needs to be added to a BSC or NITB before it can be configured')
43
44 pwsup_opt = self.conf.get('power_supply', {})
45 if not pwsup_opt:
46 raise log.Error('No power_supply attribute provided in conf!')
47 pwsup_type = pwsup_opt.get('type')
48 if not pwsup_type:
49 raise log.Error('No type attribute provided in power_supply conf!')
50
51 self.pwsup = powersupply.get_instance_by_type(pwsup_type, pwsup_opt)
52
53########################
54# PUBLIC - INTERNAL API
55########################
56
57 def conf_for_bsc(self):
58 values = config.get_defaults('bsc_bts')
59 config.overlay(values, config.get_defaults('nanobts'))
60 if self.lac is not None:
61 config.overlay(values, { 'location_area_code': self.lac })
62 if self.rac is not None:
63 config.overlay(values, { 'routing_area_code': self.rac })
64 if self.cellid is not None:
65 config.overlay(values, { 'cell_identity': self.cellid })
66 if self.bvci is not None:
67 config.overlay(values, { 'bvci': self.bvci })
68 config.overlay(values, self.conf)
69
70 sgsn_conf = {} if self.sgsn is None else self.sgsn.conf_for_client()
71 config.overlay(values, sgsn_conf)
72
73 config.overlay(values, { 'osmobsc_bts_type': 'nanobts' })
74
75 self.dbg(conf=values)
76 return values
77
78
79 def cleanup(self):
80 if self.pwsup:
81 self.dbg('Powering off NanoBTS')
82 self.pwsup.power_set(False)
83
84###################
85# PUBLIC (test API included)
86###################
87
88 def start(self):
89 for attr in ['net_device', 'ipa_unit_id']:
90 if self.conf.get(attr) is None:
91 raise log.Error('No attribute %s provided in conf!' % attr)
92 self.run_dir = util.Dir(self.suite_run.get_test_run_dir().new_dir(self.name()))
93 self._configure()
94
95 iface = self.conf.get('net_device')
96 unitid = int(self.conf.get('ipa_unit_id'))
97 bts_ip = self.remote_addr()
98
99 # Make sure nanoBTS is powered and in a clean state:
100 self.pwsup.power_cycle(1.0)
101
102 pcap_recorder.PcapRecorder(self.suite_run, self.run_dir.new_dir('pcap'), iface,
103 'host %s and port not 22' % self.remote_addr())
104
105 self.log('Finding nanobts %s...' % bts_ip)
106 ipfind = AbisIpFind(self.suite_run, self.run_dir, iface, 'preconf')
107 ipfind.start()
108 ipfind.wait_bts_ready(bts_ip)
109 running_unitid = ipfind.get_unitid_by_ip(bts_ip)
110 self.log('Found nanobts %s with unit_id %d' % (bts_ip, running_unitid))
111 ipfind.stop()
112
113 ipconfig = IpAccessConfig(self.suite_run, self.run_dir, bts_ip)
114 if running_unitid != unitid:
115 if not ipconfig.set_unit_id(unitid, False):
116 raise log.Error('Failed configuring unit id %d' % unitid)
117 # Apply OML IP and restart nanoBTS as it is required to apply the changes.
118 if not ipconfig.set_oml_ip(self.bsc.addr(), True):
119 raise log.Error('Failed configuring OML IP %s' % bts_ip)
120
121 # Let some time for BTS to restart. It takes much more than 20 secs, and
122 # this way we make sure we don't catch responses in abisip-find prior to
123 # BTS restarting.
124 event_loop.sleep(self, 20)
125
126 self.log('Starting to connect to', self.bsc)
127 ipfind = AbisIpFind(self.suite_run, self.run_dir, iface, 'postconf')
128 ipfind.start()
129 ipfind.wait_bts_ready(bts_ip)
130 self.log('nanoBTS configured and running')
131 ipfind.stop()
132
133 event_loop.wait(self, self.bsc.bts_is_connected, self, timeout=600)
134 self.log('nanoBTS connected to BSC')
135
136 #According to roh, it can be configured to use a static IP in a permanent way:
137 # 1- use abisip-find to find the default address
138 # 2- use ./ipaccess-config --ip-address IP/MASK
139 # 3- use ./ipaccess-config --ip-gateway IP to set the IP of the main unit
140 # 4- use ./ipaccess-config --restart to restart and apply the changes
141
142 #Start must do the following:
143 # 1- use abisip-find to find the default address
144 # 2- use ./ipaccess-config --unit-id UNIT_ID
145 # 3- use ./ipaccess-config --oml-ip --restart to set the IP of the BSC and apply+restart.
146 # According to roh, using the 3 of them together was not reliable to work properly.
147
148 def ready_for_pcu(self):
149 """We don't really care as we use a Dummy PCU class."""
150 return True
151
152 def pcu(self):
153 if not self._pcu:
154 self._pcu = pcu.PcuDummy(self.suite_run, self, self.conf)
155 return self._pcu
156
157
158class AbisIpFind(log.Origin):
159 suite_run = None
160 parent_run_dir = None
161 run_dir = None
162 inst = None
163 env = None
164 iface = None
165 proc = None
166
167 BIN_ABISIP_FIND = 'abisip-find'
168 BTS_UNIT_ID_RE = re.compile("Unit_ID='(?P<unit_id>\d+)/\d+/\d+'")
169
170 def __init__(self, suite_run, parent_run_dir, iface, name_suffix):
171 super().__init__(log.C_RUN, AbisIpFind.BIN_ABISIP_FIND + '-' + name_suffix)
172 self.suite_run = suite_run
173 self.parent_run_dir = parent_run_dir
174 self.iface = iface
175 self.env = {}
176
177 def launch_process(self, binary_name, *args):
178 binary = os.path.abspath(self.inst.child('bin', binary_name))
179 run_dir = self.run_dir.new_dir(binary_name)
180 if not os.path.isfile(binary):
181 raise RuntimeError('Binary missing: %r' % binary)
182 proc = process.Process(binary_name, run_dir,
183 (binary,) + args,
184 env=self.env)
185 self.suite_run.remember_to_stop(proc)
186 proc.launch()
187 return proc
188
189 def start(self):
190 self.run_dir = util.Dir(self.parent_run_dir.new_dir(self.name()))
191 self.inst = util.Dir(os.path.abspath(self.suite_run.trial.get_inst('osmo-bsc')))
192 lib = self.inst.child('lib')
193 if not os.path.isdir(lib):
194 raise log.Error('No lib/ in %r' % self.inst)
195 ipfind_path = self.inst.child('bin', AbisIpFind.BIN_ABISIP_FIND)
196 # setting capabilities will later disable use of LD_LIBRARY_PATH from ELF loader -> modify RPATH instead.
197 self.log('Setting RPATH for', AbisIpFind.BIN_ABISIP_FIND)
198 util.change_elf_rpath(ipfind_path, util.prepend_library_path(lib), self.run_dir.new_dir('patchelf'))
199 # osmo-bty-octphy requires CAP_NET_RAW to open AF_PACKET socket:
200 self.log('Applying CAP_NET_RAW capability to', AbisIpFind.BIN_ABISIP_FIND)
201 util.setcap_net_raw(ipfind_path, self.run_dir.new_dir('setcap_net_raw'))
202 self.proc = self.launch_process(AbisIpFind.BIN_ABISIP_FIND, self.iface)
203
204 def stop(self):
205 self.suite_run.stop_process(self.proc)
206
207 def get_line_by_ip(self, ipaddr):
208 """Get latest line (more up to date) from abisip-find based on ip address."""
209 token = "IP_Address='%s'" % ipaddr
210 myline = None
211 for line in (self.proc.get_stdout() or '').splitlines():
212 if token in line:
213 myline = line
214 return myline
215
216 def get_unitid_by_ip(self, ipaddr):
217 line = self.get_line_by_ip(ipaddr)
218 if line is None:
219 return None
220 res = AbisIpFind.BTS_UNIT_ID_RE.search(line)
221 if res:
222 unit_id = int(res.group('unit_id'))
223 return unit_id
224 raise log.Error('abisip-find unit_id field for nanobts %s not found in %s' %(ipaddr, line))
225
226 def bts_ready(self, ipaddr):
227 return self.get_line_by_ip(ipaddr) is not None
228
229 def wait_bts_ready(self, ipaddr):
230 event_loop.wait(self, self.bts_ready, ipaddr)
231
232
233class IpAccessConfig(log.Origin):
234 suite_run = None
235 parent_run_dir = None
236 run_dir = None
237 inst = None
238 env = None
239 bts_ip = None
240
241 BIN_IPACCESS_CONFIG = 'ipaccess-config'
242
243 def __init__(self, suite_run, parent_run_dir, bts_ip):
244 super().__init__(log.C_RUN, IpAccessConfig.BIN_IPACCESS_CONFIG)
245 self.suite_run = suite_run
246 self.parent_run_dir = parent_run_dir
247 self.bts_ip = bts_ip
248 self.env = {}
249
250 def launch_process(self, binary_name, *args):
251 binary = os.path.abspath(self.inst.child('bin', binary_name))
252 run_dir = self.run_dir.new_dir(binary_name)
253 if not os.path.isfile(binary):
254 raise RuntimeError('Binary missing: %r' % binary)
255 proc = process.Process(binary_name, run_dir,
256 (binary,) + args,
257 env=self.env)
258 proc.launch()
259 return proc
260
261 def run(self, name_suffix, *args):
262 self.run_dir = util.Dir(self.parent_run_dir.new_dir(self.name()+'-'+name_suffix))
263 self.inst = util.Dir(os.path.abspath(self.suite_run.trial.get_inst('osmo-bsc')))
264 lib = self.inst.child('lib')
265 self.env = { 'LD_LIBRARY_PATH': util.prepend_library_path(lib) }
266 self.proc = self.launch_process(IpAccessConfig.BIN_IPACCESS_CONFIG, *args)
267 try:
268 event_loop.wait(self, self.proc.terminated)
269 except Exception as e:
270 self.proc.terminate()
271 raise e
272 return self.proc.result
273
274 def set_unit_id(self, unitid, restart=False):
275 if restart:
276 retcode = self.run('unitid', '--restart', '--unit-id', '%d/0/0' % unitid, self.bts_ip)
277 else:
278 retcode = self.run('unitid', '--unit-id', '%d/0/0' % unitid, self.bts_ip)
279 if retcode != 0:
280 log.err('ipaccess-config --unit-id %d/0/0 returned error code %d' % (unitid, retcode))
281 return retcode == 0
282
283 def set_oml_ip(self, omlip, restart=False):
284 if restart:
285 retcode = self.run('oml', '--restart', '--oml-ip', omlip, self.bts_ip)
286 else:
287 retcode = self.run('oml', '--oml-ip', omlip, self.bts_ip)
288 if retcode != 0:
289 self.error('ipaccess-config --oml-ip %s returned error code %d' % (omlip, retcode))
290 return retcode == 0
291
292# vim: expandtab tabstop=4 shiftwidth=4