ryu
python openflow library で、Tutorial に載っていそうで見つけられずに苦労したことのメモ。
まずはじめに ryu
では、「イベント駆動」アプリケーションとして書きます。イベントはryu.event.EventBase
の派生クラスです。
openflow アプリケーションを書くときに「最初に~する」というのはよくあります。ryu
で、この時に使うべきイベントに迷うのですが、こうするのが良さそうです。
from ryu.base import app_manager
from ryu.controller import ofp_event
from ryu.controller.handler import set_ev_cls, MAIN_DISPATCHER
class App1(app_manager.RyuApp):
@set_ev_cls(ofp_event.EventOFPStateChange, MAIN_DISPATCHER)
def on_switch_ready(self, ev):
assert isinstance(ev, ofp_event.EventOFPStateChange)
print("switch ready")
実行例。例えば Ubuntu で ryu-bin
パッケージを入れると実行できるようになります。
ryu-manager --config-file=/dev/null app.py
Event
ryu はイベント駆動なので、イベントを知らないことには話が始まりません。標準的に使われる ryu.controller 配下には、次のようなイベントがあります。まずはざっと列挙します。後に解説を書きます。
from は Event の publisher 側、to は subscriber 側を表現しています。
ryu.controller.ofp_event
比較的 openflow プロトコルのメッセージに 1:1 対応したイベントが扱われている。
datapath state 通知系
- EventOFPStateChange
- from: ryu.controller.controller.Datapath [HANDSHAKE_DISPATCHER]
- from: ryu.controller.ofp_handler.OFPHandler.hello_handler [CONFIG_DISPATCHER]
- from(<1.3): ryu.controller.ofp_handler.OFPHandler.switch_features_handler [MAIN_DISPATCHER]
- from(>=1.3): ryu.controller.ofp_handler.OFPHandler.multipart_reply_handler [MAIN_DISPATCHER]
- from: ryu.controller.controller.Datapath.close [DEAD_DISPATCHER]
- to: ryu.controller.dpset.DPSet.dispatcher_change [MAIN_DISPATCHER, DEAD_DISPATCHER]
以下は EventOFPMsgBase
のサブクラスとして動的に生成されている。全て recv loop からイベントが生成される。
- EventOFPHello
- from: ryu.controller.controller.Datapath._recv_loop
- to: ryu.controller.ofp_handler.OFPHandler.hello_handler [HANDSHAKE_DISPATCHER]
- EventOFPSwitchFeatures
- from: ryu.controller.controller.Datapath._recv_loop
- to: ryu.controller.ofp_handler.OFPHandler.switch_features_handler [CONFIG_DISPATCHER]
- to: ryu.controller.dpset.DPSet.switch_features_handler [CONFIG_DISPATCHER]
- EventOFPPortDescStatsReply
- from: ryu.controller.controller.Datapath._recv_loop
- to: ryu.controller.ofp_handler.OFPHandler.multipart_reply_handler [CONFIG_DISPATCHER]
- EventOFPEchoRequest
- from: ryu.controller.controller.Datapath._recv_loop
- to: ryu.controller.ofp_handler.OFPHandler.echo_request_handler [HANDSHAKE_DISPATCHER, CONFIG_DISPATCHER, MAIN_DISPATCHER]
- EventOFPEchoReply
- from: ryu.controller.controller.Datapath._recv_loop
- to: ryu.controller.ofp_handler.OFPHandler.echo_reply_handler [HANDSHAKE_DISPATCHER, CONFIG_DISPATCHER, MAIN_DISPATCHER]
- EventOFPPortStatus
- from: ryu.controller.controller.Datapath._recv_loop
- to: ryu.controller.ofp_handler.OFPHandler.port_status_handler [MAIN_DISPATCHER]
- to: ryu.controller.dpset.DPSet.port_status_handler [MAIN_DISPATCHER]
- EventOFPErrorMsg
- from: ryu.controller.controller.Datapath._recv_loop
- to: ryu.controller.ofp_handler.OFPHandler.error_msg_handler [HANDSHAKE_DISPATCHER, CONFIG_DISPATCHER, MAIN_DISPATCHER]
port state 通知系。EventOFPPortStatus の後に発行される。
- EventOFPPortStateChange
- from: ryu.controller.ofp_handler.OFPHandler.port_status_handler
ryu.controller.dpset
openflow プロトコル直接というよりは、もう一段上の層で datapath の有効無効やポートの状態をハンドルできるイベント。DPSet Service として使えるようになっている。
- EventDP
- from: ryu.controller.dpset.DPSet._register
- from: ryu.controller.dpset.DPSet._unregister
- EventDPReconnected
- from: ryu.controller.dpset.DPSet._register
- EventPortAdd
- from: ryu.controller.dpset.DPSet.port_status_handler
- EventPortDelete
- from: ryu.controller.dpset.DPSet.port_status_handler
- EventPortModify
- from: ryu.controller.dpset.DPSet.port_status_handler
Event 解説
総合すると、openflow 1.3 なスイッチとの接続では、次のようにイベントが自動的に流れます。
- TCP 接続確立
- EventOFPStateChange
- from: ryu.controller.controller.Datapath [HANDSHAKE_DISPATCHER]
- 対向が接続確立後に OFPT_HELLO を送ってくる
- EventOFPHello
- from: ryu.controller.controller.Datapath._recv_loop
- to: ryu.controller.ofp_handler.OFPHandler.hello_handler [HANDSHAKE_DISPATCHER]
- EventOFPStateChange
- from: ryu.controller.ofp_handler.OFPHandler.hello_handler [CONFIG_DISPATCHER]
- ryu.controller.ofp_handler.OFPHandler.hello_handler が OFPFeaturesRequest を送る
- 対向が OFPT_FEATURES を送ってくる
- EventOFPSwitchFeatures
- from: ryu.controller.controller.Datapath._recv_loop
- to: ryu.controller.ofp_handler.OFPHandler.switch_features_handler [CONFIG_DISPATCHER]
- to: ryu.controller.dpset.DPSet.switch_features_handler [CONFIG_DISPATCHER]
- ryu.controller.ofp_handler.OFPHandler.switch_features_handler が OFPPortDescStatsRequest を送る
- 対向が OFPT_MULTIPART_REPLY/OFPMP_PORT_DESC を送ってくる
- EventOFPPortDescStatsReply
- from: ryu.controller.controller.Datapath._recv_loop
- to: ryu.controller.ofp_handler.OFPHandler.multipart_reply_handler [CONFIG_DISPATCHER]
- EventOFPStateChange
- from(>=1.3): ryu.controller.ofp_handler.OFPHandler.multipart_reply_handler [MAIN_DISPATCHER]
- to: ryu.controller.dpset.DPSet.dispatcher_change [MAIN_DISPATCHER]
- EventDP
- from: ryu.controller.dpset.DPSet._register
なので、いわゆる on switch ready みたいなポイントは、EventOFPStateChange
, MAIN_DISPATCHER
あるいは、EventDP
となります。
Service
上記の DPSet のように、イベント発信元はサービスとして追加できるようになっています。サービスは ryu
の内部では文字列で識別されています。ryu の中には次のようなサービスが存在します。これらは通常は event handler を登録して使い始める(set_ev_cls
)と、自動的に有効化されるようになっています。
- ryu.controller.dpset
- ryu.controller.ofp_handler
- ryu.services.protocols.ovsdb.manager
- ryu.services.protocols.vrrp.manager
- ryu.topology.switches
例えば ryu.controller.dpset
を使う場合は次のようにします。
from ryu.base import app_manager
from ryu.controller import dpset, ofp_event
from ryu.controller.handler import set_ev_cls
class App2(app_manager.RyuApp):
@set_ev_cls(dpset.EventDP)
def on_dp_change(self, ev):
print("datapath event", ev.enter)
Service を使った on switch ready なポイントとして、次のような例もアリです。
import ryu.topology.event
from ryu.base import app_manager
from ryu.controller.handler import set_ev_cls
class App3(app_manager.RyuApp):
@set_ev_cls(ryu.topology.event.EventSwitchEnter)
def on_enter(self, ev):
print("switch ready")
Dependent singleton
サービス化されていない形のものとして、RyuApp._CONTEXTS
に明示的に依存するクラスを登録して使う方法もあります。起動時に singleton として生成され、RyuApp.__init__
の kwargs 経由で渡されます。
from ryu.base import app_manager
class A(object):
def __init__(self):
print("init A")
class App4(app_manager.RyuApp):
_CONTEXTS = dict(a=A)
def __init__(self, *args, **kwargs):
super(App4, self).__init__(*args, **kwargs)
print(kwargs)
この例は openflow メッセージに対するハンドラを設定していないので、初期化完了後に exit します。ryu
自体は event hub であり、openflow プロトコルは ofp_event
カテゴリのイベントで駆動されるアプリケーションの一つ、という発想で作られているようです。