From 2ee9ac46d8cc4de627be868d7cb36ad0a11e898d Mon Sep 17 00:00:00 2001
From: Yaroslav Brustinov <ybrustin@cisco.com>
Date: Mon, 22 Aug 2016 22:16:27 +0300
Subject: interactive creation of config, finish returning DPDK interfaces to
 Linux command.

---
 scripts/dpdk_nic_bind.py    |  41 +++++---
 scripts/dpdk_setup_ports.py | 236 ++++++++++++++++++++++++++++++++------------
 2 files changed, 196 insertions(+), 81 deletions(-)

(limited to 'scripts')

diff --git a/scripts/dpdk_nic_bind.py b/scripts/dpdk_nic_bind.py
index b1f38c09..bd4ec739 100755
--- a/scripts/dpdk_nic_bind.py
+++ b/scripts/dpdk_nic_bind.py
@@ -40,6 +40,7 @@ sys.path.append(text_tables_path)
 import texttable
 sys.path.remove(text_tables_path)
 import re
+import termios
 
 # The PCI device class for ETHERNET devices
 ETHERNET_CLASS = "0200"
@@ -341,27 +342,34 @@ def get_pid_using_pci(pci_list):
             f_cont = f.read()
         for pci in pci_list:
             if '/%s/' % pci in f_cont:
-                with open('/proc/%s/status' % pid) as f:
-                    for line in f.readlines():
-                        key, val = line.split(':', 1)
-                        if key.strip() == 'Tgid' and val.strip() == pid:
-                            return int(pid)
+                return int(pid)
 
 def read_pid_cmdline(pid):
     cmdline_file = '/proc/%s/cmdline' % pid
     if not os.path.exists(cmdline_file):
         return None
     with open(cmdline_file, 'rb') as f:
-        return f.read().replace(b'\0', b' ').decode()
+        return f.read().replace(b'\0', b' ').decode(errors = 'replace')
 
 def confirm(msg, default = False):
     try:
         if not os.isatty(1):
             return default
+        termios.tcflush(sys.stdin, termios.TCIOFLUSH)
         return strtobool(raw_input(msg))
     except:
         return default
 
+def read_line(msg = '', default = ''):
+    try:
+        if not os.isatty(1):
+            return default
+        termios.tcflush(sys.stdin, termios.TCIOFLUSH)
+        return raw_input(msg).strip()
+    except KeyboardInterrupt:
+        print('')
+        sys.exit(1)
+
 def unbind_one(dev_id, force):
     '''Unbind the device identified by "dev_id" from its current driver'''
     dev = devices[dev_id]
@@ -550,10 +558,10 @@ def show_status():
     display_devices("Other network devices", no_drv,\
                     "unused=%(Module_str)s")
 
-def get_macs_from_trex(pci_addr_list):
+def get_info_from_trex(pci_addr_list):
     if not pci_addr_list:
         return {}
-    pci_mac_dict = {}
+    pci_info_dict = {}
     run_command = 'sudo ./t-rex-64 --dump-interfaces %s' % ' '.join(pci_addr_list)
     proc = subprocess.Popen(shlex.split(run_command), stdout=subprocess.PIPE,
                             stderr=subprocess.STDOUT, universal_newlines = True)
@@ -564,18 +572,19 @@ def get_macs_from_trex(pci_addr_list):
         else:
             print('Error upon running TRex to get MAC info:\n%s' % stdout)
         sys.exit(1)
-    pci_mac_str = 'PCI: (\S+).+?MAC: (\S+)'
+    pci_mac_str = 'PCI: (\S+).+?MAC: (\S+).+?Driver: (\S+)'
     pci_mac_re = re.compile(pci_mac_str)
     for line in stdout.splitlines():
         match = pci_mac_re.match(line)
         if match:
             pci = match.group(1)
-            mac = match.group(2)
             if pci not in pci_addr_list: # sanity check, should not happen
-                print('Internal error while getting MACs of DPDK bound interfaces, unknown PCI: %s' % pci)
-                return {}
-            pci_mac_dict[pci] = mac
-    return pci_mac_dict
+                print('Internal error while getting info of DPDK bound interfaces, unknown PCI: %s' % pci)
+                sys.exit(1)
+            pci_info_dict[pci] = {}
+            pci_info_dict[pci]['MAC'] = match.group(2)
+            pci_info_dict[pci]['TRex_Driver'] = match.group(3)
+    return pci_info_dict
 
 def show_table():
     '''Function called when the script is passed the "--table" option.
@@ -586,11 +595,11 @@ def show_table():
         if devices[d].get("Driver_str") in dpdk_drivers:
             dpdk_drv.append(d)
 
-    for pci, mac in get_macs_from_trex(dpdk_drv).items():
+    for pci, info in get_info_from_trex(dpdk_drv).items():
         if pci not in dpdk_drv: # sanity check, should not happen
             print('Internal error while getting MACs of DPDK bound interfaces, unknown PCI: %s' % pci)
             return
-        devices[pci]['MAC'] = mac
+        devices[pci].update(info)
         
     table = texttable.Texttable(max_width=-1)
     table.header(['ID', 'NUMA', 'PCI', 'MAC', 'Name', 'Driver', 'Linux IF', 'Active'])
diff --git a/scripts/dpdk_setup_ports.py b/scripts/dpdk_setup_ports.py
index b32163da..ab85d97c 100755
--- a/scripts/dpdk_setup_ports.py
+++ b/scripts/dpdk_setup_ports.py
@@ -57,15 +57,11 @@ class ConfigCreator(object):
         if self.has_zero_lcore:
             del self.cpu_topology[zero_lcore_numa][zero_lcore_core]
             self.cpu_topology[zero_lcore_numa][zero_lcore_core] = zero_lcore_siblings
-        Device_str = None
         for interface in self.interfaces:
             for mandatory_interface_field in ConfigCreator.mandatory_interface_fields:
                 if mandatory_interface_field not in interface:
                     raise DpdkSetup("Expected '%s' field in interface dictionary, got: %s" % (mandatory_interface_field, interface))
-            if Device_str is None:
-                Device_str = interface['Device_str']
-            elif Device_str != interface['Device_str']:
-                raise DpdkSetup('Interfaces should be of same type, got:\n\t* %s\n\t* %s' % (Device_str, interface['Device_str']))
+        Device_str = self._verify_devices_same_type(self.interfaces)
         if '40Gb' in Device_str:
             self.speed = 40
         else:
@@ -95,11 +91,20 @@ class ConfigCreator(object):
         self.zmq_rpc_port        = zmq_rpc_port
         self.ignore_numa         = ignore_numa
 
-    def _convert_mac(self, mac_string):
+    @staticmethod
+    def _convert_mac(mac_string):
         if not ConfigCreator.mac_re.match(mac_string):
             raise DpdkSetup('MAC address should be in format of 12:34:56:78:9a:bc, got: %s' % mac_string)
         return ', '.join([('0x%s' % elem).lower() for elem in mac_string.split(':')])
 
+    @staticmethod
+    def _verify_devices_same_type(interfaces_list):
+        Device_str = interfaces_list[0]['Device_str']
+        for interface in interfaces_list:
+            if Device_str != interface['Device_str']:
+                raise DpdkSetup('Interfaces should be of same type, got:\n\t* %s\n\t* %s' % (Device_str, interface['Device_str']))
+        return Device_str
+
     def create_config(self, filename = None, print_config = False):
         config_str = '### Config file generated by dpdk_setup_ports.py ###\n\n'
         config_str += '- port_limit: %s\n' % len(self.interfaces)
@@ -167,22 +172,16 @@ class ConfigCreator(object):
         except Exception as e:
             raise DpdkSetup('Could not create correct yaml config.\nGenerated YAML:\n%s\nEncountered error:\n%s' % (config_str, e))
 
+        if print_config:
+            print(config_str)
         if filename:
             if os.path.exists(filename):
-                result = None
-                try:
-                    result = strtobool(raw_input('File %s already exist, overwrite? (y/N)' % filename))
-                except:
-                    pass
-                finally:
-                    if not result:
-                        print('Skipping.')
-                        return config_str
+                if not dpdk_nic_bind.confirm('File %s already exist, overwrite? (y/N)' % filename):
+                    print('Skipping.')
+                    return config_str
             with open(filename, 'w') as f:
                 f.write(config_str)
             print('Saved.')
-        if print_config:
-            print(config_str)
         return config_str
 
 
@@ -354,7 +353,84 @@ Other network devices
                 print('DPDK interfaces are in use. Unbinding them might cause following process to hang:\n%s' % cmdline)
                 if not dpdk_nic_bind.confirm('Confirm (y/N):'):
                     return
-        print('TODO: unbind %s' % dpdk_interfaces)
+        drivers_table = {
+            'rte_ixgbe_pmd': 'ixgbe',
+            'rte_igb_pmd': 'igb',
+            'rte_i40e_pmd': 'i40e',
+            'rte_em_pmd': 'e1000',
+            'rte_vmxnet3_pmd': 'vmxnet3',
+            'rte_virtio_pmd': 'virtio',
+            'rte_enic_pmd': 'enic',
+        }
+        for pci, info in dpdk_nic_bind.get_info_from_trex(dpdk_interfaces).items():
+            if pci not in self.m_devices:
+                raise DpdkSetup('Internal error: PCI %s is not found among devices' % pci)
+            dev = self.m_devices[pci]
+            if info['TRex_Driver'] not in drivers_table:
+                print('Got unknown driver %s, description: %s' % (info['TRex_Driver'], dev['Device_str']))
+            else:
+                print('Returning to Linux %s' % pci)
+                dpdk_nic_bind.bind_one(pci, drivers_table[info['TRex_Driver']], False)
+
+    def _get_cpu_topology(self):
+        cpu_topology_file = '/proc/cpuinfo'
+        # physical processor -> physical core -> logical processing units (threads)
+        cpu_topology = OrderedDict()
+        if not os.path.exists(cpu_topology_file):
+            raise DpdkSetup('File with CPU topology (%s) does not exist.' % cpu_topology_file)
+        with open(cpu_topology_file) as f:
+            for lcore in f.read().split('\n\n'):
+                if not lcore:
+                    continue
+                lcore_dict = OrderedDict()
+                for line in lcore.split('\n'):
+                    key, val = line.split(':', 1)
+                    lcore_dict[key.strip()] = val.strip()
+                numa = int(lcore_dict['physical id'])
+                if numa not in cpu_topology:
+                    cpu_topology[numa] = OrderedDict()
+                core = int(lcore_dict['core id'])
+                if core not in cpu_topology[numa]:
+                    cpu_topology[numa][core] = []
+                cpu_topology[numa][core].append(int(lcore_dict['processor']))
+        return cpu_topology
+
+    # input: list of different descriptions of interfaces: index, pci, name etc.
+    # binds to dpdk not bound to any driver wanted interfaces
+    # output: list of maps of devices in dpdk_* format (self.m_devices.values())
+    def _get_wanted_interfaces(self, input_interfaces):
+        wanted_interfaces = []
+        sorted_pci = sorted(self.m_devices.keys())
+        for interface in input_interfaces:
+            dev = None
+            try:
+                interface = int(interface)
+                if interface < 0 or interface >= len(sorted_pci):
+                    raise DpdkSetup('Index of an interface should be in range 0:%s' % (len(sorted_pci) - 1))
+                dev = self.m_devices[sorted_pci[interface]]
+            except ValueError:
+                for d in self.m_devices.values():
+                    if interface in (d['Interface'], d['Slot'], d['Slot_str']):
+                        dev = d
+                        break
+            if not dev:
+                raise DpdkSetup('Could not find information about this interface: %s' % interface)
+            if dev in wanted_interfaces:
+                raise DpdkSetup('Interface %s is specified twice' % interface)
+            dev['Interface_argv'] = interface
+            wanted_interfaces.append(dev)
+
+        unbound = []
+        for interface in wanted_interfaces:
+            if 'Driver_str' not in interface:
+                unbound.append(interface['Slot'])
+        if unbound:
+            for pci, info in dpdk_nic_bind.get_info_from_trex(unbound).items():
+                if pci not in self.m_devices:
+                    raise DpdkSetup('Internal error: PCI %s is not found among devices' % pci)
+                self.m_devices[pci].update(info)
+
+        return wanted_interfaces
 
     def do_create(self):
         create_interfaces = map_driver.args.create_interfaces
@@ -367,35 +443,12 @@ Other network devices
         # gather info about NICS from dpdk_nic_bind.py
         if not self.m_devices:
             self.run_dpdk_lspci()
-        wanted_interfaces = []
-        for interface in copy.copy(create_interfaces):
-            for m_device in self.m_devices.values():
-                if interface in (m_device['Interface'], m_device['Slot'], m_device['Slot_str']):
-                    if m_device in wanted_interfaces:
-                        raise DpdkSetup('Interface %s is specified twice' % interface)
-                    m_device['Interface_argv'] = interface
-                    wanted_interfaces.append(m_device)
-                    create_interfaces.remove(interface)
-                    break
-        # verify all interfaces identified
-        if len(create_interfaces):
-            raise DpdkSetup('Could not find information about those interfaces: %s' % create_interfaces)
-
-        dpdk_bound = []
-        for interface in wanted_interfaces:
-            if 'Driver_str' not in interface:
-                self.do_bind_one(interface['Slot'])
-                dpdk_bound.append(interface['Slot'])
-            elif interface['Driver_str'] in dpdk_nic_bind.dpdk_drivers:
-                dpdk_bound.append(interface['Slot'])
-        if dpdk_bound:
-            for pci, mac in dpdk_nic_bind.get_macs_from_trex(dpdk_bound).items():
-                self.m_devices[pci]['MAC'] = mac
+        wanted_interfaces = self._get_wanted_interfaces(create_interfaces)
 
         dest_macs = map_driver.args.dest_macs
         for i, interface in enumerate(wanted_interfaces):
             if 'MAC' not in interface:
-                raise DpdkSetup('Cound not determine MAC of interface: %s. Please verify with -t flag.' % wanted_interfaces[i]['Interface_argv'])
+                raise DpdkSetup('Cound not determine MAC of interface: %s. Please verify with -t flag.' % interface['Interface_argv'])
             interface['src_mac'] = interface['MAC']
             if isinstance(dest_macs, list) and len(dest_macs) > i:
                 interface['dest_mac'] = dest_macs[i]
@@ -404,32 +457,76 @@ Other network devices
                 wanted_interfaces[dual_index]['dest_mac'] = interface['MAC'] # loopback
                 wanted_interfaces[dual_index]['loopback_dest'] = True
 
-        cpu_topology_file = '/proc/cpuinfo'
-        # physical processor -> physical core -> logical processing units (threads)
-        cpu_topology = OrderedDict()
-        if not os.path.exists(cpu_topology_file):
-            raise DpdkSetup('File with CPU topology (%s) does not exist.' % cpu_topology_file)
-        with open(cpu_topology_file) as f:
-            for lcore in f.read().split('\n\n'):
-                if not lcore:
-                    continue
-                lcore_dict = OrderedDict()
-                for line in lcore.split('\n'):
-                    key, val = line.split(':', 1)
-                    lcore_dict[key.strip()] = val.strip()
-                numa = int(lcore_dict['physical id'])
-                if numa not in cpu_topology:
-                    cpu_topology[numa] = OrderedDict()
-                core = int(lcore_dict['core id'])
-                if core not in cpu_topology[numa]:
-                    cpu_topology[numa][core] = []
-                cpu_topology[numa][core].append(int(lcore_dict['processor']))
-
-        config = ConfigCreator(cpu_topology, wanted_interfaces, include_lcores = map_driver.args.create_include, exclude_lcores = map_driver.args.create_exclude,
+        config = ConfigCreator(self._get_cpu_topology(), wanted_interfaces, include_lcores = map_driver.args.create_include, exclude_lcores = map_driver.args.create_exclude,
                                only_first_thread = map_driver.args.no_ht, ignore_numa = map_driver.args.ignore_numa,
                                prefix = map_driver.args.prefix, zmq_rpc_port = map_driver.args.zmq_rpc_port, zmq_pub_port = map_driver.args.zmq_pub_port)
         config.create_config(filename = map_driver.args.o, print_config = map_driver.args.dump)
 
+    def do_interactive_create(self):
+        if not self.m_devices:
+            self.run_dpdk_lspci()
+        dpdk_nic_bind.show_table()
+        print('Please choose interfaces either by ID, PCI or Linux IF.')
+        print('Stateful will use order of interfaces: Client1 Server1 Client2 Server2 etc. for flows.')
+        print('Stateless can be in any order.')
+        print('Try to set each pair of interfaces on same NUMA for performance.')
+        input = dpdk_nic_bind.read_line('List of interfaces: ')
+        create_interfaces = input.replace(',', ' ').replace(';', ' ').split()
+        if not len(create_interfaces):
+            raise DpdkSetup('Please specify interfaces to use in the config')
+        if len(create_interfaces) % 2:
+            raise DpdkSetup('Please specify even number of interfaces')
+        wanted_interfaces = self._get_wanted_interfaces(create_interfaces)
+        ConfigCreator._verify_devices_same_type(wanted_interfaces)
+        print('')
+
+        dest_macs = map_driver.args.dest_macs
+        ignore_numa = False
+        for i, interface in enumerate(wanted_interfaces):
+            if 'MAC' not in interface:
+                raise DpdkSetup('Cound not determine MAC of interface: %s. Please verify with -t flag.' % interface['Interface_argv'])
+            interface['src_mac'] = interface['MAC']
+            dual_index = i + 1 - (i % 2) * 2
+            dual_int = wanted_interfaces[dual_index]
+            if not ignore_numa and interface['NUMA'] != dual_int['NUMA']:
+                print('NUMA is different at pair of interfaces: %s and %s. It will reduce performance.' % (interface['Interface_argv'], dual_int['Interface_argv']))
+                if dpdk_nic_bind.confirm('Ignore and continue? (y/N): '):
+                    ignore_numa = True
+                else:
+                    return
+            if isinstance(dest_macs, list) and len(dest_macs):
+                dest_mac = dest_macs.pop(0)
+                loopback_dest = False
+                print('For interface %s, take destination MAC from --dest-mac argument (%s)' % (interface['Interface_argv'], dest_mac))
+            else:
+                dest_mac = dual_int['MAC']
+                loopback_dest = True
+                print("For interface %s, take destination MAC of it's dual interface %s (%s). Change it in case of router DUT." % (interface['Interface_argv'], dual_int['Interface_argv'], dest_mac))
+            try:
+                input_mac = dpdk_nic_bind.read_line('Press ENTER to confirm or enter new destination MAC: ')
+                if input_mac:
+                    ConfigCreator._convert_mac(input_mac) # verify format
+                    dest_mac = input_mac
+                    loopback_dest = False
+            except KeyboardInterrupt:
+                print('')
+                return
+            wanted_interfaces[i]['dest_mac'] = dest_mac
+            wanted_interfaces[i]['loopback_dest'] = loopback_dest
+
+        config = ConfigCreator(self._get_cpu_topology(), wanted_interfaces, include_lcores = map_driver.args.create_include, exclude_lcores = map_driver.args.create_exclude,
+                               only_first_thread = map_driver.args.no_ht, ignore_numa = map_driver.args.ignore_numa or ignore_numa,
+                               prefix = map_driver.args.prefix, zmq_rpc_port = map_driver.args.zmq_rpc_port, zmq_pub_port = map_driver.args.zmq_pub_port)
+        if dpdk_nic_bind.confirm('Print preview of generated config? (Y/n)', default = True):
+            config.create_config(print_config = True)
+        if dpdk_nic_bind.confirm('Save the config to file? (Y/n)', default = True):
+            print('Default filename is /etc/trex_cfg.yaml')
+            filename = dpdk_nic_bind.read_line('Press ENTER to confirm or enter new file: ')
+            if not filename:
+                filename = '/etc/trex_cfg.yaml'
+            config.create_config(filename = filename)
+
+
 def parse_parent_cfg (parent_cfg):
     parent_parser = argparse.ArgumentParser()
     parent_parser.add_argument('--cfg', default='')
@@ -446,6 +543,9 @@ Examples:
 To return to Linux the DPDK bound interfaces (for ifconfig etc.)
   sudo ./dpdk_set_ports.py -l
 
+To create TRex config file using interactive mode
+  sudo ./dpdk_set_ports.py -l
+
 To create a default config file (example1)
   sudo ./dpdk_setup_ports.py -c 02:00.0 02:00.1 -o /etc/trex_cfg.yaml
 
@@ -474,6 +574,10 @@ To see more detailed info on interfaces (table):
                       help=argparse.SUPPRESS
      )
 
+    parser.add_argument("-i", "--interactive", action='store_true',
+                      help=""" Create TRex config in interactive mode """,
+     )
+
     parser.add_argument("-c", "--create", nargs='*', default=None, dest='create_interfaces', metavar='<interface>',
                       help="""Try to create a configuration file by specifying needed interfaces by PCI address or Linux names: eth1 etc.""",
      )
@@ -553,6 +657,8 @@ def main ():
 
         if map_driver.args.create_interfaces is not None:
             obj.do_create();
+        elif map_driver.args.interactive:
+            obj.do_interactive_create();
         elif map_driver.args.linux:
             obj.do_return_to_linux();
         else:
-- 
cgit