summaryrefslogtreecommitdiffstats
path: root/src/osmo_gsm_tester/bts_nanobts.py
blob: 4cdb98ef66f01a671e2555166ea9a74fb80d46a5 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
# osmo_gsm_tester: specifics for running an ip.access nanoBTS
#
# Copyright (C) 2018 by sysmocom - s.f.m.c. GmbH
#
# Author: Pau Espin Pedrol <pespin@sysmocom.de>
#
# 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 <http://www.gnu.org/licenses/>.

import os
import re
import json
from . import log, config, util, process, pcap_recorder, bts, pcu
from . import powersupply
from .event_loop import MainLoop

class NanoBts(bts.Bts):

##############
# PROTECTED
##############
    def __init__(self, suite_run, conf):
        super().__init__(suite_run, conf, 'nanobts_%s' % conf.get('label', 'nolabel'), 'nanobts')
        self.pwsup_list = []
        self._pcu = None

    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')

        for trx_i in range(self.num_trx()):
            pwsup_opt = self.conf.get('trx_list')[trx_i].get('power_supply', {})
            if not pwsup_opt:
                raise log.Error('No power_supply attribute provided in conf for TRX %d!' % trx_i)
            pwsup_type = pwsup_opt.get('type')
            if not pwsup_type:
                raise log.Error('No type attribute provided in power_supply conf for TRX %d!' % trx_i)
            self.pwsup_list.append(powersupply.get_instance_by_type(pwsup_type, pwsup_opt))


    def get_pcap_filter_all_trx_ip(self):
        ret = "("
        for trx_i in range(self.num_trx()):
            if trx_i != 0:
                ret = ret + " or "
            bts_trx_ip = self.conf.get('trx_list')[trx_i].get('addr')
            ret = ret + "host " + bts_trx_ip
        ret = ret + ")"
        return ret

########################
# PUBLIC - INTERNAL API
########################

    def conf_for_bsc(self):
        values = self.conf_for_bsc_prepare()
        # Hack until we have proper ARFCN resource allocation support (OS#2230)
        band = values.get('band')
        trx_list = values.get('trx_list')
        if band == 'GSM-1900':
            for trx_i in range(len(trx_list)):
                config.overlay(trx_list[trx_i], { 'arfcn' : str(531 + trx_i * 2) })
        elif band == 'GSM-900':
            for trx_i in range(len(trx_list)):
                config.overlay(trx_list[trx_i], { 'arfcn' : str(50 + trx_i * 2) })

        config.overlay(values, { 'osmobsc_bts_type': 'nanobts' })

        self.dbg(conf=values)
        return values


    def cleanup(self):
        for pwsup in self.pwsup_list:
            self.dbg('Powering off NanoBTS TRX')
            pwsup.power_set(False)
        self.pwsup_list = []

###################
# PUBLIC (test API included)
###################

    def start(self, keepalive=False):
        if self.conf.get('ipa_unit_id') is None:
            raise log.Error('No attribute ipa_unit_id provided in conf!')
        self.run_dir = util.Dir(self.suite_run.get_test_run_dir().new_dir(self.name()))
        self._configure()

        unitid = int(self.conf.get('ipa_unit_id'))

        # Make sure all nanoBTS TRX are powered and in a clean state:
        for pwsup in self.pwsup_list:
            self.dbg('Powering cycling NanoBTS TRX')
            pwsup.power_cycle(1.0)

        pcap_recorder.PcapRecorder(self.suite_run, self.run_dir.new_dir('pcap'), None,
                                   '%s and port not 22' % self.get_pcap_filter_all_trx_ip())


        # TODO: If setting N TRX, we should set up them in parallel instead of waiting for each one.
        for trx_i in range(self.num_trx()):
            bts_trx_ip = self.conf.get('trx_list')[trx_i].get('addr')
            # This fine for now, however concurrent tests using Nanobts may run into "address already in use" since dst is broadcast.
            # 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.
            local_bind_ip = util.dst_ip_get_local_bind(bts_trx_ip)

            self.log('Finding nanobts %s, binding on %s...' % (bts_trx_ip, local_bind_ip))
            ipfind = AbisIpFind(self.suite_run, self.run_dir, local_bind_ip, 'preconf')
            ipfind.start()
            ipfind.wait_bts_ready(bts_trx_ip)
            running_unitid, running_trx = ipfind.get_unitid_by_ip(bts_trx_ip)
            self.log('Found nanobts %s with unit_id %d trx %d' % (bts_trx_ip, running_unitid, running_trx))
            ipfind.stop()

            ipconfig = IpAccessConfig(self.suite_run, self.run_dir, bts_trx_ip)
            running_oml_ip = ipconfig.get_oml_ip()

            if running_unitid != unitid or running_trx != trx_i:
                if not ipconfig.set_unit_id(unitid, trx_i, False):
                    raise log.Error('Failed configuring unit id %d trx %d' % (unitid, trx_i))

            if running_oml_ip != self.bsc.addr():
                # Apply OML IP and restart nanoBTS as it is required to apply the changes.
                self.dbg('Current OML IPaddr "%s" does not match BSC IPaddr "%s", reconfiguring and restarting it' % (running_oml_ip, self.bsc.addr()))
                if not ipconfig.set_oml_ip(self.bsc.addr(), True):
                    raise log.Error('Failed configuring OML IP %s' % bts_trx_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.
                MainLoop.sleep(self, 20)

                self.dbg('Starting to connect id %d trx %d to' % (unitid, trx_i), self.bsc)
                ipfind = AbisIpFind(self.suite_run, self.run_dir, local_bind_ip, 'postconf')
                ipfind.start()
                ipfind.wait_bts_ready(bts_trx_ip)
                self.log('nanoBTS id %d trx %d configured and running' % (unitid, trx_i))
                ipfind.stop()
            else:
                self.dbg('nanoBTS id %d trx %d no need to change OML IP (%s) and restart' % (unitid, trx_i, running_oml_ip))

        MainLoop.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
    bind_ip = None
    proc = None

    BIN_ABISIP_FIND = 'abisip-find'
    BTS_UNIT_ID_RE = re.compile("Unit_ID='(?P<unit_id>\d+)/\d+/(?P<trx_id>\d+)'")

    def __init__(self, suite_run, parent_run_dir, bind_ip, 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.bind_ip = bind_ip
        self.env = {}

    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)
        if not os.path.isfile(ipfind_path):
            raise RuntimeError('Binary missing: %r' % ipfind_path)

        env = { 'LD_LIBRARY_PATH': util.prepend_library_path(lib) }
        self.proc = process.Process(self.name(), self.run_dir,
                            (ipfind_path, '-i', '1', '-b', self.bind_ip),
                            env=env)
        self.suite_run.remember_to_stop(self.proc)
        self.proc.launch()

    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'))
                trx_id = int(res.group('trx_id'))
                return (unit_id, trx_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):
        MainLoop.wait(self, self.bts_ready, ipaddr)
        # There's a period of time after boot in which nanobts answers to
        # abisip-find but tcp RSTs ipacces-config conns. Let's wait in here a
        # bit more time to avoid failing after stating the BTS is ready.
        MainLoop.sleep(self, 2)

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 create_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)
        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.create_process(IpAccessConfig.BIN_IPACCESS_CONFIG, *args)
        return self.proc.launch_sync(raise_nonsuccess=False)

    def set_unit_id(self, unitid, trx_num, restart=False):
        uid_str = '%d/0/%d' % (unitid, trx_num)
        if restart:
            retcode = self.run('setunitid', '--restart', '--unit-id', '%s' % uid_str, self.bts_ip)
        else:
            retcode = self.run('setunitid', '--unit-id', '%s' % uid_str, self.bts_ip)
        if retcode != 0:
            self.err('ipaccess-config --unit-id %s returned error code %d' % (uid_str, retcode))
        return retcode == 0

    def set_oml_ip(self, omlip, restart=False):
        if restart:
            retcode = self.run('setoml', '--restart', '--oml-ip', omlip, self.bts_ip)
        else:
            retcode = self.run('setoml', '--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

    def get_oml_ip(self):
        retcode = self.run('getoml', '-q', '-G', self.bts_ip)
        if retcode != 0:
            raise log.Error('ipaccess-config -q -G %s returned error code %d' % (self.bts_ip, retcode))
        output = self.proc.get_stdout()
        # Our logging system adds "launched on" line at the start, so let's skip until the json code:
        output_json = output[output.index('{'):]
        json_data = json.loads(output_json)
        return json_data['primary_oml_ip']

# vim: expandtab tabstop=4 shiftwidth=4