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