POX学习笔记:从POX启动开始——boot.py文件解读

POX学习笔记系列的开篇之作。

鄙人学习SDN也有半年了,虽然也没有一门心思扑在上面,只是觉得也写了这么多的代码,总是修修改改拼拼凑凑,也不知道它是怎么工作的,于是萌生出写博客的想法来激励自己,来分享知识。

首先我默认大家都知道启动pox该使用什么命令。命令 “./pox.py”虽然执行的是pox.py,但是实际上是调用了boot.py中的boot()函数。

def boot():
  """
  Start up POX.
  """
  # Add pox directory to path 添加到系统路径,这就是为什么将自己写的组件放到ext里,运行的时候可以直接跟在pox.py后面
  sys.path.append( os.path.abspath(os.path.join (sys. path[0], 'pox' )))
  sys.path.append( os.path.abspath(os.path.join (sys. path[0], 'ext' )))
  # sys.path:A list of strings that specifies the search path for modules. 
  # Initialized from the environment variable PYTHONPATH, plus an installation-dependent default.
  # 类似于环境变量
  try:
    argv = sys.argv[1:]   # agrv是./pox.py后面带的参数,比如我会使用./pox.py log.level --DEBUG --packet=WARN
                          # 除了这些参数,还包括要运行的的组件,例如'InterTcp', 'multidomain_test1_1',以下均用这个例子
    # Always load cli (first!)
    # TODO: Can we just get rid of the normal options yet?
    pre = []
    while len(argv):      #在没有任何参数的情况下,这个值是0
      if argv[0].startswith("-" ):   #从第一个参数开始,找到前n个连续的以'-'开头的参数,放在pre中
        pre.append(argv.pop(0))     
      else:
        break
    argv = pre + "py --disable".split() + argv   #实际上把参数拆开,在中间加上"py --disable"
    #在这个例子中:argv = ['py', '--disable', 'log.level', '--DEBUG', '--packet=WARN', 'InterTcp', 'multidomain_test1_1']
    if _do_launch(argv): # 这个_do_launch函数仍然定义在boot.py中,详细的解读请往下看
      _post_startup()    # 这句的主要作用是启动了pox.openflow.of_01,参考这个组件的解读(另外一章)
      core.goUp()        # 参考对core.py的解读,执行的是POXcore中的goUP()方法,个人认为其实就是抛出了pox启动的事件
    else:
      return
  except SystemExit:     # 异常处理我们暂时就不看了 
    return
  except:
    traceback.print_exc ()
    return

  if _main_thread_function :     # 条件判断不通过
    _main_thread_function ()
  else:
    #core.acquire()
    try:
      while core.running:
        time.sleep(10)
    except:
      pass
    #core.scheduler._thread.join() # Sleazy

下面看一下函数 _do_launch 函数

def _do_launch (argv):     # argv = ['py', '--disable', 'log.level', '--DEBUG', '--packet=WARN', 'InterTcp', 'multidomain_test1_1']
  component_order = []     
  components = {}
  curargs = {}
  pox_options = curargs

  for arg in argv:
    if not arg.startswith("-" ):      # arg = 'py' / 'log.level' / 'InterTcp' / 'multidomain_test1_1'
      if arg not in components:
        components[arg] = []
      curargs = {}
      components[arg].append(curargs)   
      component_order.append(arg)       
    else:                             # arg = '--disable' / '--DEBUG' / '--packet=WARN'
      arg = arg.lstrip("-").split("=",1)      # 去掉了前面的'-',并且对有'='号的进行拆分,这里可以学到两个内建函数lstrip和split
      arg[0] = arg[0].replace("-", "_")       # 对于没有'='的参数,列表arg只有一项,但是有'='好的就有两项
      if len(arg) == 1: arg.append(True)      # arg = ['disable', True]
      curargs[arg[0]] = arg[1]                # curages = {'disable': True}
  # 执行到这一步的时候:
  # components={'multidomain_test1_1': [{}], 'py': [{'disable': True}], 'InterTcp': [{}], 'log.level': [{'DEBUG': True, 'packet': 'WARN'}]} 注意到py这一项是程序加上去的,不是我们手动加上去的
  # curages={}   这一项等于多少完全取决与最后一个参数,如果最后一个参数没有以'-'开头,它就一定是一个空的列表
  # 如果不加入自己的组件的?这项就成了{'DEBUG': True, 'packet': 'WARN'},即最后几项'-'开头的参数
  # component_order = ['py', 'log.level', 'InterTcp', 'multidomain_test1_1']组件,所有前面没有'-'的参
  # 对于以'-'为开头的参数,受益者是前面一个非'-'开头的(component),作为该component的参数,都添加到了components中,前面的用"py --disable"把参数隔开,也保证了这一点的实现
  # 自己添加的组件没有参数,当然应该也是可以加参数的,只是我还没这个干过

  _options.process_options(pox_options)      # pox_options={}
  # 这里pox_options指的是./pox.py到第一个组件之间的参数,在这个例子里与第一个组件'py'之间没有参数,也就为空
  # _options的定义为_options = POXOptions(),因此它是POXOptions类的一个对象,这个类也是在boot.py中定义的
  # process_options具体请看下面详解
  _pre_startup()       # _pre_startup请看下面详解
 
  inst = {}
  for name in component_order:                 # component_order = ['py', 'log.level', 'InterTcp', 'multidomain_test1_1']
    cname = name                               # maybe cname is short for component name
    inst[name] = inst.get(name, -1) + 1        # 参考字典的get方法,inst原来是空的,每个cname对应的值都是-1+1=0
    params = components[name][inst[name]]      # components[name]即该组件的参数,inst[name]=0,即取第0项,params是一个字典
    name = name.split(":", 1)                  # 对cname中有':'号进行拆分
    launch = name[1] if len(name) == 2 else "launch"        # 在这里由于没有被拆分的,因此launch='launch'
    name = name[0]                                          # name还是name

    r = _do_import(name)             # 请看下面详解
    # import the named component.Returns its module name if it was loaded or False on failure.
    if r is False : return False     # 我们先假设r不是False了吧,返回的r是module name
    name = r      
    #print(">>",name)                

    # 到这步,所有的组件都已经import到sys.modules中了
    if launch in sys.modules[name].__dict__:    # 有launch函数的组件才能判断通过
      # 这里有一点容易产生混淆,'pox.py'指的是pox路径下的py.py组件
      f = sys.modules[name].__dict__[launch]    # 获得该组件launch函数的地址
      if f.__class__ is not _do_launch.__class__:
        print(launch , "in" , name, "isn't a function!" )
        return False
     
      multi = False
      if f.func_code.co_argcount > 0:    
        # 判断组件中launch函数的参数个数是否大于0
        if (f.func_code.co_varnames[f.func_code.co_argcount -1]  # launch函数参数的最后一项是不是'__INSTANCE__'
            == '__INSTANCE__' ):                      
          # It's a multi-instance-aware component.

          multi = True
          # Special __INSTANCE__ paramter gets passed a tuple with:
          # 1. The number of this instance (0...n-1)
          # 2. The total number of instances for this module
          # 3. True if this is the last instance, False otherwise
          # The last is just a comparison between #1 and #2, but it's
          # convenient.
          params['__INSTANCE__'] = (inst[cname], len(components[cname]),      
           inst[cname] + 1 == len(components[cname]))
          # 在params中添加一项
          # 以cname = 'py'为例,inst[cname]=0, len(components[cname])=1(只有一项参数),inst[cname]+1==len(components[cname])为True
          # params的值:params = {'__INSTANCE__': (0, 1, True), 'disable': True}
     
      if multi == False and len(components [cname]) != 1:   # multi == True,判断不通过
        print(name , "does not accept multiple instances" )
        return False

      try:
        f(**params)   # 执行该组件的launch函数,参数为params
      except TypeError as exc:
        # ...........................
        # 不要把宝贵的时间浪费在看except上
    elif len(params) > 0 or launch is not "launch" :    # cname中有':'或者cname带参数
      print("Module %s has no %s(), but it was specified or passed " \     
            "arguments" % (name, launch ))    # 大概意思是没有launch函数还带了参数,这样是不对滴
      return False

  return True      # 返回True

下面来详细看一下 _options.process_options (pox_options) 这句话干了什么

_options的定义为_options = POXOptions(),而POXOptions是继承于Options类的子类,process_options函数是定义在Options类中的

class Options (object):     
  def set ( self, given_name , value):           
    name = given_name.replace("-", "_")          
    if name.startswith("_") or hasattr(Options, name ):
      # Hey, what's that about?
      print("Illegal option:" , given_name)
      return False
    has_field = hasattr (self, name)
    has_setter = hasattr (self, "_set_" + name)
    if has_field == False and has_setter == False:
      print("Unknown option:" , given_name)
      return False
    if has_setter :
      setter = getattr(self, "_set_" + name)
      setter (given_name, name, value )
    else:
      if isinstance (getattr(self, name ), bool):
        # Automatic bool-ization
        value = str_to_bool(value)
      setattr (self, name, value )
    return True

  def process_options( self, options ):   # 调用到这里options = {},这样就不用看了
    for k,v in options.iteritems():          
      if self.set(k,v) is False:             
        # Bad option!
        sys.exit(1)
#########################  _pre_startup() ###############################
def _pre_startup ():
  """
  This function is called after all the POX options have been read in
  but before any components are loaded.  This gives a chance to do
  early setup (e.g., configure logging before a component has a chance
  to try to log something!).
  """
  #注释讲的很清楚了
  _setup_logging()      #参考下面这个函数的解读

  if _options.verbose:            #初始化的时候为False
    logging.getLogger().setLevel(logging.DEBUG )
 
  if _options.enable_openflow:    #初始化的时候为True
    pox.openflow.launch()          #Default OpenFlow launch,启动openflow.launch,参考openflow.py这个文件的解读
############################  _post_startup() ###############################
和上面的_pre_startup()方法遥相呼应
def _post_startup():
  if _options.enable_openflow:
    pox.openflow.of_01.launch() # Usually, we launch of_01,启动of_01.launch
##############################  _do_import  ##################################
def _do_import(name):
  """
  Try to import the named component.
  Returns its module name if it was loaded or False on failure.
  """
  def show_fail():                    #在do_import2的异常处理中被调用
    traceback.print_exc()
    print("Could not import module:", name)

  def do_import2( base_name, names_to_try ):   #以'py'为例,base_name = 'py', names_to_try = ['pox.py','py']
    if len(names_to_try ) == 0:
      print( "Module not found:",base_name)
      return False

    name = names_to_try.pop(0)

    if name in sys.modules:                   #如果'pox.py'在sys.modules中,就直接返回'pox.py'
      return name

    try:
      __import__(name, globals(), locals())   #python内建函数,参考python标准库,应该也是添加到sys.modules中了
      return name
    except ImportError :
      #异常处理,balabalabalabala...以后再看

  return do_import2 (name, ["pox." + name, name ])      
  # _do_import()就执行这一句,以第一个组件'py'为例,看do_import2函数

对boot.py中的boot()函数也分析的七七八八了,除此之外,还有一点比较关键的地方,比如在开头以下语句:

from pox. core import core

import pox. openflow

import pox. openflow.of_01

可以参考这些组件的解读分析,有什么新认识以后再更新吧!