やってみよう NETCONF
知ったかぶりしない NETCONF に続く, NETCONF エントリーです.
今回はNETCONF の実装をいくつか試す.
前のエントリーでは「NETCONF 便利そうだけど, データモデル*1 が共通じゃないので使うのつらそう. 特にいろんな種類のものがある場合は」というようなことを書いた.
一方で「できる範囲でいいから物理ルーター/スイッチを自動管理したい」ニーズはあるので, 今回は「現状どんなことができるの?」を確認しようと思う.
たくさん試したわけではないので, 他に便利アプリがあるかもしれない.
オススメ情報があったらぜひ教えてください.
- 2014-11-04 追記
管理対象 (Netconf Server)
今回はJuniper Firefly Perimeter (junos-vsr-12.1X46-D-10-domestic) を使ってみる.
- NETCONF の設定は省略
- 詳しくはJuniper ドキュメント 参照
A Ruby gem for NETCONF
- https://github.com/Juniper/net-netconf
- BSD 2-Clause License
- Juniper Networks 製の NETCONF client ライブラリー
- NETCONF を使って設定を取得/変更する
- デバイス管理機能はない
- Ruby 向け
実装されている機能
NETCONF Standard RPCs
- get-config, edit-config
- lock, unlock
- validate, discard-changes
Flexible RPC mechanism
- Netconf::RPC::Builder to metaprogram RPCs
- Vendor extension framework for custom RPCs
( 引用: https://github.com/Juniper/net-netconf )
- RPC mechanism はモジュールによって抽象化されているが, 実装はJuniper 用のみ
使用例
インストール
$ gem install netconf
設定取得
password は環境変数に入ってるとして,
require 'net/netconf' Netconf::SSH.new(target: '192.168.0.74', username: 'codeout', password: ENV['PASSWORD']) do |device| config = device.rpc.get_config {|x| # 以下の <rpc> message を送信 x.configuration { x.system { x.services } } } # <rpc> # <get-config> # <source> # <running/> # </source> # <filter type="subtree"> # <configuration> # <system> # <services/> # </system> # </configuration> # </filter> # </get-config> # </rpc> puts config # 以下の設定が取得できる (Nokogiri::XML::Element オブジェクト) # <data> # <configuration xmlns="http://xml.juniper.net/xnm/1.1/xnm" junos:commit-seconds="1413833291" # junos:commit-localtime="2014-10-20 19:28:11 UTC" junos:commit-user="codeout"> # <system> # <services> # <ssh> # </ssh> # <netconf> # <ssh> # </ssh> # </netconf> # <web-management> # <http> # <interface>ge-0/0/0.0</interface> # </http> # </web-management> # </services> # </system> # </configuration> # </data> puts config.xpath('//services/netconf').to_xml # xpath を用いてフィルターできる # <netconf> # <ssh> # </ssh> # </netconf> end
使うには JUNOS 設定の構造を知っていなければならない.
状態取得
require 'net/netconf' Netconf::SSH.new(target: '192.168.0.74', username: 'codeout', password: ENV['PASSWORD']) do |device| puts device.rpc.get_firmware_information # 以下の <rpc> message を送信 # <rpc> # <get-firmware-information/> # </rpc> # 以下の状態が取得できる # <firmware-information xmlns="http://xml.juniper.net/junos/12.1X46/junos-chassis"> # <chassis junos:style="firmware"> # <chassis-module> # <name>FPC</name> # <firmware> # <type>O/S</type> # <firmware-version>Version 12.1X46-D10 by builder on 2013-12-05 15:05:41 UTC</firmware-version> # </firmware> # </chassis-module> # <chassis-module> # <name>FWDD</name> # <firmware> # <type>O/S</type> # <firmware-version>Version 12.1X46-D10 by builder on 2013-12-05 15:05:41 UTC</firmware-version> # </firmware> # </chassis-module> # </chassis> # </firmware-information> puts device.rpc.get_interface_information(interface_name: 'ge-0/0/0', terse: true) # 以下の <rpc> message を送信 # <rpc> # <get-interface-information> # <interface-name>ge-0/0/0</interface-name> # <terse/> # </get-interface-information> # </rpc> # 以下の状態が取得できる # <interface-information xmlns="http://xml.juniper.net/junos/12.1X46/junos-interface" junos:style="terse"> # <physical-interface> # <name> # ge-0/0/0 # </name> # <admin-status> # up # </admin-status> # <oper-status> # up # </oper-status> # <logical-interface> # <name> # ge-0/0/0.0 # </name> # <admin-status> # up # </admin-status> # <oper-status> # up # </oper-status> # <filter-information> # </filter-information> # <address-family> # <address-family-name> # inet # </address-family-name> # <interface-address> # <ifa-local junos:emit="emit"> # 192.168.0.74/24 # </ifa-local> # </interface-address> # </address-family> # </logical-interface> # </physical-interface> # </interface-information> end
- 動く
- パラメーターは, JUNOS XML API そのまま
出力は JUNOS 仕様のXML
method-missing
のしくみを使って 動的にXML を生成- JUNOS XML API に対応するメソッドがいちいち存在するわけではない
- ヘルパー的な便利動作は少ない
2014-11-04 追記
「show
コマンドが分かっていれば | display xml rpc
でXML を調べることができる」と教えてもらいました.
例:
> show chassis firmware | display xml rpc <rpc-reply xmlns:junos="http://xml.juniper.net/junos/12.1X46/junos"> <rpc> # <get-firmware-information> # ここを使う </get-firmware-information> # </rpc> # <cli> <banner></banner> </cli> </rpc-reply>
設定変更
require 'net/netconf' Netconf::SSH.new(target: '192.168.0.74', username: 'codeout', password: ENV['PASSWORD']) do |device| puts device.rpc.lock(:candidate) # 以下の <rpc> message を送信 # <rpc> # <lock> # <target> # <candidate/> # </target> # </lock> # </rpc> # 結果 # <ok/> puts device.rpc.edit_config {|x| # 以下の <rpc> message を送信 x.configuration { x.interfaces { x.interface { x.name 'ge-0/0/0' x.description 'foo' } } } } # <rpc> # <edit-config> # <target><candidate/></target> # <config><configuration><interfaces><interface><name>ge-0/0/0</name><description>foo</description></interface></interfaces></configuration></config> # </edit-config> # </rpc> # 結果 # <ok/> puts device.rpc.validate(:candidate) # 以下の <rpc> message を送信 # <rpc> # <validate> # <source> # <candidate/> # </source> # </validate> # </rpc> # 結果 # <commit-results> # </commit-results> puts device.rpc.commit # 以下の <rpc> message を送信 # <rpc> # <commit/> # </rpc> # 結果 # <ok/> puts device.rpc.unlock :candidate # 以下の <rpc> message を送信 # <rpc> # <unlock> # <target> # <candidate/> # </target> # </unlock> # </rpc> # 結果 # <ok/> end
- 動く
- パラメーターは, JUNOS 設定の構造をXML で表現したもの
- 変更対象を指定するためのキーを知っておく必要がある
- たとえば, <interface> を特定するためのキーは<name>
想像だけでXML を作るのは難しい. 戦略としては次のような感じになると思う.
- <get-config> して設定を取得
- キーを推測
- <get-xnm-information> しておけばXSD が手に入る -> 2. は不要
- 変更したい部分をXML で記述
結局, これは何?
NETCONF のwrapper
CLI をparse するより安全そう
Netopeer
- https://code.google.com/p/netopeer/
- New BSD License
- アーキテクチャー
の3つから成る. IAサーバーなどでnetopeer-agent + netopeer-server を常駐させ, netopeer-cli / gui からのSSHを終端する. その上でnetopeer-cli / gui (NETCONF クライアント) からnetopeer-server(NETCONF サーバー) 経由でIA サーバーを管理する.
( 引用: https://code.google.com/p/netopeer/ )
管理対象として 現時点ではLinux をターゲットにしているようだけれど「NETCONF に準拠していれば ルーターでもある程度動くんでは?」という淡い期待のもと, Netopeer-GUI を試してみた.
実装されている機能
- persistent connection to multiple NETCONF servers via backend process
- presentation of state and configuration information from datastores using YIN (/Yang) models
- creation of configuration with advisor of possible information in current context
- asynchronous NETCONF notifications using HTTP websockets
- calling model defined RPC methods
( 引用: https://code.google.com/p/netopeer/ )
- “persistant connection” をどうやって実現しているかというと, mod_netconf というapache module が常駐してSSH を終端
- GUI 本体はphp + symfony + DB
これが動いたらすごいですよね!
GitHub wiki を見てもらえると, ワクワク感が伝わるかもしれません.
使用例
インストール
Debian jessie にインストールした.
をソースからビルドする.
ビルド用スクリプトにパッチしたり, Firefly 向けにNETCONF capability の名前空間を変えたりする必要があったが, 今回のポイントではないので省略.
デバイスを登録
Firefly は, SSH ポート(22/tcp) もしくはNETCONF ポート(830/tcp) で登録できる.
きちんとcapability の交換ができている.
それ以上, ぜんぜん動かない…
データモデルに差があるとはいえ「初手では動かないだろうけど, 送るXML のテンプレート + XML を読むparser に手を入れたら動くだろう」くらいに思ってた… が, かなりつらい.
なにがつらいかというと, NETCONF の3+1 レイヤーのうち
Layer Example +-------------+ +-----------------+ +----------------+ (4) | Content | | Configuration | | Notification | | | | data | | data | +-------------+ +-----------------+ +----------------+ | | | YANG ---------------------------------------------------------------------------- | | | NETCONF +-------------+ +-----------------+ | (3) | Operations | | <edit-config> | | | | | | | +-------------+ +-----------------+ | | | | +-------------+ +-----------------+ +----------------+ (2) | Messages | | <rpc>, | | <notification> | | | | <rpc-reply> | | | +-------------+ +-----------------+ +----------------+ | | | +-------------+ +-----------------------------------------+ (1) | Secure | | SSH, TLS, BEEP/TLS, SOAP/HTTP/TLS, ... | | Transport | | | +-------------+ +-----------------------------------------+
Transport + Messages レイヤーはapache module, Operations + Content レイヤーはsymfony アプリとして実装されているが,
- XML parser のふるまいを変えるのに apache module とsymfony 両方の面倒をみる必要があって, 時には情報の橋渡しをしてあげないとダメだったり…
- NETCONF に使うXML 構造が, symfony 上でテンプレート化されていない…
予想どおりだけど, できたのは<hello> の交換までだった. 「デバイス種別+メーカーに応じてXML を出し分ける」とか 現時点では厳しそう.
まとめ
Juniper Firefly をコントロールするため, いくつかの実装を試してみた.
(ちなみにNETCONF WG のサイト から 他の実装も探せる)
上位レイヤー(Operations + Content) のモデル化が進んでいない現状では, 下位レイヤー(Transport + Messages) を薄くwrap するライブラリーを使うのが良いと思う. 上位レイヤーは, 自分たちが必要な範囲内で, 自分たちで抽象化する.
「なんだNETCONF 単体じゃあできないのか」というガッカリ感が先行するけれど, CLI を相手にするのも面倒だし.
リッチなアプリケーションはレールから外れたときに痛い目を見るし, ネットワークデバイス側は融通が効かない分 レールに乗せるための制約が大きすぎるとも感じた.
一方で, Netopeer のようなアプローチも “将来的には” 成功する可能性があると思っている. Netopeer 本来の使いかたであれば, netopeer-server が管理サーバー内で上位レイヤーの抽象化を担ってくれる. たとえば クライアント - サーバー間のAPI を厳密に規定する代わりに, サーバー内でnetopeer-server API を作ったほうがうまくいく場合もあると思う.
MBaaS のようにSDK を配ってAPI を呼んでもらうやりかたも一般的になってきたし, PyEZ みたいなやつで抽象化レイヤーを作れるかもしれない.
少しずつではあるが, NETCONF データのモデル化が進んできている ことにも注目したい. モデルが標準化されれば, 状況は変わってくると期待している.