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類的實(shí)例對象

可以在命令行或者配置文件中設(shè)置的配置選項(xiàng)
配置選項(xiàng)管理器:API用于注冊選項(xiàng)模式陌兑,分組選項(xiàng)啥供,解析選項(xiàng)值和解鎖選項(xiàng)值
對'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:命令行參數(shù)(默認(rèn)為sys.argv [1:])
param prog:程序的名稱(默認(rèn)為sys.argv [0] basename舅锄,不帶擴(kuò)展名.py) #Ryu中此處為None
project:頂層項(xiàng)目名稱谜悟,用于定位配置文件
version:程序版本
default_config_files:默認(rèn)使用的配置文件

沒有指定應(yīng)用程序孵奶,默認(rèn)使用ofp_handler

app_lists = ['ryu.controller.ofp_handler']

創(chuàng)建AppManager實(shí)例對象

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

通過app_mgr.load_apps加載應(yīng)用

app_mgr.load_apps(app_lists)

load_apps | app_manager.py

cls = self.load_app(app_cls_name) #下一個(gè)標(biāo)題有這個(gè)函數(shù)
self.applications_cls[app_cls_name] = cls #存放應(yīng)用程序的實(shí)例對象
services = []
for key, context_cls in cls.context_iteritems():  #cls.context_iteritems()返回應(yīng)用程序的_CONTEXTS鍵值對
    v = self.contexts_cls.setdefault(key, context_cls)  #存放依賴服務(wù)的實(shí)例對象
    assert v == context_cls
    context_modules.append(context_cls.__module__)  #存放服務(wù)的模塊名稱

    if issubclass(context_cls, RyuApp):
          services.extend(get_dependent_services(context_cls)) ##將依賴的服務(wù)模塊的依賴也添加到services列表中,其中還包括要監(jiān)聽的事件的模塊
# 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])         #服務(wù)添加到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) ,將已經(jīng)存放到contexts_cls的模塊加入到context_modules了袁,防止重復(fù)添加
這個(gè)函數(shù)主要是為了獲得app_lists中的_CONTEXT的類以及_CONTEXT類所依賴的類朗恳,需要監(jiān)聽的事件的類
將服務(wù)也添加到app_lists中,用于后續(xù)的create_contexts

get_dependent_services

def get_dependent_services(cls):
    services = []
    for _k, m in inspect.getmembers(cls, _is_method): #返回實(shí)例對象的方法
        if _has_caller(m):  #判斷方法是否有caller屬性载绿,即裝飾器的函數(shù)
            for ev_cls, c in m.callers.items(): #{事件:_Caller對象}粥诫,_Caller屬性:1.生成事件的階段 2.生成事件的模塊
                service = getattr(sys.modules[ev_cls.__module__],
                                  '_SERVICE_NAME', None)  #事件所在的模塊實(shí)例
                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)   #動(dòng)態(tài)導(dǎo)入模塊
#返回加載應(yīng)用的類: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   #創(chuàng)建好的應(yīng)用程序存放到contexts中
return self.contexts

由上面可知contexts_cls{}存放的是未初始化的類,初始化結(jié)束的存放到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的應(yīng)用程序怕享,就能看到None

    if hasattr(cls, 'OFP_VERSIONS') and cls.OFP_VERSIONS is not None:
        ofproto_protocol.set_app_supported_versions(cls.OFP_VERSIONS)  #查看應(yīng)用程序的版本是否支持执赡,不支持的話拋出異常

    if app_name is not None:
        assert app_name not in self.applications
    app = cls(*args, **kwargs)    #創(chuàng)建類實(shí)例對象
    register_app(app)     #注冊應(yīng)用程序?qū)嵗?,存到SERVICE_BRICKS中函筋,SERVICE_BRICKS[app.name] = app沙合。
    assert app.name not in self.applications
    self.applications[app.name] = app      #將應(yīng)用程序保存到applications的字典中
    return app

初次調(diào)用的時(shí)候傳入的app_name = None,通過--verbose啟動(dòng)應(yīng)用程序時(shí)候可以看到:

$ryu-manager ryu.app.ws_topology --verbose 
loading app ryu.app.ws_topology
loading app ryu.controller.ofp_handler
instantiating app None of Switches          #初次調(diào)用的時(shí)候傳入的app_name = None
creating context switches
creating context wsgi

register_app(app)主要是注冊應(yīng)用實(shí)例跌帐,存放到SERVICE_BRICKS{“應(yīng)用名”:應(yīng)用}首懈,同時(shí)注冊應(yīng)用程序里裝飾器的handler。
self.applications存放的是已經(jīng)初始化好的應(yīng)用谨敛,注意此處存的是context中繼承RyuApp的應(yīng)用究履。

register_app(app) | handler.py

SERVICE_BRICKS[app.name] = app
之后調(diào)用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)   #將事件的處理函數(shù)注冊,在app_manager.py中有對應(yīng)的函數(shù)

事件處理器注冊到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)    #剛才調(diào)用這個(gè)函數(shù)是初始化_CONTEXT里的,現(xiàn)在是對應(yīng)用程序的初始化

        self._update_bricks() #注冊事件監(jiān)聽器
        self.report_bricks()  #啟動(dòng)應(yīng)用時(shí)添加”--verbose“可以查看到事件監(jiān)聽的信息肥惭,如:PROVIDES EventOFPPortStatus TO {'switches': set(['main']), 'monitor': set(['main'])}


        threads = []
        for app in self.applications.values():
            t = app.start() #調(diào)用應(yīng)用程序的啟動(dòng)方法
            if t is not None:
                app.set_main_thread(t)  #設(shè)置當(dāng)前應(yīng)用程序的線程盯仪,如果不設(shè)置,就沒辦法在stop()函數(shù)里刪除這個(gè)線程
                threads.append(t) #將這個(gè)線程添加到線程列表里
        return threads

self._instantiate(app_name, cls, *args, **kwargs)
此處初始化的是啟動(dòng)的應(yīng)用程序和非RyuApp的應(yīng)用蜜葱,剛才已經(jīng)將應(yīng)用程序的依賴已經(jīng)初始化完成了全景。
app.start()調(diào)用應(yīng)用程序的啟動(dòng)函數(shù),如默認(rèn)啟動(dòng)的應(yīng)用程序OFPHandler的start()會(huì)將OpenFlowController啟動(dòng)

_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)   #取出定義事件的模塊取出對應(yīng)的SERVICE實(shí)例
                if brick:
                    brick.register_observer(ev_cls, i.name,
                                            c.dispatchers)      #c.dispatchers代表事件產(chǎn)生的階段爸黄,如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)

注冊監(jiān)聽器
基類里說明了_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是子類生成的事件列表滞伟,但是當(dāng)且僅當(dāng)事件類是在RyuApp子類的不同python模塊中定義時(shí)才應(yīng)該指定。

不知道理解有沒有問題炕贵,查看Ryu的文檔梆奈,自定義類看看

register_observer | app_manager.py

注冊事件監(jiān)聽器

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) #將對應(yīng)事件的狀態(tài)更新到鍵值對中

report_bricks() | app_manager.py

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

報(bào)告監(jiān)聽的事件信息,可以啟動(dòng)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

啟動(dòng)初始化后調(diào)用
self.threads.append(hub.spawn(self._event_loop))
當(dāng)有新的事件來了亩钟,獲取事件對應(yīng)的handlers,然后再依次把事件放到handler中處理
不同的應(yīng)用程序還有進(jìn)行其他處理鳖轰,如OFPHandler的start()會(huì)將OpenFlowController啟動(dòng)

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

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

    return None

僅有依賴的服務(wù)模塊中含有WSGIApplication的時(shí)候才啟動(dòng)WSGI服務(wù)的實(shí)例清酥,提供web應(yīng)用。

hub.joinall(services) | manager.py

將所有的應(yīng)用作為任務(wù)蕴侣,作為coroutine的task去執(zhí)行焰轻,join使得程序必須等待所有的task都執(zhí)行完成才可以退出程序

app_mgr.close() | manager.py

關(guān)閉程序,釋放資源

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末昆雀,一起剝皮案震驚了整個(gè)濱河市辱志,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌忆肾,老刑警劉巖荸频,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異客冈,居然都是意外死亡旭从,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門场仲,熙熙樓的掌柜王于貴愁眉苦臉地迎上來和悦,“玉大人,你說我怎么就攤上這事渠缕「胨兀” “怎么了?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵亦鳞,是天一觀的道長馍忽。 經(jīng)常有香客問我,道長燕差,這世上最難降的妖魔是什么遭笋? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮徒探,結(jié)果婚禮上瓦呼,老公的妹妹穿的比我還像新娘。我一直安慰自己测暗,他們只是感情好央串,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布磨澡。 她就那樣靜靜地躺著,像睡著了一般质和。 火紅的嫁衣襯著肌膚如雪稳摄。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天侦另,我揣著相機(jī)與錄音秩命,去河邊找鬼。 笑死褒傅,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的袄友。 我是一名探鬼主播殿托,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼剧蚣!你這毒婦竟也來了支竹?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤鸠按,失蹤者是張志新(化名)和其女友劉穎礼搁,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體目尖,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡馒吴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了瑟曲。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片饮戳。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖洞拨,靈堂內(nèi)的尸體忽然破棺而出扯罐,到底是詐尸還是另有隱情,我是刑警寧澤烦衣,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布歹河,位于F島的核電站,受9級特大地震影響花吟,放射性物質(zhì)發(fā)生泄漏秸歧。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一示辈、第九天 我趴在偏房一處隱蔽的房頂上張望寥茫。 院中可真熱鬧,春花似錦矾麻、人聲如沸纱耻。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽弄喘。三九已至玖喘,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間蘑志,已是汗流浹背累奈。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留急但,地道東北人澎媒。 一個(gè)月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像波桩,于是被迫代替她去往敵國和親戒努。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容