Juniper JUNOS PyEz (python library)を試すメモ ３ ~PyEzによる設定変更~

時間が空いてしまいましたが、[前回](http://qiita.com/hiromiarts/items/f44ec5eab6a7508f050a)までに環境構築とJUNOSの情報取得をしたため、今回は、JUNOSの設定変更を試してみる。

#1.概要
PyEzのjnpr.junos.utils.config.Configモジュールを使用することで、ASCII text, Junos XML, or Junos OS setコマンドのファイルを読み込み、設定変更をしたり、Jinja2 Template objectsを使ったテンプレートベースの設定変更が可能。

設定変更を行う場合は、JUNOSのCLIと同じように設定読込とCommitによる反映という、以下のようなプロセスで行われる。
 
 Configuration Process 

 + lock() - コンフィグレーションのロック
 + load() - 設定の読込 (rollback()やrescue()による設定戻しも可)
 + commit() - 設定の反映
 + unlock() - コンフィグレーションのアンロック


commit時の[オプション](https://www.juniper.net/techpubs/en_US/junos-pyez1.2/topics/task/program/junos-pyez-program-configuration-committing.html)を指定して、コメントを残したり、RE冗長時にsynchさせたりすることもできる。

尚、設定の読込は、 こちらもJUNOSのCLIの load replace, load merge, or load overwriteと同じく、上書きや追加の指定して設定変更する。
これは、loadオペレーションのときに、[mergeおよびoverwriteパラメータを指定](https://www.juniper.net/techpubs/en_US/junos-pyez1.2/topics/reference/general/junos-pyez-configuration-process-and-data-formats.html)することで、選択可能。

今回はASCII textのファイルを読み込んで設定追加をする方法と、Jinja2を使ったテンプレートベースの設定追加を試してみる。

#2.ファイルからのコンフィグレーション読み込み
[ファイルの読み込み](https://www.juniper.net/techpubs/en_US/junos-pyez1.2/topics/task/program/junos-pyez-program-configuration-data-loading.html)の場合、拡張子でASCII text(.conf,.text,.txt), Junosのsetコマンド(.set), XML(.xml)を指定する。

読み読み時に、pathパラメータでファイルのパスを指定することで、そのファイルを読み込む。
例えば、

``` 
conf_file = "configs/junos-config-add-syslog-ntp.conf"
cu.load(path=conf_file, format="text", merge=True)
``` 

サンプルとして、syslogのntp notice設定を追加するのをPyEzで行う場合を示す。
pythonファイルの他に読込用のコンフィグレーションファイルを用意。

``` conf:junos-config-add-syslog-ntp.conf
system {
    syslog {
        file messages {
            ntp notice;
        }
    }
}
``` 

こちらがPythonのサンプル。

``` python:ch-config-ntp.py
from jnpr.junos import Device
from jnpr.junos.utils.config import Config

conf_file = 'configs/junos-config-add-syslog-ntp.conf'

# open a connection with the device and start a NETCONF session
dev = Device(host='10.0.0.243', user='xxx', password='xxx', port='22', gather_facts=False)
dev.open()
dev.bind(cu=Config)

# Lock the configuration, load configuration changes, and commit as load merge
print "Locking the configuration"
dev.cu.lock()

print "Loading configuration changes"
dev.cu.load(path=conf_file, merge=True)

print "Committing the configuration"
dev.cu.commit(comment='Loaded by example.')

print "Unlocking the configuration"
dev.cu.unlock()

# End the NETCONF session and close the connection
dev.close()
``` 

尚、 [jnpr.junos.exception](http://junos-pyez.readthedocs.io/en/2.0.1/jnpr.junos.html#module-jnpr.junos.exception)モジュールでexceptionも用意されているため、実行できなかったときのエラー確認のために、それぞれのプロセスでexceptしておくと以下のようになる。

``` python:ch-config-ntp-exception.py
from jnpr.junos import Device
from jnpr.junos.utils.config import Config
from jnpr.junos.exception import ConnectError
from jnpr.junos.exception import LockError
from jnpr.junos.exception import UnlockError
from jnpr.junos.exception import ConfigLoadError
from jnpr.junos.exception import CommitError

conf_file = 'configs/junos-config-add-syslog-ntp.conf'


def main():
    # open a connection with the device and start a NETCONF session
    try:
        dev = Device(host='10.0.0.243', user='xxxx', password='xxxx', port='22
', gather_facts=False)
        dev.open()
    except ConnectError as err:
        print "Cannot connect to device: {0}".format(err)
        return

    dev.bind(cu=Config)

    # Lock the configuration, load configuration changes, and commit
    print "Locking the configuration"
    try:
        dev.cu.lock()
    except LockError as err:
        print "Unable to lock configuration: {0}".format(err)
        dev.close()
        return

    print "Loading configuration changes"
    try:
        dev.cu.load(path=conf_file, merge=True)
    except (ConfigLoadError, Exception) as err:
        print "Unable to load configuration changes: {0}".format(err)
        print "Unlocking the configuration"
        try:
                dev.cu.unlock()
        except UnlockError:
            print "Unable to unlock configuration: {0}".format(err)
        dev.close()
        return

    print "Committing the configuration"
    try:
        dev.cu.commit(comment='Loaded by example.')
    except CommitError as err:
        print "Unable to commit configuration: {0}".format(err)
        print "Unlocking the configuration"
        try:
            dev.cu.unlock()
        except UnlockError as err:
            print "Unable to unlock configuration: {0}".format(err)
        dev.close()
        return

    print "Unlocking the configuration"
    try:
        dev.cu.unlock()
    except UnlockError as err:
        print "Unable to unlock configuration: {0}".format(err)

    # End the NETCONF session and close the connection
    dev.close()

if __name__ == "__main__":
        main()
```


#3.Jinja2テンプレートの活用
Jinja2を使うことで、テンプレートを利用して、類似コンフィグが容易に作成することもできる。
Jinja2とは、Pythonのテンプレートエンジンで、[名前の由来](http://qiita.com/yasumodev/items/ae11047e2c8694867892)は、テンプレート ⇒ テンプル ⇒ 神社、のようだ。
Jinja2のテンプレートは、ファイル読込と同じく、サポートされたフォーマットのASCII text, Junos XML elements, Junos OS setコマンドを使用する。
Jinjaのテンプレートの[フォーマット](https://showa-yojyo.github.io/notebook/python-jinja2.html#id3)は、{% ... %} や {{ ... }} が可変部分で、それ以外の部分はすべて定型文となる。
　{% ... %} は変数宣言・代入やループ処理などを実行
　{{ ... }} は結果の表示
{%に使用するの制御構造は[こちら](http://jinja.pocoo.org/docs/dev/templates/#list-of-control-structures)を参照。


テンプレートを使用する方法は、load()メソッドの[オプション](https://www.juniper.net/techpubs/en_US/junos-pyez1.2/topics/task/program/junos-pyez-program-configuration-data-loading.html)でテンプレートを指定。
template_pathでテンプレートのファイルを指定し、template_varsでパラメータを指定。

```
cu.load(template_path=conf_file, template_vars=config, merge=True)
```


今回は、各interfaceにmpls,rsvpの設定追加をするテンプレートを使用し、パラメータであるinterface名などはyamlファイルで読込んでみる。テンプレートもASCII textにしている。

テンプレートファイルはこちら

```conf:junos-config-interfaces-mpls.conf
interfaces {
    {% for item in interfaces %}
    {{ item }} {
        description "{{ description }}";
        unit 0 {
            family {{ family }};
        }
    } {% endfor %}
}
protocols {
    mpls {
        {% for item in interfaces %}
        interface {{ item }};
        {% endfor %}
    }
    rsvp {
        {% for item in interfaces %}
        interface {{ item }};
        {% endfor %}
    }

}
```

パラメータファイルはこちら

```yaml:mpls_interface.yml
---
interfaces:
    - ge-0/0/0
    - ge-0/0/1
description: 'MPLS interface'
family: mpls
```


基本的には、ファイル読み込みの時と同じスクリプト(ch-config-ntp-exception.py)をベースにして、load()でテンプレートやパラメータを指定しているだけのため、異なる部分だけ抜粋する。


```python:add-config-mpls-template.py抜粋

import yaml

conf_file = 'configs/junos-config-interfaces-mpls.conf'
yml_config = yaml.load(open('configs/mpls_interface.yml'))

dev.cu.load(template_path=conf_file, template_vars=yml_config, merge=Tru
e)
```
これで指定したinterfaceに対するmpls, rsvpの設定が追加できる。

#4.まとめ
今回もつらつら書いたが、PyEzを使って、JUNOSの設定変更ができることが確認できた。
PyEzでは、情報取得や設定変更以外にも、configの比較、OSのアップグレード、scpによるファイル転送なども可能。

JUNOSとPyEzの両方を把握しないと触りにくいかもしれないが、Pythonを詳しく知らないエンジニアでも、簡単に必要なパラメータの確認や、設定変更ができそう。これらを使って、webで叩けるように仕組みを作っておくと、エキスパートのネットワークエンジニア以外でも確認や変更ができたり、他システムと連携させることで、ダイナミックに状況にあわせて設定変更が可能なネットワークインフラを作るなど、アイデア次第で活用方法は広がりそうだ。

今後は不定期に、ほかのAutomation Stackなども試してみる予定。