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