blob: aa94f3c9cf1bf765d9be9a1303b8b882e9d5eac2 [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
32from .obj import nitb_osmo, hlr_osmo, mgcpgw_osmo, mgw_osmo, msc_osmo, bsc_osmo, stp_osmo, ggsn_osmo, sgsn_osmo, esme, osmocon, ms_driver, iperf3
33from .obj import run_node
34from .obj import epc
35from .obj import enb
36from .obj import bts
37from .obj import ms
38
Neels Hofmeyr3531a192017-03-28 14:30:28 +020039trial = None
40suite = None
41test = None
42resources = None
43log = None
44dbg = None
45err = None
46wait = None
Pau Espin Pedrol927344b2017-05-22 16:38:49 +020047wait_no_raise = None
Neels Hofmeyr3531a192017-03-28 14:30:28 +020048sleep = None
49poll = None
50prompt = None
Pau Espin Pedrol2d16f6f2017-05-30 15:33:57 +020051Sms = None
Pau Espin Pedrol878b2c62018-05-18 15:57:06 +020052process = None
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +020053
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +020054class Timeout(Exception):
55 pass
56
57class TestEnv(log_module.Origin):
58 def __init__(self, suite_run, test):
59 super().__init__(log_module.C_TST, test.name())
60 self.suite_run = suite_run
61 self._test = test
62 self.trial = suite_run.trial # backward compat with objects
63 self.resources_pool = suite_run.resource_pool() # backward compat with objects
64 self._processes = []
65 self.test_import_modules_to_clean_up = []
66 self.objects_to_clean_up = None
67 MainLoop.register_poll_func(self.poll)
68
69 def suite(self):
70 return self.suite_run
71
72 # backward compat with objects
73 def get_test_run_dir(self):
74 return self._test.get_run_dir()
75
76 # backward compat with objects
77 def config(self):
78 return self.suite_run.config()
79
80 def remember_to_stop(self, process, respawn=False):
81 '''Ask suite to monitor and manage lifecycle of the Process object. If a
82 process managed by suite finishes before cleanup time, the current test
83 will be marked as FAIL and end immediatelly. If respwan=True, then suite
84 will respawn() the process instead.'''
85 self._processes.insert(0, (process, respawn))
86
87 def stop_processes(self):
88 if len(self._processes) == 0:
89 return
90 strategy = process_module.ParallelTerminationStrategy()
91 while self._processes:
92 proc, _ = self._processes.pop()
93 strategy.add_process(proc)
94 strategy.terminate_all()
95
96 def stop_process(self, process):
97 'Remove process from monitored list and stop it'
98 for proc_respawn in self._processes:
99 proc, respawn = proc_respawn
100 if proc == process:
101 self._processes.remove(proc_respawn)
102 proc.terminate()
103
104 def register_for_cleanup(self, *obj):
105 assert all([hasattr(o, 'cleanup') for o in obj])
106 self.objects_to_clean_up = self.objects_to_clean_up or []
107 self.objects_to_clean_up.extend(obj)
108
109 def objects_cleanup(self):
110 while self.objects_to_clean_up:
111 obj = self.objects_to_clean_up.pop()
112 try:
113 obj.cleanup()
114 except Exception:
115 log_module.log_exn()
116
117 def test_import_modules_register_for_cleanup(self, mod):
118 '''
119 Tests are required to call this API for any module loaded from its own
120 lib subdir, because they are loaded in the global namespace. Otherwise
121 later tests importing modules with the same name will re-use an already
122 loaded module.
123 '''
124 if mod not in self.test_import_modules_to_clean_up:
125 self.dbg('registering module %r for cleanup' % mod)
126 self.test_import_modules_to_clean_up.append(mod)
127
128 def test_import_modules_cleanup(self):
129 while self.test_import_modules_to_clean_up:
130 mod = self.test_import_modules_to_clean_up.pop()
131 try:
132 self.dbg('Cleaning up module %r' % mod)
133 del sys.modules[mod.__name__]
134 del mod
135 except Exception:
136 log_module.log_exn()
137
138 def poll(self):
139 for proc, respawn in self._processes:
140 if proc.terminated():
141 if respawn == True:
142 proc.respawn()
143 else:
144 proc.log_stdout_tail()
145 proc.log_stderr_tail()
146 log_module.ctx(proc)
147 raise log_module.Error('Process ended prematurely: %s' % proc.name())
148
149 def stop(self):
150 # if sys.exit() called from signal handler (e.g. SIGINT), SystemExit
151 # base exception is raised. Make sure to stop processes in this
152 # finally section. Resources are automatically freed with 'atexit'.
153 self.stop_processes()
154 self.objects_cleanup()
155 self.suite_run.reserved_resources.put_all()
156 MainLoop.unregister_poll_func(self.poll)
157 self.test_import_modules_cleanup()
158
159 def prompt(self, *msgs, **msg_details):
160 'ask for user interaction. Do not use in tests that should run automatically!'
161 if msg_details:
162 msgs = list(msgs)
163 msgs.append('{%s}' %
164 (', '.join(['%s=%r' % (k,v)
165 for k,v in sorted(msg_details.items())])))
166 msg = ' '.join(msgs) or 'Hit Enter to continue'
167 self.log('prompt:', msg)
168 sys.__stdout__.write('\n\n--- PROMPT ---\n')
169 sys.__stdout__.write(msg)
170 sys.__stdout__.write('\n')
171 sys.__stdout__.flush()
172 entered = util.input_polling('> ', MainLoop.poll)
173 self.log('prompt entered:', repr(entered))
174 return entered
175
176 def get_reserved_resource(self, resource_class_str, specifics=None):
177 return self.suite_run.get_reserved_resource(resource_class_str, specifics)
178
179 def ip_address(self, specifics=None):
180 return self.get_reserved_resource(resource.R_IP_ADDRESS, specifics)
181
182 def nitb(self, ip_address=None):
183 if ip_address is None:
184 ip_address = self.ip_address()
185 return nitb_osmo.OsmoNitb(self, ip_address)
186
187 def hlr(self, ip_address=None):
188 if ip_address is None:
189 ip_address = self.ip_address()
190 return hlr_osmo.OsmoHlr(self, ip_address)
191
192 def ggsn(self, ip_address=None):
193 if ip_address is None:
194 ip_address = self.ip_address()
195 return ggsn_osmo.OsmoGgsn(self, ip_address)
196
197 def sgsn(self, hlr, ggsn, ip_address=None):
198 if ip_address is None:
199 ip_address = self.ip_address()
200 return sgsn_osmo.OsmoSgsn(self, hlr, ggsn, ip_address)
201
202 def mgcpgw(self, ip_address=None, bts_ip=None):
203 if ip_address is None:
204 ip_address = self.ip_address()
205 return mgcpgw_osmo.OsmoMgcpgw(self, ip_address, bts_ip)
206
207 def mgw(self, ip_address=None):
208 if ip_address is None:
209 ip_address = self.ip_address()
210 return mgw_osmo.OsmoMgw(self, ip_address)
211
212 def msc(self, hlr, mgcpgw, stp, ip_address=None):
213 if ip_address is None:
214 ip_address = self.ip_address()
215 return msc_osmo.OsmoMsc(self, hlr, mgcpgw, stp, ip_address)
216
217 def bsc(self, msc, mgw, stp, ip_address=None):
218 if ip_address is None:
219 ip_address = self.ip_address()
220 return bsc_osmo.OsmoBsc(self, msc, mgw, stp, ip_address)
221
222 def stp(self, ip_address=None):
223 if ip_address is None:
224 ip_address = self.ip_address()
225 return stp_osmo.OsmoStp(self, ip_address)
226
227 def ms_driver(self):
228 ms = ms_driver.MsDriver(self)
229 self.register_for_cleanup(ms)
230 return ms
231
232 def bts(self, specifics=None):
233 bts_obj = bts.Bts.get_instance_by_type(self, self.get_reserved_resource(resource.R_BTS, specifics=specifics))
234 bts_obj.set_lac(self.lac())
235 bts_obj.set_rac(self.rac())
236 bts_obj.set_cellid(self.cellid())
237 bts_obj.set_bvci(self.bvci())
238 self.register_for_cleanup(bts_obj)
239 return bts_obj
240
241 def modem(self, specifics=None):
242 conf = self.get_reserved_resource(resource.R_MODEM, specifics=specifics)
243 ms_obj = ms.MS.get_instance_by_type(self, conf)
244 self.register_for_cleanup(ms_obj)
245 return ms_obj
246
247 def modems(self, count):
248 l = []
249 for i in range(count):
250 l.append(self.modem())
251 return l
252
253 def all_resources(self, resource_func):
254 """Returns all yielded resource."""
255 l = []
256 while True:
257 try:
258 l.append(resource_func())
259 except resource.NoResourceExn:
260 return l
261
262 def esme(self):
263 esme_obj = esme.Esme(self.msisdn())
264 self.register_for_cleanup(esme_obj)
265 return esme_obj
266
267 def run_node(self, specifics=None):
268 return run_node.RunNode.from_conf(self.get_reserved_resource(resource.R_RUN_NODE, specifics=specifics))
269
270 def enb(self, specifics=None):
271 enb_obj = enb.eNodeB.get_instance_by_type(self, self.get_reserved_resource(resource.R_ENB, specifics=specifics))
272 self.register_for_cleanup(enb_obj)
273 return enb_obj
274
275 def epc(self, run_node=None):
276 if run_node is None:
277 run_node = self.run_node()
278 epc_obj = epc.EPC.get_instance_by_type(self, run_node)
279 self.register_for_cleanup(epc_obj)
280 return epc_obj
281
282 def osmocon(self, specifics=None):
283 conf = self.get_reserved_resource(resource.R_OSMOCON, specifics=specifics)
284 osmocon_obj = osmocon.Osmocon(self, conf=conf)
285 self.register_for_cleanup(osmocon_obj)
286 return osmocon_obj
287
288 def iperf3srv(self, ip_address=None):
289 if ip_address is None:
290 ip_address = self.ip_address()
291 iperf3srv_obj = iperf3.IPerf3Server(self, ip_address)
292 return iperf3srv_obj
293
294 def msisdn(self):
295 msisdn = self.suite_run.resource_pool().next_msisdn(self)
296 self.log('using MSISDN', msisdn)
297 return msisdn
298
299 def lac(self):
300 lac = self.suite_run.resource_pool().next_lac(self)
301 self.log('using LAC', lac)
302 return lac
303
304 def rac(self):
305 rac = self.suite_run.resource_pool().next_rac(self)
306 self.log('using RAC', rac)
307 return rac
308
309 def cellid(self):
310 cellid = self.suite_run.resource_pool().next_cellid(self)
311 self.log('using CellId', cellid)
312 return cellid
313
314 def bvci(self):
315 bvci = self.suite_run.resource_pool().next_bvci(self)
316 self.log('using BVCI', bvci)
317 return bvci
318
319
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200320def setup(suite_run, _test):
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200321 from .core.event_loop import MainLoop
322 from .obj.sms import Sms as Sms_class
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200323
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200324 global trial, suite, test, resources, log, dbg, err, wait, wait_no_raise, sleep, poll, prompt, Sms, process
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200325
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200326 trial = suite_run.trial
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200327 test = _test
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200328 resources = suite_run.reserved_resources # TODO: remove this global, only used in selftest
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200329 log = test.log
330 dbg = test.dbg
331 err = test.err
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200332 wait = lambda *args, **kwargs: MainLoop.wait(suite_run, *args, **kwargs)
333 wait_no_raise = lambda *args, **kwargs: MainLoop.wait_no_raise(suite_run, *args, **kwargs)
334 sleep = lambda *args, **kwargs: MainLoop.sleep(suite_run, *args, **kwargs)
335 poll = MainLoop.poll
Pau Espin Pedrolee217b02020-05-04 19:06:47 +0200336 Sms = Sms_class
Pau Espin Pedrol878b2c62018-05-18 15:57:06 +0200337 process = process_module
Pau Espin Pedrolaa1cbdc2020-05-04 20:21:31 +0200338 suite = TestEnv(suite_run, _test) # stored in "suite" for backward compatibility
339 prompt = suite.prompt
340 return suite
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200341
Neels Hofmeyrdae3d3c2017-03-28 12:16:58 +0200342# vim: expandtab tabstop=4 shiftwidth=4