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
18 from math import isnan
26 from functools import wraps
29 from nfvbench.traffic_gen.traffic_utils import multiplier_map
30 from novaclient.exceptions import NotFound
32 class TimeoutError(Exception):
36 def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
38 def _handle_timeout(_signum, _frame):
39 raise TimeoutError(error_message)
41 def wrapper(*args, **kwargs):
42 signal.signal(signal.SIGALRM, _handle_timeout)
45 result = func(*args, **kwargs)
50 return wraps(func)(wrapper)
55 def save_json_result(result, json_file, std_json_path, service_chain, service_chain_count,
56 flow_count, frame_sizes, user_id=None, group_id=None):
57 """Save results in json format file."""
60 filepaths.append(json_file)
62 name_parts = [service_chain, str(service_chain_count), str(flow_count)] + list(frame_sizes)
63 filename = '-'.join(name_parts) + '.json'
64 filepaths.append(os.path.join(std_json_path, filename))
67 for file_path in filepaths:
68 LOG.info('Saving results in json file: %s...', file_path)
69 with open(file_path, 'w', encoding="utf-8") as jfp:
74 separators=(',', ': '),
75 default=lambda obj: obj.to_json())
76 # possibly change file ownership
79 if user_id is not None:
80 os.chown(file_path, user_id, group_id)
83 def dict_to_json_dict(record):
84 return json.loads(json.dumps(record, default=lambda obj: obj.to_json()))
87 def get_intel_pci(nic_slot=None, nic_ports=None):
88 """Returns two PCI address that will be used for NFVbench
90 @param nic_slot: The physical PCIe slot number in motherboard
91 @param nic_ports: Array of two integers indicating the ports to use on the NIC
93 When nic_slot and nic_ports are both supplied, the function will just return
94 the PCI addresses for them. The logic used is:
95 (1) Run "dmidecode -t slot"
96 (2) Grep for "SlotID:" with given nic_slot, and derive the bus address;
97 (3) Based on given nic_ports, generate the pci addresses based on above
100 When either nic_slot or nic_ports is not supplied, the function will
101 traverse all Intel NICs which use i40e or ixgbe driver, sorted by PCI
102 address, and return first two available ports which are not bonded
106 if nic_slot and nic_ports:
107 dmidecode = subprocess.check_output(['dmidecode', '-t', 'slot'])
108 regex = r"(?<=SlotID:{}).*?(....:..:..\..)".format(nic_slot)
109 match = re.search(regex, dmidecode.decode('utf-8'), flags=re.DOTALL)
114 # On some servers, the "Bus Address" returned by dmidecode is not the
115 # base pci address of the NIC. So only keeping the bus part of the
116 # address for better compability.
117 bus = match.group(1)[:match.group(1).rindex(':') + 1] + "00."
118 for port in nic_ports:
119 pcis.append(bus + str(port))
124 regex = r'({hx}{{4}}:({hx}{{2}}:{hx}{{2}}\.{hx}{{1}})).*(drv={driver}|.*unused=.*{driver})'
127 trex_base_dir = '/opt/trex'
128 contents = os.listdir(trex_base_dir)
129 trex_dir = os.path.join(trex_base_dir, contents[0])
130 with subprocess.Popen(['python', 'dpdk_setup_ports.py', '-s'],
132 stdout=subprocess.PIPE,
133 stderr=subprocess.PIPE) as process:
134 devices, _ = process.communicate()
138 for driver in ['i40e', 'ixgbe']:
139 matches = re.findall(regex.format(hx=hx, driver=driver), devices.decode("utf-8"))
144 device_list = list(x[0].split('.')[0] for x in matches)
145 device_ports_list = {i: {'ports': device_list.count(i)} for i in device_list}
147 intf_name = glob.glob("/sys/bus/pci/devices/%s/net/*" % port[0])
149 intf_name = intf_name[0][intf_name[0].rfind('/') + 1:]
150 with subprocess.Popen(['ip', '-o', '-d', 'link', 'show', intf_name],
151 stdout=subprocess.PIPE,
152 stderr=subprocess.PIPE) as process:
153 intf_info, _ = process.communicate()
154 if re.search('team_slave|bond_slave', intf_info.decode("utf-8")):
155 device_ports_list[port[0].split('.')[0]]['busy'] = True
157 if not device_ports_list[port[0].split('.')[0]].get('busy'):
165 def parse_flow_count(flow_count):
166 flow_count = str(flow_count)
167 input_fc = flow_count
169 if flow_count[-1].upper() in multiplier_map:
170 multiplier = multiplier_map[flow_count[-1].upper()]
171 flow_count = flow_count[:-1]
174 flow_count = int(flow_count)
176 raise Exception("Unknown flow count format '{}'".format(input_fc)) from ValueError
178 return flow_count * multiplier
181 def cast_integer(value):
182 # force 0 value if NaN value from TRex to avoid error in JSON result parsing
183 return int(value) if not isnan(value) else 0
186 class RunLock(object):
188 Attempts to lock file and run current instance of NFVbench as the first,
189 otherwise raises exception.
192 def __init__(self, path='/tmp/nfvbench.lock'):
198 self._fd = os.open(self._path, os.O_CREAT)
199 fcntl.flock(self._fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
200 except (OSError, IOError) as e:
201 raise Exception('Other NFVbench process is running. Please wait') from e
203 def __exit__(self, *args):
204 fcntl.flock(self._fd, fcntl.LOCK_UN)
208 # Try to remove the lock file, but don't try too hard because it is unnecessary.
210 os.unlink(self._path)
211 except (OSError, IOError):
216 for i in range(1, int(n / 2) + 1):
224 Calculate the maximum possible value for both IP and ports,
225 eventually for maximum possible flow.
227 if a != 0 and b != 0:
228 lcm_value = a * b // gcd(a, b)
230 raise TypeError(" IP size or port range can't be zero !")
233 def find_tuples_equal_to_lcm_value(a, b, lcm_value):
235 Find numbers from two list matching a LCM value.
239 if lcm(x, y) == lcm_value:
243 def find_max_size(max_size, tuples, flow):
245 if max_size > tuples[-1][0]:
246 max_size = tuples[-1][0]
248 if max_size > tuples[-1][1]:
249 max_size = tuples[-1][1]
252 for i in range(max_size, 1, -1):
258 def delete_server(nova_client, server):
260 LOG.info('Deleting instance %s...', server.name)
261 nova_client.servers.delete(server.id)
263 LOG.exception("Instance %s deletion failed", server.name)
266 def instance_exists(nova_client, server):
268 nova_client.servers.get(server.id)
274 def waiting_servers_deletion(nova_client, servers):
275 LOG.info(' Waiting for %d instances to be fully deleted...', len(servers))
276 retry_count = 15 + len(servers) * 5
279 servers = [server for server in servers if instance_exists(nova_client, server)]
284 LOG.info(' %d yet to be deleted by Nova, retries left=%d...',
285 len(servers), retry_count)
289 ' instance deletion verification time-out: %d still not deleted',