Ryu 源码解读(Main)

注册命令行解析

  CONF.register_cli_opts([
    cfg.ListOpt('app-lists', default=[],
                help='application module name to run'),
    cfg.MultiStrOpt('app', positional=True, default=[],
                    help='application module name to run'),
    cfg.StrOpt('pid-file', default=None, help='pid file name'),
    cfg.BoolOpt('enable-debugger', default=False,
                help='don\'t overwrite Python standard threading library'
                '(use only for debugging)')
])

CONF=ConfigOpts(),ConfigOpts类的实例对象

可以在命令行或者配置文件中设置的配置选项
配置选项管理器:API用于注册选项模式,分组选项,解析选项值和解锁选项值
对'config_file'和'config_dir'的支持

解析配置文件

 CONF(args=args, prog=prog,
 project='ryu', version='ryu-manager %s' % version,
 default\_config\_files=['/usr/local/etc/ryu/ryu.conf’])

args:命令行参数(默认为sys.argv [1:])
param prog:程序的名称(默认为sys.argv [0] basename,不带扩展名.py) #Ryu中此处为None
project:顶层项目名称,用于定位配置文件
version:程序版本
default_config_files:默认使用的配置文件

没有指定应用程序,默认使用ofp_handler

app_lists = ['ryu.controller.ofp_handler']

创建AppManager实例对象

app_mgr = AppManager.get_instance() #初始化:self.applications_cls={},self.applications={},self.context_cls={},self.context={}

通过app_mgr.load_apps加载应用

app_mgr.load_apps(app_lists)

load_apps | app_manager.py

cls = self.load_app(app_cls_name) #下一个标题有这个函数
self.applications_cls[app_cls_name] = cls #存放应用程序的实例对象
services = []
for key, context_cls in cls.context_iteritems():  #cls.context_iteritems()返回应用程序的_CONTEXTS键值对
    v = self.contexts_cls.setdefault(key, context_cls)  #存放依赖服务的实例对象
    assert v == context_cls
    context_modules.append(context_cls.__module__)  #存放服务的模块名称

    if issubclass(context_cls, RyuApp):
          services.extend(get_dependent_services(context_cls)) ##将依赖的服务模块的依赖也添加到services列表中,其中还包括要监听的事件的模块
# we can't load an app that will be initiataed for
# contexts.
for i in get_dependent_services(cls):
    if i not in context_modules:
        services.append(i)
if services:
    app_lists.extend([s for s in set(services)
                      if s not in app_lists])         #服务添加到app_lists中 

self.applications_cls存放的是还未初始化的类
cls.context_iteritems() :来自RyuApp的类方法,返回application的_CONTEXTS的迭代器

#ryu.app.ws_topology.py例子
_CONTEXTS = {
      'wsgi': WSGIApplication,
      'switches': switches.Switches,
  }

context_modules.append(context_cls.module) ,将已经存放到contexts_cls的模块加入到context_modules,防止重复添加
这个函数主要是为了获得app_lists中的_CONTEXT的类以及_CONTEXT类所依赖的类,需要监听的事件的类
将服务也添加到app_lists中,用于后续的create_contexts

get_dependent_services

def get_dependent_services(cls):
    services = []
    for _k, m in inspect.getmembers(cls, _is_method): #返回实例对象的方法
        if _has_caller(m):  #判断方法是否有caller属性,即装饰器的函数
            for ev_cls, c in m.callers.items(): #{事件:_Caller对象},_Caller属性:1.生成事件的阶段 2.生成事件的模块
                service = getattr(sys.modules[ev_cls.__module__],
                                  '_SERVICE_NAME', None)  #事件所在的模块实例
                if service:
                    # 避免注册自己事件的cls(比如ofp_handler)
                    if cls.__module__ != service:
                        services.append(service)   #事件的模块放到services列表中

    m = sys.modules[cls.__module__]
    services.extend(getattr(m, '_REQUIRED_APP', []))
    services = list(set(services))
    return services

load_app | app_manager.py

mod = utils.import_module(name)   #动态导入模块
#返回加载应用的类:1)是否是类、2)是否是RyuApp子类、3)类名是否等于模块名
clses = inspect.getmembers(mod,
                           lambda cls: (inspect.isclass(cls) and
                                        issubclass(cls, RyuApp) and
                                        mod.__name__ ==
                                        cls.__module__))
if clses:
    return clses[0][1]  #返回类的对象,clses[0][0]是类名
return None

app_mgr.create_contexts

contexts = app_mgr.create_contexts()

create_contexts() | app_manager.py

for key, cls in self.contexts_cls.items():
    if issubclass(cls, RyuApp):
        # hack for dpset
        context = self._instantiate(None, cls)
    else:
        context = cls() #参考ws_topology.py中的‘wsgi’的WSGIApplication并不是其RyuAPP
    LOG.info('creating context %s', key)
    assert key not in self.contexts   
    self.contexts[key] = context   #创建好的应用程序存放到contexts中
return self.contexts

由上面可知contexts_cls{}存放的是未初始化的类,初始化结束的存放到contexts{}中

_instantiate(None,cls) | app_manager.py

def _instantiate(self, app_name, cls, *args, **kwargs):
    # for now, only single instance of a given module
    # Do we need to support multiple instances?
    # Yes, maybe for slicing.
    LOG.info('instantiating app %s of %s', app_name, cls.__name__)    #如果跑带有_CONTEXT的应用程序,就能看到None

    if hasattr(cls, 'OFP_VERSIONS') and cls.OFP_VERSIONS is not None:
        ofproto_protocol.set_app_supported_versions(cls.OFP_VERSIONS)  #查看应用程序的版本是否支持,不支持的话抛出异常

    if app_name is not None:
        assert app_name not in self.applications
    app = cls(*args, **kwargs)    #创建类实例对象
    register_app(app)     #注册应用程序实例 ,存到SERVICE_BRICKS中,SERVICE_BRICKS[app.name] = app。
    assert app.name not in self.applications
    self.applications[app.name] = app      #将应用程序保存到applications的字典中
    return app

初次调用的时候传入的app_name = None,通过--verbose启动应用程序时候可以看到:

$ryu-manager ryu.app.ws_topology --verbose 
loading app ryu.app.ws_topology
loading app ryu.controller.ofp_handler
instantiating app None of Switches          #初次调用的时候传入的app_name = None
creating context switches
creating context wsgi

register_app(app)主要是注册应用实例,存放到SERVICE_BRICKS{“应用名”:应用},同时注册应用程序里装饰器的handler。
self.applications存放的是已经初始化好的应用,注意此处存的是context中继承RyuApp的应用。

register_app(app) | handler.py

SERVICE_BRICKS[app.name] = app
之后调用register_instance

def register_instance(i):
    for _k, m in inspect.getmembers(i, inspect.ismethod):
        # LOG.debug('instance %s k %s m %s', i, _k, m)
        if _has_caller(m):                  #查看有无装饰器
            for ev_cls, c in m.callers.items():
                i.register_handler(ev_cls, m)   #将事件的处理函数注册,在app_manager.py中有对应的函数

事件处理器注册到event_handlers {“事件”:[方法1,方法2,...]}

services.extend(app_mgr.instantiate_apps(**contexts))

    def instantiate_apps(self, *args, **kwargs):
        for app_name, cls in self.applications_cls.items():
            self._instantiate(app_name, cls, *args, **kwargs)    #刚才调用这个函数是初始化_CONTEXT里的,现在是对应用程序的初始化

        self._update_bricks() #注册事件监听器
        self.report_bricks()  #启动应用时添加”--verbose“可以查看到事件监听的信息,如:PROVIDES EventOFPPortStatus TO {'switches': set(['main']), 'monitor': set(['main'])}


        threads = []
        for app in self.applications.values():
            t = app.start() #调用应用程序的启动方法
            if t is not None:
                app.set_main_thread(t)  #设置当前应用程序的线程,如果不设置,就没办法在stop()函数里删除这个线程
                threads.append(t) #将这个线程添加到线程列表里
        return threads

self._instantiate(app_name, cls, *args, **kwargs)
此处初始化的是启动的应用程序和非RyuApp的应用,刚才已经将应用程序的依赖已经初始化完成了。
app.start()调用应用程序的启动函数,如默认启动的应用程序OFPHandler的start()会将OpenFlowController启动

_update_bricks | app_manager.py

def _update_bricks(self):
    for i in SERVICE_BRICKS.values():
        for _k, m in inspect.getmembers(i, inspect.ismethod):
            if not hasattr(m, 'callers'):     #对不是装饰器的方法不处理
                continue
            for ev_cls, c in m.callers.items():
                if not c.ev_source:   #ev_source为定义的模块,可以到handle.py中查看
                    continue

                brick = _lookup_service_brick_by_mod_name(c.ev_source)   #取出定义事件的模块取出对应的SERVICE实例
                if brick:
                    brick.register_observer(ev_cls, i.name,
                                            c.dispatchers)      #c.dispatchers代表事件产生的阶段,如MAIN_DISPATCHER

                # allow RyuApp and Event class are in different module
                for brick in SERVICE_BRICKS.values():
                    if ev_cls in brick._EVENTS:
                        brick.register_observer(ev_cls, i.name,
                                                c.dispatchers)

注册监听器
基类里说明了_EVENTS:

"""
    A list of event classes which this RyuApp subclass would generate.
    This should be specified if and only if event classes are defined in
    a different python module from the RyuApp subclass is.
"""

_EVENTS是子类生成的事件列表,但是当且仅当事件类是在RyuApp子类的不同python模块中定义时才应该指定。

不知道理解有没有问题,查看Ryu的文档,自定义类看看

register_observer | app_manager.py

注册事件监听器

def register_observer(self, ev_cls, name, states=None):
        states = states or set()
        ev_cls_observers = self.observers.setdefault(ev_cls, {})
        ev_cls_observers.setdefault(name, set()).update(states) #将对应事件的状态更新到键值对中

report_bricks() | app_manager.py

@staticmethod
  def report_bricks():
      for brick, i in SERVICE_BRICKS.items():
          AppManager._report_brick(brick, i)

报告监听的事件信息,可以启动ws_topology.py,通过--verbose查看

BRICK switches
  PROVIDES EventLinkAdd TO {'WebSocketTopology': set([])}
  PROVIDES EventSwitchEnter TO {'WebSocketTopology': set([])}
  PROVIDES EventLinkDelete TO {'WebSocketTopology': set([])}
  PROVIDES EventSwitchLeave TO {'WebSocketTopology': set([])}
  PROVIDES EventHostAdd TO {'WebSocketTopology': set([])}
  CONSUMES EventSwitchRequest
  CONSUMES EventOFPStateChange
  CONSUMES EventHostRequest
  CONSUMES EventOFPPacketIn
  CONSUMES EventLinkRequest
  CONSUMES EventOFPPortStatus
BRICK WebSocketTopology
  CONSUMES EventLinkAdd
  CONSUMES EventSwitchEnter
  CONSUMES EventLinkDelete
  CONSUMES EventSwitchLeave
  CONSUMES EventHostAdd
BRICK ofp_event
  PROVIDES EventOFPPacketIn TO {'switches': set(['main'])}
  PROVIDES EventOFPPortStatus TO {'switches': set(['main'])}
  PROVIDES EventOFPStateChange TO {'switches': set(['main', 'dead'])}
  CONSUMES EventOFPSwitchFeatures
  CONSUMES EventOFPPortDescStatsReply
  CONSUMES EventOFPHello
  CONSUMES EventOFPErrorMsg
  CONSUMES EventOFPEchoRequest
  CONSUMES EventOFPPortStatus
  CONSUMES EventOFPEchoReply

app.start | app_manager.py

启动初始化后调用
self.threads.append(hub.spawn(self._event_loop))
当有新的事件来了,获取事件对应的handlers,然后再依次把事件放到handler中处理
不同的应用程序还有进行其他处理,如OFPHandler的start()会将OpenFlowController启动

webapp=wsgi.start_service(app_mgr)| manager.py

def start_service(app_mgr):
    for instance in app_mgr.contexts.values():
        if instance.__class__ == WSGIApplication:  #如果实例的类是WSGIApplication
            return WSGIServer(instance)

    return None

仅有依赖的服务模块中含有WSGIApplication的时候才启动WSGI服务的实例,提供web应用。

hub.joinall(services) | manager.py

将所有的应用作为任务,作为coroutine的task去执行,join使得程序必须等待所有的task都执行完成才可以退出程序

app_mgr.close() | manager.py

关闭程序,释放资源

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 159,716评论 4 364
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,558评论 1 294
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 109,431评论 0 244
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 44,127评论 0 209
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,511评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,692评论 1 222
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,915评论 2 313
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,664评论 0 202
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,412评论 1 246
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,616评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,105评论 1 260
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,424评论 2 254
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,098评论 3 238
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,096评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,869评论 0 197
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,748评论 2 276
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,641评论 2 271

推荐阅读更多精彩内容

  • 22年12月更新:个人网站关停,如果仍旧对旧教程有兴趣参考 Github 的markdown内容[https://...
    tangyefei阅读 35,127评论 22 257
  • 参考学习资料 SDN启动流程 命令解析库 OSLOhttp://lingxiankong.github.io/bl...
    lifesmily阅读 1,546评论 0 2
  • Python 面向对象Python从设计之初就已经是一门面向对象的语言,正因为如此,在Python中创建一个类和对...
    顺毛阅读 4,167评论 4 16
  • 转至元数据结尾创建: 董潇伟,最新修改于: 十二月 23, 2016 转至元数据起始第一章:isa和Class一....
    40c0490e5268阅读 1,636评论 0 9
  • 还想看更多文章的朋友可以访问我的个人博客 转载自人人网分享 想查看更多精彩文章,请关注我的 卷首语 Android...
    我是才子阅读 40,804评论 1 8