-
Notifications
You must be signed in to change notification settings - Fork 67
/
helper.py
547 lines (441 loc) · 15.7 KB
/
helper.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
"""This module includes all the functions that are frequently used
in different parts of the code. These functions usually perform low level
operations on data.
"""
import os
import re
import subprocess
import sys
import json
import time
import types
import random
import psutil
import signal
import hashlib
import importlib
from networkx.readwrite.json_graph import node_link_graph
from p4utils.utils.topology import NetworkGraph
from p4utils.mininetlib.log import info, output, error, warning, debug
_prefixLenMatchRegex = re.compile('netmask (\d+\.\d+\.\d+\.\d+)')
def wait_condition(func, value, args=[], kwargs={}, timeout=0):
"""Waits for the function to return the specified value.
Args:
func (types.FunctionType): function to check
value : condition to meet
args (list) : positional arguments of the function
kwargs (dict) : key-word arguments of the function
timeout (float) : time to wait for condition in seconds
Returns:
bool: **True** if the condition is met before the timeout
expires, **False** otherwise.
Note:
If ``timeout`` is set to ``0``, this function will wait forever.
"""
start_time = time.time()
if timeout > 0:
while func(*args, **kwargs) != value:
if time.time() - start_time >= timeout:
return False
else:
return True
else:
while func(*args, **kwargs) != value:
pass
else:
return True
def merge_dict(dst, src):
"""Merges source dictionary fields and subfields into destionation dictionary.
Args:
dst (dict): destination dictionary
src (dict): source dictionary
"""
stack = [(dst, src)]
while stack:
current_dst, current_src = stack.pop()
for key in current_src:
if key not in current_dst:
current_dst[key] = current_src[key]
else:
if isinstance(
current_src[key],
dict) and isinstance(
current_dst[key],
dict):
stack.append((current_dst[key], current_src[key]))
else:
current_dst[key] = current_src[key]
def next_element(elems, minimum=None, maximum=None):
"""Given a list of integers, return the lowest number not already
present in the set, starting from minimum and ending in maximum.
Args:
elems (list) : list of integers
minimum (int): minimum value allowed for elements
maximum (int): maximum value allowed for elements
Returns:
int: the lowest number not already present in the set.
"""
elements = set(elems)
if len(elems) != len(elements):
raise Exception('the list contains duplicates.')
if len(elems) == 0:
return minimum
else:
if maximum is None:
maximum = max(elements)
if minimum is None:
minimum = min(elements)
else:
# Remove elements lower than minimum
del_elements = set()
for elem in elements:
if elem < minimum:
del_elements.add(elem)
elements.difference_update(del_elements)
# Update maximum
maximum = max(maximum, minimum)
if len(elements) == (maximum - minimum) + 1:
return maximum + 1
elif len(elements) < (maximum - minimum) + 1:
for elem in range(minimum, maximum+1):
if elem not in elements:
return elem
else:
raise Exception('too many elements in the list.')
def rand_mac():
"""Generate a random, non-multicas MAC address.
Returns:
str: MAC address.
"""
hex_str = hex(random.randint(1, 2**48-1) &
0xfeffffffffff | 0x020000000000)[2:]
hex_str = '0'*(12-len(hex_str)) + hex_str
mac_str = ''
i = 0
while i < len(hex_str):
mac_str += hex_str[i]
mac_str += hex_str[i+1]
mac_str += ':'
i += 2
return mac_str[:-1]
def dpidToStr(id):
"""Compute a string **dpid** from an integer **id**.
Args:
id (int): integer device id
Returns:
str: device dpid.
"""
strDpid = hex(id)[2:]
if len(strDpid) < 16:
return '0'*(16-len(strDpid)) + strDpid
return strDpid
def check_listening_on_port(port):
"""Checks if the given port is listening in the main namespace.
Args:
port (int): port number
Returns:
bool: **True** if the port is listening, **False** otherwise.
"""
for c in psutil.net_connections(kind='inet'):
if c.status == 'LISTEN' and c.laddr[1] == port:
return True
return False
def cksum(filename):
"""Returns the md5 checksum of a file.
Args:
filename (str): path to the file
Returns:
str: md5 checksum of the file.
"""
return hashlib.md5(open(filename, 'rb').read()).hexdigest()
def get_node_attr(node, attr_name, default=None):
"""Finds the value of the specified attribute of a *Mininet* node
by looking also inside its unparsed parameters.
Args:
node (object) : *Mininet* node object
attr_name (string) : attribute to look for
Returns:
the value of the requested attribute.
"""
try:
return getattr(node, attr_name)
except AttributeError:
try:
params = getattr(node, 'params')
if attr_name in params.keys():
return params[attr_name]
else:
return default
except AttributeError:
return default
def get_by_attr(attr_name, attr_value, obj_list):
"""Return the first object in the list that has an attribute matching with
the attribute name and value provided.
Args:
attr_name (string) : attribute name
attr_value : attrubute value
obj_list (list) : list of objects
Returns:
object: the requested object or **None**.
"""
for obj in obj_list:
if attr_value == getattr(obj, attr_name):
return obj
else:
return None
def ip_address_to_mac(ip):
"""Generate MAC from IP address.
Args:
ip (str): IPv4 address
Returns:
str: MAC address obtained from the IPv4 value.
"""
if "/" in ip:
ip = ip.split('/')[0]
split_ip = list(map(int, ip.split('.')))
mac_address = '00:%02x' + ':%02x:%02x:%02x:%02x' % tuple(split_ip)
return mac_address
def is_compiled(p4_src, compilers):
"""Check if a file has been already compiled by at least one compiler in the list.
Arguments:
p4_src (string) : P4 file path
compilers (list): list of P4 compiler objects (see compiler.py)
Returns:
bool: **True** if the file has been already compiled, **False** otherwise.
"""
for compiler in compilers:
if getattr(
compiler, 'compiled') and getattr(
compiler, 'p4_src') == p4_src:
return True
else:
return False
def load_conf(conf_file):
"""Load JSON application configuration file.
Args:
conf_file (str): path to the JSON network configuration file
Returns:
dict: network configuration dictionary.
"""
with open(conf_file, 'r') as f:
config = json.load(f)
return config
def load_topo(json_path):
"""Load the topology from the path provided.
Args:
json_path (string): path of the JSON file to load
Returns:
p4utils.utils.topology.NetworkGraph: the topology graph.
"""
with open(json_path, 'r') as f:
graph_dict = json.load(f)
graph = node_link_graph(graph_dict)
return NetworkGraph(graph)
def load_custom_object(obj):
"""Loads object from module.
Args:
dict: JSON object to load
Returns:
object: Python object retrieved from the module.
Example:
This function takes as input a module JSON object::
{
"file_path": <path to module> (string) (*),
"module_name": <module file_name> (string),
"object_name": <module object name> (string),
}
Note:
None of the fields marked with ``(*)`` is mandatory. The ``file_path`` field
is optional and has to be used if the module is not present in ``sys.path``.
"""
file_path = obj.get('file_path', '.')
sys.path.insert(0, file_path)
module_name = obj['module_name']
object_name = obj['object_name']
module = importlib.import_module(module_name)
return getattr(module, object_name)
def old_run_command(command):
"""Execute command in the main namespace.
Args:
command (str): command to execute
Returns:
int: an integer value used by a process.
"""
debug(command+'\n')
return os.WEXITSTATUS(os.system(command))
def run_command(command, out_file=None):
"""Execute command in the main namespace.
Args:
command (str) : command to execute
out_file (str): where to redirect *stdout* and *stderr*
Returns:
int: returns parent pid.
"""
if isinstance(command, str):
debug(command+'\n')
command = command.split()
else:
debug(' '.join(command) + '\n')
if not out_file:
of = subprocess.DEVNULL
else:
of = open(out_file, 'w')
proc = subprocess.Popen(command, stdout=of, stderr=of)
return proc.pid
def parse_line(line):
"""Parse text line returning a list of substrings.
Args:
line (str): line to parse
Returns:
list: list of args obtained from the parsing.
Example:
As an example, consider the following string::
'ahjdjf djdfkfo1 --jdke hdjejeek --dfjfj "vneovn rijvtg"'
The function will parse it and give as output the following list::
["ahjdjf", "djdfkfo1", "--jdke", "hdjejeek", "--dfjfj", "vneovn rijvtg"]
"""
# Isolate "" terms
args1 = line.split('"')
args2 = []
for i in range(len(args1)):
if i % 2 == 0:
# Isolate and append spaced terms
args2.extend(args1[i].split())
else:
# Append "" terms
args2.append(args1[i])
return args2
def parse_task_line(line, def_mod='p4utils.utils.traffic_utils'):
"""Parse text line and return all the parameters needed
to create a task with :py:func:`p4utils.mininetlib.network_API.NetworkAPI.addTask()`.
Args:
line (str) : string containing all the task information
def_mod (str): default module where to look for exe functions
Returns:
tuple: a tuple (**args**, **kwargs**) where **args** is a list of arguments and **kwargs**
is a dictionary of key-word pairs.
Example:
The file has to be a set of lines, where each has the following syntax::
<node> <start> <duration> <exe> [<arg1>] ... [<argN>] [--mod <module>] [--<key1> <kwarg1>] ... [--<keyM> <kwargM>]
Note:
A non-default module can be specified in the command with ``--mod <module>``.
"""
args = []
kwargs = {}
skip_next = False
mod = importlib.import_module(def_mod)
parsed_cmd = parse_line(line)
if len(parsed_cmd) < 4:
error(
'usage: <node> <start> <duration> <exe> [<arg1>] ... [<argN>] [--mod <module>] [--<key1> <kwarg1>] ... [--<keyM> <kwargM>]\n')
for i in range(len(parsed_cmd)):
if skip_next:
skip_next = False
continue
# Parse node (index 0 in args)
if i == 0:
args.append(parsed_cmd[i])
# Parse start
elif i == 1:
kwargs['start'] = float(parsed_cmd[i])
# Parse duration
elif i == 2:
kwargs['duration'] = float(parsed_cmd[i])
# Parse exe (index 1 in args)
elif i == 3:
args.append(parsed_cmd[i])
# Parse args and kwargs
elif i >= 4:
# Parse kwargs
if len(parsed_cmd[i]) > 2 and parsed_cmd[i][:2] == '--':
# Parse module
if parsed_cmd[i] == '--mod':
mod = importlib.import_module(parsed_cmd[i+1])
else:
kwargs.setdefault('kwargs', {})
kwargs['kwargs'][parsed_cmd[i][2:]] = parsed_cmd[i+1]
skip_next = True
# Parse args
else:
kwargs.setdefault('args', [])
kwargs['args'].append(parsed_cmd[i])
try:
# Import function from module
exe = getattr(mod, args[1])
# Set function as the executable
args[1] = exe
except AttributeError:
# Interpret the executable as a command
pass
return args, kwargs
def kill_proc_tree(pid, sig=signal.SIGKILL, include_parent=True,
timeout=None, on_terminate=None):
"""Kills a process tree (including children).
Args:
pid (int) : PID of the parent process
sig (int) : signal used to kill the tree
include_parent (bool) : whether to kill the parent process or not
timeout (int or float) : time to wait for a process to terminate
on_terminate (types.FunctionType): callback function executed as soon as a child terminates.
Returns:
tuple: ``(gone, still_alive)``.
"""
assert pid != os.getpid(), "won't kill myself"
parent = psutil.Process(pid)
children = parent.children(recursive=True)
if include_parent:
children.append(parent)
for p in children:
try:
p.send_signal(sig)
except psutil.NoSuchProcess:
pass
gone, alive = psutil.wait_procs(children, timeout=timeout,
callback=on_terminate)
return (gone, alive)
class WrapFunc:
"""Wraps a function is such a way that they can be executed
across different Python interpreters in the same system.
Args:
func (types.FunctionType): function to wrap
"""
def __init__(self, func):
# Sanity check
assert isinstance(func, types.FunctionType)
# Set function name
self.f_name = func.__name__
# Set module nome
if func.__module__ == '__main__':
self.m_name, _ = os.path.splitext(
os.path.basename(sys.modules[func.__module__].__file__))
else:
self.m_name = func.__module__
# Get module relative path from package
m_rel_path = str.replace(self.m_name, '.', '/')
# Get module absolute path
m_abs_path, _ = os.path.splitext(
os.path.realpath(sys.modules[func.__module__].__file__))
# Get package absolute path
if m_abs_path[-len(m_rel_path):] == m_rel_path:
self.p_path = m_abs_path[:len(m_abs_path)-len(m_rel_path)]
else:
raise Exception('module name does not match its path!')
def __repr__(self):
return 'function {}.{}'.format(self.m_name, self.f_name)
def unwrap(self):
"""Unwraps function and returns it."""
# Add path in the sys.path
for path in sys.path:
# Get absolute path
abs_path = os.path.realpath(path)
# Check if module path is a subpath of a path already in sys.path
if os.path.commonpath([abs_path, self.p_path]) == abs_path:
# Break loop
break
# If the path is not in sys.path, add it
else:
sys.path.append(self.p_path)
# Import module
module = importlib.import_module(self.m_name)
# Return function
return getattr(module, self.f_name)