1 # Copyright 2016 Cisco Systems, Inc. All rights reserved.
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
7 # http://www.apache.org/licenses/LICENSE-2.0
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
17 from math import isnan
25 from functools import wraps
28 from nfvbench.traffic_gen.traffic_utils import multiplier_map
30 class TimeoutError(Exception):
34 def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
36 def _handle_timeout(_signum, _frame):
37 raise TimeoutError(error_message)
39 def wrapper(*args, **kwargs):
40 signal.signal(signal.SIGALRM, _handle_timeout)
43 result = func(*args, **kwargs)
48 return wraps(func)(wrapper)
53 def save_json_result(result, json_file, std_json_path, service_chain, service_chain_count,
54 flow_count, frame_sizes, user_id=None, group_id=None):
55 """Save results in json format file."""
58 filepaths.append(json_file)
60 name_parts = [service_chain, str(service_chain_count), str(flow_count)] + list(frame_sizes)
61 filename = '-'.join(name_parts) + '.json'
62 filepaths.append(os.path.join(std_json_path, filename))
65 for file_path in filepaths:
66 LOG.info('Saving results in json file: %s...', file_path)
67 with open(file_path, 'w') as jfp:
72 separators=(',', ': '),
73 default=lambda obj: obj.to_json())
74 # possibly change file ownership
77 if user_id is not None:
78 os.chown(file_path, user_id, group_id)
81 def dict_to_json_dict(record):
82 return json.loads(json.dumps(record, default=lambda obj: obj.to_json()))
85 def get_intel_pci(nic_slot=None, nic_ports=None):
86 """Returns two PCI address that will be used for NFVbench
88 @param nic_slot: The physical PCIe slot number in motherboard
89 @param nic_ports: Array of two integers indicating the ports to use on the NIC
91 When nic_slot and nic_ports are both supplied, the function will just return
92 the PCI addresses for them. The logic used is:
93 (1) Run "dmidecode -t slot"
94 (2) Grep for "SlotID:" with given nic_slot, and derive the bus address;
95 (3) Based on given nic_ports, generate the pci addresses based on above
98 When either nic_slot or nic_ports is not supplied, the function will
99 traverse all Intel NICs which use i40e or ixgbe driver, sorted by PCI
100 address, and return first two available ports which are not bonded
104 if nic_slot and nic_ports:
105 dmidecode = subprocess.check_output(['dmidecode', '-t', 'slot'])
106 regex = r"(?<=SlotID:{}).*?(....:..:..\..)".format(nic_slot)
107 match = re.search(regex, dmidecode.decode('utf-8'), flags=re.DOTALL)
112 # On some servers, the "Bus Address" returned by dmidecode is not the
113 # base pci address of the NIC. So only keeping the bus part of the
114 # address for better compability.
115 bus = match.group(1)[:match.group(1).rindex(':') + 1] + "00."
116 for port in nic_ports:
117 pcis.append(bus + str(port))
122 regex = r'({hx}{{4}}:({hx}{{2}}:{hx}{{2}}\.{hx}{{1}})).*(drv={driver}|.*unused=.*{driver})'
125 trex_base_dir = '/opt/trex'
126 contents = os.listdir(trex_base_dir)
127 trex_dir = os.path.join(trex_base_dir, contents[0])
128 process = subprocess.Popen(['python', 'dpdk_setup_ports.py', '-s'],
130 stdout=subprocess.PIPE,
131 stderr=subprocess.PIPE)
132 devices, _ = process.communicate()
136 for driver in ['i40e', 'ixgbe']:
137 matches = re.findall(regex.format(hx=hx, driver=driver), devices.decode("utf-8"))
142 device_list = list(x[0].split('.')[0] for x in matches)
143 device_ports_list = {i: {'ports': device_list.count(i)} for i in device_list}
145 intf_name = glob.glob("/sys/bus/pci/devices/%s/net/*" % port[0])
147 intf_name = intf_name[0][intf_name[0].rfind('/') + 1:]
148 process = subprocess.Popen(['ip', '-o', '-d', 'link', 'show', intf_name],
149 stdout=subprocess.PIPE,
150 stderr=subprocess.PIPE)
151 intf_info, _ = process.communicate()
152 if re.search('team_slave|bond_slave', intf_info.decode("utf-8")):
153 device_ports_list[port[0].split('.')[0]]['busy'] = True
155 if not device_ports_list[port[0].split('.')[0]].get('busy'):
163 def parse_flow_count(flow_count):
164 flow_count = str(flow_count)
165 input_fc = flow_count
167 if flow_count[-1].upper() in multiplier_map:
168 multiplier = multiplier_map[flow_count[-1].upper()]
169 flow_count = flow_count[:-1]
172 flow_count = int(flow_count)
174 raise Exception("Unknown flow count format '{}'".format(input_fc)) from ValueError
176 return flow_count * multiplier
179 def cast_integer(value):
180 # force 0 value if NaN value from TRex to avoid error in JSON result parsing
181 return int(value) if not isnan(value) else 0
184 class RunLock(object):
186 Attempts to lock file and run current instance of NFVbench as the first,
187 otherwise raises exception.
190 def __init__(self, path='/tmp/nfvbench.lock'):
196 self._fd = os.open(self._path, os.O_CREAT)
197 fcntl.flock(self._fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
198 except (OSError, IOError) as e:
199 raise Exception('Other NFVbench process is running. Please wait') from e
201 def __exit__(self, *args):
202 fcntl.flock(self._fd, fcntl.LOCK_UN)
206 # Try to remove the lock file, but don't try too hard because it is unnecessary.
208 os.unlink(self._path)
209 except (OSError, IOError):
214 for i in range(1, int(n / 2) + 1):
222 Calculate the maximum possible value for both IP and ports,
223 eventually for maximum possible flow.
225 if a != 0 and b != 0:
226 lcm_value = a * b // gcd(a, b)
228 raise TypeError(" IP size or port range can't be zero !")
231 def find_tuples_equal_to_lcm_value(a, b, lcm_value):
233 Find numbers from two list matching a LCM value.
237 if lcm(x, y) == lcm_value:
241 def find_max_size(max_size, tuples, flow):
243 if max_size > tuples[-1][0]:
244 max_size = tuples[-1][0]
246 if max_size > tuples[-1][1]:
247 max_size = tuples[-1][1]
250 for i in range(max_size, 1, -1):