Trema/MyRoutingSwitch(7), Topology Change and Re-route(10)

前回の続き。OVS patch port はどうも状態が固定されているらしく、ポートの状態変更ができないとか、ポート消しても link down を検出できないとか、そうなってしまうようです。とはいえ、OpenFlowテスト環境をつくる(7) でやったみたいに、overlay 的な環境を組み始めると、ブリッジ間(スイッチ間)リンクにおいて明示的な port down が検出できない可能性が考えられるでしょう。patch port 使わないからいいや…と放置していいようにも思えません。

いや、それを言うなら、何のために定期的な LLDP Flooding 流しているのか、という話になるわけですね。これ、もともとはリンクが増えることを検出する作りになっていました。ブリッジ(スイッチ)の増加・リンクの増加、というのの検出です。リンクが減る、というのを LLDP Flooding を元に検出できていないというのはそれはそれで問題でしょう。ということで、LLDP Flooding 情報を元にしたリンク障害検出(port_status メッセージによる明示的なトポロジ変更検出では検出できないトラブルへの対処)を考えてみます。

対処

いま、コントローラ内部では、LLDP Packet をキャッチして各ブリッジのポートやリンクの情報を保持するようになっています。Flooding して LLDP Packet をキャッチしたときにタイムスタンプをつけてやって、一定時間参照がないものを消す→トポロジ変更検出(ノード間最短経路)を再計算するという流れにします。何のことはない、arp_table とかと同等に age out する仕掛けを入れておきます。

タイマ処理

arp_table は一定時間おきに arp entry の age out 確認をおこなっているので、その中で link array (in class Topology, @links) が持つ各 link entry のタイムアウトチェックを回します。ここで厄介なのが、いくつかのタイマ処理の関係ですね。

  • my-routing-switch.rb
class MyRoutingSwitch < Controller
  periodic_timer_event :age_tables, 10
  periodic_timer_event :flood_lldp_frames, 5
  periodic_timer_event :build_topology, 3

  # (略)

  def age_tables
    @arp_table.age
    @topology.age_links
  end

end
  • lib/topology.rb
class Topology

  LINK_ENTRY_MAX_AGE = 5

  # (略)

  def add_link_by dpid, packet_in
    raise "Not an LLDP packet!" if not packet_in.lldp?

    link = Link.new( dpid, packet_in, DEFAULT_LINK_COST, LINK_ENTRY_MAX_AGE )

    if @links.include?( link )
      @links.each { | each | each.update if each == link }
    else
      puts "add link: #{link.dpid1.to_hex}/#{link.port1} - #{link.dpid2.to_hex}/#{link.port2}"
      @links << link
      @links.sort!
      changed
      notify_observers self
    end
  end

  # (略)

  def age_links
    @links.each do |each|
      if each.aged_out?
        puts "[Topology::age_links], link: #{each.to_s} aged-out"
        @links -= [ each ]
        changed
      end
    end

    notify_observers self
  end

end
  • 一定時間で LLDP Flooding をしています。
  • Link entry の寿命 (LINK_ENTRY_MAX_AGE) は LLDP flooding interval よりも長くなっている必要があります。
  • LLDP flooding interval の間隔設定と、link entry max age の設定でトポロジ変化検出にかかる時間と安定性の設定ができます。(flooding 何回分失敗したらリンクダウンと判断する、という設定)

イベント間の関連性

非同期イベントでいろいろ動くのでちょっと…というかだいぶわかりにくいですね。

ということで周期イベント別に3種類ほど動きを整理しました。トポロジ変更の対処は 2 種類です。まずは、変更検知→変更通知させるというものです。

  • (A): link entry のエイジアウト処理…明示されないリンク減少の検出
  • (B): LLDP による未知のリンクの検出(リンク増加の検出)

(A),(B) ともに、何かしらの形でトポロジの変化を探し出して、変化があれば observer に通知します。observer (@linkindex) は変更があったら内部のリンク情報を作り直して変更があったよ、という通知フラグをセットしておきます。今回の話の中であがったイベントだけ書いていますが、これまでにやっていた port_status 受信によるリンクアップダウン検知とそのときの動作も同様で、とにかくリンク/ポートの情報を収集して、observer (linkindex) に通知する、という動作をします。

もう一つが変更があった場合にフロー情報をどうにかする物です。(C)はリンク情報(@linkindex)を元に、ノード間最短経路の再計算 + flow rule の設定を行うためのタイマです。通常、リンクダウンでは複数のイベントが発生するので、個別のイベントごとに計算を回すのではなく、一定時間ごとにまとめて処理するようにしてあります。

テスト

トポロジはTopology Change and Re-route(5) でつかったこういうやつです。

sw2-sw3 の間を落として(patch port 削除して)います。

  • patch port 削除前
---------------------
t=0x4
dist :
0x1 : 1=>     0, 2=>    10, 3=>    20, 4=>    10, 5=>    20, 6=>    20,
0x2 : 1=>    10, 2=>     0, 3=>    10, 4=>    20, 5=>    20, 6=>    30,
0x3 : 1=>    20, 2=>    10, 3=>     0, 4=>    20, 5=>    10, 6=>    20,
0x4 : 1=>    10, 2=>    20, 3=>    20, 4=>     0, 5=>    10, 6=>    10,
0x5 : 1=>    20, 2=>    20, 3=>    10, 4=>    10, 5=>     0, 6=>    10,
0x6 : 1=>    20, 2=>    30, 3=>    20, 4=>    10, 5=>    10, 6=>     0,
pred :
0x1 : 1=>      , 2=>     1, 3=>     2, 4=>     1, 5=>     4, 6=>     4,
0x2 : 1=>     2, 2=>      , 3=>     2, 4=>     1, 5=>     3, 6=>     5,
0x3 : 1=>     2, 2=>     3, 3=>      , 4=>     5, 5=>     3, 6=>     5,
0x4 : 1=>     4, 2=>     1, 3=>     5, 4=>      , 5=>     4, 6=>     4,
0x5 : 1=>     4, 2=>     3, 3=>     5, 4=>     5, 5=>      , 6=>     5,
0x6 : 1=>     4, 2=>     3, 3=>     5, 4=>     6, 5=>     6, 6=>      ,
[MyRoutingSwitch::rewrite_flows]
  • patch port 削除
[MyRoutingSwitch::port_status] switch:0x2
- port number    : 9
- port name      : patch2-3
- port local?    : false
- port link_down?: false
- port link_up?  : true
- port port_down?: false
- port port_up?  : true
[Link::aged_out?] Age out: An Link entry, [0x2/9 - 0x3/1] has been aged-out
[Topology::age_links], link: 0x2 (port 9) <-> 0x3 (port 1) aged-out
[Link::aged_out?] Age out: An Link entry, [0x3/1 - 0x2/9] has been aged-out
[Topology::age_links], link: 0x3 (port 1) <-> 0x2 (port 9) aged-out
0x1 (port 1) <-> 0x4 (port 3)
0x1 (port 2) <-> 0x2 (port 1)
0x2 (port 1) <-> 0x1 (port 2)
0x3 (port 2) <-> 0x5 (port 1)
0x4 (port 1) <-> 0x5 (port 2)
0x4 (port 2) <-> 0x6 (port 2)
0x4 (port 3) <-> 0x1 (port 1)
0x5 (port 1) <-> 0x3 (port 2)
0x5 (port 2) <-> 0x4 (port 1)
0x5 (port 3) <-> 0x6 (port 1)
0x6 (port 1) <-> 0x5 (port 3)
0x6 (port 2) <-> 0x4 (port 2)
topology updated
(略)
---------------------
t=0x4
dist :
0x1 : 1=>     0, 2=>    10, 3=>    30, 4=>    10, 5=>    20, 6=>    20,
0x2 : 1=>    10, 2=>     0, 3=>    40, 4=>    20, 5=>    30, 6=>    30,
0x3 : 1=>    30, 2=>    40, 3=>     0, 4=>    20, 5=>    10, 6=>    20,
0x4 : 1=>    10, 2=>    20, 3=>    20, 4=>     0, 5=>    10, 6=>    10,
0x5 : 1=>    20, 2=>    30, 3=>    10, 4=>    10, 5=>     0, 6=>    10,
0x6 : 1=>    20, 2=>    30, 3=>    20, 4=>    10, 5=>    10, 6=>     0,
pred :
0x1 : 1=>      , 2=>     1, 3=>     5, 4=>     1, 5=>     4, 6=>     4,
0x2 : 1=>     2, 2=>      , 3=>     5, 4=>     1, 5=>     4, 6=>     4,
0x3 : 1=>     4, 2=>     1, 3=>      , 4=>     5, 5=>     3, 6=>     5,
0x4 : 1=>     4, 2=>     1, 3=>     5, 4=>      , 5=>     4, 6=>     4,
0x5 : 1=>     4, 2=>     1, 3=>     5, 4=>     5, 5=>      , 6=>     5,
0x6 : 1=>     4, 2=>     1, 3=>     5, 4=>     6, 5=>     6, 6=>      ,
[MyRoutingSwitch::rewrite_flows]

まあ、タイマの動作とかが文面からわかるわけじゃないのでアレですが。変更前後の経路情報(すなわちトポロジの変化)とかは dist/pred の 0x2, 0x3 の行あるいは列が変わっていることからわかります。

まとめ

リンクダウンの処理の前提として、これまで

  • ポートダウン(リンクダウン)は明示的に通知される (port_status)
  • ポートダウンが起きると対向側のポートもダウンする
  • ポートがアップしたときは、1リンク2ポートがセットでアップする(片側ポートだけが UP になることはない)

という前提があったんですが、特に patch port を使用している場合に上の前提が成立しない、ということです。

また、これまで LLDP Flooding によってリンクの増加を検出する処理はあったのですが、リンクの減少を検出する処理がありませんでした。そこで今回は、リンク情報に寿命を設定して、一定時間検出されないリンクについては削除・トポロジ再計算を行うようにしてみました。イベントごとに何がどう回るのかを整理しておかないと全体の動きがわかりにくくなります…。

…というようなあたりでいったんここまで。