模塊間通信機(jī)制分析之Ryu篇
Ryu是一款非常輕便的SDN控制器,在科研方面得到了廣泛的應(yīng)用。相比其他控制器,受益于Python語言,在Ryu上開發(fā)SDN應(yīng)用的效率要遠(yuǎn)高于其他控制器。為了解決復(fù)雜的業(yè)務(wù),有時需要在Ryu上開發(fā)多模塊來協(xié)同工作,從而共同完成復(fù)雜的業(yè)務(wù)。本文將介紹Ryu模塊之間通信,包括Context等方式的多種通信方式。
_CONTEXTS
在RyuApp類中有一個屬性是\_CONTEXTS。\_CONTEXTS中的內(nèi)容將作為當(dāng)前模塊的服務(wù)在模塊初始化時得到加載。示例如下:
_CONTEXTS = {
"Network_Aware": network_aware.Network_Aware,
"Network_Monitor": network_monitor.Network_Monitor,
}
def __init__(self, *args, **kwargs):
super(Shortest_forwarding, self).__init__(*args, **kwargs)
self.name = 'shortest_forwarding'
self.network_aware = kwargs["Network_Aware"]
self.network_monitor = kwargs["Network_Monitor"]
在模塊啟動時,首先會將\_CONTEXTS中的模塊先啟動,在模塊的初始化函數(shù)中可以通過self.network_aware = kwargs["Network_Aware"]的形式獲得該服務(wù)模塊的實(shí)例,從而獲取到該模塊的數(shù)據(jù),并具有完全的讀寫能力。這種模式很清晰地體現(xiàn)了模塊之間的關(guān)系。然而在Ryu的實(shí)現(xiàn)中,這個機(jī)制并不***,或者有所限制。首先,當(dāng)某個模塊作為別的模塊的服務(wù)啟動時,就無法在啟動Ryu時手動啟動。這種做法應(yīng)該是出于保證模塊啟動順序,從而順利完成多模塊啟動而設(shè)計(jì)。另一方面,Ryu不支持多級的服務(wù)關(guān)系,如A是B的服務(wù),那么B就不能作為其他模塊的服務(wù),也即這種服務(wù)關(guān)系只有兩層。所以在設(shè)計(jì)模塊時,若完全使用\_CONTEXTS方式來傳遞信息則需將架構(gòu)設(shè)計(jì)成兩層以內(nèi)。若希望不受此限制,開發(fā)者可以自己修改其源碼解除這個限制。
app\_manager.lookup\_service\_brick()
在某些業(yè)務(wù)場景,我們需要使用其他模塊的數(shù)據(jù),但是又不希望將對方作為自己的服務(wù)來加載,則可以通過app\_manager.lookup\_service\_brick('module name')來獲取運(yùn)行中的某個模塊的實(shí)例,從而獲取其數(shù)據(jù)。典型案例可以參考controller/controller.py中的Datapath類。示例如下:
self.ofp_brick = ryu.base.app_manager.lookup_service_brick('ofp_event')
def set_state(self, state):
self.state = state
ev = ofp_event.EventOFPStateChange(self)
ev.state = state
self.ofp_brick.send_event_to_observers(ev, state)
這種做法區(qū)別于import, import引入的是靜態(tài)的數(shù)據(jù),如某個類的函數(shù)的定義,靜態(tài)數(shù)據(jù)的定義。當(dāng)涉及到動態(tài)的數(shù)據(jù),import則無法獲取到對應(yīng)的數(shù)據(jù)。如名為app的模塊中有一個屬性self.domain = Domain(),那么import可以獲得其類的定義,而實(shí)際上,我們需要的是運(yùn)行狀態(tài)時Domain的實(shí)例,而import無法做到這一點(diǎn)。通過app = app\_manager.lookup\_service\_brick(‘app’)可以獲得當(dāng)前的app實(shí)例,進(jìn)而通過app.domain來獲取當(dāng)前的domain實(shí)例的數(shù)據(jù)。
Event
通過事件系統(tǒng)來通信是模塊之間通信的最普通的形式。每當(dāng)交換機(jī)和Ryu建立連接,都會實(shí)例化一個Datapath對象來處理這個連接。在Datapath對象中,會將接收到的數(shù)據(jù)解析成對應(yīng)的報文,進(jìn)而轉(zhuǎn)化成對應(yīng)的事件,然后發(fā)布。注冊了對應(yīng)事件的模塊將收到事件,然后調(diào)用對應(yīng)的handler處理事件。示例如下:
[module: controller] if msg: ev = ofp_event.ofp_msg_to_ev(msg) self.ofp_brick.send_event_to_observers(ev, self.state) dispatchers = lambda x: x.callers[ev.__class__].dispatchers handlers = [handler for handler in self.ofp_brick.get_handlers(ev) if self.state in dispatchers(handler)] for handler in handlers: handler(ev) [module:simple_switch_13.py] @set_ev_cls(ofp_event.EventOFPPacketIn, MAIN_DISPATCHER) def _packet_in_handler(self, ev): msg = ev.msg datapath = msg.datapath
編譯運(yùn)行之后,simple\_switch\_13模塊的\_packet\_in\_handler函數(shù)注冊了事件ofp\_event.EventOFPPacketIn, 當(dāng)Controller模塊中的Datapath分發(fā)ofp\_event.EventOFPPacketIn事件時, 將會分發(fā)到\_packet\_in\_handler函數(shù),在Datapath中調(diào)用handler(ev)來處理事件,從而完成了信息在模塊之間的通信。
公共文件讀寫
除了以上的形式以外,某些數(shù)據(jù)的通信則通過讀寫公共文件完成。最典型的案例是oslo.config的使用。oslo是OpenStack的開源庫。oslo.config提供一個全局的配置文件,同時也完成命令行的解析。通過讀寫公共文件的內(nèi)容,可以完成信息的傳遞,如模塊A將config中CONF對象的某個參數(shù)arg的i值修改為1, B再讀取對應(yīng)的參數(shù)arg,則可以獲得數(shù)值1, 從而完成通信。面對配置信息等全局信息時,公共文件的使用可以避免不同模塊之間的沖突,從而實(shí)現(xiàn)全局?jǐn)?shù)據(jù)的統(tǒng)一。但是這種做法會頻繁地讀寫文件,效率不高。且此類數(shù)據(jù)僅適合靜態(tài)數(shù)據(jù)的傳遞,不適合存在于實(shí)例中的動態(tài)數(shù)據(jù)。
總結(jié)
在使用Ryu開發(fā)SDN網(wǎng)絡(luò)應(yīng)用的過程中,多模塊協(xié)同工作是非常常見的場景。使用\_CONTEXTS形式可以更清晰地體現(xiàn)模塊之間的關(guān)系,代碼架構(gòu)可讀性更高;采用app\_manager.lookup\_service\_brick()形式可以得到運(yùn)行的實(shí)例,可以達(dá)到\_CONTEXTS的效果,適用與僅需使用某模塊某小部分功能集合,模塊之間沒有明顯的服務(wù)關(guān)系的場景;Event是最普通的模塊見通信,可以實(shí)現(xiàn)訂閱發(fā)布模式的多模塊協(xié)同工作場景,實(shí)現(xiàn)模塊之間解耦;采用公共文件作為信息的中轉(zhuǎn)站是***的選擇,效率比較低,適用于全局信息的傳遞。以上的幾種方式是筆者在實(shí)驗(yàn)過程中總結(jié)的通信方式,若有錯誤指出,敬請指出,萬分感謝。



























