Hierarchical Configurationを使ってCisco機器の差分Configを自動生成してみた

# はじめに
Network to Codeの[Awesome Network Automation](https://github.com/networktocode/awesome-network-automation)で紹介されていた「Hierarchical Configuration」というPythonライブラリが気になったので、簡単な動作確認をしてみました。

# Hierarchical Configurationとは
NW機器のRunning Configと想定Configを比較し、差分Configを出力してくれるPythonライブラリです。
Cisco IOS/IOS-XR/NX-OSやArista EOSで使われてきたようですが、IOSと同様のCLIベースの機器であれば動きそうです。

参考URL:
 [GitHub netdevops/hier_config](https://github.com/netdevops/hier_config)
[Code Documentation » hier_config](https://netdevops.io/hier_config/)

個人的には、以下の用途で使えると考えています。

- ある時点のConfigに戻したい場合(作業時のRollbackを含む)
- 設定Configと想定Config両方の作成が必要な場合(変更箇所が多い時や、取り決めがある時)

# インストール
今回はWindows端末のコマンドプロンプト上で、GitHubから直接インストールしました。
※Python: 3.6.5、hier_config: 1.6.0
※Gitをインストールしていない場合は、[こちら](https://git-scm.com/download/win)から入手できます。

```
git clone https://github.com/netdevops/hier_config.git
cd .\hier_config
python setup.py install
```

# 比較するConfigの格納
インストール時に作成された「hier_config > tests > files」フォルダ配下に、ホスト名`brborder2`のRunning Configと想定Configを格納しました。

```
hier_config
└─tests
    │  tests.py
    │  test_hier_config.py
    │  test_host.py
    │  test_pep8.py
    │  test_text_match.py
    │  __init__.py
    │
    └─files
            brborder2_intended.conf   # 想定Config
            brborder2_running.conf   # Running Config
            compiled_config.conf
            running_config.conf
            test_options_ios.yml
            test_tags_ios.yml
```

2つのConfigの差分は以下の6点です。  
  
 (1) QoSの既存C-DATA1クラスにACL1行を追加
 (2) QoSに新規C-DATA2クラスを追加
 (3) GigabitEthernet0/1を新規設定し、EIGRPを有効化
 (4) Syslogの送信先を追加
 (5) ログインバナーを削除
 (6) NTPサーバのアドレスを変更

![compare.jpg](https://qiita-image-store.s3.amazonaws.com/0/310082/1623be6b-3eee-2994-347f-acf4fd77ba27.jpeg)

# Pythonスクリプト
「hier_config」フォルダ直下に、以下のファイルを格納します。
`options`で指定している`test_options_ios.yml`で、Config差分が見つかった時の処理方法をコマンド毎に細かく定義できます。今回はテスト用のものをそのまま使います。

```python:hier_config_sample.py
from hier_config import HConfig
from hier_config.host import Host
import yaml

options = yaml.load(open('./tests/files/test_options_ios.yml'))
host = Host('brborder2', 'ios', options)

# Build HConfig object for the Running Config

running_config_hier = HConfig(host=host)
running_config_hier.load_from_file('./tests/files/brborder2_running.conf')

# Build Hierarchical Configuration object for the Compiled Config

compiled_config_hier = HConfig(host=host)
compiled_config_hier.load_from_file('./tests/files/brborder2_intended.conf')

# Build Hierarchical Configuration object for the Remediation Config

remediation_config_hier = running_config_hier.config_to_get_to(compiled_config_hier)

for line in remediation_config_hier.all_children():
     print(line.cisco_style_text())
```

# 実行結果① (Running⇒想定の差分)
生成された差分Configと、確認結果は以下の通りです。
  
 (1) QoSの既存C-DATA1クラスにACL1行を追加　⇒**OK**
 (2) QoSに新規C-DATA2クラスを追加　⇒**OK**(ポリシーマップも一式書き換えられている)
 (3) GigabitEthernet0/1を新規設定し、EIGRPを有効化　⇒**OK**(明示的にno shutdownが入っている)
 (4) Syslogの送信先を追加　⇒**OK**
 (5) ログインバナーを削除　⇒**NG**(`no banner login ^C`の後に、不要な4行が含まれている。)
 (6) NTPサーバのアドレスを変更　⇒**OK**(旧アドレスが削除され、新アドレスが追加されている)


```text:
C:\Users\XXXXX\hier_config>python hier_config_sample.py
no banner login ^C
============NOTICE==============
| This is test device for demo |
================================
^C
no ntp server 10.10.1.44
class-map match-all C-DATA2
  match access-group name C-DATA2_ACL
policy-map P-CHILD
  class C-DATA1
    no priority percent 60
    priority percent 40
  class C-DATA2
    bandwidth percent 30
  class class-default
    no bandwidth percent 40
    bandwidth percent 30
interface GigabitEthernet0/1
  no shutdown
  description << To LAN >>
  ip address 10.1.1.2 255.255.255.252
router eigrp 1
  network 10.1.1.0 0.0.0.3
ip access-list extended C-DATA1_ACL
  20 permit ip any 192.168.101.0 0.0.0.255
ip access-list extended C-DATA2_ACL
  10 permit ip any 192.168.102.0 0.0.0.255
logging host 192.168.100.108
ntp server 10.10.1.47
```

# (参考) 実行結果② (想定⇒Runningの差分)
問題発生時のRollback Configを作成するイメージで、逆もやってみました。
  
 (1) QoSの既存C-DATA1クラスのACL1行を削除　⇒**OK**
 (2) QoSに新規C-DATA2クラスを削除　⇒**NG**(冒頭で`no class-map match-all C-DATA2`を実行すると、以下メッセージが表示されエラーとなる。「% Class-map C-DATA2 is being used」)
 (3) GigabitEthernet0/1を閉塞し、EIGRPを無効化　⇒**OK**
 (4) Syslogの送信先を削除　⇒**OK**
 (5) ログインバナーを追加　⇒**OK**
 (6) NTPサーバのアドレスを変更　⇒**OK**

```
C:\Users\XXXXX\hier_config>python hier_config_sample.py
no class-map match-all C-DATA2
no ip access-list extended C-DATA2_ACL
no logging host 192.168.100.108
no ntp server 10.10.1.47
policy-map P-CHILD
  no class C-DATA2
  class C-DATA1
    no priority percent 40
    priority percent 60
  class class-default
    no bandwidth percent 30
    bandwidth percent 40
interface GigabitEthernet0/1
  no description << To LAN >>
  no ip address 10.1.1.2 255.255.255.252
  shutdown
router eigrp 1
  no network 10.1.1.0 0.0.0.3
ip access-list extended C-DATA1_ACL
  no 20 permit ip any 192.168.101.0 0.0.0.255
banner login ^C
============NOTICE==============
| This is test device for demo |
================================
^C
ntp server 10.10.1.44
```

# 最後に
正しい差分Configが生成されなかったケースは、`options`のYAMLファイルでルールを定義すれば対応できるかもしれません。
また、順序の考慮が必要な場合も、上記のYAMLで順序を指定できるようですので、(大変だとは思いますが)対応できそうです。
ただ、特殊な設定を含めて完全に対応させるのは大変そうなので、自動生成されたものをレビュー者が最終チェックする前提であれば、非常に便利なツールだと思いました。