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