vMX(VCP/VFP)をPyEZで制御してみるだけの優しい世界
Posted on 2016/09/22(Thu) 05:40 in technical
イマココ
個人アカウントでvMX Trialのダウンロードができるようになった ので、
ついでに vMX(VCP/VFP)でVPLSを動かしてみる ところまで実施しました。
今度はそれを PyEZ(py-junos-eznc) で制御してみようと思います。
PyEZ
Juniperが提供している、Junos制御用の自動化ツールキットです。
NETCONFで接続して、JunosのRPC APIを叩いたりとか、Ansibleよろしくfactの収集がサポートされていたりとか、Junosに限って言えば色々できます。
1.x系は動作環境がPython 2.6 or 2.7でしたが、先日出た2.x系はpython 3.4以上をサポートするようになりました。
リリースノートにそう書いてあったもん!: https://github.com/Juniper/py-junos-eznc/releases/tag/2.0.0
まぁそういうやつです。
依存関係も比較的少なくて、コードも簡単とくれば、Teratermマクロの次の1歩としては丁度良いんじゃないですかね。Juniperなら。
Juniperに思うところがあるんじゃなくて、僕が装置を触ったりコードを書く機会から遠ざけられているからいじけているだけです。
あと今回のやつの参考とか。
- http://www.juniper.net/techpubs/en_US/junos-pyez2.0/topics/reference/general/junos-pyez-configuration-process-and-data-formats.html
- http://www.juniper.net/techpubs/en_US/junos-pyez2.0/topics/task/program/junos-pyez-program-configuration-comparing.html
- http://junos-pyez.readthedocs.io/en/2.0.1/jnpr.junos.utils.html
- http://qiita.com/hiromiarts/items/aade90a161acd63bc8ed
- http://qiita.com/hiromiarts/items/f44ec5eab6a7508f050a
- http://qiita.com/hiromiarts/items/ccaaeed4709d9961cdcc
環境
環境は、前回の vMX(VCP/VFP)でVPLSを動かしてみる 時のやつと大体一緒です。
左上に変なの(PyEZ動かすやつ)が増えてるくらいですね。
PyEZって書いてあるけど、ただのubuntu 16.04の仮想マシンです。
PyEZのインストール
インストール:
sudo apt update sudo apt install -y python3-pip sudo apt install -y libssl-dev pip3 install -U pip sudo pip3 install junos-eznc
おしまい。
- aptからpipを入れて、pip installすれば良いです。
- libssl-devはparamiko(pythonのsshライブラリ)が使うpycryptoのコンパイル用。
- pip3 install -U pip は、aptから入るpip3だと、依存関係の解消中に、requirements.txtの解釈に失敗する場合があるので、アップデートしてます。
Junosの設定
configure set system services netconf ssh port 830 commit
こんな感じです。
接続テスト
警告
例外キャッチしてない完全正常系のコードしか使わないのでご容赦ください。
PyEZノード:
sudo ip link set up ens4 sudo ip addr add 172.16.0.21/24 dev ens4
アドレスは適当に設定。制御対象は 172.16.0.1/24 が設定されてるものとします。
PyEZノードからpython3の対話シェルを起動して、Junosに接続します。:
$ python3 Python 3.5.2 (default, Jul 5 2016, 12:43:10) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. >>> >>> from jnpr.junos import Device >>> dev=Device(host='172.16.0.1', user="root", password="vpls2016", port='830') >>> dev.open() Device(172.16.0.1) >>> print(dev.facts) {'switch_style': 'BRIDGE_DOMAIN', 'vc_capable': False, 'version_info': junos.version_info(major=(16, 1), type=R, minor=1, build=7), 'fqdn': 'router', 'version': '16.1R1.7', 'personality': 'MX', 'model': 'VMX', '2RE': False, 'ifd_style': 'CLASSIC', 'serialnumber': 'VM57DEF69785', 'master': 'RE0', 'hostname': 'router', 'version_RE0': '16.1R1.7', 'domain': None, 'RE0': {'status': 'OK', 'mastership_state': 'master', 'last_reboot_reason': 'Router rebooted after a normal shutdown.', 'up_time': '2 days, 23 hours, 31 minutes, 1 second', 'model': 'RE-VMX'}, 'HOME': '/root', 'virtual': True} >>>
まぁこんな感じ。
情報収集
インデントが無くて少々汚いけど、Junosにログインして show vpls connections logical-system VPLS と同じ内容を取得します。:
$ python3 Python 3.5.2 (default, Jul 5 2016, 12:43:10) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from lxml import etree >>> from jnpr.junos import Device >>> dev=Device(host='172.16.0.1', user="root", password="vpls2016", port='830', gather_facts=False) >>> dev.open() Device(172.16.0.1) >>> print(dev.display_xml_rpc('show vpls connections logical-system VPLS', format='text')) <get-vpls-connection-information> <logical-system>VPLS</logical-system> </get-vpls-connection-information> >>> tree=dev.rpc.get_vpls_connection_information(logical_system="VPLS") >>> print(etree.tostring(tree).decode('utf-8')) <vpls-connection-information> <instance style="normal"> <instance-name>vpls2001</instance-name> <edge-protection>Not-Primary</edge-protection> <reference-site> <local-site-id>vpls-site (1)</local-site-id> <connection heading="connection-site Type St Time last up # Up trans"> <connection-id>2</connection-id> <connection-type>rmt</connection-type> <connection-status>Up</connection-status> <last-change>Sep 18 22:25:43 2016 </last-change> <up-transitions>1</up-transitions> <remote-pe>2.2.2.2</remote-pe> <control-word>No</control-word> <inbound-label>262146</inbound-label> <outbound-label>262145</outbound-label> <local-interface> <interface-name>lsi.17826048</interface-name> <interface-status>Up</interface-status> <interface-encapsulation>VPLS</interface-encapsulation> <profile-name/> <profile-varset-name/> <interface-description>Intf - vpls vpls2001 local site 1 remote site 2</interface-description> </local-interface> <vc-flow-label-transmit>No</vc-flow-label-transmit> <vc-flow-label-receive>No</vc-flow-label-receive> </connection> </reference-site> </instance> </vpls-connection-information> >>>
dev.display_xml_rpc は、ルータで <command> | display xml rpc と打った時に返ってくる情報と同じです。
こんなの。:
root@router> show interfaces | display xml rpc <rpc-reply xmlns:junos="http://xml.juniper.net/junos/16.1R1/junos"> <rpc> <get-interface-information> </get-interface-information> </rpc> <cli> <banner></banner> </cli> </rpc-reply>
で、 '-' を '_' に変えるとメソッド名になるので、それを叩くと、プロンプトでshowコマンドを叩いているときと同じ情報が手に入ります。
上記の場合なら、 <get-interface-information> なので、 get_interface_information() になります。
pythonならelement名を抜き出して、dev.rpcからgetattrすればメソッド抜けるんじゃないですかね。確認してないですけど。
設定変更
設定も変更してみよう。
今こんな感じになっているとしましょう。:
root@router> show configuration interfaces ge-0/0/0 { vlan-tagging; } ge-0/0/1 { vlan-tagging; } ge-0/0/4 { flexible-vlan-tagging; encapsulation vlan-vpls; } fxp0 { unit 0 { family inet { address 172.16.0.1/24; } } }
そこで、 ge-0/0/5 にも同じような設定を追加してみます。
こんなやつ。:
ge-0/0/5 { flexible-vlan-tagging; encapsulation vlan-vpls; }
ではさっきと同様に。:
$ python3 Python 3.5.2 (default, Jul 5 2016, 12:43:10) [GCC 5.4.0 20160609] on linux Type "help", "copyright", "credits" or "license" for more information. >>> from jnpr.junos import Device >>> from jnpr.junos.utils.config import Config >>> conf = ''' ... interfaces { ... replace: ge-0/0/5 { ... flexible-vlan-tagging; ... encapsulation vlan-vpls; ... } ... } ... ''' >>> dev=Device(host='172.16.0.1', user="root", password="vpls2016", port='830', gather_facts=False) >>> dev.open() Device(172.16.0.1) >>> dev.bind(cu=Config) >>> >>> dev.cu.lock() True >>> dev.cu.load(conf, format='text') <Element load-configuration-results at 0x7f87c2440448> >>> print(dev.cu.diff()) [edit interfaces] + ge-0/0/5 { + flexible-vlan-tagging; + encapsulation vlan-vpls; + } >>> dev.cu.commit(comment='[PyEZ] commit') True >>> dev.cu.unlock() True >>> dev.close()
そして、ルータでコンフィグとコミット情報を見てみます。:
root@router> show configuration interfaces ge-0/0/0 { vlan-tagging; } ge-0/0/1 { vlan-tagging; } ge-0/0/4 { flexible-vlan-tagging; encapsulation vlan-vpls; } ge-0/0/5 { flexible-vlan-tagging; encapsulation vlan-vpls; } fxp0 { unit 0 { family inet { address 172.16.0.1/24; } } } root@router> show system commit revision detail Revision: re0-1474487932-20 User : root Client : netconf Time : 2016-09-21 19:58:55 UTC Log : [PyEZ] commit
とまぁこんな感じ。
コミットメッセージにトランザクションIDなんか振ったりして、お洒落な感じにしたい気持ちになるね。
dev.cu.lock() をしている最中は、 configure exclusive と同等の状態になるので、他のユーザーとのcommit衝突は起こらないようにできます。
既に他のユーザーが設定変更途中である場合は、 dev.cu.lock() の時にこんな例外を受け取れるはずです。:
>>> dev.cu.lock() Traceback (most recent call last): File "/usr/local/lib/python3.5/dist-packages/jnpr/junos/device.py", line 516, in execute rpc_rsp_e = self._rpc_reply(rpc_cmd_e) File "/usr/local/lib/python3.5/dist-packages/jnpr/junos/device.py", line 943, in _rpc_reply return self._conn.rpc(rpc_cmd_e)._NCElement__doc File "/usr/local/lib/python3.5/dist-packages/ncclient/manager.py", line 171, in wrapper return self.execute(op_cls, *args, **kwds) File "/usr/local/lib/python3.5/dist-packages/ncclient/manager.py", line 231, in execute raise_mode=self._raise_mode).request(*args, **kwds) File "/usr/local/lib/python3.5/dist-packages/ncclient/operations/third_party/juniper/rpc.py", line 42, in request return self._request(rpc) File "/usr/local/lib/python3.5/dist-packages/ncclient/operations/rpc.py", line 337, in _request raise self._reply.error ncclient.operations.rpc.RPCError: configuration database modified During handling of the above exception, another exception occurred: Traceback (most recent call last): File "/usr/local/lib/python3.5/dist-packages/jnpr/junos/utils/config.py", line 461, in lock self.rpc.lock_configuration() File "/usr/local/lib/python3.5/dist-packages/jnpr/junos/rpcmeta.py", line 156, in _exec_rpc return self._junos.execute(rpc, **dec_args) File "/usr/local/lib/python3.5/dist-packages/jnpr/junos/decorators.py", line 71, in wrapper return function(*args, **kwargs) File "/usr/local/lib/python3.5/dist-packages/jnpr/junos/decorators.py", line 26, in wrapper return function(*args, **kwargs) File "/usr/local/lib/python3.5/dist-packages/jnpr/junos/device.py", line 529, in execute raise e(cmd=rpc_cmd_e, rsp=rsp, errs=err) jnpr.junos.exception.RpcError: RpcError(severity: error, bad_element: None, message: configuration database modified) During handling of the above exception, another exception occurred: Traceback (most recent call last): File "<stdin>", line 1, in <module> File "/usr/local/lib/python3.5/dist-packages/jnpr/junos/utils/config.py", line 464, in lock raise LockError(rsp=err.rsp) jnpr.junos.exception.LockError: LockError(severity: error, bad_element: None, message: configuration database modified)
まぁ、この辺りの状態遷移については他にも色々考えなきゃいけないけども。
VPNのように複数ルータにコンフィグを入れるアトミック操作の場合は、複数ルータのロックを取得してからコミット、全台のコミットが確認できたらロックを解放する、という流れが作れそうですね。
一部のマシンでコミットに失敗した場合は、残りのマシンはロールバックするとか。
そうそう、ロールバックしたいときは:
dev.cu.rollback()
他にも、コンフィグをset文で書くのであれば、:
conf = ''' set system host-name hostname-pyez set interfaces ge-0/0/5 flexible-vlan-tagging ''' dev.cu.load(conf, format='set')
のように書いてもいい。これならルータは触れるけどプログラムはちょっと、って人にも良い感じじゃないかな。
jinja2を使ったテンプレートの方は、ファイルに吐き出さないといけないっぽいので、ここでは割愛。
この辺を見てくると良いんじゃないかと思う。
おしまい
PyEZはなかなかシンプルに作られてるし、Junosが多い環境ならきっとかなり楽しいんだろうなぁ。
適当に触れてみた程度でも、案外動くものだという感触を得たので、Juniperがんばえー。