Merge "Fix for bug with external chain and no arp"
[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 import glob
16 from math import isnan
17 import os
18 import re
19 import signal
20 import subprocess
21
22 import errno
23 import fcntl
24 from functools import wraps
25 import json
26 from log import LOG
27
28
29 class TimeoutError(Exception):
30     pass
31
32
33 def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
34     def decorator(func):
35         def _handle_timeout(_signum, _frame):
36             raise TimeoutError(error_message)
37
38         def wrapper(*args, **kwargs):
39             signal.signal(signal.SIGALRM, _handle_timeout)
40             signal.alarm(seconds)
41             try:
42                 result = func(*args, **kwargs)
43             finally:
44                 signal.alarm(0)
45             return result
46
47         return wraps(func)(wrapper)
48
49     return decorator
50
51
52 def save_json_result(result, json_file, std_json_path, service_chain, service_chain_count,
53                      flow_count, frame_sizes):
54     """Save results in json format file."""
55     filepaths = []
56     if json_file:
57         filepaths.append(json_file)
58     if std_json_path:
59         name_parts = [service_chain, str(service_chain_count), str(flow_count)] + list(frame_sizes)
60         filename = '-'.join(name_parts) + '.json'
61         filepaths.append(os.path.join(std_json_path, filename))
62
63     if filepaths:
64         for file_path in filepaths:
65             LOG.info('Saving results in json file: %s...', file_path)
66             with open(file_path, 'w') as jfp:
67                 json.dump(result,
68                           jfp,
69                           indent=4,
70                           sort_keys=True,
71                           separators=(',', ': '),
72                           default=lambda obj: obj.to_json())
73
74
75 def byteify(data, ignore_dicts=False):
76     # if this is a unicode string, return its string representation
77     if isinstance(data, unicode):
78         return data.encode('utf-8')
79     # if this is a list of values, return list of byteified values
80     if isinstance(data, list):
81         return [byteify(item, ignore_dicts=ignore_dicts) for item in data]
82     # if this is a dictionary, return dictionary of byteified keys and values
83     # but only if we haven't already byteified it
84     if isinstance(data, dict) and not ignore_dicts:
85         return {byteify(key, ignore_dicts=ignore_dicts): byteify(value, ignore_dicts=ignore_dicts)
86                 for key, value in data.iteritems()}
87     # if it's anything else, return it in its original form
88     return data
89
90
91 def dict_to_json_dict(record):
92     return json.loads(json.dumps(record, default=lambda obj: obj.to_json()))
93
94
95 def get_intel_pci(nic_ports):
96     """Returns the first two PCI addresses of sorted PCI list for Intel NIC (i40e, ixgbe)"""
97     hx = r'[0-9a-fA-F]'
98     regex = r'({hx}{{4}}:({hx}{{2}}:{hx}{{2}}\.{hx}{{1}})).*(drv={driver}|.*unused=.*{driver})'
99     pcis = []
100
101     try:
102         trex_base_dir = '/opt/trex'
103         contents = os.listdir(trex_base_dir)
104         trex_dir = os.path.join(trex_base_dir, contents[0])
105         process = subprocess.Popen(['python', 'dpdk_setup_ports.py', '-s'],
106                                    cwd=trex_dir,
107                                    stdout=subprocess.PIPE,
108                                    stderr=subprocess.PIPE)
109         devices, _ = process.communicate()
110     except Exception:
111         devices = ''
112
113     for driver in ['i40e', 'ixgbe']:
114         matches = re.findall(regex.format(hx=hx, driver=driver), devices)
115         if not matches:
116             continue
117
118         matches.sort()
119         if nic_ports:
120             if max(nic_ports) > len(matches) - 1:
121                 # If this is hard requirements (i.e. ports are defined
122                 # explictly), but there are not enough ports for the
123                 # current NIC, just skip the current NIC and looking for
124                 # next available one.
125                 continue
126             else:
127                 return [matches[idx][1] for idx in nic_ports]
128         else:
129             for port in matches:
130                 intf_name = glob.glob("/sys/bus/pci/devices/%s/net/*" % port[0])
131                 if not intf_name:
132                     # Interface is not bind to kernel driver, so take it
133                     pcis.append(port[1])
134                 else:
135                     intf_name = intf_name[0][intf_name[0].rfind('/') + 1:]
136                     process = subprocess.Popen(['ip', '-o', '-d', 'link', 'show', intf_name],
137                                                stdout=subprocess.PIPE,
138                                                stderr=subprocess.PIPE)
139                     intf_info, _ = process.communicate()
140                     if not re.search('team_slave|bond_slave', intf_info):
141                         pcis.append(port[1])
142
143                 if len(pcis) == 2:
144                     break
145
146     return pcis
147
148
149 multiplier_map = {
150     'K': 1000,
151     'M': 1000000,
152     'G': 1000000000
153 }
154
155
156 def parse_flow_count(flow_count):
157     flow_count = str(flow_count)
158     input_fc = flow_count
159     multiplier = 1
160     if flow_count[-1].upper() in multiplier_map:
161         multiplier = multiplier_map[flow_count[-1].upper()]
162         flow_count = flow_count[:-1]
163
164     try:
165         flow_count = int(flow_count)
166     except ValueError:
167         raise Exception("Unknown flow count format '{}'".format(input_fc))
168
169     return flow_count * multiplier
170
171
172 def cast_integer(value):
173     return int(value) if not isnan(value) else value
174
175
176 class RunLock(object):
177     """
178     Attempts to lock file and run current instance of NFVbench as the first,
179     otherwise raises exception.
180     """
181
182     def __init__(self, path='/tmp/nfvbench.lock'):
183         self._path = path
184         self._fd = None
185
186     def __enter__(self):
187         try:
188             self._fd = os.open(self._path, os.O_CREAT)
189             fcntl.flock(self._fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
190         except (OSError, IOError):
191             raise Exception('Other NFVbench process is running. Please wait')
192
193     def __exit__(self, *args):
194         fcntl.flock(self._fd, fcntl.LOCK_UN)
195         os.close(self._fd)
196         self._fd = None
197
198         # Try to remove the lock file, but don't try too hard because it is unnecessary.
199         try:
200             os.unlink(self._path)
201         except (OSError, IOError):
202             pass