blob: 42288aa283eacbe90cc4a705efd4df22256bfc03 [file] [log] [blame]
Neels Hofmeyr3531a192017-03-28 14:30:28 +02001# osmo_gsm_tester: context for individual test runs
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +02002#
3# Copyright (C) 2016-2017 by sysmocom - s.f.m.c. GmbH
4#
5# Author: Neels Hofmeyr <neels@hofmeyr.de>
6#
7# This program is free software: you can redistribute it and/or modify
Harald Welte27205342017-06-03 09:51:45 +02008# it under the terms of the GNU General Public License as
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +02009# 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
Harald Welte27205342017-06-03 09:51:45 +020015# GNU General Public License for more details.
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020016#
Harald Welte27205342017-06-03 09:51:45 +020017# You should have received a copy of the GNU General Public License
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020018# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
Neels Hofmeyr3531a192017-03-28 14:30:28 +020020# These will be initialized before each test run.
21# A test script can thus establish its context by doing:
Pau Espin Pedroldfe38ad2017-11-09 13:57:39 +010022# from osmo_gsm_tester.testenv import *
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +020023
24import sys
25
26from .core import process
27from .core import log as log_module
28from .core import process as process_module
29from .core import resource
30from .core.event_loop import MainLoop
31
Neels Hofmeyr3531a192017-03-28 14:30:28 +020032suite = None
Neels Hofmeyr3531a192017-03-28 14:30:28 +020033resources = None
34log = None
35dbg = None
36err = None
37wait = None
Pau Espin Pedrol927344b2017-05-22 16:38:49 +020038wait_no_raise = None
Neels Hofmeyr3531a192017-03-28 14:30:28 +020039sleep = None
40poll = None
41prompt = None
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020042Sms = None
Pau Espin Pedrol878b2c62018-05-18 15:57:06 +020043process = None
Pau Espin Pedrol40c7bc72020-05-05 13:41:42 +020044tenv = None
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020045
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +020046class Timeout(Exception):
47 pass
48
49class TestEnv(log_module.Origin):
50 def __init__(self, suite_run, test):
51 super().__init__(log_module.C_TST, test.name())
52 self.suite_run = suite_run
53 self._test = test
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +020054 self._processes = []
55 self.test_import_modules_to_clean_up = []
56 self.objects_to_clean_up = None
57 MainLoop.register_poll_func(self.poll)
58
Pau Espin Pedrola442cb82020-05-05 12:54:37 +020059 def test(self):
60 return self._test
61
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +020062 def suite(self):
63 return self.suite_run
64
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +020065 def remember_to_stop(self, process, respawn=False):
66 '''Ask suite to monitor and manage lifecycle of the Process object. If a
67 process managed by suite finishes before cleanup time, the current test
68 will be marked as FAIL and end immediatelly. If respwan=True, then suite
69 will respawn() the process instead.'''
70 self._processes.insert(0, (process, respawn))
71
72 def stop_processes(self):
73 if len(self._processes) == 0:
74 return
75 strategy = process_module.ParallelTerminationStrategy()
76 while self._processes:
77 proc, _ = self._processes.pop()
78 strategy.add_process(proc)
79 strategy.terminate_all()
80
81 def stop_process(self, process):
82 'Remove process from monitored list and stop it'
83 for proc_respawn in self._processes:
84 proc, respawn = proc_respawn
85 if proc == process:
86 self._processes.remove(proc_respawn)
87 proc.terminate()
88
89 def register_for_cleanup(self, *obj):
90 assert all([hasattr(o, 'cleanup') for o in obj])
91 self.objects_to_clean_up = self.objects_to_clean_up or []
92 self.objects_to_clean_up.extend(obj)
93
94 def objects_cleanup(self):
95 while self.objects_to_clean_up:
96 obj = self.objects_to_clean_up.pop()
97 try:
98 obj.cleanup()
99 except Exception:
100 log_module.log_exn()
101
102 def test_import_modules_register_for_cleanup(self, mod):
103 '''
104 Tests are required to call this API for any module loaded from its own
105 lib subdir, because they are loaded in the global namespace. Otherwise
106 later tests importing modules with the same name will re-use an already
107 loaded module.
108 '''
109 if mod not in self.test_import_modules_to_clean_up:
110 self.dbg('registering module %r for cleanup' % mod)
111 self.test_import_modules_to_clean_up.append(mod)
112
113 def test_import_modules_cleanup(self):
114 while self.test_import_modules_to_clean_up:
115 mod = self.test_import_modules_to_clean_up.pop()
116 try:
117 self.dbg('Cleaning up module %r' % mod)
118 del sys.modules[mod.__name__]
119 del mod
120 except Exception:
121 log_module.log_exn()
122
123 def poll(self):
124 for proc, respawn in self._processes:
125 if proc.terminated():
126 if respawn == True:
127 proc.respawn()
128 else:
129 proc.log_stdout_tail()
130 proc.log_stderr_tail()
131 log_module.ctx(proc)
132 raise log_module.Error('Process ended prematurely: %s' % proc.name())
133
134 def stop(self):
135 # if sys.exit() called from signal handler (e.g. SIGINT), SystemExit
136 # base exception is raised. Make sure to stop processes in this
137 # finally section. Resources are automatically freed with 'atexit'.
138 self.stop_processes()
139 self.objects_cleanup()
140 self.suite_run.reserved_resources.put_all()
141 MainLoop.unregister_poll_func(self.poll)
142 self.test_import_modules_cleanup()
143
144 def prompt(self, *msgs, **msg_details):
145 'ask for user interaction. Do not use in tests that should run automatically!'
146 if msg_details:
147 msgs = list(msgs)
148 msgs.append('{%s}' %
149 (', '.join(['%s=%r' % (k,v)
150 for k,v in sorted(msg_details.items())])))
151 msg = ' '.join(msgs) or 'Hit Enter to continue'
152 self.log('prompt:', msg)
153 sys.__stdout__.write('\n\n--- PROMPT ---\n')
154 sys.__stdout__.write(msg)
155 sys.__stdout__.write('\n')
156 sys.__stdout__.flush()
157 entered = util.input_polling('> ', MainLoop.poll)
158 self.log('prompt entered:', repr(entered))
159 return entered
160
161 def get_reserved_resource(self, resource_class_str, specifics=None):
162 return self.suite_run.get_reserved_resource(resource_class_str, specifics)
163
164 def ip_address(self, specifics=None):
165 return self.get_reserved_resource(resource.R_IP_ADDRESS, specifics)
166
167 def nitb(self, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200168 from .obj.nitb_osmo import OsmoNitb
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200169 if ip_address is None:
170 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200171 return OsmoNitb(self, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200172
173 def hlr(self, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200174 from .obj.hlr_osmo import OsmoHlr
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200175 if ip_address is None:
176 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200177 return OsmoHlr(self, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200178
179 def ggsn(self, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200180 from .obj.ggsn_osmo import OsmoGgsn
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200181 if ip_address is None:
182 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200183 return OsmoGgsn(self, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200184
185 def sgsn(self, hlr, ggsn, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200186 from .obj import sgsn_osmo
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200187 if ip_address is None:
188 ip_address = self.ip_address()
189 return sgsn_osmo.OsmoSgsn(self, hlr, ggsn, ip_address)
190
191 def mgcpgw(self, ip_address=None, bts_ip=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200192 from .obj.mgcpgw_osmo import OsmoMgcpgw
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200193 if ip_address is None:
194 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200195 return OsmoMgcpgw(self, ip_address, bts_ip)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200196
197 def mgw(self, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200198 from .obj.mgw_osmo import OsmoMgw
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200199 if ip_address is None:
200 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200201 return OsmoMgw(self, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200202
203 def msc(self, hlr, mgcpgw, stp, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200204 from .obj import msc_osmo
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200205 if ip_address is None:
206 ip_address = self.ip_address()
207 return msc_osmo.OsmoMsc(self, hlr, mgcpgw, stp, ip_address)
208
209 def bsc(self, msc, mgw, stp, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200210 from .obj.bsc_osmo import OsmoBsc
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200211 if ip_address is None:
212 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200213 return OsmoBsc(self, msc, mgw, stp, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200214
215 def stp(self, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200216 from .obj.stp_osmo import OsmoStp
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200217 if ip_address is None:
218 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200219 return OsmoStp(self, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200220
221 def ms_driver(self):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200222 from .obj.ms_driver import MsDriver
223 ms = MsDriver(self)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200224 self.register_for_cleanup(ms)
225 return ms
226
227 def bts(self, specifics=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200228 from .obj.bts import Bts
229 bts_obj = Bts.get_instance_by_type(self, self.get_reserved_resource(resource.R_BTS, specifics=specifics))
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200230 bts_obj.set_lac(self.lac())
231 bts_obj.set_rac(self.rac())
232 bts_obj.set_cellid(self.cellid())
233 bts_obj.set_bvci(self.bvci())
234 self.register_for_cleanup(bts_obj)
235 return bts_obj
236
237 def modem(self, specifics=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200238 from .obj.ms import MS
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200239 conf = self.get_reserved_resource(resource.R_MODEM, specifics=specifics)
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200240 ms_obj = MS.get_instance_by_type(self, conf)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200241 self.register_for_cleanup(ms_obj)
242 return ms_obj
243
244 def modems(self, count):
245 l = []
246 for i in range(count):
247 l.append(self.modem())
248 return l
249
250 def all_resources(self, resource_func):
251 """Returns all yielded resource."""
252 l = []
253 while True:
254 try:
255 l.append(resource_func())
256 except resource.NoResourceExn:
257 return l
258
259 def esme(self):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200260 from .obj.esme import Esme
261 esme_obj = Esme(self.msisdn())
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200262 self.register_for_cleanup(esme_obj)
263 return esme_obj
264
265 def run_node(self, specifics=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200266 from .obj.run_node import RunNode
267 return RunNode.from_conf(self.get_reserved_resource(resource.R_RUN_NODE, specifics=specifics))
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200268
269 def enb(self, specifics=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200270 from .obj.enb import eNodeB
271 enb_obj = eNodeB.get_instance_by_type(self, self.get_reserved_resource(resource.R_ENB, specifics=specifics))
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200272 self.register_for_cleanup(enb_obj)
273 return enb_obj
274
275 def epc(self, run_node=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200276 from .obj.epc import EPC
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200277 if run_node is None:
278 run_node = self.run_node()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200279 epc_obj = EPC.get_instance_by_type(self, run_node)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200280 self.register_for_cleanup(epc_obj)
281 return epc_obj
282
283 def osmocon(self, specifics=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200284 from .obj.osmocon import Osmocon
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200285 conf = self.get_reserved_resource(resource.R_OSMOCON, specifics=specifics)
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200286 osmocon_obj = Osmocon(self, conf=conf)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200287 self.register_for_cleanup(osmocon_obj)
288 return osmocon_obj
289
290 def iperf3srv(self, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200291 from .obj.iperf3 import IPerf3Server
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200292 if ip_address is None:
293 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200294 iperf3srv_obj = IPerf3Server(self, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200295 return iperf3srv_obj
296
297 def msisdn(self):
298 msisdn = self.suite_run.resource_pool().next_msisdn(self)
299 self.log('using MSISDN', msisdn)
300 return msisdn
301
302 def lac(self):
303 lac = self.suite_run.resource_pool().next_lac(self)
304 self.log('using LAC', lac)
305 return lac
306
307 def rac(self):
308 rac = self.suite_run.resource_pool().next_rac(self)
309 self.log('using RAC', rac)
310 return rac
311
312 def cellid(self):
313 cellid = self.suite_run.resource_pool().next_cellid(self)
314 self.log('using CellId', cellid)
315 return cellid
316
317 def bvci(self):
318 bvci = self.suite_run.resource_pool().next_bvci(self)
319 self.log('using BVCI', bvci)
320 return bvci
321
322
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200323def setup(suite_run, _test):
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200324 from .core.event_loop import MainLoop
325 from .obj.sms import Sms as Sms_class
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200326
Pau Espin Pedrol40c7bc72020-05-05 13:41:42 +0200327 global test, resources, log, dbg, err, wait, wait_no_raise, sleep, poll, prompt, Sms, process, tenv
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200328
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200329 test = _test
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200330 resources = suite_run.reserved_resources # TODO: remove this global, only used in selftest
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200331 log = test.log
332 dbg = test.dbg
333 err = test.err
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200334 wait = lambda *args, **kwargs: MainLoop.wait(suite_run, *args, **kwargs)
335 wait_no_raise = lambda *args, **kwargs: MainLoop.wait_no_raise(suite_run, *args, **kwargs)
336 sleep = lambda *args, **kwargs: MainLoop.sleep(suite_run, *args, **kwargs)
337 poll = MainLoop.poll
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200338 Sms = Sms_class
Pau Espin Pedrol878b2c62018-05-18 15:57:06 +0200339 process = process_module
Pau Espin Pedrol40c7bc72020-05-05 13:41:42 +0200340 tenv = TestEnv(suite_run, _test)
341 prompt = tenv.prompt
342 return tenv
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200343
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200344# vim: expandtab tabstop=4 shiftwidth=4