Python / JUNOS環境で、NETCONF動作を試してみる
SDN時代のインフラ構築には、APIベースの設定作業が標準になりつつあります。
通常、仮想VMであれば、OpenStackなどが活用されるところでしょう。
SDNインフラ環境でも既存ネットワークとの相互運用は必須になりますが、NW機器での諸設定が従来通りの手作業によるコンフィグ設定が行なわれている事例がまだ多いと思います。
昨今のSDN技術の台頭により、作業効率の向上、および、オペミス防止の観点から、従来のNW機器もSDNオーケストレーション的なアプローチでAPIベースで制御してしまおうという風潮が高まりつつあります。
そこで、 NETCONFというプロトコルが注目されるわけですね。
◼️ NETCONF確認用のSRXトポロジ環境
今回は、NETCONFを活用したSRX実機でのBGP設定にチャレンジしたいと思います。
なお、BGP構成は、以前の記事と全く同じ構成にします。
なお、NETCONFサーバ動作環境の構築方法は、以前のブログ記事「vSRXを活用した、JUNOSのお勉強まとめ【JUNOS初期設定 + BGP基本編】」と同じです。
SRX実機も、vSRXも同じJUNOSなので、環境構築上の違いはないです。詳しくは、こちらの参照ください。
ttsubo.hatenablog.com
今回は、さらに、NETCONF動作環境を追加しているイメージです。
+----------+ | -MacOS X-| | NETCONF | | (Client) | +----------+ + | | 192.168.100.0/24 | + .101 +----------+ +----------+ .1 | -SRX100- | .1 i-BGP .2 | -BHR4GRV-| .1 ... -------------+ | NETCONF | +----------------------+ | OpenWRT | +------------- ... 172.16.0.0/24 | (Server) | 192.168.0.0/30 | (Quagga) | 172.16.1.0/24 +----------+ +----------+ < AS65000 > < AS65000 >
SRXトポロジj構成として、SRX実機に加えて、対向BGPルータを用意する必要があります。
ただ、環境構築になるべくお金を掛けたくなかったので、対向BGPには、Buffalo"BHR-4GRV"に、OpenWRT環境をセットアップした上で、Quaggaをインストールして代用しました。
ttsubo.hatenablog.com
◼️ NETCONF環境を設定する
1. NETCONFクライアント側の環境設定
SDNオーケストレーションなどの自動制御を担う動作環境で、NETCONFを動作させるには、
オープンソースの"ncclient: Python library for NETCONF clients"が活用されることが多いようです。
github.com
ncclientは、次のような多種多様なNETCONF装置に対応しているようです。
Supported device handlers
ncclient/README.rst at master · ncclient/ncclient · GitHub
(1) 事前準備
まず、ncclientをインストールします。
$ pip install ncclient
(2) BGP設定サンプルアプリ[create-bgp.py]の配置
NETCONFを活用して、BGPコンフィグ設定を行うサンプルアプリを配置します。
from ncclient import manager from ncclient.xml_ import * import time def connect(host, port, user, password, config): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=30, device_params = {'name':'junos'}, hostkey_verify=False) print "------------" print "1. conn.lock" print "------------" try: lock = conn.lock('candidate') print lock.tostring except Exception as e: print ("Error in 'conn.lock': type=[%s], message=[%s]"%(type(e), e.message)) raise print "--------------------------" print "2. conn.load_configuration" print "--------------------------" try: load_conf = conn.load_configuration(target="candidate", config=config["routing_options"], format='xml') print load_conf.tostring load_conf = conn.load_configuration(target="candidate", config=config["protocols"], format='xml') print load_conf.tostring except Exception as e: print ("Error in 'conn.load_configuration': type=[%s], message=[%s]"%(type(e), e.message)) raise print "----------------" print "3. conn.validate" print "----------------" try: validate = conn.validate() print validate.tostring except Exception as e: print ("Error in 'conn.validate': type=[%s], message=[%s]"%(type(e), e.message)) raise print "-----------------------------" print "4. conn.compare_configuration" print "-----------------------------" try: compare = conn.compare_configuration() print compare.tostring except Exception as e: print ("Error in 'conn.compare_configuration': type=[%s], message=[%s]"%(type(e), e.message)) raise print "---------------" print "5. conn.commit" print "---------------" try: commit = conn.commit() print commit.tostring except Exception as e: print ("Error in 'conn.commit': type=[%s], message=[%s]"%(type(e), e.message)) raise print "---------------" print "6. conn.unlock" print "---------------" try: unlock = conn.unlock() print unlock.tostring except Exception as e: print ("Error in 'conn.unlock': type=[%s], message=[%s]"%(type(e), e.message)) raise if __name__ == '__main__': protocols = new_ele('protocols') bgp = sub_ele(protocols, 'bgp') group = sub_ele(bgp, 'group') group_name = sub_ele(group, 'name').text = 'INTERNAL' sub_ele(group, 'type').text = 'internal' sub_ele(group, 'export').text = 'export-bgp' sub_ele(group, 'neighbor').text = '192.168.0.2' routing_options = new_ele('routing-options') sub_ele(routing_options, 'router-id').text = '10.0.0.1' sub_ele(routing_options, 'autonomous-system').text = '65000' config = {} config["routing_options"] = routing_options config["protocols"] = protocols connect('192.168.100.101', 830, 'tsubo', 'xxxxxxx', config)
(3) BGP確認サンプルアプリ[show-bgp.py]の配置
NETCONFを活用して、BGP動作確認を行うサンプルアプリを配置します。
from ncclient import manager from ncclient.xml_ import * import time def connect(host, port, user, password): conn = manager.connect(host=host, port=port, username=user, password=password, timeout=30, device_params = {'name':'junos'}, hostkey_verify=False) print "-------------------------------------" print "1. conn.get_configuration 'protocols'" print "-------------------------------------" config_filter = new_ele('configuration') sub_ele(config_filter, 'routing-options') sub_ele(config_filter, 'protocols') get_conf = conn.get_configuration(format='text', filter=config_filter) print get_conf.tostring print "----------------------------------" print "2. conn.command 'show bgp summary'" print "----------------------------------" time.sleep(10) print "root@SRX> show bgp summary" result = conn.command(command='show bgp summary', format='text') print result.xpath('output')[0].text print "-------------------------------------------------------------" print "3. conn.command 'show route receive-protocol bgp 192.168.0.2'" print "-------------------------------------------------------------" print "root@SRX> show route receive-protocol bgp 192.168.0.2" result = conn.command(command='show route receive-protocol bgp 192.168.0.2', format='text') print result.xpath('output')[0].text if __name__ == '__main__': connect('192.168.100.101', 830, 'tsubo', 'xxxxxxx')
2. NETCONFサーバ側の環境設定
NETCONFサーバ環境として、JUNOS搭載のNW機器が活用されることが多いと思います、
そこで、今回は、JUNOS搭載のNETCONFサーバ環境として、SRX実機を採用しました。
(1) 事前準備
NETCONFクライアント環境からの制御を受信できるように準備しておきます。
具体的には、NETCONFプロトコル(830ポート)を有効にしておきます。
root@SRX> configure Entering configuration mode [edit] root@SRX# set system services netconf ssh port 830
(2) その他、事前に行っておくコンフィグ設定
今回のNETCONF動作として、"protocols"と"routing-options "を設定することにします。
従って、それ以外の各種コンフィグ設定は、事前に設定しておきます。
root@SRX> show configuration |display set set version 12.1X44-D35.5 set system host-name SRX set system time-zone Asia/Tokyo set system root-authentication encrypted-password "$1$kAd32lTR$F7o4dwAbW9vY1CIV.a9kt." set system login user tsubo uid 2000 set system login user tsubo class super-user set system login user tsubo authentication encrypted-password "$1$eT7nqVkt$uCwT9W8WuBhrEhBcyKoQ5/" set system services ssh root-login allow set system services netconf ssh port 830 set system syslog archive size 100k set system syslog archive files 3 set system syslog user * any emergency set system syslog file messages any any set system syslog file messages authorization info set system syslog file interactive-commands interactive-commands any set system max-configurations-on-flash 5 set system max-configuration-rollbacks 5 set system license autoupdate url https://ae1.juniper.net/junos/key_retrieval set system ntp set interfaces fe-0/0/0 unit 0 family inet address 192.168.100.101/24 set interfaces fe-0/0/1 unit 0 family inet address 192.168.0.1/24 set interfaces fe-0/0/2 unit 0 family inet address 172.16.0.1/24 set interfaces lo0 unit 0 family inet address 10.0.0.1/32 set policy-options policy-statement export-bgp term 1 from route-filter 172.16.0.0/24 exact set policy-options policy-statement export-bgp term 1 then accept set security forwarding-options family inet6 mode packet-based set security forwarding-options family mpls mode packet-based
◼️ いよいよ、NETCONF動作を試してみる
1. BGP設定を行う
まずは、NETCONFを活用して、"routing-options"と"protocols"のコンフィグ設定を行います。
$ python create-bgp.py ------------ 1. conn.lock ------------ <rpc-reply message-id="urn:uuid:06ced48c-03d4-11e6-81aa-a45e60ba8c55"> <ok/> </rpc-reply> -------------------------- 2. conn.load_configuration -------------------------- <rpc-reply message-id="urn:uuid:06e4c133-03d4-11e6-ab26-a45e60ba8c55"> <load-configuration-results> <ok/> </load-configuration-results> </rpc-reply> <rpc-reply message-id="urn:uuid:06f798fd-03d4-11e6-afa3-a45e60ba8c55"> <load-configuration-results> <ok/> </load-configuration-results> </rpc-reply> ---------------- 3. conn.validate ---------------- <rpc-reply message-id="urn:uuid:070b022b-03d4-11e6-bca1-a45e60ba8c55"> <commit-results> </commit-results> <ok/> </rpc-reply> ----------------------------- 4. conn.compare_configuration ----------------------------- <rpc-reply message-id="urn:uuid:0a753c47-03d4-11e6-8b4f-a45e60ba8c55"> <configuration-information> <configuration-output> [edit] + routing-options { + router-id 10.0.0.1; + autonomous-system 65000; + } + protocols { + bgp { + group INTERNAL { + type internal; + export export-bgp; + neighbor 192.168.0.2; + } + } + } </configuration-output> </configuration-information> </rpc-reply> --------------- 5. conn.commit --------------- <rpc-reply message-id="urn:uuid:0b0273cc-03d4-11e6-a844-a45e60ba8c55"> <ok/> </rpc-reply> --------------- 6. conn.unlock --------------- <rpc-reply message-id="urn:uuid:1663174f-03d4-11e6-bf75-a45e60ba8c55"> <ok/> </rpc-reply>
特に、エラーも発生せずに、BGP設定を行うことができたようです。
2. BGP動作を確認してみる
それでは、NETCONFを活用して、"routing-options"と"protocols"のコンフィグ設定が正しく行われたことを確認してみます。
$ python show-bgp.py ------------------------------------- 1. conn.get_configuration 'protocols' ------------------------------------- <rpc-reply message-id="urn:uuid:2119510a-03d4-11e6-9a47-a45e60ba8c55"> <configuration-text> ## Last changed: 2016-04-16 22:06:42 JST routing-options { router-id 10.0.0.1; autonomous-system 65000; } ## Last changed: 2016-04-16 22:06:42 JST protocols { bgp { group INTERNAL { type internal; export export-bgp; neighbor 192.168.0.2; } } } </configuration-text> </rpc-reply> ---------------------------------- 2. conn.command 'show bgp summary' ---------------------------------- root@SRX> show bgp summary Groups: 1 Peers: 1 Down peers: 0 Table Tot Paths Act Paths Suppressed History Damp State Pending inet.0 1 1 0 0 0 0 Peer AS InPkt OutPkt OutQ Flaps Last Up/Dwn State|#Active/Received/Accepted/Damped... 192.168.0.2 65000 3 4 0 0 18 1/1/1/0 0/0/0/0 ------------------------------------------------------------- 3. conn.command 'show route receive-protocol bgp 192.168.0.2' ------------------------------------------------------------- root@SRX> show route receive-protocol bgp 192.168.0.2 inet.0: 8 destinations, 8 routes (8 active, 0 holddown, 0 hidden) Prefix Nexthop MED Lclpref AS path * 172.16.1.0/24 192.168.0.2 0 100 I
さらに、対向BGPルータにて、SRXから配布されたBGP経路が受信できたことを確認してみます。
Quagga-3# show ip bgp BGP table version is 0, local router ID is 10.0.1.3 Status codes: s suppressed, d damped, h history, * valid, > best, i - internal, r RIB-failure, S Stale, R Removed Origin codes: i - IGP, e - EGP, ? - incomplete Network Next Hop Metric LocPrf Weight Path *>i172.16.0.0/24 192.168.0.1 100 0 i *> 172.16.1.0/24 0.0.0.0 0 32768 i Total number of prefixes 2
以上より、NETCONFを活用することにより、NW機器への各種コンフィグ設定/確認が自動的に実施できるよう、Pythonスクリプト化することができました。
◼️ NETCONFエラー判定動作を確認する
ncclient側でのAPI制御結果として、エラー判定された時のNETCONF動作を確認しておきます。
事例1「他ユーザが、コンフィグ処理中のとき」
例えば、lo0インタフェースのDescriptionの設定に関わるコンフィグ設定が完了しないときに、NETCONFを活用して、BGP設定を行ってみます。
(1) 他ユーザによるDescriptionの設定
Description設定後、commitを行わず、放置しておきます。
root@SRX# set interfaces lo0 description lo0_description [edit] root@SRX# show |compare [edit interfaces lo0] + description lo0_description; [edit]
(2) NETCONFによるBGP設定
このとき、NETCONFのlock処理がエラー判定となるので、BGP設定サンプルアプリ[create-bgp.py]が異常終了することを確認します。
$ python create-bgp.py ------------ 1. conn.lock ------------ Error in 'conn.lock': type=[<class 'ncclient.operations.rpc.RPCError'>], message=[ configuration database modified ] Traceback (most recent call last): File "create-bgp.py", line 99, in <module> connect('192.168.100.101', 830, 'tsubo', 'xxxxxxxx', config) File "create-bgp.py", line 18, in connect lock = conn.lock('candidate') File "/usr/local/lib/python2.7/site-packages/ncclient/manager.py", line 158, in wrapper return self.execute(op_cls, *args, **kwds) File "/usr/local/lib/python2.7/site-packages/ncclient/manager.py", line 228, in execute raise_mode=self._raise_mode).request(*args, **kwds) File "/usr/local/lib/python2.7/site-packages/ncclient/operations/lock.py", line 35, in request return self._request(node) File "/usr/local/lib/python2.7/site-packages/ncclient/operations/rpc.py", line 336, in _request raise self._reply.error ncclient.operations.rpc.RPCError: configuration database modified
期待どおりに、ncclient側でのNETCONF処理でエラー判定された場合には、Python処理がException終了してくれました。
事例2「事前設定のコンフィグ不備のとき」
BGP設定サンプルアプリ[create-bgp.py]では、事前にpolicy-statement "export-bgp"を設定しておく必要があります。
ここでは、あえて、事前に、policy-statementを削除してみます。
(1) policy-statement "export-bgp"のコンフィグ設定不備
policy-statementを削除しておきます。
root@SRX# delete policy-options [edit] root@SRX# show |compare [edit] - policy-options { - policy-statement export-bgp { - term 1 { - from { - route-filter 172.16.0.0/24 exact; - } - then accept; - } - } - } [edit] root@SRX# commit commit complete [edit]
(2) NETCONFによるBGP設定
このとき、NETCONFのvalidate処理がエラー判定となるので、BGP設定サンプルアプリ[create-bgp.py]が異常終了することを確認します。
$ python create-bgp.py ------------ 1. conn.lock ------------ <rpc-reply message-id="urn:uuid:8853948f-0454-11e6-975b-a45e60ba8c55"> <ok/> </rpc-reply> -------------------------- 2. conn.load_configuration -------------------------- <rpc-reply message-id="urn:uuid:88685466-0454-11e6-a6e1-a45e60ba8c55"> <load-configuration-results> <ok/> </load-configuration-results> </rpc-reply> <rpc-reply message-id="urn:uuid:887ab21c-0454-11e6-bdd1-a45e60ba8c55"> <load-configuration-results> <ok/> </load-configuration-results> </rpc-reply> ---------------- 3. conn.validate ---------------- Error in 'conn.validate': type=[<class 'ncclient.operations.rpc.RPCError'>], message=[error: Policy error: Policy export-bgp referenced but not defined error: BGP: export list not applied error: configuration check-out failed] Traceback (most recent call last): File "create-bgp.py", line 99, in <module> connect('192.168.100.101', 830, 'tsubo', 'xxxxxxxx', config) File "create-bgp.py", line 44, in connect validate = conn.validate() File "/usr/local/lib/python2.7/site-packages/ncclient/manager.py", line 158, in wrapper return self.execute(op_cls, *args, **kwds) File "/usr/local/lib/python2.7/site-packages/ncclient/manager.py", line 228, in execute raise_mode=self._raise_mode).request(*args, **kwds) File "/usr/local/lib/python2.7/site-packages/ncclient/operations/edit.py", line 123, in request return self._request(node) File "/usr/local/lib/python2.7/site-packages/ncclient/operations/rpc.py", line 334, in _request raise RPCError(to_ele(self._reply._raw), errs=errors) ncclient.operations.rpc.RPCError: error: Policy error: Policy export-bgp referenced but not defined error: BGP: export list not applied error: configuration check-out failed
期待どおりに、ncclient側でのNETCONF処理でエラー判定された場合には、Python処理がException終了してくれました。
◼️ 最後に、
SDNインフラと相互接続しているNW機器を、SDNオーケストレーション層から順序制御できる仕組みとして、NETCONFの活用は、とても有効であると思います。
今後も、クラウド基盤として、OpenStackが活用される事例は、ますます増えていくと思います。OpenStackとNETCONFの動作連携を担う仕組みとして、heat プロジェクトの役割りの重要性が、どんどん増していくのでしょう。