NFVBENCH-175 pylint w0707 correction
[nfvbench.git] / nfvbench / utils.py
index 4d9749c..06f643c 100644 (file)
 #    License for the specific language governing permissions and limitations
 #    under the License.
 
-import errno
-import fcntl
-from functools import wraps
-import json
-from log import LOG
+import glob
+from math import isnan
 import os
 import re
 import signal
 import subprocess
 
+import errno
+import fcntl
+from functools import wraps
+import json
+from .log import LOG
+from nfvbench.traffic_gen.traffic_utils import multiplier_map
 
 class TimeoutError(Exception):
     pass
@@ -29,7 +32,7 @@ class TimeoutError(Exception):
 
 def timeout(seconds=10, error_message=os.strerror(errno.ETIME)):
     def decorator(func):
-        def _handle_timeout(signum, frame):
+        def _handle_timeout(_signum, _frame):
             raise TimeoutError(error_message)
 
         def wrapper(*args, **kwargs):
@@ -59,7 +62,7 @@ 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: ' + file_path + "...")
+            LOG.info('Saving results in json file: %s...', file_path)
             with open(file_path, 'w') as jfp:
                 json.dump(result,
                           jfp,
@@ -69,31 +72,49 @@ def save_json_result(result, json_file, std_json_path, service_chain, service_ch
                           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
-
-
 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)
@@ -107,22 +128,31 @@ def get_intel_pci(nic_ports):
         devices = ''
 
     for driver in ['i40e', 'ixgbe']:
-        matches = re.findall(regex.format(hx=hx, driver=driver), devices)
-        if matches:
-            pcis = map(lambda x: x[0], matches)
-            if len(pcis) < 2:
-                continue
-            pcis.sort()
-            return [pcis[port_index] for port_index in nic_ports]
-
-    return []
-
+        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:]
+                process = subprocess.Popen(['ip', '-o', '-d', 'link', 'show', intf_name],
+                                           stdout=subprocess.PIPE,
+                                           stderr=subprocess.PIPE)
+                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
 
-multiplier_map = {
-    'K': 1000,
-    'M': 1000000,
-    'G': 1000000000
-}
 
 
 def parse_flow_count(flow_count):
@@ -136,11 +166,16 @@ 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):
+    # 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):
     """
     Attempts to lock file and run current instance of NFVbench as the first,
@@ -155,8 +190,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)