blob: 21653bee0e978a22eeb08da8538d565734ea74e2 [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 Pedrol7e02d202018-05-08 15:28:48 +0200103def import_path_prepend(pathname):
104 dir = os.path.realpath(pathname)
105 if dir not in sys.path:
106 sys.path.insert(0, dir)
107
108def import_path_remove(pathname):
109 dir = os.path.realpath(pathname)
Pau Espin Pedrolf32c4152018-05-14 15:34:40 +0200110 if dir in sys.path:
Pau Espin Pedrol7e02d202018-05-08 15:28:48 +0200111 sys.path.remove(dir)
112
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200113class listdict(dict):
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200114 'a dict of lists { "a": [1, 2, 3], "b": [1, 2] }'
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200115
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200116 def add(self, name, item):
117 l = self.get(name)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200118 if not l:
119 l = []
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200120 self[name] = l
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200121 l.append(item)
122 return l
123
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200124 def add_dict(self, d):
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200125 for k,v in d.items():
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200126 self.add(k, v)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200127
128class DictProxy:
129 '''
130 allow accessing dict entries like object members
131 syntactical sugar, adapted from http://stackoverflow.com/a/31569634
132 so that e.g. templates can do ${bts.member} instead of ${bts['member']}
133 '''
134 def __init__(self, obj):
135 self.obj = obj
136
137 def __getitem__(self, key):
138 return dict2obj(self.obj[key])
139
140 def __getattr__(self, key):
Your Name44af3412017-04-13 03:11:59 +0200141 'provide error information to know which template item was missing'
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200142 try:
143 return dict2obj(getattr(self.obj, key))
144 except AttributeError:
145 try:
146 return self[key]
147 except KeyError:
148 raise AttributeError(key)
149
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200150def dict2obj(value):
Your Name44af3412017-04-13 03:11:59 +0200151 if is_list(value) or is_dict(value):
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200152 return DictProxy(value)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200153 return value
154
155
156class FileLock:
157 def __init__(self, path, owner):
158 self.path = path
159 self.owner = owner
160 self.f = None
161
162 def __enter__(self):
163 if self.f is not None:
164 return
Neels Hofmeyr936a81e2017-09-14 01:31:41 +0200165 self.fd = os.open(self.path, os.O_CREAT | os.O_WRONLY)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200166 fcntl.flock(self.fd, fcntl.LOCK_EX)
167 os.truncate(self.fd, 0)
168 os.write(self.fd, str(self.owner).encode('utf-8'))
169 os.fsync(self.fd)
170
171 def __exit__(self, *exc_info):
172 #fcntl.flock(self.fd, fcntl.LOCK_UN)
173 os.truncate(self.fd, 0)
174 os.fsync(self.fd)
175 os.close(self.fd)
176 self.fd = -1
177
178 def lock(self):
179 self.__enter__()
180
181 def unlock(self):
182 self.__exit__()
183
184
185class Dir():
186 LOCK_FILE = 'lock'
187
188 def __init__(self, path):
189 self.path = path
190 self.lock_path = os.path.join(self.path, Dir.LOCK_FILE)
191
192 def lock(self, origin_id):
193 '''
194 return lock context, usage:
195
196 with my_dir.lock(origin):
197 read_from(my_dir.child('foo.txt'))
198 write_to(my_dir.child('bar.txt'))
199 '''
200 self.mkdir()
201 return FileLock(self.lock_path, origin_id)
202
203 @staticmethod
204 def ensure_abs_dir_exists(*path_elements):
205 l = len(path_elements)
206 if l < 1:
207 raise RuntimeError('Cannot create empty path')
208 if l == 1:
209 path = path_elements[0]
210 else:
211 path = os.path.join(*path_elements)
212 if not os.path.isdir(path):
213 os.makedirs(path)
214
215 def child(self, *rel_path):
216 if not rel_path:
217 return self.path
218 return os.path.join(self.path, *rel_path)
219
220 def mk_parentdir(self, *rel_path):
221 child = self.child(*rel_path)
222 child_parent = os.path.dirname(child)
223 Dir.ensure_abs_dir_exists(child_parent)
224 return child
225
226 def mkdir(self, *rel_path):
227 child = self.child(*rel_path)
228 Dir.ensure_abs_dir_exists(child)
229 return child
230
231 def children(self):
232 return os.listdir(self.path)
233
234 def exists(self, *rel_path):
235 return os.path.exists(self.child(*rel_path))
236
237 def isdir(self, *rel_path):
238 return os.path.isdir(self.child(*rel_path))
239
240 def isfile(self, *rel_path):
241 return os.path.isfile(self.child(*rel_path))
242
243 def new_child(self, *rel_path):
244 attempt = 1
245 prefix, suffix = os.path.splitext(self.child(*rel_path))
246 rel_path_fmt = '%s%%s%s' % (prefix, suffix)
247 while True:
248 path = rel_path_fmt % (('_%d'%attempt) if attempt > 1 else '')
249 if not os.path.exists(path):
250 break
251 attempt += 1
252 continue
253 Dir.ensure_abs_dir_exists(os.path.dirname(path))
254 return path
255
256 def rel_path(self, path):
257 return os.path.relpath(path, self.path)
258
259 def touch(self, *rel_path):
260 touch_file(self.child(*rel_path))
261
262 def new_file(self, *rel_path):
263 path = self.new_child(*rel_path)
264 touch_file(path)
265 return path
266
267 def new_dir(self, *rel_path):
268 path = self.new_child(*rel_path)
269 Dir.ensure_abs_dir_exists(path)
270 return path
271
272 def __str__(self):
273 return self.path
274 def __repr__(self):
275 return self.path
276
277def touch_file(path):
278 with open(path, 'a') as f:
279 f.close()
280
281def is_dict(l):
282 return isinstance(l, dict)
283
284def is_list(l):
285 return isinstance(l, (list, tuple))
286
287
288def dict_add(a, *b, **c):
289 for bb in b:
290 a.update(bb)
291 a.update(c)
292 return a
293
294def _hash_recurse(acc, obj, ignore_keys):
295 if is_dict(obj):
296 for key, val in sorted(obj.items()):
297 if key in ignore_keys:
298 continue
299 _hash_recurse(acc, val, ignore_keys)
300 return
301
302 if is_list(obj):
303 for item in obj:
304 _hash_recurse(acc, item, ignore_keys)
305 return
306
307 acc.update(str(obj).encode('utf-8'))
308
309def hash_obj(obj, *ignore_keys):
310 acc = hashlib.sha1()
311 _hash_recurse(acc, obj, ignore_keys)
312 return acc.hexdigest()
313
314
315def md5(of_content):
316 if isinstance(of_content, str):
317 of_content = of_content.encode('utf-8')
318 return hashlib.md5(of_content).hexdigest()
319
320def md5_of_file(path):
321 with open(path, 'rb') as f:
322 return md5(f.read())
323
324_tempdir = None
325
326def get_tempdir(remove_on_exit=True):
327 global _tempdir
328 if _tempdir is not None:
329 return _tempdir
330 _tempdir = tempfile.mkdtemp()
331 if remove_on_exit:
332 atexit.register(lambda: shutil.rmtree(_tempdir))
333 return _tempdir
334
335
336if hasattr(importlib.util, 'module_from_spec'):
337 def run_python_file(module_name, path):
338 spec = importlib.util.spec_from_file_location(module_name, path)
339 spec.loader.exec_module( importlib.util.module_from_spec(spec) )
340else:
341 from importlib.machinery import SourceFileLoader
342 def run_python_file(module_name, path):
343 SourceFileLoader(module_name, path).load_module()
344
345def msisdn_inc(msisdn_str):
346 'add 1 and preserve leading zeros'
347 return ('%%0%dd' % len(msisdn_str)) % (int(msisdn_str) + 1)
348
Neels Hofmeyracf0c932017-05-06 16:05:33 +0200349class InputThread(threading.Thread):
350 def __init__(self, prompt):
351 super().__init__()
352 self.prompt = prompt
353 self.result = None
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200354
Neels Hofmeyracf0c932017-05-06 16:05:33 +0200355 def run(self):
356 self.result = input(self.prompt)
357
358def input_polling(prompt, poll_func):
359 input_thread = InputThread(prompt)
360 input_thread.start()
361
362 while input_thread.is_alive():
363 poll_func()
364 time.sleep(1)
365
366 input_thread.join()
367 return input_thread.result
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200368
Pau Espin Pedrol404e1502017-08-22 11:17:43 +0200369def str2bool(val):
370 if val is None or not val:
371 return False
372 if val.upper() in ['FALSE', 'NO', 'OFF']:
373 return False
374 if val.upper() in ['TRUE','YES', 'ON']:
375 return True
376 raise ValueError('Invalid BOOL field: %r' % val)
377
Pau Espin Pedrol43737da2017-08-28 14:04:07 +0200378def list_validate_same_elem_type(li):
379 '''
380 Checks that all elements in the list are of the same type and returns that type.
381 If the list is empty, returns None
382 If one of the elements is not of the same type, it throws a ValueError exception.
383 '''
384 if len(li) == 0:
385 return None
386 t = type(li[0])
387 for elem in li:
388 if type(elem) != t:
389 raise ValueError('List contains elements of different types: %r vs %r' % (t, type(elem)))
390 return t
391
392def empty_instance_type(t):
393 if t == dict:
394 return {}
395 elif t == list:
396 return []
397 elif t == tuple:
398 return ()
399 raise ValueError('type %r not supported!' % t)
400
Pau Espin Pedrolabd556a2017-09-04 16:26:08 +0200401def encryption2osmovty(val):
402 assert val[:3] == 'a5_'
403 return 'a5 ' + val[3:]
404
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200405# vim: expandtab tabstop=4 shiftwidth=4