#! /usr/bin/python
# hhaim 
import sys,site
site.addsitedir('python-lib') 
import yaml
import os.path
import os
import dpdk_nic_bind
import re
import argparse;



class map_driver(object):
     args=None;
     cfg_file='/etc/trex_cfg.yaml'

class DpdkSetup(Exception):
    pass

class CIfMap:

    def __init__(self, cfg_file):
        self.m_cfg_file =cfg_file;
        self.m_cfg_dict={};
        self.m_devices={};

    def dump_error (self,err):
        s="""%s  
From this TRex version a configuration file must exist in /etc/ folder "
The name of the configuration file should be /etc/trex_cfg.yaml "
The minimum configuration file should include something like this
- version       : 2 # version 2 of the configuration file 
  interfaces    : ["03:00.0","03:00.1","13:00.1","13:00.0"]  # list of the interfaces to bind run ./dpdk_nic_bind.py --status to see the list 
  port_limit      : 2 # number of ports to use valid is 2,4,6,8 

example of already bind devices 

$ ./dpdk_nic_bind.py --status

Network devices using DPDK-compatible driver
============================================
0000:03:00.0 '82599ES 10-Gigabit SFI/SFP+ Network Connection' drv=igb_uio unused=
0000:03:00.1 '82599ES 10-Gigabit SFI/SFP+ Network Connection' drv=igb_uio unused=
0000:13:00.0 '82599ES 10-Gigabit SFI/SFP+ Network Connection' drv=igb_uio unused=
0000:13:00.1 '82599ES 10-Gigabit SFI/SFP+ Network Connection' drv=igb_uio unused=

Network devices using kernel driver
===================================
0000:02:00.0 '82545EM Gigabit Ethernet Controller (Copper)' if=eth2 drv=e1000 unused=igb_uio *Active*

Other network devices
=====================


          """ % (err);
        return s;


    def raise_error  (self,err):
        s= self.dump_error (err)
        raise DpdkSetup(s)

    def load_config_file (self):

        fcfg=self.m_cfg_file

        if not os.path.isfile(fcfg) :
            self.raise_error ("There is no valid configuration file %s " % fcfg)

        try:
          stream = open(fcfg, 'r')
          self.m_cfg_dict= yaml.load(stream)
        except Exception,e:
          print e;
          raise e

        stream.close();

        if not self.m_cfg_dict[0].has_key('version') :
            self.raise_error ("Configuration file %s is old, should include version field" % fcfg )

        if int(self.m_cfg_dict[0]['version'])<2 :
            self.raise_error ("Configuration file %s is old, should include version field with value greater than 2" % fcfg)


        if not self.m_cfg_dict[0].has_key('interfaces') :
            self.raise_error ("Configuration file %s is old, should include interfaces field with 2,4,6,8 number of elemets" % fcfg)

        if_list=self.m_cfg_dict[0]['interfaces']
        if not (len(if_list) in [2,4,6,8]):
            self.raise_error ("Configuration file %s should include interfaces field with 2,4,6,8 number of elemets" % fcfg)

    def do_bind_one (self,key):
        cmd='./dpdk_nic_bind.py --force --bind=igb_uio %s ' % ( key)
        print cmd
        res=os.system(cmd);
        if res!=0:
            raise DpdkSetup('')



    def pci_name_to_full_name (self,pci_name):
          c='[0-9A-Fa-f]';
          sp='[:]'
          s_short=c+c+sp+c+c+'[.]'+c;
          s_full=c+c+c+c+sp+s_short
          re_full = re.compile(s_full)
          re_short = re.compile(s_short)

          if re_short.match(pci_name):
              return '0000:'+pci_name

          if re_full.match(pci_name):
              return pci_name

          err=" %s is not a valid pci address \n" %pci_name;
          raise DpdkSetup(err)


    def run_dpdk_lspci (self):
            dpdk_nic_bind.get_nic_details()
            self.m_devices= dpdk_nic_bind.devices

    def do_run (self):
        self.load_config_file ()
        self.run_dpdk_lspci ()

        if_list=self.m_cfg_dict[0]['interfaces']

        for obj in if_list:
            key= self.pci_name_to_full_name (obj)
            if not self.m_devices.has_key(key) :
                err=" %s does not exist " %key;
                raise DpdkSetup(err)


            if self.m_devices[key].has_key('Driver_str'):
                if self.m_devices[key]['Driver_str'] !='igb_uio' :
                    self.do_bind_one (key)
            else:
                self.do_bind_one (key)


    def do_create (self):
        print " not supported yet !"


def parse_parent_cfg (parent_cfg):
    l=parent_cfg.split(" ");
    cfg_file='';
    next=False;
    for obj in l:
        if next:
            cfg_file=obj
            next=False;
        if obj == '--cfg':
            next=True

    return (cfg_file)

def process_options ():
    parser = argparse.ArgumentParser(usage=""" 

Examples:
---------

To unbind the interfaces using the trex configuration file 
  dpdk_set_ports.py -l

To create a default file 
  dpdk_set_ports.py -c

To show status
  dpdk_set_ports.py -s

    """,
    description=" unbind dpdk interfaces ",
    epilog=" written by hhaim");

    parser.add_argument("-l", "--load", action='store_true',
                      help=""" unbind the interfaces using the configuration file given  """,
     )

    parser.add_argument("--cfg",
                      help=""" configuration file name  """,
     )

    parser.add_argument("--parent",  
                      help=""" parent configuration CLI, extract --cfg from it if given  """,
     )

    parser.add_argument("-c", "--create", action='store_true',
                      help=""" try to create a configuration file. It is heuristic try to look into the file before """,
     )

    parser.add_argument("-s", "--show", action='store_true',
                      help=""" show the status """,
     )

    parser.add_argument('--version', action='version',
                        version="0.1" )

    map_driver.args = parser.parse_args();

    if map_driver.args.parent :
        cfg = parse_parent_cfg (map_driver.args.parent)
        if cfg != '':
            map_driver.cfg_file = cfg;
    if  map_driver.args.cfg :
        map_driver.cfg_file = map_driver.args.cfg;

def main ():
    try:
        process_options ()

        if map_driver.args.show:
            res=os.system('./dpdk_nic_bind.py --status');
            return(res);

        obj =CIfMap(map_driver.cfg_file);

        if map_driver.args.create:
            obj.do_create();
        else:
            obj.do_run();
    except Exception,e:
      print e
      exit(-1)

main();