Ryu tutorial 補遺

`ryu` python openflow library で、Tutorial に載っていそうで見つけられずに苦労したことのメモ。

まずはじめに `ryu` では、「イベント駆動」アプリケーションとして書きます。イベントは`ryu.event.EventBase` の派生クラスです。

openflow アプリケーションを書くときに「最初に～する」というのはよくあります。`ryu` で、この時に使うべきイベントに迷うのですが、こうするのが良さそうです。

```python:app.py
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` を使う場合は次のようにします。

```python:app2.py
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 なポイントとして、次のような例もアリです。

```python:app3.py
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 経由で渡されます。

```python:app4.py
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` カテゴリのイベントで駆動されるアプリケーションの一つ、という発想で作られているようです。