blob: a74891dbb2d155a9e129ac348e6cfe0dae04f8a6 [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
Pau Espin Pedrol7cb6bad2020-09-18 15:17:08 +020026from .core import util
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +020027from .core import process
Pau Espin Pedrol166dc102020-06-04 18:44:42 +020028from .core import template
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +020029from .core import log as log_module
30from .core import process as process_module
31from .core import resource
32from .core.event_loop import MainLoop
33
Neels Hofmeyr3531a192017-03-28 14:30:28 +020034suite = None
Neels Hofmeyr3531a192017-03-28 14:30:28 +020035log = None
36dbg = None
37err = None
38wait = None
Pau Espin Pedrol927344b2017-05-22 16:38:49 +020039wait_no_raise = None
Neels Hofmeyr3531a192017-03-28 14:30:28 +020040sleep = None
41poll = None
42prompt = None
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020043Sms = None
Pau Espin Pedrol878b2c62018-05-18 15:57:06 +020044process = None
Pau Espin Pedrol40c7bc72020-05-05 13:41:42 +020045tenv = None
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020046
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +020047class Timeout(Exception):
48 pass
49
50class TestEnv(log_module.Origin):
51 def __init__(self, suite_run, test):
52 super().__init__(log_module.C_TST, test.name())
53 self.suite_run = suite_run
54 self._test = test
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +020055 self._processes = []
56 self.test_import_modules_to_clean_up = []
57 self.objects_to_clean_up = None
58 MainLoop.register_poll_func(self.poll)
Pau Espin Pedrolc3cf6822020-06-12 17:54:55 +020059 if self._test.timeout is not None: # aimed at firing once
Pau Espin Pedrol683d1962020-06-15 11:39:41 +020060 MainLoop.register_poll_func(self._timeout_expired, timestep=self._test.timeout)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +020061
Pau Espin Pedrola442cb82020-05-05 12:54:37 +020062 def test(self):
63 return self._test
64
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +020065 def suite(self):
66 return self.suite_run
67
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +020068 def remember_to_stop(self, process, respawn=False):
69 '''Ask suite to monitor and manage lifecycle of the Process object. If a
70 process managed by suite finishes before cleanup time, the current test
71 will be marked as FAIL and end immediatelly. If respwan=True, then suite
72 will respawn() the process instead.'''
73 self._processes.insert(0, (process, respawn))
74
75 def stop_processes(self):
76 if len(self._processes) == 0:
77 return
78 strategy = process_module.ParallelTerminationStrategy()
79 while self._processes:
80 proc, _ = self._processes.pop()
81 strategy.add_process(proc)
82 strategy.terminate_all()
83
84 def stop_process(self, process):
85 'Remove process from monitored list and stop it'
86 for proc_respawn in self._processes:
87 proc, respawn = proc_respawn
88 if proc == process:
89 self._processes.remove(proc_respawn)
90 proc.terminate()
91
92 def register_for_cleanup(self, *obj):
93 assert all([hasattr(o, 'cleanup') for o in obj])
94 self.objects_to_clean_up = self.objects_to_clean_up or []
95 self.objects_to_clean_up.extend(obj)
96
97 def objects_cleanup(self):
98 while self.objects_to_clean_up:
99 obj = self.objects_to_clean_up.pop()
100 try:
101 obj.cleanup()
102 except Exception:
103 log_module.log_exn()
104
105 def test_import_modules_register_for_cleanup(self, mod):
106 '''
107 Tests are required to call this API for any module loaded from its own
108 lib subdir, because they are loaded in the global namespace. Otherwise
109 later tests importing modules with the same name will re-use an already
110 loaded module.
111 '''
112 if mod not in self.test_import_modules_to_clean_up:
113 self.dbg('registering module %r for cleanup' % mod)
114 self.test_import_modules_to_clean_up.append(mod)
115
116 def test_import_modules_cleanup(self):
117 while self.test_import_modules_to_clean_up:
118 mod = self.test_import_modules_to_clean_up.pop()
119 try:
120 self.dbg('Cleaning up module %r' % mod)
121 del sys.modules[mod.__name__]
122 del mod
123 except Exception:
124 log_module.log_exn()
125
Pau Espin Pedrol683d1962020-06-15 11:39:41 +0200126 def _timeout_expired(self):
Pau Espin Pedrolc3cf6822020-06-12 17:54:55 +0200127 # Avoid timeout being called several times:
Pau Espin Pedrol683d1962020-06-15 11:39:41 +0200128 MainLoop.unregister_poll_func(self._timeout_expired)
Pau Espin Pedrolc3cf6822020-06-12 17:54:55 +0200129 raise log_module.Error('Test Timeout triggered: %d seconds elapsed' % self._test.elapsed_time())
130
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200131 def poll(self):
132 for proc, respawn in self._processes:
133 if proc.terminated():
134 if respawn == True:
135 proc.respawn()
136 else:
137 proc.log_stdout_tail()
138 proc.log_stderr_tail()
139 log_module.ctx(proc)
140 raise log_module.Error('Process ended prematurely: %s' % proc.name())
141
142 def stop(self):
143 # if sys.exit() called from signal handler (e.g. SIGINT), SystemExit
144 # base exception is raised. Make sure to stop processes in this
145 # finally section. Resources are automatically freed with 'atexit'.
146 self.stop_processes()
147 self.objects_cleanup()
148 self.suite_run.reserved_resources.put_all()
149 MainLoop.unregister_poll_func(self.poll)
Pau Espin Pedrol683d1962020-06-15 11:39:41 +0200150 MainLoop.unregister_poll_func(self._timeout_expired)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200151 self.test_import_modules_cleanup()
Pau Espin Pedrol166dc102020-06-04 18:44:42 +0200152 self.set_overlay_template_dir(None)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200153
Pau Espin Pedrol30637302020-05-06 21:11:02 +0200154 def config_suite_specific(self):
155 return self.suite_run.config_suite_specific()
156
157 def config_test_specific(self):
Pau Espin Pedrola75f85a2020-06-12 17:13:26 +0200158 return self._test.config_test_specific()
Pau Espin Pedrol30637302020-05-06 21:11:02 +0200159
Pau Espin Pedrol166dc102020-06-04 18:44:42 +0200160 def set_overlay_template_dir(self, template_dir=None):
161 '''Overlay a directory on top of default one when looking for
162 directories. It must be called everytime a template file is updated.'''
163 if template_dir is None:
164 template.set_templates_dir(template.default_templates_dir())
165 else:
Pau Espin Pedrola4bb6d32020-06-11 16:14:47 +0200166 self.dbg('template dir overlay set: %s' % template_dir)
Pau Espin Pedrol166dc102020-06-04 18:44:42 +0200167 template.set_templates_dir(template_dir, template.default_templates_dir())
168
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200169 def prompt(self, *msgs, **msg_details):
170 'ask for user interaction. Do not use in tests that should run automatically!'
171 if msg_details:
172 msgs = list(msgs)
173 msgs.append('{%s}' %
174 (', '.join(['%s=%r' % (k,v)
175 for k,v in sorted(msg_details.items())])))
176 msg = ' '.join(msgs) or 'Hit Enter to continue'
177 self.log('prompt:', msg)
178 sys.__stdout__.write('\n\n--- PROMPT ---\n')
179 sys.__stdout__.write(msg)
180 sys.__stdout__.write('\n')
181 sys.__stdout__.flush()
182 entered = util.input_polling('> ', MainLoop.poll)
183 self.log('prompt entered:', repr(entered))
184 return entered
185
186 def get_reserved_resource(self, resource_class_str, specifics=None):
187 return self.suite_run.get_reserved_resource(resource_class_str, specifics)
188
189 def ip_address(self, specifics=None):
190 return self.get_reserved_resource(resource.R_IP_ADDRESS, specifics)
191
192 def nitb(self, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200193 from .obj.nitb_osmo import OsmoNitb
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200194 if ip_address is None:
195 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200196 return OsmoNitb(self, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200197
198 def hlr(self, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200199 from .obj.hlr_osmo import OsmoHlr
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200200 if ip_address is None:
201 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200202 return OsmoHlr(self, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200203
204 def ggsn(self, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200205 from .obj.ggsn_osmo import OsmoGgsn
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200206 if ip_address is None:
207 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200208 return OsmoGgsn(self, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200209
210 def sgsn(self, hlr, ggsn, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200211 from .obj import sgsn_osmo
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200212 if ip_address is None:
213 ip_address = self.ip_address()
214 return sgsn_osmo.OsmoSgsn(self, hlr, ggsn, ip_address)
215
216 def mgcpgw(self, ip_address=None, bts_ip=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200217 from .obj.mgcpgw_osmo import OsmoMgcpgw
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200218 if ip_address is None:
219 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200220 return OsmoMgcpgw(self, ip_address, bts_ip)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200221
222 def mgw(self, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200223 from .obj.mgw_osmo import OsmoMgw
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200224 if ip_address is None:
225 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200226 return OsmoMgw(self, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200227
228 def msc(self, hlr, mgcpgw, stp, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200229 from .obj import msc_osmo
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200230 if ip_address is None:
231 ip_address = self.ip_address()
232 return msc_osmo.OsmoMsc(self, hlr, mgcpgw, stp, ip_address)
233
234 def bsc(self, msc, mgw, stp, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200235 from .obj.bsc_osmo import OsmoBsc
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200236 if ip_address is None:
237 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200238 return OsmoBsc(self, msc, mgw, stp, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200239
240 def stp(self, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200241 from .obj.stp_osmo import OsmoStp
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200242 if ip_address is None:
243 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200244 return OsmoStp(self, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200245
246 def ms_driver(self):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200247 from .obj.ms_driver import MsDriver
248 ms = MsDriver(self)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200249 self.register_for_cleanup(ms)
250 return ms
251
252 def bts(self, specifics=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200253 from .obj.bts import Bts
254 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 +0200255 bts_obj.set_lac(self.lac())
256 bts_obj.set_rac(self.rac())
257 bts_obj.set_cellid(self.cellid())
258 bts_obj.set_bvci(self.bvci())
259 self.register_for_cleanup(bts_obj)
260 return bts_obj
261
262 def modem(self, specifics=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200263 from .obj.ms import MS
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200264 conf = self.get_reserved_resource(resource.R_MODEM, specifics=specifics)
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200265 ms_obj = MS.get_instance_by_type(self, conf)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200266 self.register_for_cleanup(ms_obj)
267 return ms_obj
268
269 def modems(self, count):
270 l = []
271 for i in range(count):
272 l.append(self.modem())
273 return l
274
275 def all_resources(self, resource_func):
276 """Returns all yielded resource."""
277 l = []
278 while True:
279 try:
280 l.append(resource_func())
281 except resource.NoResourceExn:
282 return l
283
284 def esme(self):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200285 from .obj.esme import Esme
286 esme_obj = Esme(self.msisdn())
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200287 self.register_for_cleanup(esme_obj)
288 return esme_obj
289
290 def run_node(self, specifics=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200291 from .obj.run_node import RunNode
292 return RunNode.from_conf(self.get_reserved_resource(resource.R_RUN_NODE, specifics=specifics))
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200293
294 def enb(self, specifics=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200295 from .obj.enb import eNodeB
296 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 +0200297 self.register_for_cleanup(enb_obj)
298 return enb_obj
299
300 def epc(self, run_node=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200301 from .obj.epc import EPC
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200302 if run_node is None:
303 run_node = self.run_node()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200304 epc_obj = EPC.get_instance_by_type(self, run_node)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200305 self.register_for_cleanup(epc_obj)
306 return epc_obj
307
308 def osmocon(self, specifics=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200309 from .obj.osmocon import Osmocon
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200310 conf = self.get_reserved_resource(resource.R_OSMOCON, specifics=specifics)
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200311 osmocon_obj = Osmocon(self, conf=conf)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200312 self.register_for_cleanup(osmocon_obj)
313 return osmocon_obj
314
315 def iperf3srv(self, ip_address=None):
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200316 from .obj.iperf3 import IPerf3Server
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200317 if ip_address is None:
318 ip_address = self.ip_address()
Pau Espin Pedrol5b9c7b92020-05-05 14:35:04 +0200319 iperf3srv_obj = IPerf3Server(self, ip_address)
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200320 return iperf3srv_obj
321
Pau Espin Pedrol969a4ee2020-07-02 13:21:08 +0200322 def stress(self, run_node=None):
323 from .obj.stress import StressTool
324 stress_obj = StressTool(self, run_node)
325 return stress_obj
326
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200327 def msisdn(self):
328 msisdn = self.suite_run.resource_pool().next_msisdn(self)
329 self.log('using MSISDN', msisdn)
330 return msisdn
331
332 def lac(self):
333 lac = self.suite_run.resource_pool().next_lac(self)
334 self.log('using LAC', lac)
335 return lac
336
337 def rac(self):
338 rac = self.suite_run.resource_pool().next_rac(self)
339 self.log('using RAC', rac)
340 return rac
341
342 def cellid(self):
343 cellid = self.suite_run.resource_pool().next_cellid(self)
344 self.log('using CellId', cellid)
345 return cellid
346
347 def bvci(self):
348 bvci = self.suite_run.resource_pool().next_bvci(self)
349 self.log('using BVCI', bvci)
350 return bvci
351
352
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200353def setup(suite_run, _test):
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200354 from .core.event_loop import MainLoop
355 from .obj.sms import Sms as Sms_class
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200356
Pau Espin Pedrol315ba572020-06-04 17:09:16 +0200357 global test, log, dbg, err, wait, wait_no_raise, sleep, poll, prompt, Sms, process, tenv
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200358
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200359 test = _test
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200360 log = test.log
361 dbg = test.dbg
362 err = test.err
Pau Espin Pedrol664e3832020-06-10 19:30:33 +0200363 tenv = TestEnv(suite_run, _test)
364 wait = MainLoop.wait
365 wait_no_raise = MainLoop.wait_no_raise
366 sleep = MainLoop.sleep
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200367 poll = MainLoop.poll
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200368 Sms = Sms_class
Pau Espin Pedrol878b2c62018-05-18 15:57:06 +0200369 process = process_module
Pau Espin Pedrol40c7bc72020-05-05 13:41:42 +0200370 prompt = tenv.prompt
371 return tenv
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200372
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200373# vim: expandtab tabstop=4 shiftwidth=4