[NFVBENCH-59] Add Unit Testing of the NDR/PDR convergence algorithm using the dummy...
[nfvbench.git] / nfvbench / utils.py
1 # Copyright 2016 Cisco Systems, Inc.  All rights reserved.
2 #
3 #    Licensed under the Apache License, Version 2.0 (the "License"); you may
4 #    not use this file except in compliance with the License. You may obtain
5 #    a copy of the License at
6 #
7 #         http://www.apache.org/licenses/LICENSE-2.0
8 #
9 #    Unless required by applicable law or agreed to in writing, software
10 #    distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11 #    WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12 #    License for the specific language governing permissions and limitations
13 #    under the License.
14
15 from math import isnan
16 import os
17 import re
18 import signal
19 import subprocess
20
21 import errno
22 import fcntl
23 from functools import wraps
24 import json
25 from log import LOG
26
27
28 class TimeoutError(Exception):
29     pass
30
31
32 def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
33     def decorator(func):
34         def _handle_timeout(_signum, _frame):
35             raise TimeoutError(error_message)
36
37         def wrapper(*args, **kwargs):
38             signal.signal(signal.SIGALRM, _handle_timeout)
39             signal.alarm(seconds)
40             try:
41                 result = func(*args, **kwargs)
42             finally:
43                 signal.alarm(0)
44             return result
45
46         return wraps(func)(wrapper)
47
48     return decorator
49
50
51 def save_json_result(result, json_file, std_json_path, service_chain, service_chain_count,
52                      flow_count, frame_sizes):
53     """Save results in json format file."""
54     filepaths = []
55     if json_file:
56         filepaths.append(json_file)
57     if std_json_path:
58         name_parts = [service_chain, str(service_chain_count), str(flow_count)] + list(frame_sizes)
59         filename = '-'.join(name_parts) + '.json'
60         filepaths.append(os.path.join(std_json_path, filename))
61
62     if filepaths:
63         for file_path in filepaths:
64             LOG.info('Saving results in json file: %s...', file_path)
65             with open(file_path, 'w') as jfp:
66                 json.dump(result,
67                           jfp,
68                           indent=4,
69                           sort_keys=True,
70                           separators=(',', ': '),
71                           default=lambda obj: obj.to_json())
72
73
74 def byteify(data, ignore_dicts=False):
75     # if this is a unicode string, return its string representation
76     if isinstance(data, unicode):
77         return data.encode('utf-8')
78     # if this is a list of values, return list of byteified values
79     if isinstance(data, list):
80         return [byteify(item, ignore_dicts=ignore_dicts) for item in data]
81     # if this is a dictionary, return dictionary of byteified keys and values
82     # but only if we haven't already byteified it
83     if isinstance(data, dict) and not ignore_dicts:
84         return {byteify(key, ignore_dicts=ignore_dicts): byteify(value, ignore_dicts=ignore_dicts)
85                 for key, value in data.iteritems()}
86     # if it's anything else, return it in its original form
87     return data
88
89
90 def dict_to_json_dict(record):
91     return json.loads(json.dumps(record, default=lambda obj: obj.to_json()))
92
93
94 def get_intel_pci(nic_ports):
95     """Returns the first two PCI addresses of sorted PCI list for Intel NIC (i40e, ixgbe)"""
96     hx = r'[0-9a-fA-F]'
97     regex = r'{hx}{{4}}:({hx}{{2}}:{hx}{{2}}\.{hx}{{1}}).*(drv={driver}|.*unused=.*{driver})'
98
99     try:
100         trex_base_dir = '/opt/trex'
101         contents = os.listdir(trex_base_dir)
102         trex_dir = os.path.join(trex_base_dir, contents[0])
103         process = subprocess.Popen(['python', 'dpdk_setup_ports.py', '-s'],
104                                    cwd=trex_dir,
105                                    stdout=subprocess.PIPE,
106                                    stderr=subprocess.PIPE)
107         devices, _ = process.communicate()
108     except Exception:
109         devices = ''
110
111     for driver in ['i40e', 'ixgbe']:
112         matches = re.findall(regex.format(hx=hx, driver=driver), devices)
113         if matches:
114             pcis = [x[0] for x in matches]
115             if len(pcis) < 2:
116                 continue
117             pcis.sort()
118             return [pcis[port_index] for port_index in nic_ports]
119
120     return []
121
122
123 multiplier_map = {
124     'K': 1000,
125     'M': 1000000,
126     'G': 1000000000
127 }
128
129
130 def parse_flow_count(flow_count):
131     flow_count = str(flow_count)
132     input_fc = flow_count
133     multiplier = 1
134     if flow_count[-1].upper() in multiplier_map:
135         multiplier = multiplier_map[flow_count[-1].upper()]
136         flow_count = flow_count[:-1]
137
138     try:
139         flow_count = int(flow_count)
140     except ValueError:
141         raise Exception("Unknown flow count format '{}'".format(input_fc))
142
143     return flow_count * multiplier
144
145
146 def cast_integer(value):
147     return int(value) if not isnan(value) else value
148
149
150 class RunLock(object):
151     """
152     Attempts to lock file and run current instance of NFVbench as the first,
153     otherwise raises exception.
154     """
155
156     def __init__(self, path='/tmp/nfvbench.lock'):
157         self._path = path
158         self._fd = None
159
160     def __enter__(self):
161         try:
162             self._fd = os.open(self._path, os.O_CREAT)
163             fcntl.flock(self._fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
164         except (OSError, IOError):
165             raise Exception('Other NFVbench process is running. Please wait')
166
167     def __exit__(self, *args):
168         fcntl.flock(self._fd, fcntl.LOCK_UN)
169         os.close(self._fd)
170         self._fd = None
171
172         # Try to remove the lock file, but don't try too hard because it is unnecessary.
173         try:
174             os.unlink(self._path)
175         except (OSError, IOError):
176             pass