マルチベンダルータ制御APIライブラリ NAPALMを触ってみた

(追記)2016年12月24日に本記事の続編を書きました。そちらのほうがより詳しく記載してますので、ご興味あればみてください。
[続・マルチベンダルータ制御APIライブラリ NAPALMを触ってみた](http://qiita.com/taijijiji/items/9dfcaa6d868958985095)

# NAPALM概要
NAPALMというルータ制御APIライブラリが、NANOG64にて紹介されました。
https://www.nanog.org/meetings/abstract?id=2588

NAPALM (読み方は、日本人的には「ナパーム」でしょうか。NANOGでは「ネイパーム」と呼ばれていました)は、Spotify社が提供しているOSSのPythonライブラリであり、名前は「Network Automation and Programmability Abstraction Layer with Multivendor support」の略のようです。
ソースコードはgithubで公開されています。
https://github.com/spotify/napalm

NAPALMはその名の通り、下記の複数メーカの装置を統一化されたAPIにて制御することが可能です。今後さらに対応するルータOSが増えてくるかもしれません。

対応ルータOS

- Arista製スイッチOSのEOS
- Juniper製ルータOSのJUNOS
- Cisco製ルータOSのIOS-XR
- Fortinet製アプライアンスOSのFortiOS

# NAPALMでできること
Githubのcommit logを見る限り 2015年3月頃から本格的な開発が始まっており、実装されている機能はコンフィグ設定部分にフォーカスされています。NAPALM自身では、現在のソフトウェアをversion0.12と位置付けているようです。(setup.pyに記載があります)

[NANOG64発表資料](https://www.nanog.org/sites/default/files//meetings/NANOG64/1043/20150602_Jasinska_Network_Automation_And_v2.pdf)では、NAPALMが持つ機能を下記のように紹介されています。

##Supported Methods v0.1

- load_replace_config
 - 全てのコンフィグの上書き
- load_merge_config
 - 一部コンフィグのマージ
- diff_config
  - 投入コンフィグと動作しているコンフィグの差分取得
- discard
  - 投入コンフィグの廃棄
- commit
  - 投入コンフィグのコミット
- rollback 
  - 前回コミットした状態へのロールバック

##Supported Methods v0.2(beta)

- get_facts
  - 機器の基本情報の取得
- get_interfaces
  - インタフェースごとの情報の取得
- get_bgp_neighbors
  - BGPセッションの情報取得 
- get_lldp_neighbors
  - LLDPネイバーの状態取得

2015年6月7日時点では、v0.1の部分のみ実装されているようです。

これらに加えて、構成管理ツールであるAnsibleから呼び出すための拡張モジュールが提供されており、NANOG64ではAnsibleを活用したコンフィグをルータに投入/commitするデモが行われてました。

Ansible Modules

-  Module to push configurations
-  Module to get facts

当日の発表とデモの風景は[NANOG Youtubeチャンネル](https://www.youtube.com/watch?v=93q-dHC0u0I)で公開されています。


# NAPALMを使ってみた
napalmの使い方を理解するために[公式ドキュメントのチュートリアル](http://napalm.readthedocs.org/en/latest/first_steps_config.html)を眺めながら進めました。



NAPALMのインストールは簡単です。

```
pip install napalm 
```

pipを使ったことがない方はこちらの記事を参考にpipをインストールしてください。

Pythonで一番最初に入れるべきパッケージ setuptools と pip
http://www.lifewithpython.com/2012/11/Python-package-setuptools-pip.html

##IOS-XRで試してみた
まずはcisco IOS-XRで試してみます。
NAPALMで使っているpyIOSXR( https://github.com/fooelisa/pyiosxr )というモジュールでは、[CISCO XML API]( http://www.cisco.com/c/en/us/td/docs/ios_xr_sw/iosxr_r4-1/xml/programming/guide/xl41apidoc/xl41over.html)を利用してIOS-XRの設定を実現しています。
そのためIOS-XRが動くルータでXML入力を許可するために、事前に以下のコンフィグを設定しておく必要があります。タイマー値は適当ですので、必要に応じて変更してください。

```
xml agent tty
 session timeout 30
!
```

[公式ドキュメントのチュートリアル]( http://napalm.readthedocs.org/en/latest/first_steps_config.html)を参考にして、下記のようなPythonアプリケーションを作ってみました。

アプリケーションの流れとしては、ルータホスト名を'router1_changed_by_NAPALM'という名前に変更するコンフィグファイル(change_hostname_configIOSXR.txt)をルータに設定投入し、コンフィグ差分を確認して、問題がなければcommit、もしくはdiscardする流れになっています。
一つ一つ正常に動作しているか途中経過を確認したかったので、見栄えは悪いですがprint文多めにしています。

```python:run_napalm.py
#! /usr/bin/env python

from napalm import get_network_driver


driver = get_network_driver('IOSXR')
print 'Step.1 Create ios-xr instance : OK'


device = driver('192.168.0.1', 'test_user', 'test_passwd')
print 'Step.2 Set router information : OK'


device.open()
print 'Step.3 Open session : OK'


device.load_merge_candidate(filename='./change_hostname_configIOSXR.txt')
print 'Step.4 load_merge_candidate : OK'


print '### CHECK CANDIDATE CONFIG ###'
print device.compare_config()
print '### END CONFIG###'
print 'Step.5 Compare_config : OK'


print 'Do you commit? y/n'
choice = raw_input().lower()
if choice == 'y':
    device.commit_config()
    print 'Step.6 commit_config : OK'
elif choice == 'n':
    device.discard_config()
    print 'Step.6 Discard_config : OK'
else:
    print 'Please input y or n. Discard candidate config'
    device.discard_config()
    print 'Step.6 Discard_config : OK'


device.close()
print 'Step.7 Close session : OK'


print 'Successful !!'
```

```change_hostname_configIOSXR.txt
hostname router1_changed_by_NAPALM
```

まず、上記のツールを実行する前に、IOS-XRルータのコンフィグの状態をルータ上で確認します。

```
RP/0/RSP0/CPU0:router1#show running-config

Sun Jun  7 10:53:33.157 JST
Building configuration...
!! IOS XR Configuration #####
!! Last configuration change at Sun Jun  7 10:42:57 2015 by test_user
!
hostname router1
clock timezone JST 9
logging trap debugging
logging console debugging
...
...
...
```

では次に、作成したツール(run_napalm.py)を実行してみます。実行すると以下のように結果が出力されます。

```
$ python run_napalm.py

Step.1 Create ios-xr instance : OK
Step.2 Set router information : OK
Step.3 Open session : OK
Step.4 load_merge_candidate : OK
### CHECK CANDIDATE CONFIG ###
---
+++
@@ -3 +3 @@
-hostname router1
+hostname router1_changed_by_NAPALM
@@ -280,0 +281 @@
+!

### END CONFIG###
Step.5 Compare_config : OK
Do you commit? y/n
y
Step.6 commit_config : OK
Step.7 Close session : OK
Successful !!
```


ツール実行後、ルータで実際に設定が反映されているか確認してみます。

```
RP/0/RSP0/CPU0:router1_changed_by_NAPALM#show running-config
Sun Jun  7 10:59:42.313 JST
Building configuration...
!! IOS XR Configuration ######
!! Last configuration change at Sun Jun  7 10:56:07 2015 by test_user!
hostname router1_changed_by_NAPALM
clock timezone JST 9
logging trap debugging
logging console debugging
...
...
...
```

正しくIOS-XRルータのホスト名を変更できたようです。
NAPALMで用意された関数を使うことで、比較的簡単にルータ設定のアプリケーションを書くことができました。

##JUNOSで試してみた

次は、IOS-XRで使ったプログラムとほぼ同じスクリプトを使って、JUNOSルータのホスト名を変更してみます。

実施したアプリケーションのソースコードです。

```py:run_napalm.py

#! /usr/bin/env python

from napalm import get_network_driver

driver = get_network_driver('JUNOS')
print 'Step.1 Create junos instance : OK'

device = driver('192.168.0.2', 'test_user', 'test_passwd')
print 'Step.2 Set router information : OK'

device.open()
print 'Step.3 Open session : OK'

device.load_merge_candidate(filename='./change_hostname_configJUNOS.txt')
print 'Step.4 load_merge_candidate : OK'

print '### CHECK CANDIDATE CONFIG ###'
print device.compare_config()
print '### END CONFIG###'
print 'Step.5 Compare_config : OK'


print 'Do you commit? y/n'
choice = raw_input().lower()
if choice == 'y':
    device.commit_config()
    print 'Step.6 commit_config : OK'
elif choice == 'n':
    device.discard_config()
    print 'Step.6 Discard_config : OK'
else:
    print 'Please input y or n. Discard candidate config'
    device.discard_config()
    print 'Step.6 Discard_config : OK'


device.close()
print 'Step.7 Close session : OK'

print 'Successful !!'

```

実行結果はこちらです。

```
$ python run_napalm.py

Step.1 Create junos instance : OK
Step.2 Set router information : OK
No handlers could be found for logger "paramiko.hostkeys"
Traceback (most recent call last):
  File "./run_napalm.py", line 14, in <module>
    device.open()
  File "/usr/lib/python2.7/site-packages/napalm/junos.py", line 32, in open
    self.device.open()
  File "/usr/lib/python2.7/site-packages/jnpr/junos/device.py", line 433, in open
    raise EzErrors.ConnectTimeoutError(self)
jnpr.junos.exception.ConnectTimeoutError: ConnectTimeoutError(192.168.0.2)

```

あれ、失敗してしまいました。。。
SSHモジュールであるparamikoのログイン認証の部分で失敗しているようです。こちらはJUNOSルータの設定不足(netconfの部分？)か、プログラムのbugかも知れないので、引き続き調査してみます。

もしJUNOSルータに対して、うまく動作できた方がいればご教授いただきたいです。

#所感

実装が始まったばかりのNAPALMですが、機種やメーカを意識せずにルータを操作するアプリケーションが書けるので、ツール開発が非常にやりやすくなりました。まだコンフィグ設定機能しか実装されていないものの、複数のルータに対して同じ設定を入れるような場面では、現段階の実装でも十分に使えそうです。

ただし、現時点では実装途中の機能やbugも含まれそうなので、利用する際は十分な検証を行ってから実施してください。

NAPALM自身のソースコードを覗いた感じでは、プログラム的に難しい処理をしているわけではなく、基本部分(base.py)でも100行程度であり、個人レベルでも十分に開発に参加できそうな感じでした。

githubのソースコードを見ていて驚いたのが、このNAPALMに限らず、中で呼び出しているpyIOSXRやpyEOS、pyFGの大部分を、AutherであるDavid Barroso、Elisa Jasinskaの両氏がほぼ単独で実装していることでした。「メーカが用意しないなら自分たち自身でAPIを作ってしまおう！」という意気込みがこのプロジェクトから感じられました。