はじめに
初期ファイナル・
こうした悲劇を防ぐ方法の1つがソフトウェアテストです。OpenFlowコントローラのように動作シーケンスが複雑なソフトウェアが壊れていないことを確認するためには、
TremaはOpenFlowコントローラをテストするためのテストツールが充実しています。今回はこれを使って、
ではさっそく実際の例を見ていきましょう。
リピータハブの設計
まずは、
OpenFlowプロトコル的に何が起こっているかを図1に示します。host1がパケットを送信すると、

最初のテスト
ではさっそく、
テストコードの最初のバージョンはリスト1のとおりです
require File.join(File.dirname(__FILE__), "spec_helper")
describe RepeaterHub do
end
RepeaterHubを定義していないのでエラーになることはわかりきっていますが、
$ rspec -fs -c ./spec/repeater-hub_spec.rb
.../spec/repeater-hub_spec.rb:3: uninitialized constant
RepeaterHub (Name Error)
予想どおり、
require File.join(File.dirname(__FILE__), "spec_helper")
class RepeaterHub ← 空のクラスを追加した
end
describe RepeaterHub do
end
本来、
$ rspec -fs -c spec/repeater-hub_spec.rb
No examples found.
Finished in 0.00003 seconds 0 examples, 0 failures
やった! これで最初のテストにパスしました。
このようにテストファーストでは、
パケット受信のテスト
では、
「ホスト3台
テストコードはリスト3のようにitブロックの中に記述します。
describe RepeaterHub do
it "は、入ってきたパケットを他のすべてのポートに転送する" do
(テストコードをここに書く)
end
end
テストシナリオをテストコードに置き換えるには、
- 【Given】
ホスト3つ (host1、 host2、 host3) がスイッチにつながっているとき、 - 【When】
host1がhost2にパケットを送ると、 - 【Then】
host2とhost3がパケットを受け取る
では、
【Given】ネットワークの構成
シナリオの前提条件
describe RepeaterHub do
it "は、入ってきたパケットを他のすべてのポートに転送する" do
network { ← ホスト3台、スイッチ1台のネットワーク
vswitch("switch") { dpid "0xabc" }
vhost("host1") { promisc "on" } ← 自分宛ではないパケットも受け取る
vhost("host2") { promisc "on" }
vhost("host3") { promisc "on" }
link "switch", "host1"
link "switch", "host2"
link "switch", "host3"
}
end
end
これはネットワーク設定とまったく同じ文法ですね! ここで、
【When】パケットの送信
Whenは
run(RepeaterHub)は、
describe RepeaterHub do
it "は、入ってきたパケットを他のすべてのポートに転送する" do
network {
(省略)
}.run(RepeaterHub) {
send_packets "host1", "host2"
}
end
end
【Then】受信パケット数のテスト
Thenには
describe RepeaterHub do
it "は、入ってきたパケットを他のすべてのポートに転送する" do
network {
(省略)
}.run(RepeaterHub) {
send_packets "host1", "host2"
vhost("host2").stats(:rx).should have(1).packets
vhost("host3").stats(:rx).should have(1).packets
}
end
end
vhost("ホスト名")は仮想ホストにアクセスするためのメソッドで、
テストを実行
ではさっそく実行してみましょう。
Failure/Error: vhost("host2").stats(:rx).should
have( 1 ).packets
expected 1 packets, got 0
失敗しました。
describe RepeaterHub do
it "は、入ってきたパケットを他のすべてのポートに転送する" do
pending "あとで実装する" ← この行を追加する
network {
(省略)
今度は実行結果が次のように変わり、
Pending:
1) は、入ってきたパケットを他のすべての ポートに転送する
# あとで実装する
ここでの失敗の原因は、
フローエントリのテスト
まずは、
- 【Given】
ホスト3つ (host1、 host2、 host3) がスイッチにつながっているとき、 - 【When】
host1 がhost2 にパケットを送ると、 - 【Then】
パケットをばらまくフローエントリをスイッチに追加する
では、
it "は、パケットをばらまくフローエントリをスイッチに追加する" do
network {
(省略)
}.run(RepeaterHub) {
send_packets "host1", "host2"
vswitch("switch").should have(1).flows
vswitch("switch").flows.first.actions.should == "FLOOD"
}
end
ネットワーク構成のコード
Failure/Error: vswitch("switch").should have(1).flows
expected 1 flows, got 0
「スイッチにフローエントリが1つあるはずがなかった」
class RepeaterHub def packet_in dpid, message
send_flow_mod_add dpid
end
en
Failure/Error: vswitch("switch").flows.first.actions.
should == "FLOOD"
expected: "FLOOD"
got: "drop" (using ==)
別のエラーになりました。
class RepeaterHub :actions => ActionOutput.new( OFPP_FLOOD )
)
end
end
今度はテストが通りました! それでは、
describe RepeaterHub do
it "は、入ってきたパケットを他のすべてのポートに転送する" do
network {
vswitch("switch") { dpid "0xabc" }
vhost("host1") { promisc "on"; ip "192.168.0.1" }
vhost("host2") { promisc "on"; ip "192.168.0.2" }
vhost("host3") { promisc "on"; ip "192.168.0.3" }
link "switch", "host1"
link "switch", "host2"
link "switch", "host3"
}.run(RepeaterHub) {
send_packets "host1", "host2"
vswitch("switch").should have(1).flows
flow = vswitch("switch").flows.first
flow.actions.should == "FLOOD"
flow.nw_src.should == "192.168.0.1"
flow.nw_dst.should == "192.168.0.2"
}
end
end
ここではホストにIPアドレスを振り、
Failure/Error: flow.nw_src.should == "192.168.0.1"
expected: "192.168.0.1"
got: nil (using ==)
失敗しました。フローの srcには、
class RepeaterHub :match => ExactMatch.from(message),
:actions => ActionOutput.new( OFPP_FLOOD )
)
end
end
テストにパスしました! これで、
テストコードのリファクタリング
テストが通ったので、
describe RepeaterHub do
around do | example |
network {
(省略)
}.run(RepeaterHub) {
example.run ← それぞれのitブロックをここで実行
}
end
it "は、入ってきたパケットを他のすべてのポートに転送する" do
send_packets "host1", "host2"
pending "あとで実装する"
……
end
it "は、パケットをばらまくフローエントリをスイッチに追加する" do
send_packets "host1", "host2"
vswitch("switch").should have(1).flows
……
end
end
再びパケット受信のテスト
いよいよ完成間近です。パケットがhost2とhost3に届くことをテストします
Failure/Error: vhost("host2").stats(:rx).should
have( 1 ).packets
expected 1 packets, got 0
失敗してしまいました。host2 がパケットを受信できていません。そういえば、
class RepeaterHub send_packet_out(
dpid,
:packet_in => message,
:actions => ActionOutput.new(OFPP_FLOOD)
)
end
end
RepeaterHub
は、入ってきたパケットを他のすべてのポートに転送する
は、パケットをばらまくフローエントリをスイッチに追加する
Finished in 15.66 seconds
2 examples, 0 failures
すべてのテストに通りました! これでリピータハブとテストコード一式が完成です。
まとめ
Tremaのユニットテストフレームワークを使ってリピータハブを作りました。Tremaのsrc/
- コントローラをユニットテストする方法を学びました。Trema はRuby のユニットテストフレームワークRSpecと統合されており、
仮想スイッチのフローテーブルや仮想ホストの受信パケット数などについてのテストを書けます。 - テストをGiven、
When、 Thenの3ステップに分けて分析/ 設計する方法を学びました。それぞれのステップをRSpecのテストコードに置き換えることで、 テストコードが完成します。
次回はTremaプロジェクト入門と題して、
最後に、