blob: 691b489792ca75ea4371ddc6a99ebac250983e55 [file] [log] [blame]
Neels Hofmeyr3531a192017-03-28 14:30:28 +02001# osmo_gsm_tester: language snippets
2#
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 Hofmeyr3531a192017-03-28 14:30:28 +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 Hofmeyr3531a192017-03-28 14:30:28 +020016#
Harald Welte27205342017-06-03 09:51:45 +020017# You should have received a copy of the GNU General Public License
Neels Hofmeyr3531a192017-03-28 14:30:28 +020018# along with this program. If not, see <http://www.gnu.org/licenses/>.
19
20import os
21import sys
22import time
23import fcntl
24import hashlib
25import tempfile
26import shutil
27import atexit
28import threading
29import importlib.util
Pau Espin Pedrol13143bc2017-05-08 17:07:28 +020030import subprocess
Pau Espin Pedrol600c7992020-11-09 21:17:51 +010031from watchdog.observers import Observer
32from watchdog.events import FileSystemEventHandler
Neels Hofmeyr3531a192017-03-28 14:30:28 +020033
Neels Hofmeyr0af893c2017-12-14 15:18:05 +010034# This mirrors enum osmo_auth_algo in libosmocore/include/osmocom/crypt/auth.h
35# so that the index within the tuple matches the enum value.
36OSMO_AUTH_ALGO_NONE = 'none'
37ENUM_OSMO_AUTH_ALGO = (OSMO_AUTH_ALGO_NONE, 'comp128v1', 'comp128v2', 'comp128v3', 'xor', 'milenage')
38
39def osmo_auth_algo_by_name(algo_str):
40 'Return enum osmo_auth_algo numeric value as from libosmocore, raise ValueError if not defined.'
41 return ENUM_OSMO_AUTH_ALGO.index(algo_str.lower())
42
Pau Espin Pedrole39c6f12017-05-08 16:21:38 +020043def prepend_library_path(path):
44 lp = os.getenv('LD_LIBRARY_PATH')
45 if not lp:
46 return path
47 return path + ':' + lp
48
Pau Espin Pedroled6d4452017-10-25 17:06:53 +020049def change_elf_rpath(binary, paths, run_dir):
50 '''
51 Change RPATH field in ELF executable binary.
52 This feature can be used to tell the loaded to load the trial libraries, as
53 LD_LIBRARY_PATH is disabled for paths with modified capabilities.
54 '''
55 from .process import Process
56 proc = Process('patchelf', run_dir, ['patchelf', '--set-rpath', paths, binary])
Pau Espin Pedrolb27827b2019-04-04 12:56:37 +020057 proc.launch_sync()
Pau Espin Pedroled6d4452017-10-25 17:06:53 +020058
Pau Espin Pedrol13143bc2017-05-08 17:07:28 +020059def ip_to_iface(ip):
60 try:
61 for iface in os.listdir('/sys/class/net'):
62 proc = subprocess.Popen(['ip', 'addr', 'show', 'dev', iface], stdout=subprocess.PIPE, universal_newlines=True)
63 for line in proc.stdout.readlines():
64 if 'inet' in line and ' ' + ip + '/' in line:
Pau Espin Pedrol74a76762017-08-10 12:07:49 +020065 return line.split()[-1]
Holger Hans Peter Freyther34dce0e2019-02-27 04:34:00 +000066 except Exception:
Pau Espin Pedrol13143bc2017-05-08 17:07:28 +020067 pass
68 return None
69
Pau Espin Pedrola238ed92018-03-26 13:06:12 +020070def dst_ip_get_local_bind(ip):
71 '''Retrieve default IP addr to bind to in order to route traffic to dst addr'''
72 try:
73 proc = subprocess.Popen(['ip', 'route', 'get', ip], stdout=subprocess.PIPE, universal_newlines=True)
74 output = proc.stdout.readlines()
75 words = output[0].split()
76 i = 0
77 while i < len(words):
78 if words[i] == 'src':
79 return words[i+1]
80 i += 1
Holger Hans Peter Freyther34dce0e2019-02-27 04:34:00 +000081 except Exception:
Pau Espin Pedrola238ed92018-03-26 13:06:12 +020082 pass
83 return None
84
Pau Espin Pedrol33c154b2017-10-25 16:16:25 +020085def setcap_net_raw(binary, run_dir):
86 '''
87 This functionality requires specific setup on the host running
88 osmo-gsm-tester. See osmo-gsm-tester manual for more information.
89 '''
90 from .process import Process
Pau Espin Pedrolf0b8e372017-12-13 16:08:35 +010091 SETCAP_NET_RAW_BIN = 'osmo-gsm-tester_setcap_net_raw.sh'
92 proc = Process(SETCAP_NET_RAW_BIN, run_dir, ['sudo', SETCAP_NET_RAW_BIN, binary])
Pau Espin Pedrolb27827b2019-04-04 12:56:37 +020093 proc.launch_sync()
Pau Espin Pedrolf0b8e372017-12-13 16:08:35 +010094
95def setcap_net_admin(binary, run_dir):
96 '''
97 This functionality requires specific setup on the host running
98 osmo-gsm-tester. See osmo-gsm-tester manual for more information.
99 '''
100 from .process import Process
101 SETCAP_NET_ADMIN_BIN = 'osmo-gsm-tester_setcap_net_admin.sh'
102 proc = Process(SETCAP_NET_ADMIN_BIN, run_dir, ['sudo', SETCAP_NET_ADMIN_BIN, binary])
Pau Espin Pedrolb27827b2019-04-04 12:56:37 +0200103 proc.launch_sync()
Pau Espin Pedrol33c154b2017-10-25 16:16:25 +0200104
Pau Espin Pedrol73464722020-02-11 17:41:58 +0100105def setcap_netsys_admin(self, binary, run_dir):
106 '''
107 This functionality requires specific setup on the host running
108 osmo-gsm-tester. See osmo-gsm-tester manual for more information.
109 '''
110 from .process import Process
111 SETCAP_NETSYS_ADMIN_BIN = 'osmo-gsm-tester_setcap_netsys_admin.sh'
112 proc = Process(SETCAP_NETSYS_ADMIN_BIN, run_dir, ['sudo', SETCAP_NETSYS_ADMIN_BIN, binary])
113 proc.launch_sync()
114
Pau Espin Pedrol69b2cf32020-02-24 10:58:59 +0100115def create_netns(netns, run_dir):
Pau Espin Pedrol4c8cd7b2019-04-04 16:08:27 +0200116 '''
Pau Espin Pedrol69b2cf32020-02-24 10:58:59 +0100117 It creates the netns if it doesn't already exist.
Pau Espin Pedrol4c8cd7b2019-04-04 16:08:27 +0200118 '''
119 from .process import Process
120 NETNS_SETUP_BIN = 'osmo-gsm-tester_netns_setup.sh'
Pau Espin Pedrol69b2cf32020-02-24 10:58:59 +0100121 proc = Process('create_netns', ('sudo', NETNS_SETUP_BIN, netns))
122 proc.launch_sync()
123
124def move_iface_to_netns(ifname, netns, run_dir):
125 '''
126 Moves an iface to a netns. It creates the netns if it doesn't exist.
127 '''
128 from .process import Process
129 NETNS_SETUP_BIN = 'osmo-gsm-tester_netns_setup.sh'
130 proc = Process('move_netns', run_dir, ['sudo', NETNS_SETUP_BIN, netns, ifname])
Pau Espin Pedrol4c8cd7b2019-04-04 16:08:27 +0200131 proc.launch_sync()
132
Pau Espin Pedrol7e02d202018-05-08 15:28:48 +0200133def import_path_prepend(pathname):
134 dir = os.path.realpath(pathname)
135 if dir not in sys.path:
136 sys.path.insert(0, dir)
137
138def import_path_remove(pathname):
139 dir = os.path.realpath(pathname)
Pau Espin Pedrolf32c4152018-05-14 15:34:40 +0200140 if dir in sys.path:
Pau Espin Pedrol7e02d202018-05-08 15:28:48 +0200141 sys.path.remove(dir)
142
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200143class listdict(dict):
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200144 'a dict of lists { "a": [1, 2, 3], "b": [1, 2] }'
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200145
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200146 def add(self, name, item):
147 l = self.get(name)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200148 if not l:
149 l = []
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200150 self[name] = l
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200151 l.append(item)
152 return l
153
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200154 def add_dict(self, d):
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200155 for k,v in d.items():
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200156 self.add(k, v)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200157
158class DictProxy:
159 '''
160 allow accessing dict entries like object members
161 syntactical sugar, adapted from http://stackoverflow.com/a/31569634
162 so that e.g. templates can do ${bts.member} instead of ${bts['member']}
163 '''
164 def __init__(self, obj):
165 self.obj = obj
166
167 def __getitem__(self, key):
168 return dict2obj(self.obj[key])
169
170 def __getattr__(self, key):
Your Name44af3412017-04-13 03:11:59 +0200171 'provide error information to know which template item was missing'
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200172 try:
173 return dict2obj(getattr(self.obj, key))
174 except AttributeError:
175 try:
176 return self[key]
177 except KeyError:
178 raise AttributeError(key)
179
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200180def dict2obj(value):
Your Name44af3412017-04-13 03:11:59 +0200181 if is_list(value) or is_dict(value):
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200182 return DictProxy(value)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200183 return value
184
185
186class FileLock:
187 def __init__(self, path, owner):
188 self.path = path
189 self.owner = owner
190 self.f = None
191
192 def __enter__(self):
193 if self.f is not None:
194 return
Neels Hofmeyr936a81e2017-09-14 01:31:41 +0200195 self.fd = os.open(self.path, os.O_CREAT | os.O_WRONLY)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200196 fcntl.flock(self.fd, fcntl.LOCK_EX)
197 os.truncate(self.fd, 0)
198 os.write(self.fd, str(self.owner).encode('utf-8'))
199 os.fsync(self.fd)
200
201 def __exit__(self, *exc_info):
202 #fcntl.flock(self.fd, fcntl.LOCK_UN)
203 os.truncate(self.fd, 0)
204 os.fsync(self.fd)
205 os.close(self.fd)
206 self.fd = -1
207
208 def lock(self):
209 self.__enter__()
210
211 def unlock(self):
212 self.__exit__()
213
214
215class Dir():
216 LOCK_FILE = 'lock'
217
218 def __init__(self, path):
219 self.path = path
220 self.lock_path = os.path.join(self.path, Dir.LOCK_FILE)
221
222 def lock(self, origin_id):
223 '''
224 return lock context, usage:
225
226 with my_dir.lock(origin):
227 read_from(my_dir.child('foo.txt'))
228 write_to(my_dir.child('bar.txt'))
229 '''
230 self.mkdir()
231 return FileLock(self.lock_path, origin_id)
232
233 @staticmethod
234 def ensure_abs_dir_exists(*path_elements):
235 l = len(path_elements)
236 if l < 1:
237 raise RuntimeError('Cannot create empty path')
238 if l == 1:
239 path = path_elements[0]
240 else:
241 path = os.path.join(*path_elements)
242 if not os.path.isdir(path):
243 os.makedirs(path)
244
245 def child(self, *rel_path):
246 if not rel_path:
247 return self.path
248 return os.path.join(self.path, *rel_path)
249
250 def mk_parentdir(self, *rel_path):
251 child = self.child(*rel_path)
252 child_parent = os.path.dirname(child)
253 Dir.ensure_abs_dir_exists(child_parent)
254 return child
255
256 def mkdir(self, *rel_path):
257 child = self.child(*rel_path)
258 Dir.ensure_abs_dir_exists(child)
259 return child
260
261 def children(self):
262 return os.listdir(self.path)
263
264 def exists(self, *rel_path):
265 return os.path.exists(self.child(*rel_path))
266
267 def isdir(self, *rel_path):
268 return os.path.isdir(self.child(*rel_path))
269
270 def isfile(self, *rel_path):
271 return os.path.isfile(self.child(*rel_path))
272
273 def new_child(self, *rel_path):
274 attempt = 1
275 prefix, suffix = os.path.splitext(self.child(*rel_path))
276 rel_path_fmt = '%s%%s%s' % (prefix, suffix)
277 while True:
278 path = rel_path_fmt % (('_%d'%attempt) if attempt > 1 else '')
279 if not os.path.exists(path):
280 break
281 attempt += 1
282 continue
283 Dir.ensure_abs_dir_exists(os.path.dirname(path))
284 return path
285
286 def rel_path(self, path):
287 return os.path.relpath(path, self.path)
288
289 def touch(self, *rel_path):
290 touch_file(self.child(*rel_path))
291
292 def new_file(self, *rel_path):
293 path = self.new_child(*rel_path)
294 touch_file(path)
295 return path
296
297 def new_dir(self, *rel_path):
298 path = self.new_child(*rel_path)
299 Dir.ensure_abs_dir_exists(path)
300 return path
301
302 def __str__(self):
303 return self.path
304 def __repr__(self):
305 return self.path
306
Pau Espin Pedrol600c7992020-11-09 21:17:51 +0100307class FileWatch(FileSystemEventHandler):
308 def __init__(self, origin, watch_path, event_func):
309 FileSystemEventHandler.__init__(self)
310 self.origin = origin
311 self.watch_path = watch_path
312 self.event_func = event_func
313 self.observer = Observer()
314 self.watch = None
315 self.mutex = threading.Lock()
316
317 def get_lock(self):
318 return self.mutex
319
320 def start(self):
321 dir = os.path.abspath(os.path.dirname(self.watch_path))
322 self.origin.dbg('FileWatch: scheduling watch for directory %s' % dir)
323 self.watch = self.observer.schedule(self, dir, recursive = False)
324 self.observer.start()
325
326 def stop(self):
327 if self.watch:
328 self.origin.dbg('FileWatch: unscheduling watch %r' % self.watch)
329 self.observer.unschedule(self.watch)
330 self.watch = None
331 if self.observer.is_alive():
332 self.observer.stop()
333 self.observer.join()
334
335 def __del__(self):
336 self.stop()
337 self.observer = None
338
339 # Override from FileSystemEventHandler
340 def on_any_event(self, event):
341 if event.is_directory:
342 return None
343 if os.path.abspath(event.src_path) != os.path.abspath(self.watch_path):
344 return None
345 self.origin.dbg('FileWatch: received event %r' % event)
346 try:
347 self.mutex.acquire()
348 self.event_func(event)
349 finally:
350 self.mutex.release()
351
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200352def touch_file(path):
353 with open(path, 'a') as f:
354 f.close()
355
356def is_dict(l):
357 return isinstance(l, dict)
358
359def is_list(l):
360 return isinstance(l, (list, tuple))
361
362
363def dict_add(a, *b, **c):
364 for bb in b:
365 a.update(bb)
366 a.update(c)
367 return a
368
369def _hash_recurse(acc, obj, ignore_keys):
370 if is_dict(obj):
371 for key, val in sorted(obj.items()):
372 if key in ignore_keys:
373 continue
374 _hash_recurse(acc, val, ignore_keys)
375 return
376
377 if is_list(obj):
378 for item in obj:
379 _hash_recurse(acc, item, ignore_keys)
380 return
381
382 acc.update(str(obj).encode('utf-8'))
383
384def hash_obj(obj, *ignore_keys):
385 acc = hashlib.sha1()
386 _hash_recurse(acc, obj, ignore_keys)
387 return acc.hexdigest()
388
389
390def md5(of_content):
391 if isinstance(of_content, str):
392 of_content = of_content.encode('utf-8')
393 return hashlib.md5(of_content).hexdigest()
394
395def md5_of_file(path):
396 with open(path, 'rb') as f:
397 return md5(f.read())
398
399_tempdir = None
400
401def get_tempdir(remove_on_exit=True):
402 global _tempdir
403 if _tempdir is not None:
404 return _tempdir
405 _tempdir = tempfile.mkdtemp()
406 if remove_on_exit:
407 atexit.register(lambda: shutil.rmtree(_tempdir))
408 return _tempdir
409
410
411if hasattr(importlib.util, 'module_from_spec'):
412 def run_python_file(module_name, path):
413 spec = importlib.util.spec_from_file_location(module_name, path)
414 spec.loader.exec_module( importlib.util.module_from_spec(spec) )
415else:
416 from importlib.machinery import SourceFileLoader
417 def run_python_file(module_name, path):
418 SourceFileLoader(module_name, path).load_module()
419
Pau Espin Pedrolea8c3d42020-05-04 12:05:05 +0200420def run_python_file_method(module_name, func_name, fail_if_missing=True):
421 module_obj = __import__(module_name, globals(), locals(), [func_name])
422 try:
423 func = getattr(module_obj, func_name)
424 except AttributeError as e:
425 if fail_if_missing:
426 raise RuntimeError('function %s not found in %s (%s)' % (func_name, module_name))
427 else:
428 return None
429 return func()
430
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200431def msisdn_inc(msisdn_str):
432 'add 1 and preserve leading zeros'
433 return ('%%0%dd' % len(msisdn_str)) % (int(msisdn_str) + 1)
434
Neels Hofmeyracf0c932017-05-06 16:05:33 +0200435class InputThread(threading.Thread):
436 def __init__(self, prompt):
437 super().__init__()
438 self.prompt = prompt
439 self.result = None
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200440
Neels Hofmeyracf0c932017-05-06 16:05:33 +0200441 def run(self):
442 self.result = input(self.prompt)
443
444def input_polling(prompt, poll_func):
445 input_thread = InputThread(prompt)
446 input_thread.start()
447
448 while input_thread.is_alive():
449 poll_func()
450 time.sleep(1)
451
452 input_thread.join()
453 return input_thread.result
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200454
Pau Espin Pedrol404e1502017-08-22 11:17:43 +0200455def str2bool(val):
456 if val is None or not val:
457 return False
458 if val.upper() in ['FALSE', 'NO', 'OFF']:
459 return False
460 if val.upper() in ['TRUE','YES', 'ON']:
461 return True
462 raise ValueError('Invalid BOOL field: %r' % val)
463
Pau Espin Pedrol43737da2017-08-28 14:04:07 +0200464def list_validate_same_elem_type(li):
465 '''
466 Checks that all elements in the list are of the same type and returns that type.
467 If the list is empty, returns None
468 If one of the elements is not of the same type, it throws a ValueError exception.
469 '''
470 if len(li) == 0:
471 return None
472 t = type(li[0])
473 for elem in li:
474 if type(elem) != t:
475 raise ValueError('List contains elements of different types: %r vs %r' % (t, type(elem)))
476 return t
477
478def empty_instance_type(t):
479 if t == dict:
480 return {}
481 elif t == list:
482 return []
483 elif t == tuple:
484 return ()
485 raise ValueError('type %r not supported!' % t)
486
Pau Espin Pedrolabd556a2017-09-04 16:26:08 +0200487def encryption2osmovty(val):
488 assert val[:3] == 'a5_'
489 return 'a5 ' + val[3:]
490
Pau Espin Pedrold6deb282020-10-16 18:49:39 +0200491# Return abs path to external dir, where utils to run out of the process are placed
492def external_dir():
493 # ../external/
494 return os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'external'))
495
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200496# vim: expandtab tabstop=4 shiftwidth=4