これは何かというと
Open Stack Advent Calendarのエントリ(12/06)です。季節物(?)ということで、若干、おふざけ感のある内容については、ご容赦ください。
話のネタは、Red Hat版のRHEL6用Folsomで、Quantumを動かそうとがんばった際に、やむにやまれず読んだソースコードの思い出です。
netns未対応 orz
Quantumの機能(?)の1つに「Network Namespace(以下netns)対応」があります。netns自体は、もともとLXC(コンテナ)で使うために追加された(はずの)Linuxの機能で、プロセスごとに独立したネットワーク設定が持てるというものです。Quantumでは、これを利用して、「Overlapping IP Address」をサポートしています。簡単にいうと、テナントごとに専用の仮想ルータを用意してあげて、そのルータの後ろでは、各テナントでIPアドレスを自由にアサインできるというものです。テナント内外の通信は、ルータでNATするので、テナント間でサブネットが重複しても大丈夫です。(自宅LANをブロードバンドルータでインターネットにつなぐ感覚ですね。)
なのですが・・・。RHEL6.3のカーネルでは、Quantumからnetnsを利用するのに必要な機能が不足しており、泣く泣く、netns対応を無効にしてセットアップを行いました。
(参考)Bug 869004 - iproute2 is missing netns support
ついでに言うと、Open vSwitchも未サポートなので、Linux Bridge Agentでがんばりました。(Open vSwitchは、RHEL6.4から入る予定のようです。)
DHCPでIPアドレスがとれない問題
当然ながら、セットアップ中にいろいろな問題にぶつかるわけですが、その中の1つに、「Network Nodeを再起動すると、インスタンスがDHCPでIPアドレスを取れなくなる」という問題がありました。問題判別の正確な経緯は面倒なので省略しますが、この手の仮想ネットワーク関連の機能は、サーバにブリッジやら仮想デバイスやらIPアドレスやらをつけたり外したりを繰り返すので、そのうち、Routing Tableが狂って通信できなることが時々あります。(昔々、XenのDom 0を手作業でセットアップしていたころの教訓だったような・・・。)
というわけで、Routing Tableの変化を注視していると、サーバ再起動前後で、予想通りに変化がありました。結論としては、dnsmasqの仕様で、こいつがListenしているインターフェースがRouting Tableの頭にないと、うまく動かないということでした。(正確にいうと、同じサブネットのIPを持つインターフェースのRouting Tableエントリが、dnsmasqがListenしているインターフェースのエントリより上にあるとNG。)これに気づくまでに、dnsmasqのソースも読みこんだのですが、そこの思い出は省略で。
ちなみに、これは、netnsが有効だと起きない問題のようです。(netnsが有効な場合、dnsmasqは独自のNetwork Namespaceを持つので、その中ではインターフェースの競合はおきないので。)アップストリームのみなさんは、きっと、netnsは無効にしないので、気づかなかったのでしょう。
パッチ書きますか
というわけで、Routing Tableのエントリを入れ替えるパッチでも書こうかと、Quantumのソースを読み始めるわけです。Pythonなので、実行環境のソースをそのまま読んでデバッグしちゃいましょう。「rpm -ql hogehoge」でソースのありかを確認して、「grep -Hr dnsmasq ./」「grep -Hr DHCP ./」などであたりをつけて、dnsmasqを起動するあたりのコードから追跡をはじめます。
長くなるので、途中経過は超絶に省略しますが、最終的に、dnsmasqを起動した直後に、Routing Tableを入れ替えるコードを呼び出すことにしました。
ちなみに、上のリンク先のパッチは、最初、Red HatのBugzillaにあげて、Gary先生としばしディスカッションした上で、彼からアップストリームに投げてもらいました。アップストリームに突撃する勇気のない方は、ぜひ、Red HatのBugzillaを活用してくださいね。
アップストリームのコードレビューでは、元木先生にもご協力いただきました。
青年の葛藤
そして、このパッチを書くためにソースを読んでいて気づいたのが、(というか、agentのログを見ていて、薄々感づいていたのですが・・・)ネットワーク周りの設定は、結構あちこちで、Pythonから、「ip」コマンドを外部コマンドとして呼び出しているということでした。ちょっと意外(でもない?)。将来、ipコマンドの仕様が変わったらどうするんだろう・・・。
しかし、コードの統一性を損なうのもいまいちで、それが理由でRejectされる可能性もあるので、既存のipコマンド呼び出しライブラリを流用して、Routing Tableの入れ替えコードを書いていきます。(変数のネーミングもなんとなく周り(?)の空気を読みつつ。。。)
しかし・・・、ipコマンドの仕様が変わって、自分の書いたコードがバグの原因になるのも嫌なので、ipコマンドの仕様依存を下げるべく、出力のパースやら、規定外の使い方をするユーザを想定した例外処理やらを考え始めます。
しかし・・・・・・、自分の書いた部分だけ、異様に凝った例外処理が入るのも、それはそれで、コードの統一性の観点でよくないので・・・・。
あーもう、眠れなくなっちゃう。
ということで、そもそも既存のコードがどの程度、ipコマンドの仕様依存を持っているかをまずは確認することにしました。
で、発見したのがこの部分。
quantum/agent/linux/ip_lib.py
230 class IpAddrCommand(IpDeviceCommandBase): 231 COMMAND = 'addr' ... 265 for line in self._run('show', self.name, *filters).split('\n'): 266 line = line.strip() 267 if not line.startswith('inet'): 268 continue 269 parts = line.split() 270 if parts[0] == 'inet6': # ← ここはぎりぎり仕方ない? 271 version = 6 272 scope = parts[3] # ← こことか。 273 broadcast = '::' 274 else: 275 version = 4 276 broadcast = parts[3] # ← こことか。 277 scope = parts[5] # ← こことか。
それではみなさんご一緒に。
「即値かよ!」
えー、あえて詳しくは説明しませんが、端的にいうと、「ip addr show」の出力をsplitして、何やらしている部分です。「こんな即値でいいなら、あのパッチももっとシンプルになったのに・・・。」と思いつつ、Gary先生の「このままでいいんじゃないの。」というコメントを受けつつ、現在の形でアップストリームにマージされたのでした。
今日は、12月6日木曜日。クリスマスまであと19日ですね。