blob: 27c71cf86d68abfab2f57b00e18eb022a9aa41a1 [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
30import fcntl
31import tty
Neels Hofmeyracf0c932017-05-06 16:05:33 +020032import readline
Pau Espin Pedrol13143bc2017-05-08 17:07:28 +020033import subprocess
Neels Hofmeyr3531a192017-03-28 14:30:28 +020034
Neels Hofmeyr0af893c2017-12-14 15:18:05 +010035# This mirrors enum osmo_auth_algo in libosmocore/include/osmocom/crypt/auth.h
36# so that the index within the tuple matches the enum value.
37OSMO_AUTH_ALGO_NONE = 'none'
38ENUM_OSMO_AUTH_ALGO = (OSMO_AUTH_ALGO_NONE, 'comp128v1', 'comp128v2', 'comp128v3', 'xor', 'milenage')
39
40def osmo_auth_algo_by_name(algo_str):
41 'Return enum osmo_auth_algo numeric value as from libosmocore, raise ValueError if not defined.'
42 return ENUM_OSMO_AUTH_ALGO.index(algo_str.lower())
43
Pau Espin Pedrole39c6f12017-05-08 16:21:38 +020044def prepend_library_path(path):
45 lp = os.getenv('LD_LIBRARY_PATH')
46 if not lp:
47 return path
48 return path + ':' + lp
49
Pau Espin Pedroled6d4452017-10-25 17:06:53 +020050def change_elf_rpath(binary, paths, run_dir):
51 '''
52 Change RPATH field in ELF executable binary.
53 This feature can be used to tell the loaded to load the trial libraries, as
54 LD_LIBRARY_PATH is disabled for paths with modified capabilities.
55 '''
56 from .process import Process
57 proc = Process('patchelf', run_dir, ['patchelf', '--set-rpath', paths, binary])
58 proc.launch()
59 proc.wait()
60 if proc.result != 0:
61 raise RuntimeError('patchelf finished with err code %d' % proc.result)
62
Pau Espin Pedrol13143bc2017-05-08 17:07:28 +020063def ip_to_iface(ip):
64 try:
65 for iface in os.listdir('/sys/class/net'):
66 proc = subprocess.Popen(['ip', 'addr', 'show', 'dev', iface], stdout=subprocess.PIPE, universal_newlines=True)
67 for line in proc.stdout.readlines():
68 if 'inet' in line and ' ' + ip + '/' in line:
Pau Espin Pedrol74a76762017-08-10 12:07:49 +020069 return line.split()[-1]
Pau Espin Pedrol13143bc2017-05-08 17:07:28 +020070 except Exception as e:
71 pass
72 return None
73
Pau Espin Pedrola238ed92018-03-26 13:06:12 +020074def dst_ip_get_local_bind(ip):
75 '''Retrieve default IP addr to bind to in order to route traffic to dst addr'''
76 try:
77 proc = subprocess.Popen(['ip', 'route', 'get', ip], stdout=subprocess.PIPE, universal_newlines=True)
78 output = proc.stdout.readlines()
79 words = output[0].split()
80 i = 0
81 while i < len(words):
82 if words[i] == 'src':
83 return words[i+1]
84 i += 1
85 except Exception as e:
86 pass
87 return None
88
Pau Espin Pedrol33c154b2017-10-25 16:16:25 +020089def setcap_net_raw(binary, run_dir):
90 '''
91 This functionality requires specific setup on the host running
92 osmo-gsm-tester. See osmo-gsm-tester manual for more information.
93 '''
94 from .process import Process
Pau Espin Pedrolf0b8e372017-12-13 16:08:35 +010095 SETCAP_NET_RAW_BIN = 'osmo-gsm-tester_setcap_net_raw.sh'
96 proc = Process(SETCAP_NET_RAW_BIN, run_dir, ['sudo', SETCAP_NET_RAW_BIN, binary])
Pau Espin Pedrol33c154b2017-10-25 16:16:25 +020097 proc.launch()
98 proc.wait()
99 if proc.result != 0:
Pau Espin Pedrolf0b8e372017-12-13 16:08:35 +0100100 raise RuntimeError('%s finished with err code %d' % (SETCAP_NET_RAW_BIN, proc.result))
101
102def setcap_net_admin(binary, run_dir):
103 '''
104 This functionality requires specific setup on the host running
105 osmo-gsm-tester. See osmo-gsm-tester manual for more information.
106 '''
107 from .process import Process
108 SETCAP_NET_ADMIN_BIN = 'osmo-gsm-tester_setcap_net_admin.sh'
109 proc = Process(SETCAP_NET_ADMIN_BIN, run_dir, ['sudo', SETCAP_NET_ADMIN_BIN, binary])
110 proc.launch()
111 proc.wait()
112 if proc.result != 0:
113 raise RuntimeError('%s finished with err code %d' % (SETCAP_NET_ADMIN_BIN, proc.result))
Pau Espin Pedrol33c154b2017-10-25 16:16:25 +0200114
Pau Espin Pedrol7e02d202018-05-08 15:28:48 +0200115def import_path_prepend(pathname):
116 dir = os.path.realpath(pathname)
117 if dir not in sys.path:
118 sys.path.insert(0, dir)
119
120def import_path_remove(pathname):
121 dir = os.path.realpath(pathname)
Pau Espin Pedrolf32c4152018-05-14 15:34:40 +0200122 if dir in sys.path:
Pau Espin Pedrol7e02d202018-05-08 15:28:48 +0200123 sys.path.remove(dir)
124
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200125class listdict(dict):
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200126 'a dict of lists { "a": [1, 2, 3], "b": [1, 2] }'
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200127
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200128 def add(self, name, item):
129 l = self.get(name)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200130 if not l:
131 l = []
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200132 self[name] = l
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200133 l.append(item)
134 return l
135
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200136 def add_dict(self, d):
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200137 for k,v in d.items():
Neels Hofmeyr803d87c2017-05-10 13:31:41 +0200138 self.add(k, v)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200139
140class DictProxy:
141 '''
142 allow accessing dict entries like object members
143 syntactical sugar, adapted from http://stackoverflow.com/a/31569634
144 so that e.g. templates can do ${bts.member} instead of ${bts['member']}
145 '''
146 def __init__(self, obj):
147 self.obj = obj
148
149 def __getitem__(self, key):
150 return dict2obj(self.obj[key])
151
152 def __getattr__(self, key):
Your Name44af3412017-04-13 03:11:59 +0200153 'provide error information to know which template item was missing'
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200154 try:
155 return dict2obj(getattr(self.obj, key))
156 except AttributeError:
157 try:
158 return self[key]
159 except KeyError:
160 raise AttributeError(key)
161
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200162def dict2obj(value):
Your Name44af3412017-04-13 03:11:59 +0200163 if is_list(value) or is_dict(value):
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200164 return DictProxy(value)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200165 return value
166
167
168class FileLock:
169 def __init__(self, path, owner):
170 self.path = path
171 self.owner = owner
172 self.f = None
173
174 def __enter__(self):
175 if self.f is not None:
176 return
Neels Hofmeyr936a81e2017-09-14 01:31:41 +0200177 self.fd = os.open(self.path, os.O_CREAT | os.O_WRONLY)
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200178 fcntl.flock(self.fd, fcntl.LOCK_EX)
179 os.truncate(self.fd, 0)
180 os.write(self.fd, str(self.owner).encode('utf-8'))
181 os.fsync(self.fd)
182
183 def __exit__(self, *exc_info):
184 #fcntl.flock(self.fd, fcntl.LOCK_UN)
185 os.truncate(self.fd, 0)
186 os.fsync(self.fd)
187 os.close(self.fd)
188 self.fd = -1
189
190 def lock(self):
191 self.__enter__()
192
193 def unlock(self):
194 self.__exit__()
195
196
197class Dir():
198 LOCK_FILE = 'lock'
199
200 def __init__(self, path):
201 self.path = path
202 self.lock_path = os.path.join(self.path, Dir.LOCK_FILE)
203
204 def lock(self, origin_id):
205 '''
206 return lock context, usage:
207
208 with my_dir.lock(origin):
209 read_from(my_dir.child('foo.txt'))
210 write_to(my_dir.child('bar.txt'))
211 '''
212 self.mkdir()
213 return FileLock(self.lock_path, origin_id)
214
215 @staticmethod
216 def ensure_abs_dir_exists(*path_elements):
217 l = len(path_elements)
218 if l < 1:
219 raise RuntimeError('Cannot create empty path')
220 if l == 1:
221 path = path_elements[0]
222 else:
223 path = os.path.join(*path_elements)
224 if not os.path.isdir(path):
225 os.makedirs(path)
226
227 def child(self, *rel_path):
228 if not rel_path:
229 return self.path
230 return os.path.join(self.path, *rel_path)
231
232 def mk_parentdir(self, *rel_path):
233 child = self.child(*rel_path)
234 child_parent = os.path.dirname(child)
235 Dir.ensure_abs_dir_exists(child_parent)
236 return child
237
238 def mkdir(self, *rel_path):
239 child = self.child(*rel_path)
240 Dir.ensure_abs_dir_exists(child)
241 return child
242
243 def children(self):
244 return os.listdir(self.path)
245
246 def exists(self, *rel_path):
247 return os.path.exists(self.child(*rel_path))
248
249 def isdir(self, *rel_path):
250 return os.path.isdir(self.child(*rel_path))
251
252 def isfile(self, *rel_path):
253 return os.path.isfile(self.child(*rel_path))
254
255 def new_child(self, *rel_path):
256 attempt = 1
257 prefix, suffix = os.path.splitext(self.child(*rel_path))
258 rel_path_fmt = '%s%%s%s' % (prefix, suffix)
259 while True:
260 path = rel_path_fmt % (('_%d'%attempt) if attempt > 1 else '')
261 if not os.path.exists(path):
262 break
263 attempt += 1
264 continue
265 Dir.ensure_abs_dir_exists(os.path.dirname(path))
266 return path
267
268 def rel_path(self, path):
269 return os.path.relpath(path, self.path)
270
271 def touch(self, *rel_path):
272 touch_file(self.child(*rel_path))
273
274 def new_file(self, *rel_path):
275 path = self.new_child(*rel_path)
276 touch_file(path)
277 return path
278
279 def new_dir(self, *rel_path):
280 path = self.new_child(*rel_path)
281 Dir.ensure_abs_dir_exists(path)
282 return path
283
284 def __str__(self):
285 return self.path
286 def __repr__(self):
287 return self.path
288
289def touch_file(path):
290 with open(path, 'a') as f:
291 f.close()
292
293def is_dict(l):
294 return isinstance(l, dict)
295
296def is_list(l):
297 return isinstance(l, (list, tuple))
298
299
300def dict_add(a, *b, **c):
301 for bb in b:
302 a.update(bb)
303 a.update(c)
304 return a
305
306def _hash_recurse(acc, obj, ignore_keys):
307 if is_dict(obj):
308 for key, val in sorted(obj.items()):
309 if key in ignore_keys:
310 continue
311 _hash_recurse(acc, val, ignore_keys)
312 return
313
314 if is_list(obj):
315 for item in obj:
316 _hash_recurse(acc, item, ignore_keys)
317 return
318
319 acc.update(str(obj).encode('utf-8'))
320
321def hash_obj(obj, *ignore_keys):
322 acc = hashlib.sha1()
323 _hash_recurse(acc, obj, ignore_keys)
324 return acc.hexdigest()
325
326
327def md5(of_content):
328 if isinstance(of_content, str):
329 of_content = of_content.encode('utf-8')
330 return hashlib.md5(of_content).hexdigest()
331
332def md5_of_file(path):
333 with open(path, 'rb') as f:
334 return md5(f.read())
335
336_tempdir = None
337
338def get_tempdir(remove_on_exit=True):
339 global _tempdir
340 if _tempdir is not None:
341 return _tempdir
342 _tempdir = tempfile.mkdtemp()
343 if remove_on_exit:
344 atexit.register(lambda: shutil.rmtree(_tempdir))
345 return _tempdir
346
347
348if hasattr(importlib.util, 'module_from_spec'):
349 def run_python_file(module_name, path):
350 spec = importlib.util.spec_from_file_location(module_name, path)
351 spec.loader.exec_module( importlib.util.module_from_spec(spec) )
352else:
353 from importlib.machinery import SourceFileLoader
354 def run_python_file(module_name, path):
355 SourceFileLoader(module_name, path).load_module()
356
357def msisdn_inc(msisdn_str):
358 'add 1 and preserve leading zeros'
359 return ('%%0%dd' % len(msisdn_str)) % (int(msisdn_str) + 1)
360
Neels Hofmeyracf0c932017-05-06 16:05:33 +0200361class InputThread(threading.Thread):
362 def __init__(self, prompt):
363 super().__init__()
364 self.prompt = prompt
365 self.result = None
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200366
Neels Hofmeyracf0c932017-05-06 16:05:33 +0200367 def run(self):
368 self.result = input(self.prompt)
369
370def input_polling(prompt, poll_func):
371 input_thread = InputThread(prompt)
372 input_thread.start()
373
374 while input_thread.is_alive():
375 poll_func()
376 time.sleep(1)
377
378 input_thread.join()
379 return input_thread.result
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200380
Pau Espin Pedrol404e1502017-08-22 11:17:43 +0200381def str2bool(val):
382 if val is None or not val:
383 return False
384 if val.upper() in ['FALSE', 'NO', 'OFF']:
385 return False
386 if val.upper() in ['TRUE','YES', 'ON']:
387 return True
388 raise ValueError('Invalid BOOL field: %r' % val)
389
Pau Espin Pedrol43737da2017-08-28 14:04:07 +0200390def list_validate_same_elem_type(li):
391 '''
392 Checks that all elements in the list are of the same type and returns that type.
393 If the list is empty, returns None
394 If one of the elements is not of the same type, it throws a ValueError exception.
395 '''
396 if len(li) == 0:
397 return None
398 t = type(li[0])
399 for elem in li:
400 if type(elem) != t:
401 raise ValueError('List contains elements of different types: %r vs %r' % (t, type(elem)))
402 return t
403
404def empty_instance_type(t):
405 if t == dict:
406 return {}
407 elif t == list:
408 return []
409 elif t == tuple:
410 return ()
411 raise ValueError('type %r not supported!' % t)
412
Pau Espin Pedrolabd556a2017-09-04 16:26:08 +0200413def encryption2osmovty(val):
414 assert val[:3] == 'a5_'
415 return 'a5 ' + val[3:]
416
Neels Hofmeyr3531a192017-03-28 14:30:28 +0200417# vim: expandtab tabstop=4 shiftwidth=4