blob: e035a7293f7442f2c9b6faefccf41a912839b61b [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
Neels Hofmeyr3531a192017-03-28 14:30:28 +020031
Neels Hofmeyr0af893c2017-12-14 15:18:05 +010032# This mirrors enum osmo_auth_algo in libosmocore/include/osmocom/crypt/auth.h
33# so that the index within the tuple matches the enum value.
34OSMO_AUTH_ALGO_NONE = 'none'
35ENUM_OSMO_AUTH_ALGO = (OSMO_AUTH_ALGO_NONE, 'comp128v1', 'comp128v2', 'comp128v3', 'xor', 'milenage')
36
37def osmo_auth_algo_by_name(algo_str):
38 'Return enum osmo_auth_algo numeric value as from libosmocore, raise ValueError if not defined.'
39 return ENUM_OSMO_AUTH_ALGO.index(algo_str.lower())
40
Pau Espin Pedrole39c6f12017-05-08 16:21:38 +020041def prepend_library_path(path):
42 lp = os.getenv('LD_LIBRARY_PATH')
43 if not lp:
44 return path
45 return path + ':' + lp
46
Pau Espin Pedroled6d4452017-10-25 17:06:53 +020047def change_elf_rpath(binary, paths, run_dir):
48 '''
49 Change RPATH field in ELF executable binary.
50 This feature can be used to tell the loaded to load the trial libraries, as
51 LD_LIBRARY_PATH is disabled for paths with modified capabilities.
52 '''
53 from .process import Process
54 proc = Process('patchelf', run_dir, ['patchelf', '--set-rpath', paths, binary])
Pau Espin Pedrolb27827b2019-04-04 12:56:37 +020055 proc.launch_sync()
Pau Espin Pedroled6d4452017-10-25 17:06:53 +020056
Pau Espin Pedrol13143bc2017-05-08 17:07:28 +020057def ip_to_iface(ip):
58 try:
59 for iface in os.listdir('/sys/class/net'):
60 proc = subprocess.Popen(['ip', 'addr', 'show', 'dev', iface], stdout=subprocess.PIPE, universal_newlines=True)
61 for line in proc.stdout.readlines():
62 if 'inet' in line and ' ' + ip + '/' in line:
Pau Espin Pedrol74a76762017-08-10 12:07:49 +020063 return line.split()[-1]
Holger Hans Peter Freyther34dce0e2019-02-27 04:34:00 +000064 except Exception:
Pau Espin Pedrol13143bc2017-05-08 17:07:28 +020065 pass
66 return None
67
Pau Espin Pedrola238ed92018-03-26 13:06:12 +020068def dst_ip_get_local_bind(ip):
69 '''Retrieve default IP addr to bind to in order to route traffic to dst addr'''
70 try:
71 proc = subprocess.Popen(['ip', 'route', 'get', ip], stdout=subprocess.PIPE, universal_newlines=True)
72 output = proc.stdout.readlines()
73 words = output[0].split()
74 i = 0
75 while i < len(words):
76 if words[i] == 'src':
77 return words[i+1]
78 i += 1
Holger Hans Peter Freyther34dce0e2019-02-27 04:34:00 +000079 except Exception:
Pau Espin Pedrola238ed92018-03-26 13:06:12 +020080 pass
81 return None
82
Pau Espin Pedrol33c154b2017-10-25 16:16:25 +020083def setcap_net_raw(binary, run_dir):
84 '''
85 This functionality requires specific setup on the host running
86 osmo-gsm-tester. See osmo-gsm-tester manual for more information.
87 '''
88 from .process import Process
Pau Espin Pedrolf0b8e372017-12-13 16:08:35 +010089 SETCAP_NET_RAW_BIN = 'osmo-gsm-tester_setcap_net_raw.sh'
90 proc = Process(SETCAP_NET_RAW_BIN, run_dir, ['sudo', SETCAP_NET_RAW_BIN, binary])
Pau Espin Pedrolb27827b2019-04-04 12:56:37 +020091 proc.launch_sync()
Pau Espin Pedrolf0b8e372017-12-13 16:08:35 +010092
93def setcap_net_admin(binary, run_dir):
94 '''
95 This functionality requires specific setup on the host running
96 osmo-gsm-tester. See osmo-gsm-tester manual for more information.
97 '''
98 from .process import Process
99 SETCAP_NET_ADMIN_BIN = 'osmo-gsm-tester_setcap_net_admin.sh'
100 proc = Process(SETCAP_NET_ADMIN_BIN, run_dir, ['sudo', SETCAP_NET_ADMIN_BIN, binary])
Pau Espin Pedrolb27827b2019-04-04 12:56:37 +0200101 proc.launch_sync()
Pau Espin Pedrol33c154b2017-10-25 16:16:25 +0200102
Pau Espin Pedrol73464722020-02-11 17:41:58 +0100103def setcap_netsys_admin(self, binary, run_dir):
104 '''
105 This functionality requires specific setup on the host running
106 osmo-gsm-tester. See osmo-gsm-tester manual for more information.
107 '''
108 from .process import Process
109 SETCAP_NETSYS_ADMIN_BIN = 'osmo-gsm-tester_setcap_netsys_admin.sh'
110 proc = Process(SETCAP_NETSYS_ADMIN_BIN, run_dir, ['sudo', SETCAP_NETSYS_ADMIN_BIN, binary])
111 proc.launch_sync()
112
Pau Espin Pedrol69b2cf32020-02-24 10:58:59 +0100113def create_netns(netns, run_dir):
Pau Espin Pedrol4c8cd7b2019-04-04 16:08:27 +0200114 '''
Pau Espin Pedrol69b2cf32020-02-24 10:58:59 +0100115 It creates the netns if it doesn't already exist.
Pau Espin Pedrol4c8cd7b2019-04-04 16:08:27 +0200116 '''
117 from .process import Process
118 NETNS_SETUP_BIN = 'osmo-gsm-tester_netns_setup.sh'
Pau Espin Pedrol69b2cf32020-02-24 10:58:59 +0100119 proc = Process('create_netns', ('sudo', NETNS_SETUP_BIN, netns))
120 proc.launch_sync()
121
122def move_iface_to_netns(ifname, netns, run_dir):
123 '''
124 Moves an iface to a netns. It creates the netns if it doesn't exist.
125 '''
126 from .process import Process
127 NETNS_SETUP_BIN = 'osmo-gsm-tester_netns_setup.sh'
128 proc = Process('move_netns', run_dir, ['sudo', NETNS_SETUP_BIN, netns, ifname])
Pau Espin Pedrol4c8cd7b2019-04-04 16:08:27 +0200129 proc.launch_sync()
130
Pau Espin Pedrol7e02d202018-05-08 15:28:48 +0200131def import_path_prepend(pathname):
132 dir = os.path.realpath(pathname)
133 if dir not in sys.path:
134 sys.path.insert(0, dir)
135
136def import_path_remove(pathname):
137 dir = os.path.realpath(pathname)
Pau Espin Pedrolf32c4152018-05-14 15:34:40 +0200138 if dir in sys.path:
Pau Espin Pedrol7e02d202018-05-08 15:28:48 +0200139 sys.path.remove(dir)
140
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200141class listdict(dict):
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200142 'a dict of lists { "a": [1, 2, 3], "b": [1, 2] }'
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200143
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200144 def add(self, name, item):
145 l = self.get(name)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200146 if not l:
147 l = []
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200148 self[name] = l
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200149 l.append(item)
150 return l
151
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200152 def add_dict(self, d):
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200153 for k,v in d.items():
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200154 self.add(k, v)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200155
156class DictProxy:
157 '''
158 allow accessing dict entries like object members
159 syntactical sugar, adapted from http://stackoverflow.com/a/31569634
160 so that e.g. templates can do ${bts.member} instead of ${bts['member']}
161 '''
162 def __init__(self, obj):
163 self.obj = obj
164
165 def __getitem__(self, key):
166 return dict2obj(self.obj[key])
167
168 def __getattr__(self, key):
Your Name44af3412017-04-13 03:11:59 +0200169 'provide error information to know which template item was missing'
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200170 try:
171 return dict2obj(getattr(self.obj, key))
172 except AttributeError:
173 try:
174 return self[key]
175 except KeyError:
176 raise AttributeError(key)
177
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200178def dict2obj(value):
Your Name44af3412017-04-13 03:11:59 +0200179 if is_list(value) or is_dict(value):
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200180 return DictProxy(value)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200181 return value
182
183
184class FileLock:
185 def __init__(self, path, owner):
186 self.path = path
187 self.owner = owner
188 self.f = None
189
190 def __enter__(self):
191 if self.f is not None:
192 return
Neels Hofmeyr936a81e2017-09-14 01:31:41 +0200193 self.fd = os.open(self.path, os.O_CREAT | os.O_WRONLY)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200194 fcntl.flock(self.fd, fcntl.LOCK_EX)
195 os.truncate(self.fd, 0)
196 os.write(self.fd, str(self.owner).encode('utf-8'))
197 os.fsync(self.fd)
198
199 def __exit__(self, *exc_info):
200 #fcntl.flock(self.fd, fcntl.LOCK_UN)
201 os.truncate(self.fd, 0)
202 os.fsync(self.fd)
203 os.close(self.fd)
204 self.fd = -1
205
206 def lock(self):
207 self.__enter__()
208
209 def unlock(self):
210 self.__exit__()
211
212
213class Dir():
214 LOCK_FILE = 'lock'
215
216 def __init__(self, path):
217 self.path = path
218 self.lock_path = os.path.join(self.path, Dir.LOCK_FILE)
219
220 def lock(self, origin_id):
221 '''
222 return lock context, usage:
223
224 with my_dir.lock(origin):
225 read_from(my_dir.child('foo.txt'))
226 write_to(my_dir.child('bar.txt'))
227 '''
228 self.mkdir()
229 return FileLock(self.lock_path, origin_id)
230
231 @staticmethod
232 def ensure_abs_dir_exists(*path_elements):
233 l = len(path_elements)
234 if l < 1:
235 raise RuntimeError('Cannot create empty path')
236 if l == 1:
237 path = path_elements[0]
238 else:
239 path = os.path.join(*path_elements)
240 if not os.path.isdir(path):
241 os.makedirs(path)
242
243 def child(self, *rel_path):
244 if not rel_path:
245 return self.path
246 return os.path.join(self.path, *rel_path)
247
248 def mk_parentdir(self, *rel_path):
249 child = self.child(*rel_path)
250 child_parent = os.path.dirname(child)
251 Dir.ensure_abs_dir_exists(child_parent)
252 return child
253
254 def mkdir(self, *rel_path):
255 child = self.child(*rel_path)
256 Dir.ensure_abs_dir_exists(child)
257 return child
258
259 def children(self):
260 return os.listdir(self.path)
261
262 def exists(self, *rel_path):
263 return os.path.exists(self.child(*rel_path))
264
265 def isdir(self, *rel_path):
266 return os.path.isdir(self.child(*rel_path))
267
268 def isfile(self, *rel_path):
269 return os.path.isfile(self.child(*rel_path))
270
271 def new_child(self, *rel_path):
272 attempt = 1
273 prefix, suffix = os.path.splitext(self.child(*rel_path))
274 rel_path_fmt = '%s%%s%s' % (prefix, suffix)
275 while True:
276 path = rel_path_fmt % (('_%d'%attempt) if attempt > 1 else '')
277 if not os.path.exists(path):
278 break
279 attempt += 1
280 continue
281 Dir.ensure_abs_dir_exists(os.path.dirname(path))
282 return path
283
284 def rel_path(self, path):
285 return os.path.relpath(path, self.path)
286
287 def touch(self, *rel_path):
288 touch_file(self.child(*rel_path))
289
290 def new_file(self, *rel_path):
291 path = self.new_child(*rel_path)
292 touch_file(path)
293 return path
294
295 def new_dir(self, *rel_path):
296 path = self.new_child(*rel_path)
297 Dir.ensure_abs_dir_exists(path)
298 return path
299
300 def __str__(self):
301 return self.path
302 def __repr__(self):
303 return self.path
304
305def touch_file(path):
306 with open(path, 'a') as f:
307 f.close()
308
309def is_dict(l):
310 return isinstance(l, dict)
311
312def is_list(l):
313 return isinstance(l, (list, tuple))
314
315
316def dict_add(a, *b, **c):
317 for bb in b:
318 a.update(bb)
319 a.update(c)
320 return a
321
322def _hash_recurse(acc, obj, ignore_keys):
323 if is_dict(obj):
324 for key, val in sorted(obj.items()):
325 if key in ignore_keys:
326 continue
327 _hash_recurse(acc, val, ignore_keys)
328 return
329
330 if is_list(obj):
331 for item in obj:
332 _hash_recurse(acc, item, ignore_keys)
333 return
334
335 acc.update(str(obj).encode('utf-8'))
336
337def hash_obj(obj, *ignore_keys):
338 acc = hashlib.sha1()
339 _hash_recurse(acc, obj, ignore_keys)
340 return acc.hexdigest()
341
342
343def md5(of_content):
344 if isinstance(of_content, str):
345 of_content = of_content.encode('utf-8')
346 return hashlib.md5(of_content).hexdigest()
347
348def md5_of_file(path):
349 with open(path, 'rb') as f:
350 return md5(f.read())
351
352_tempdir = None
353
354def get_tempdir(remove_on_exit=True):
355 global _tempdir
356 if _tempdir is not None:
357 return _tempdir
358 _tempdir = tempfile.mkdtemp()
359 if remove_on_exit:
360 atexit.register(lambda: shutil.rmtree(_tempdir))
361 return _tempdir
362
363
364if hasattr(importlib.util, 'module_from_spec'):
365 def run_python_file(module_name, path):
366 spec = importlib.util.spec_from_file_location(module_name, path)
367 spec.loader.exec_module( importlib.util.module_from_spec(spec) )
368else:
369 from importlib.machinery import SourceFileLoader
370 def run_python_file(module_name, path):
371 SourceFileLoader(module_name, path).load_module()
372
Pau Espin Pedrolea8c3d42020-05-04 12:05:05 +0200373def run_python_file_method(module_name, func_name, fail_if_missing=True):
374 module_obj = __import__(module_name, globals(), locals(), [func_name])
375 try:
376 func = getattr(module_obj, func_name)
377 except AttributeError as e:
378 if fail_if_missing:
379 raise RuntimeError('function %s not found in %s (%s)' % (func_name, module_name))
380 else:
381 return None
382 return func()
383
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200384def msisdn_inc(msisdn_str):
385 'add 1 and preserve leading zeros'
386 return ('%%0%dd' % len(msisdn_str)) % (int(msisdn_str) + 1)
387
Neels Hofmeyracf0c932017-05-06 16:05:33 +0200388class InputThread(threading.Thread):
389 def __init__(self, prompt):
390 super().__init__()
391 self.prompt = prompt
392 self.result = None
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200393
Neels Hofmeyracf0c932017-05-06 16:05:33 +0200394 def run(self):
395 self.result = input(self.prompt)
396
397def input_polling(prompt, poll_func):
398 input_thread = InputThread(prompt)
399 input_thread.start()
400
401 while input_thread.is_alive():
402 poll_func()
403 time.sleep(1)
404
405 input_thread.join()
406 return input_thread.result
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200407
Pau Espin Pedrol404e1502017-08-22 11:17:43 +0200408def str2bool(val):
409 if val is None or not val:
410 return False
411 if val.upper() in ['FALSE', 'NO', 'OFF']:
412 return False
413 if val.upper() in ['TRUE','YES', 'ON']:
414 return True
415 raise ValueError('Invalid BOOL field: %r' % val)
416
Pau Espin Pedrol43737da2017-08-28 14:04:07 +0200417def list_validate_same_elem_type(li):
418 '''
419 Checks that all elements in the list are of the same type and returns that type.
420 If the list is empty, returns None
421 If one of the elements is not of the same type, it throws a ValueError exception.
422 '''
423 if len(li) == 0:
424 return None
425 t = type(li[0])
426 for elem in li:
427 if type(elem) != t:
428 raise ValueError('List contains elements of different types: %r vs %r' % (t, type(elem)))
429 return t
430
431def empty_instance_type(t):
432 if t == dict:
433 return {}
434 elif t == list:
435 return []
436 elif t == tuple:
437 return ()
438 raise ValueError('type %r not supported!' % t)
439
Pau Espin Pedrolabd556a2017-09-04 16:26:08 +0200440def encryption2osmovty(val):
441 assert val[:3] == 'a5_'
442 return 'a5 ' + val[3:]
443
Pau Espin Pedrold6deb282020-10-16 18:49:39 +0200444# Return abs path to external dir, where utils to run out of the process are placed
445def external_dir():
446 # ../external/
447 return os.path.abspath(os.path.join(os.path.dirname(os.path.dirname(__file__)), 'external'))
448
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200449# vim: expandtab tabstop=4 shiftwidth=4