aboutsummaryrefslogtreecommitdiffstats
path: root/nfvbench/utils.py
diff options
context:
space:
mode:
Diffstat (limited to 'nfvbench/utils.py')
-rw-r--r--nfvbench/utils.py213
1 files changed, 164 insertions, 49 deletions
diff --git a/nfvbench/utils.py b/nfvbench/utils.py
index 20dc588..07a38cb 100644
--- a/nfvbench/utils.py
+++ b/nfvbench/utils.py
@@ -12,6 +12,9 @@
# License for the specific language governing permissions and limitations
# under the License.
+import glob
+import time
+from math import gcd
from math import isnan
import os
import re
@@ -22,8 +25,9 @@ import errno
import fcntl
from functools import wraps
import json
-from log import LOG
-
+from .log import LOG
+from nfvbench.traffic_gen.traffic_utils import multiplier_map
+from novaclient.exceptions import NotFound
class TimeoutError(Exception):
pass
@@ -49,7 +53,7 @@ def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
def save_json_result(result, json_file, std_json_path, service_chain, service_chain_count,
- flow_count, frame_sizes):
+ flow_count, frame_sizes, user_id=None, group_id=None):
"""Save results in json format file."""
filepaths = []
if json_file:
@@ -62,69 +66,100 @@ def save_json_result(result, json_file, std_json_path, service_chain, service_ch
if filepaths:
for file_path in filepaths:
LOG.info('Saving results in json file: %s...', file_path)
- with open(file_path, 'w') as jfp:
+ with open(file_path, 'w', encoding="utf-8") as jfp:
json.dump(result,
jfp,
indent=4,
sort_keys=True,
separators=(',', ': '),
default=lambda obj: obj.to_json())
-
-
-def byteify(data, ignore_dicts=False):
- # if this is a unicode string, return its string representation
- if isinstance(data, unicode):
- return data.encode('utf-8')
- # if this is a list of values, return list of byteified values
- if isinstance(data, list):
- return [byteify(item, ignore_dicts=ignore_dicts) for item in data]
- # if this is a dictionary, return dictionary of byteified keys and values
- # but only if we haven't already byteified it
- if isinstance(data, dict) and not ignore_dicts:
- return {byteify(key, ignore_dicts=ignore_dicts): byteify(value, ignore_dicts=ignore_dicts)
- for key, value in data.iteritems()}
- # if it's anything else, return it in its original form
- return data
+ # possibly change file ownership
+ if group_id is None:
+ group_id = user_id
+ if user_id is not None:
+ os.chown(file_path, user_id, group_id)
def dict_to_json_dict(record):
return json.loads(json.dumps(record, default=lambda obj: obj.to_json()))
-def get_intel_pci(nic_ports):
- """Returns the first two PCI addresses of sorted PCI list for Intel NIC (i40e, ixgbe)"""
- hx = r'[0-9a-fA-F]'
- regex = r'{hx}{{4}}:({hx}{{2}}:{hx}{{2}}\.{hx}{{1}}).*(drv={driver}|.*unused=.*{driver})'
+def get_intel_pci(nic_slot=None, nic_ports=None):
+ """Returns two PCI address that will be used for NFVbench
+
+ @param nic_slot: The physical PCIe slot number in motherboard
+ @param nic_ports: Array of two integers indicating the ports to use on the NIC
+
+ When nic_slot and nic_ports are both supplied, the function will just return
+ the PCI addresses for them. The logic used is:
+ (1) Run "dmidecode -t slot"
+ (2) Grep for "SlotID:" with given nic_slot, and derive the bus address;
+ (3) Based on given nic_ports, generate the pci addresses based on above
+ base address;
+
+ When either nic_slot or nic_ports is not supplied, the function will
+ traverse all Intel NICs which use i40e or ixgbe driver, sorted by PCI
+ address, and return first two available ports which are not bonded
+ (802.11ad).
+ """
+
+ if nic_slot and nic_ports:
+ dmidecode = subprocess.check_output(['dmidecode', '-t', 'slot'])
+ regex = r"(?<=SlotID:{}).*?(....:..:..\..)".format(nic_slot)
+ match = re.search(regex, dmidecode.decode('utf-8'), flags=re.DOTALL)
+ if not match:
+ return None
+ pcis = []
+ # On some servers, the "Bus Address" returned by dmidecode is not the
+ # base pci address of the NIC. So only keeping the bus part of the
+ # address for better compability.
+ bus = match.group(1)[:match.group(1).rindex(':') + 1] + "00."
+ for port in nic_ports:
+ pcis.append(bus + str(port))
+
+ return pcis
+
+ hx = r'[0-9a-fA-F]'
+ regex = r'({hx}{{4}}:({hx}{{2}}:{hx}{{2}}\.{hx}{{1}})).*(drv={driver}|.*unused=.*{driver})'
+ pcis = []
try:
trex_base_dir = '/opt/trex'
contents = os.listdir(trex_base_dir)
trex_dir = os.path.join(trex_base_dir, contents[0])
- process = subprocess.Popen(['python', 'dpdk_setup_ports.py', '-s'],
- cwd=trex_dir,
- stdout=subprocess.PIPE,
- stderr=subprocess.PIPE)
- devices, _ = process.communicate()
+ with subprocess.Popen(['python', 'dpdk_setup_ports.py', '-s'],
+ cwd=trex_dir,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE) as process:
+ devices, _ = process.communicate()
except Exception:
devices = ''
for driver in ['i40e', 'ixgbe']:
- matches = re.findall(regex.format(hx=hx, driver=driver), devices)
- if matches:
- pcis = [x[0] for x in matches]
- if len(pcis) < 2:
- continue
- pcis.sort()
- return [pcis[port_index] for port_index in nic_ports]
-
- return []
-
-
-multiplier_map = {
- 'K': 1000,
- 'M': 1000000,
- 'G': 1000000000
-}
+ matches = re.findall(regex.format(hx=hx, driver=driver), devices.decode("utf-8"))
+ if not matches:
+ continue
+
+ matches.sort()
+ device_list = list(x[0].split('.')[0] for x in matches)
+ device_ports_list = {i: {'ports': device_list.count(i)} for i in device_list}
+ for port in matches:
+ intf_name = glob.glob("/sys/bus/pci/devices/%s/net/*" % port[0])
+ if intf_name:
+ intf_name = intf_name[0][intf_name[0].rfind('/') + 1:]
+ with subprocess.Popen(['ip', '-o', '-d', 'link', 'show', intf_name],
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE) as process:
+ intf_info, _ = process.communicate()
+ if re.search('team_slave|bond_slave', intf_info.decode("utf-8")):
+ device_ports_list[port[0].split('.')[0]]['busy'] = True
+ for port in matches:
+ if not device_ports_list[port[0].split('.')[0]].get('busy'):
+ pcis.append(port[1])
+ if len(pcis) == 2:
+ break
+
+ return pcis
def parse_flow_count(flow_count):
@@ -138,13 +173,14 @@ def parse_flow_count(flow_count):
try:
flow_count = int(flow_count)
except ValueError:
- raise Exception("Unknown flow count format '{}'".format(input_fc))
+ raise Exception("Unknown flow count format '{}'".format(input_fc)) from ValueError
return flow_count * multiplier
def cast_integer(value):
- return int(value) if not isnan(value) else value
+ # force 0 value if NaN value from TRex to avoid error in JSON result parsing
+ return int(value) if not isnan(value) else 0
class RunLock(object):
@@ -161,8 +197,8 @@ class RunLock(object):
try:
self._fd = os.open(self._path, os.O_CREAT)
fcntl.flock(self._fd, fcntl.LOCK_EX | fcntl.LOCK_NB)
- except (OSError, IOError):
- raise Exception('Other NFVbench process is running. Please wait')
+ except (OSError, IOError) as e:
+ raise Exception('Other NFVbench process is running. Please wait') from e
def __exit__(self, *args):
fcntl.flock(self._fd, fcntl.LOCK_UN)
@@ -174,3 +210,82 @@ class RunLock(object):
os.unlink(self._path)
except (OSError, IOError):
pass
+
+
+def get_divisors(n):
+ for i in range(1, int(n / 2) + 1):
+ if n % i == 0:
+ yield i
+ yield n
+
+
+def lcm(a, b):
+ """
+ Calculate the maximum possible value for both IP and ports,
+ eventually for maximum possible flow.
+ """
+ if a != 0 and b != 0:
+ lcm_value = a * b // gcd(a, b)
+ return lcm_value
+ raise TypeError(" IP size or port range can't be zero !")
+
+
+def find_tuples_equal_to_lcm_value(a, b, lcm_value):
+ """
+ Find numbers from two list matching a LCM value.
+ """
+ for x in a:
+ for y in b:
+ if lcm(x, y) == lcm_value:
+ yield (x, y)
+
+
+def find_max_size(max_size, tuples, flow):
+ if tuples:
+ if max_size > tuples[-1][0]:
+ max_size = tuples[-1][0]
+ return int(max_size)
+ if max_size > tuples[-1][1]:
+ max_size = tuples[-1][1]
+ return int(max_size)
+
+ for i in range(max_size, 1, -1):
+ if flow % i == 0:
+ return int(i)
+ return 1
+
+
+def delete_server(nova_client, server):
+ try:
+ LOG.info('Deleting instance %s...', server.name)
+ nova_client.servers.delete(server.id)
+ except Exception:
+ LOG.exception("Instance %s deletion failed", server.name)
+
+
+def instance_exists(nova_client, server):
+ try:
+ nova_client.servers.get(server.id)
+ except NotFound:
+ return False
+ return True
+
+
+def waiting_servers_deletion(nova_client, servers):
+ LOG.info(' Waiting for %d instances to be fully deleted...', len(servers))
+ retry_count = 15 + len(servers) * 5
+ while True:
+ retry_count -= 1
+ servers = [server for server in servers if instance_exists(nova_client, server)]
+ if not servers:
+ break
+
+ if retry_count:
+ LOG.info(' %d yet to be deleted by Nova, retries left=%d...',
+ len(servers), retry_count)
+ time.sleep(2)
+ else:
+ LOG.warning(
+ ' instance deletion verification time-out: %d still not deleted',
+ len(servers))
+ break